Dialogs
The dialog is the main building block of conversations. All the code that forms the interaction between the bot and the user takes place inside a dialog. Every statement inside a dialog is run in sequence.
dialog greeting do
say "Hello!"
ask "What is your name?"
end
Inside a dialog you write statements, like say
, ask
, et cetera,
to create the conversation. See the statement
reference for more detail on those. The
rest of this chapter documents in which ways dialogs can be triggered.
Named dialogs¶
Dialogs can have names, the name you give a dialog is
an "internal" name, it is only used in the script. By using
invoke
, you can let the named
dialog be executed, from another place.
dialog greeting do
say "Hello!"
invoke ask_name
end
dialog ask_name do
ask "What is your name?"
end
The platform checks the dialog names that you invoke. If we try to invoke a dialog that does not exist, it is considered as a syntax error, and the bot will refuse to run.
Dialog trigger¶
Dialogs can have triggers which are matched against user input to trigger the dialog.
dialog trigger: "hi" do
say "hi there!"
end
Above we match sentences which contain the word "hi". The original message
that caused the trigger is accessible in the variable named
message
.
Dialogs are evaluated in order, meaning the first dialog that matches wins.
dialog trigger: "you look nice" do
say "that's nice of you"
end
dialog trigger: "nice " do # This dialog will never be executed
say "thank you"
end
Using BML in triggers¶
The trigger:
part of the dialog is actually a BML expression:
dialog trigger: "music | museum" do
say "you typed either music or museum"
end
Note that BML matches are case insensitive: "museum", "Museum", "MUSEUM" will all match the dialog trigger: "museum"
.
BML captures¶
You can also capture parts of the user's input by using the BML capture notation, specifying the variable name between the brackets as follows:
dialog trigger: "my name is [1-3=name]" do
say "Hello #{message.name}"
end
This dialog triggers when you say "My name is", followed by 1 to 3
words, which are then assigned to the message.name
variable.
Capturing entities¶
dialog trigger: "My age is [number=age]" do
say "so you are #{message.age} years old!"
end
This scans the string for the Duckling number
entity, and puts the
entity in message.age
.
- The entire user utterance is stored in
message
- The entities actual value (17) is stored in
message.age
.
Use the variables panel in the studio to explore even more properties of the entity.
Intent matching¶
A collection of BML string matches can be grouped together under an intent. Intent are usually assigned to a constant in order to be used in multiple places:
@hello intent(match: ["hi", "hello there", "howdy", "how are you doing"])
dialog trigger: @hello do
say "Hello to you too!"
say "The intent that matched was: " + message.intent.id
end
The Training tab of the studio also allows you to create intents. These can also be used directly in Bubblescript in the same way, but then edited through the Training CMS instead of in the Bubblescript code.
Event trigger¶
dialog event: "status_update" do
say "We received an event."
end
Match on an incoming event with the
name status_update
. The event can come from the web client, from a
webhook, from another bot or conversation, or even from yourself, when
it had been scheduled to be sent in the future.
Event triggers do not use BML matching syntax. You can only match on the exact event name.
Other dialog triggers¶
The following dialog triggers are less frequently used.
A location trigger will match any location within a given radius:
dialog location: [lon: 4.893414, lat: 52.364909], radius: 3000 do
say "Welcome in Amsterdam!"
end
Note that the location has to be sent to the client explicitly, no background location tracking is going on.
Furthermore, an attachment trigger can fire as soon as a user sends a media file to the conversation:
dialog attachment: :image do
show image attachment.url
show image attachment.url
end
Receiving other media types¶
To respond to any other media type the user uploads you can use a special system dialog:
dialog __unknown_attachment__ do
say "Thanks for the #{attachment.type} file"
end
Asking a location¶
To ask a user to provide his location you can set :location
as expectation.
location = ask "Please share your location", expecting: :location
The location latitude and longitude will be captured into the variable location
. This is a Map with two keys (lat, lon) than can be accessed as follows:
say "your latitude is #{location.lat}, and longitude is #{location.lon}"
Guard clauses¶
A powerful way to create variations of dialogs is to define the same dialog multiple times, but add a "guard clause" on it.
Inspired by Elixir's guards, Guard clauses are small expressions which can be added to the dialog definition. These guards decide whether the given dialog is matched or not:
dialog main when user.frontend == "slack" do
say "Hello slack user!"
end
dialog main do
say "Hello, other user"
end
There can be multiple dialogs with the same name or trigger, but with different guard clauses. When looking up a dialog, the guard clauses get evaluated and the first dialog with a matching guard clause gets executed.
It is important to remember that dialog resolution is always processed in order, from the top to bottom of the files.
Platform-specific dialogs¶
The following dialogs are automatically invoked by the platform, based on certain conditions that happen in the lifecycle of the conversation:
__main__
¶
__main__
is the entrypoint for any conversation. For historical
purposes, the platform defines a __main__
dialog, which invokes
main
.
dialog __main__ do
say "Hello, I am a chatbot."
end
__root__
¶
dialog __root__ do
say "What would you like to do?", expecting: [@get_quote, @reset_password]
end
The root dialog is invoked the moment the bot's stack is
empty. Implement a __root__
dialog to create a "menu"-like
interaction model where the bot keeps returning to a menu of options
when it is done processing its current dialog.
__unknown__
¶
The unknown dialog is invoked when the user sends a message, but there are no dialogs that get triggered because of that message. This is a typical place to respond with something like "Sorry, I dont Understand, I'm just a chatbot".
dialog __unknown__ do
say "I don't get what you mean..."
end
__unknown_event__
¶
When an event was sent into the conversation, but there was no event trigger defined for it.
__unknown_location__
¶
When a user sends a location pin to the chat, but there was no location trigger that caught it.
dialog __unknown_location__ do
hilversum = [5.177459, 52.224025]
d = round(distance(location, hilversum) / 1000)
say "You are #{d} km from Hilversum"
end
__unknown_attachment__
¶
When a user sends a image/video/audio file to the chat, but there was no attachment trigger that caught it.
__returning__
¶
This dialog is invoked when the stack is popped.
dialog main do
ask "Choose a color", expecting: "red", quick_replies: ["Red"]
end
dialog __unknown__ do
say "That is not right."
end
dialog __returning__ do
say "Back to the subject."
end
In this case, the user is asked to type "Red"
; when he types
something else, first __unknown__
is triggered, and then, just
before the question is repeated, __returning__
is invoked as well.
__timeout__
¶
Invoked when session timeout is reached. This can be used to clean up things, write information to a database, et cetera.
The standard timeout for a chat session is 15 minutes; it can be
changed setting the @timeout
constant.
__resume__
¶
Invoked when the conversation is resumed. After the conversation times
out, the conversation state is saved. Later, when the user returns to
the conversation, the conversation state is looked up again, and just
before the conversation resumes where it left off, __resume__
will
be invoked.
__ask_timeout__
¶
Invoked when an ask
statement has a timeout:
value, but when there
was no timeout_dialog
specified.
To set a global timeout on all ask
statements, define the @ask_timeout
constant:
# Skips any question after 1 minute
@ask_timeout "1m"
__error__
¶
Invoked when a runtime error is encountered. In that case, the error
variable
is filled with an object containing the error message
and the location
(script file + line number).
The default implementation of the error dialog looks like this:
dialog __error__ do
if debugging do
say "Error: #{error.message}"
ask "On #{error.location}", expecting: "OK", quick_replies: ["OK"]
else
tag "error"
say "Oops, that went wrong!"
ask "Sorry about that…! Let's try it again.", quick_replies: ["Restart"]
invoke __main__, :reset
end
end
Executing the fatal
statement also causes the error dialog to be triggered.
Platform-specific tasks¶
__init__
¶
The __init__
tasks are executed whenever the bot is started (before
__main__
, and __resume__
). This could be used, for example, to fetch user
data from a CRM before starting the conversation.
Contextual variables¶
In the runtime, there are several variables exposed that contain information about the dialog stacking system.
dialog.stack
- the current stack of dialog names, topmost dialog first. The name of the current dialog isdialog.stack[0]
.dialog.history
- the list of dialog names that the user has passed through, ordered by most recently visited dialog.dialog.last_user_utterances
- the list of utterances (last messages) sent by the user; maximum 5; most recent first.dialog.last_bot_utterances
- the list of utterances (last messages) sent by the bot; maximum 5; most recent first.dialog.last_action_id
- the ID of the last outgoing bot action (any message).