Botsquad documentation logo

The Botsquad chatbot language (Bubblescript) is a simple programming language you use to program chatbots in the Botsquad studio. This introduction helps you get started with building your own chatbot.

Lets start off with a simple example:

dialog main do

  type 3

  say "To the optimist, the glass is half-full."

  say "To the pessimist, the glass is half-empty."

  say "To the engineer, the glass is twice as big as it needs to be."

  pause 10

  ask "Did you laugh?", expecting: ["Yes", "No"]
  if answer == "Yes" do
    say "You must be an engineer!"
  else
    say "You're not an engineer are you?"
  end

end

Hopefully you already get the gist of what this bot will do. Don’t worry if you don’t. In the following paragraphs the scripting language used will be explained in more detail.

Dialogs

Dialogs are the main building blocks of bot conversations. Dialogs contain interactions with the user, grouped together and ran in sequence, one after the other. The main interactions are say, ask and show.

dialog main do
  say "Hi there!"
  say "I'm a chatbot"
end

Interactions (like ‘say’, ‘ask’, ‘show’) can only be used inside dialogs, not outside of them.

Do … end blocks

Blocks (everything starting with do and ending with end) are another fundamental construct within Bubblescript. They hold multiple interactions (statements and expressions) that are run in sequence when they get “in the path of execution”.

We have already seen blocks in both the dialog:

dialog welcome do
  say "Welcome! nice to meet you"
end

as well as in conditions:

  if answer == "Yes" do
    say "You must be an engineer!"
  else
    say "You're not an engineer are you?"
  end

Every do needs to have a closing end. This can be tricky with nested blocks. The editor will help you by automatically indenting.

Named dialogs

Dialog can have names, these names are used to refer to dialogs from other dialogs.

In the above earlier script we named the dialog main. This name, main, has a special meaning: the main dialog dialog is started automatically when the bot starts. Each bot has exactly one main dialog.

The name of a dialog can be used to start (invoke) another dialog:

dialog main do
  invoke welcome
  say "How can I help you?"
end

dialog welcome do
  say "Welcome! Nice to meet you."
  say "I hope you have a wonderful time here."
end

In this example the bot will start in main, but will then immediately start welcome. After welcome is done, it continues in main, saying “How can I help you?” last.

Dialog triggers

After the main dialog finishes new dialogs can get triggered by matching the users messages to dialog triggers:

dialog trigger: "hi" do
  say "hi there!"
end

The above dialog will be invoked when the user types “hi” anywhere in a sentence: “hi there” or just “just wanted to say hi”. Note: Dialogs are matched from top to bottom so order matters.

dialog trigger: "say hi" do
  say "thats nice of you"
end

dialog trigger: "hi" do
  say "hi there"
end

The trigger: "say hi" is first matched so “just wanted to say hi” and will match this dialog and not any of the dialogs after it like trigger: "hi".

Besides exact matches you can also match text patterns.

dialog trigger: "mus(ic|eum)" do
  say "you typed either music or museum"
end

The full message that the user typed is availabe in the variable message. The exact part that matched the trigger is available under the variable entity.value.

dialog trigger: "mus(ic|eum)" do
  say "you typed '#{message}'' matching '#{entity.value}'"
end

Note that matches are case insensitive: “museum”, “Museum”, “MUSEUM” will all match the dialog trigger: "museum".

Responding to the user

We have already seen how to respond to users with text using say and ask.

Say

  say "Hello!"

Next to normal text you can also any UTF-8 characters as well as emoji

  say "😂 Very funny!"

Type / Pause

To send a reply with an extra delay you add a type (showing a typing indicator) or pause (silent) statement after the message

  type 2
  say "Hello"
  pause 2
  say "world"

type 2 will show the typing indicator for 2 seconds, pause 2 will not show a tying indicator but just wait for 2 seconds before continuing.

Random

To give a random response use random with a block of potential responses:

  random do
    say "Hi!"
    say "Hello!"
    say "Howdy!"
  end

Just saying random things to user would not make for a good dialog. Lets ask the user something…

Ask

To ask a user for input you can use ‘ask’ and assign the result to a variable:

  name = ask "What is your name?"
  say "Hello, #{name}!"

When encountering an ask, the bot will wait until the user types a message. In the above example, the captured reply is assigned into the variable name.

You can also use ask without variable assignment:

  ask "What is your name?"

The result is then stored in the variable answer.

Validation: expecting a certain answer

  ask "Did you laugh?", expecting: ["Yes", "No"]

  if answer == "Yes" do
    say "You must be an engineer!"
  end

When using expecting:, you can enforce the user giving a certain type of answer: the ask statement will retry until the expectation has been met.

When using , expecting: ["Yes", "No"] it will also render quick-reply buttons for “Yes” and “No” and only allow those exacts answers. It will ask again when the user types anything else.

Asking with a more fuzzy expectations

To make the bot a bit more forgiving (smart) you can use named entities in the expecting:

dialog question do
  laugh = ask "Did you laugh?", expecting: [@yes, @no]

  if laugh == :yes do
    say "You must be an engineer!"
  end
end

Note that when using entities you should match against the returned atom :yes instead of the string “yes”. Think of :atoms as names for things dynamically provided by the system, where variables are names for things you/the user provide.

The defaults script that is added when you create a bot on Botsquad contains (amongst others) the entities definitions for @yes and @no. They are not magic at all, if you look at them:

@yes      learn(input: "yes|yep|sure", label: "Yes", return: :yes)
@no       learn(input: "no|nope|nah", label: "No", return: :no)

Read more about entities (@yes), string matching and learning in the NLP section.

Quick Replies

Sometimes you want to show quick-reply buttons but not restrict the user to only these values. In that case you can use quick_replies:

  name = ask "What is your name?", quick_replies: ["skip"]
  if name == "skip" do
    name = "Anonymous"
  end

or even combine it with an expectation:

  laugh = ask "Whats your email?", quick_replies: ["yep", "nah"], expecting: [@yes, @no]

the quick_replies will overwrite what the expecting would set as quick_reply. So in this case without quick_replies it would show the quick replies “yes” and “no”, with the quick_replies it renders “yep” and “nah”.

Ask timeout

Sometimes, the user might not want or forgets to respond to your question. For these cases you can add a timeout to the ask, so that the script continues after a short while.

  name = ask "Whats your name?", timeout: 30

This will wait 30 seconds for the user to respond. When the ask times out, the returned value is nil, so you can use a if !answer do... construct to check whether a timeout was triggered.

You can also use the global constant @ask_timeout to give every ask statement a default timeout value:

@ask_timeout 30

dialog ask_name do
  name = ask "Whats your name?"
  if ! name do
    say "Never mind then!"
  end
end

When you want a different value to be returned when the question times out, you can use the timeout_value option.

  name = ask "Whats your name?", timeout: 30, timeout_value: "John Doe"

Conditionals

To allow the bot to make decision based on the users input the statement if and else are used to conditionally execute blocks of code.

If

with if you can test for truthiness of a condition (predicate).

  if platform == "Yes" || app == "Yes" do
    say "Apps or platforms are not really a thing anymore."
  end

Reads: “If variable platform is equal to “Yes” and variable app is equal to “Yes” do the following”

Else

Executes the else block if the if statement evaluates to false (is not true).

  if exits != "Yes" do
    say "I believe in your product. Let's do it!"
  else
    if team > 3 do
      say "I think your team is too big."
    else
      if platform == "Yes" || app == "Yes" do
        say "Apps or platforms are not really a thing anymore."

You can nest if and else statements as deep as you want; for sanity it’s wise to keep it to max 3 levels deep. To circumvent nesting of branches, use the branch statement described below.

branch

The branch statement evaluates each expression top down until the first one that evaluates to true. It then executes all the statements inside the block.

This is especially handy if you want to branch the conversation on more than two branches:

  ask "Do you want to continue?", expecting: ["Yes", "No", "Maybe"]
  branch do
    answer == "No" ->
      say "Sorry to hear"
    answer == "Yes" ->
      say "Great!"
    answer == "Maybe" ->
      say "Make up your mind!"
  end

In the above example the expecting: assures that answer can only contain the 3 cases we branch on. If you branch on an open answer you can end with a catch all true case:

  ask "What is your name?"
  branch do
    answer == "Steve" -> say "hi Steven"
    answer == "Pete"  -> say "hi Peter"
    true              -> say "hi stranger"
  end

Operators in predicates

The following operators can be used:

  a && b    # AND
  a || b    # OR
  a !  b    # NOT
  a == b    # IS
  a != b    # IS NOT
  a <= b    # IS SMALLER OR EQUAL TO
  a >= b    # IS LARGER OR EQUAL TO
  a <  b    # IS SMALLER
  a >  b    # IS LARGER

Variables

Variables in Botsquad script are global meaning they are available from any file and or dialog. Some variables are made available by the systems, others are created by the user in the scripts by assignment. All variables are can be seen in the panel below the script editor.

  user.id
  user.frontend
  user.first_name
  user.last_name
  user.presence
  ...

Note that you can create these maps yourself as well:

  car.brand = "Volvo"
  car.engine.hp = 200

runtime variables

Some variables are set (when triggered) by the runtime itself:

  message
  answer
  entity
  intent
  schedule_id
  location
  attachment
  event
  user
  bot
  session_id

Don’t set these reservered variables yourself as they will be overwritten!

remember variables

Variables are used to provide context for the conversation and steer the dialogs. Some variables are temporary and can be forgotten the next time the user engages with the chatbot while other variables (name, preferences) you want the bot to remember. To remember variables just say remember variable. Like wise to forget remembered information you can say forget variable

dialog greeting do
  if visited_before == true do
    say "hi again!"
  end
  visited_before = true
  remember visited_before

  if user.first_name do
    say "Nice to meet you #{user.first_name}"
  else
    user.first_name = ask "whats your name?"
    say "Nice to meet you #{user.first_name}"
    remember user.first_name
  end
end

Dialogs_advanced

capturing dialogs

You can also capture parts of the user’s input in a dialog by using a notation with pointy brackets, specifying the variable name between the brackets as follows:

dialog trigger: "My name is #<name>" do
  say "Hi #{entity.name}, how are you?"
end

Note: matched entities are made available via de entity Map. So name becomes entity.name.

You can even capture multiple parts of a message in a triggers:

dialog trigger: "My #<object> is #<color>" do
  say "So you have a #{entity.color} #{entity.object}?"
end

You can also use regular expressions in the capturing group as follows:

dialog trigger: "My age is #<age:\d+>" do
  say "so you are #{entity.age} years old!"
end

which then would only match when the users enters one or more digits as an age.

__unknown__ dialog

If none of the defined dialogs match the user’s message a special dialog __unknown__ will be triggered.

dialog __unknown__ do
  say "Sorry don't understand #{message}. I am just a robot you know 😂"
end

the variable message hold the full message the user typed.

__returning__

After returning to the previous dialog on the stack the dialog __returning__ is invoked:

dialog main do
  say "Hi"
  pause 60
  say "there"
end

dialog __unknown__ do
  say "?"
end

dialog __returning__ do
  say "Back to the subject."
end

If you type anything here if will call __unknown__ and then __returning__ when returns to the main dialog to continue stepping through the script.

Advanced responses / text matching

Intent matching

Next to hard text matching you can also use a more fuzzy matching based on machine learning by defining intents:

@hello intent(learn: ["hi", "hello there", "howdy", "how are you doing"])

dialog trigger: @hello do
  say "Hello to you too!"
  say intent
end

Note: these intents are matched after all hard matches fail. So if you also have a dialog with trigger: "hi" and type “hi” this will match first even though the trained intent also include the example “hi”.

Entity extraction

The entity() function allows you to define entities which can be used in ask and in dialog triggers. Some defaults are defined in every BotSquad bot. These entities are defined in the defaults file and match multiple user inputs returning atoms :yes or :no

@yes entity(match: "yes|yep|sure", label: "Yes", return: :yes)
@no entity(match: "no|nope|nah", label: "No", return: :no)

Besides @yes and @no the following entities are also already defined in the defaults file. @postcode and @email use a ‘regular expressions’ to pattern match the user’s response. If it matches the user’s message will be accepted if it does not it will loop and if defined the help_dialog will be invoked.

@postcode entity(match: "^\d{5}(?:[-\s]\d{4})?$")

dialog main do
  laugh = ask "What is your postcode?", expecting: @postcode, help_dialog: invalid_postcode
end

dialog invalid_postcode do
  say "Sorry, that doesn't look like a postcode. It should be something like 1000 AA."
end

To learn more about regular expression matching you can use this online tool: regexr

Entity recognition with Duckling

Botsi integrates with Duckling so that you can use duckling’s entity matchers on the user’s input.

For this a special syntax is allocated: entity(duckling: "email").

@email entity(duckling: "email")
dialog duckling do
  email = ask "What is your email?", expecting: @email
  say email.value
end

The duckling matchers are smart and only extract the text which is relevant to the given entity. So when asking for an email address, the user can respond with "My email address is [email protected]!" and the email.value will only contain [email protected].

You can also use dialog triggers with duckling, like this:

@email entity(duckling: "email")
dialog trigger: @email do
  say "Thank you for your email! You entered: #{value}"
end

The following is a list of all supported entities:

Note that not all entities are supported in every language. Please refer to the Duckling documentation for exact support.

Duckling matchers are locale and time zone sensitive. By default, the locale is en_US and the time zone is Europe/Amsterdam. These can be changed by passing them in as arguments to the entity() function like this:

    @time entity(duckling: "time", locale: "nl_NL", timezone: "UTC")

Handling validation errors

To handle validation dialogs you can use the following: To direct the user on typing or clicking “Yes” or “No” you can also add a help dialog:

dialog main do
  laugh = ask "Did you laugh?", expecting: [@yes, @no], help_dialog: help
end

dialog help do
  say "Please click the quick replies or type 'Yes' or 'No'"
end

To create a gallery you can use the template statement:

  template do
  "The hat shop 1" ->
    image_url "http://via.placeholder.com/150x150"
    subtitle "We've got the right hat for everyone."
    default_action do
      open "http://www.redhat.com"
    end

    buttons do
    "View Website" ->
      open "http://www.redhat.com"
    "Say hello" ->
      postback "postback"
    end

  "The hat shop 2" ->
    image_url "http://via.placeholder.com/150x150"
    subtitle "We've got the better hat for everyone."
    default_action do
      open "http://www.redhat.com"
    end

    buttons do
    "View Website" ->
      open "http://www.redhat.com"
    "Say hello" ->
      postback "postback"
    end

  # ...
  end

Prompting the user for an image

To ask a user to upload an image you can use the expecting: with :image as expected value.

dialog image do
  image_url = ask "Please upload a selfie", expecting: :image
  say image_url
  show image image_url
end

The uploaded image url will be captured into the variable image_url and can be used later to send the same image back to the user.

Receiving images

You can also create a dialog that responds when a user sends the bot an image at any time (so not when being asked like above).

dialog attachment: :image do
  say "thanks for your image!"
  show image attachment.url
end

The following variables are created by the system when a file is received:

attachment.url  https://s3.eu-central-1.amazonaws.com/bsqd/image/png/73a51722-fb1c-4a94-8037-4d16.png
attachment.type image

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 an Map with two keys (lat, lon) than can be accessed as follows:

  say "your latitude is #{location.lat}, and longitude is #{location.lon}"

Distance functions

dialog distance do
  loc1 = [lon: 4.893414, lat: 52.364909]
  loc2 = [lon: 4.8952, lat: 52.3702]
  d = distance([4.893414, 52.364909], [4.8952, 52.3702])
  d2 = distance(loc1, loc2)
  say d
  say d2
end

Sending a location

Similar to shwowing images you can also send a small map with a location:

  show location [4.893414, 52.364909]
  show location [lon: 4.893414, lat: 52.364909]

List

To get the first item in a List you can use the bracket notation [0] Note: List like arrays in most programming languages start at 0 zero!

dialog n do
  list = [1, 2, 3]
  say list[0]
end
  length([1,2,3]) == 3

To iterate over the full array you can use repeat:

dialog iterate do
  list = [1, 2, 3]
  repeat item in list do
    if item == 3 do
      say "and.."
    end
    say "item #{loop}"
  end
end

Location matchers

A script can respond to the user sending his location (outside of an ask)

dialog location: [lon: 4.893414, lat: 52.364909], radius: 3000 do
  say "Welcome in Amsterdam!"
end

__unknown_location__

When you define a single dialog named __unknown_location__ it will be called whenever the user sends a location that is not matched by any dialog with a location: matcher.

dialog __unknown_location__ do
  hilversum = [5.177459, 52.224025]
  d = round(distance(location, hilversum) / 1000)
  say "You are #{d} km from Hilversum"
end

Emit an event

The emit statement sends an event to the runtime that is executing the bot. These events can be used for instance to trigger backchannel events or trigger events on the website.

The event itself is a string; it can have an optional extra parameter which will be sent as payload.

dialog main do
  emit "start"
  say "Hello!"
  emit "end", [user_id: user.id]
end

The event name can be a simple string or an expression. The payload is optional and can be any valid expression.

You can use events to trigger javascript on the page where the chatbot is hosted:

dialog events do
  emit "focus", [element: "#section-1"]
end

You do need to respond to these events by adding handlers in the integration Javascript.

Defaults

Defaults are set via the following @constants:

@delay 1
@typing_indicator 1

To speed up dialogs for testing purposes you can set them to 0

@delay 0
@typing_indicator 0

This way you don’t have to wait between statements.

When there is no user interaction the chatbot will “die” after a certain time. This can be set via the constant @timeout

@timeout "1h"

The notation in the string is as follows:

@timeout "1h"
@timeout "60s"
@timeout "200ms"

__timeout__

Just before the chatbot dies a special dialog is invoked allowing you to do a last interaction with the user.

dialog __timeout__ do
  say "Thanks for you visit"
  buttons "We would love to hear your opinion on the experience" do
  "Share your opinion" ->
    open "https://bsqd.me/bolflinck/chat?bot=Joan&name=#{user.first_name}"
  end
end

You can for instance use this to ask visitor to participate in a survey. Here the survey is done by linking to another bot. Note you can pass information to the next bot via parameters. You can capture this information in the next bot via the query variables:

dialog main do
  user.first_name = query.name
  chatbot = query.bot
end