NAV Navbar

Introduction

The CSML (Conversational Standard Meta Language) is a Domain-Specific Language developed for creating conversational experiences easily.

The purpose of this language is to simplify the creation and maintenance of rich conversational interactions between humans and machines. With a very expressive and text-only syntax, CSML flows are easy to understand, making it easy to deploy and maintain conversational agents. The CSML handles short and long-term memory slots, metadata injection, and connecting to any third party API or injecting arbitrary code in any programming language thanks to its powerful runtime APIs.

By using the CSML language, any developer can integrate arbitrarily complex conversational agents on any channel (Facebook Messenger, Slack, Facebook Workplace, Microsoft Teams, custom webapp, ...) and make any bot available to any end user. The CSML platform comes with a large number of channel integrations that work out of the box, but developers are free to add new custom integrations by using the CSML interfaces.

Getting started

/* Always start the conversation with this step */
start:
  /* The bot knows the name of the user */
  if (username) {
    say "Hi {{username}}!"
    say "It's nice to see you again."
    goto end
  }

  /* This is the first time we speak with the user */
  else {
    say "Hello World!"
    goto getname
  }


/* Step to retrieve the user's name */
getname:
  say OneOf(["What's your name?", "How do people call you?"])
  hold

  remember event as username
  say "OK, I now know that your name is {{event}}."
  goto end

This is a simple CSML flow example, which describes a conversation between a user and a machine.

A CSML flow contains several steps, which are defined by a name (which must be unique within a flow) followed by a colon. Each step contains instructions on how to handle the interactions within the step, what to say, what to remember, where to go. The syntax is very descriptive and contains very few keywords.

This goal of the example flow on the right is to retrieve the name of the user.

To do so, we will first check if we already have it in memory. If so, we can use it to greet our user, and close this conversation. Otherwise, we can go to a different step and simply ask for it. When the user responds, we will be able to remember the name for future use, and close the conversation.

This simple flow shows a few of the features of the language. There are many more, which we will go into in detail now!

Features

start:
  say "hi"
  goto step otherstep // note: in the following examples, we will use the shorthand notation `goto otherstep`

otherstep:
  say "I'm in otherstep"
  goto end

As mentioned above, a flow is made of at least one step (called start), but can also contain as many steps as necessary. You should think of steps as individual, simple bits of logic, linked inside the same conversation topic (which is the flow).

To go from one step to the next, you can simply use the keyword goto (or goto step) followed by the name of the step.

To finish a flow (and close the conversation), add goto end.

Sending messages

somestep:
  say "I'm a text message"
  say Wait(1500)
  say Text("I'm also a text message")
  say Typing(1500)
  say Url("https://clevy.io")
  say Image("https://gph.is/Vx9jpI")
  say Video("https://www.youtube.com/watch?v=DLzxrzFCyOs")
  say Audio("https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/253508261")
  say Question(
    title = "What is love?",
    buttons = [
      Button("baby don't hurt me"),
      Button("don't hurt me"),
      Button("no more")
    ]
  )

CSML is able to handle many types of messages by default, and can be extended to accomodate any number of custom types as well (feature documentation pending).

To send a message to the end user, simply use the keyword say followed by the message type you want to send. Below is a list of default valid message components, which are automatically converted to nicely formatted messages for the channel in which the user is talking with the bot:

name description fallback
Text(string) Output a simple string (with no styling attached). This is also the default type, alias of say "string". This component supports Markdown on channels that allow text formatting.
Wait(number) Wait number milliseconds before sending the next message
Typing(number) Display a typing indicator for number milliseconds Wait(number)
Url(string, text="text", title="title") Format string as the URL. Optionally, the text and title parameters can provide a way to make "nicer" links, where supported. Text(string)
Image(string) Display the image available at URL string Text(string)
Video(string) Display the video available at URL string. Supports Youtube, Dailymotion and Vimeo URLs, or mp4 and ogg. Url(string)
Audio(string) Display the audio available at URL string. Supports Soundcloud embed links, or mp3, wav and ogg. Url(string)
Button(string) Display string inside a clickable button Text(string*)
Question(title = string(, buttons = [Button])) Display a list of buttons with a header of string. Title parameter is optional.  Text(string) + list of buttons

Asking questions and waiting for the user's input

somestep:
  say Question("Do you like cheese?", buttons=[Button("yes"), Button("no")])
  hold

  if (event == "yes") say "I'm glad to know that you like cheese!"
  else say "Oh that's too bad!"

A conversation with a chatbot is not very different from a human-to-human dialogue: sometimes the user talks, sometimes the bot talks.

CSML provides a solution when you need the chatbot wait for the user's input: using the hold keyword, the chatbot will remember its position in the conversation and simply wait until the user says something, then continue from there.

Receiving events

When receiving an event from an end-user, the CSML interpreter will try to match it with a new flow, or consume it in the existing conversation, or trigger the default flow as defined in the bot settings, by order of priority.

CSML Events are made available in the script when a step is triggered by a user's input. The event object is a complex structure which contains a lot more than just the string that was typed by the user. It contains a lot of information that is used by the CSML interpreter, for example the custom button payload to match a list of choices offered to the user. This makes it easy to handle both a click on a "OK" button or the user typing the word "yes".

By default, events are only expected when:

In those cases, a local variable, event, is made available, with the content of the user request. When no event is available, event is set to NULL.

Memory and local variables

CSML has two types of variables: local variables, which have a very short lifecycle, and more global variables, which are called memories.

Local variables are only usable within the current step. It is rather a way to write more readable, expressive code. But they are really powerful, because they allow you to do everything that a regular memory does, but temporarily. For more information about local variables, see the use..as keyword.

Memories on the other hand are at the core of the purpose of CSML. When two persons converse, they constantly memorize information, which they can reuse in other discussions. For example, if I gathered that my friend likes Iron Man, I might propose later that we go see Captain America together. I do not have to ask them again about their favorite film, because I already learned this information before, even if it was in a different conversation, even if the topic of that conversation might have been completely unrelated.

The memory API is very powerful, and by default a bot never forgets anything about the current user. For more information, see remember..as keyword.

There is a third memory type: metadata (which can be compared to the user's context at the beginning of the conversation). This memory type is injected by the channel at the beginning of the conversation and contains any data that the channel already has about the user that can be used in the conversation later. For example, this could include the user's full name or email address, their job title, where they are from...

Simple string templating

start:
  say "Hi! My name is {{what}}"
  say "My name is {{who}}"
  say "My name is {{slim_shady}}"

// Which is the same as:
start:
  say "Hi! My name is {{eminem.what}}"
  say "My name is {{eminem.who}}"
  say "My name is {{eminem.slim_shady}}"

// Alternatively, the following syntax is possible too, but will result in only one text bubble:
start:
  say "Hi! My name is {{what}}. My name is {{who}}. My name is {{slim_shady}}"

To output the value of any variable in a string, you can use the string templating helper: double curly braces.

This makes it extremely easy to concatenate multiple values of any type into a single string. Objects, arrays and numbers will be stringified automatically and missing properties will be evaluated as NULL.

Conditional logic

As in any turing-complete programming language, CSML is able to handle any type of logic, in the form of loops and if/else statements.

As a CSML developer, you can easily implement any logic based on any variable or event, and very expressively decide how your conversation should be handled. Own of the main advantages of CSML is its descriptive textual interface, making it easy for anyone to understand the logic. It just makes sense and hides all the complexity of decision trees behind the simple concepts that every developer knows and uses.

A large part of developing CSML flows is about finding out whether an event matches a value in order to redirect the user to one step or another. The language comes with a number of helpers and operators:

Automatic type inference

/* event = "42" */

guessthenumber:
  say "pick a number!"
  hold

  /* compare the input with an arbitrary target number */
  if (event > 57) {
    say "Too high!"
    goto guessthenumber
  } else if (event < 57) {
    say "Too low!"
    goto guessthenumber
  }
  /* use the number as a string as well */
  else {
    say event
    say "You are right, {{event}} is the right number!"
    goto end
  }

CSML is dynamically typed (even though it was written in Rust, a strong-typed, memory-safe, low-level language). The reason is simple: there is no way for a chat client to differenciate between a number type and a string type, but the language must have a way to perform arithmetic operations anyway. Also, any external function used in the program can return any arbitrary JSON-like object representation, or string, or any type of value.

To handle all these cases at runtime, the CSML interpreter will automatically detect the type of any variable and make the best decision depending on how you want to use it.

Literals, Objects, Arrays

Object representation

use Object(
  title = "toto",
  body = Object(
    somekey = "somevalue",
    otherkey = 123
  )
) as obj
say obj.title /* "toto" */
say obj.body.otherkey /* 123 */

Array iteration

use ["a", "b", "c"] as items

/* iterate over all the elements in the array */
foreach (elem, index) in items {
  say "index: {{index}}, elem: {{elem}}"
}

say items[2] /* "c" */

CSML is able to natively understand literal types (int, float, string, ...) as well as more complex types such as JSON-like objects and arrays.

The CSML interpreter will automatically parse the object with the usual dot notation x.y.z and detect the type of the resulting property. If one of the properties in the chain does not exist or is not an object itself, it will evaluate to NULL.

You can create an object by returning a JSON-like object from any function, or directly in the CSML code with the Object() helper function.

You can also iterate over an array (represented by items inside square brackets: ["a", "b", "c"]) with the foreach keyword, and access any of its items by using its index in square brackets notation: items[2].

Custom code execution

findweather:
  say "Let me query the weather in {{location}} for you..."
  use Fn("weatherchannel", location = location) as weather
  say "It will be {{weather.temperature}}°C tomorrow."

When used together with custom "serverless" function runtimes (cloud-based such as AWS Lambda, Azure Functions, Google Cloud Functions), or on-premise with FnProject, OpenFaas or OpenWhisk), the CSML interpreter is able to automatically handle the execution of any payload, any code, in any language.

With CSML Functions, you can integrate your own business logic, on your own servers, in your language of choice. And by using the CSML Studio by Clevy, the heavy setup is already done for you, which means that you can import functions in java, python, nodejs, go... without any additional setup, simply by uploading your code and calling the function in your CSML script.

Your functions can return any literal or complex type, represented as a JSON string.

somestep:
  goto flow anotherflow

Similar to navigating between steps of the same flow, you can go to the beginning of any other flow by using the goto flow keyword. This will trigger the start step of the target flow and close the current flow, so coming back to the first flow will actually go back to the beginning of that flow.

Recursive flows

conversation example:

...
bot: What is your favorite color?
user: help
bot: I'm FormBot, developed by FormCorp. I'm here to ask you to fill a form. To start a new form, type "restart".
bot: What is your favorite color?
...

When a new flow is triggered, the CSML engine will first check whether or not the user was already inside another flow. If that's the case, and if the new flow is direct (i.e it does not stop to ask the user for any input at any time), the last action of the previous flow will be triggered once more.

This means that if for some reason in the middle of a long questionnaire, the user were to ask for a quick information, the bot would immediately return to the previous flow after giving the user the requested information.

Global variables

Inside any given step, a number of global variables are made available to the developer. These variables include past memories (remember), step context (use), and input metadata.

Limitations

Keywords

goto

onestep:
  goto somestep
  say "after the goto" /* will not be executed */
  goto someotherstep /* will not be executed */

somestep:
  say "hi"

someotherstep: /* will not be executed */
  say "hey"

Inside a step, goto some other step. The step's memories are saved and its messages are sent right after this instruction, and before the next step is started.

Special cases:

goto behaves as return in other languages: anything after a goto is ignored, the goto is immediately executed.

if / else if / else

if (1 > 2) {
  say "I have my doubts"
} else if ("mi casa" == "tu casa") {
  say "Welcome home"
} else {
  say "The force is strong with you"
}

shorthand notation

if (sky == "blue") goto beach
else goto restaurant

Simple logic operators if, else if and else. See examples.

say

say "The quick brown fox jumps over the lazy dog."

Send a message to the end user.

hold

somestep:
  say "What's up doc?"
  hold

  remember event as updoc
  goto end

Wait for user input: simply hold the conversation in place until the user responds.

match

use Button(
  title = "I agree",
  accept = ["OK", "yes", "right"]
) as btn

/* a direct click on the button will match the button */
/* typing "yes" will match the button */
/* typing "of course" will not match the button */
if (event match btn) {
  say "good"
} else {
  say "not good"
}

Whether or not a variable "equals", in any way, another variable.

remember..as

remember a hardcoded value

remember "tutu" as truc

remember a local variable

use 123 as myvar
remember myvar as tata

overriding a local variable's scope

use 123 as myvar // `myvar` has a local scope
remember myvar as myvar // `myvar` is now a globally-available memory
use myvar as myvar // `myvar` will still be available globally

Save a value to the bot's memory with the given key. It can later be retrieved (as soon as the next step) with "{{memory_item}}".

use..as

use Button("A") as btn1
use Button("B") as btn2

/* say and save a component at the same time */
say Question(
  title = "Pick one",
  buttons = [btn1, btn2] as btnlist
) as myquestion

/* repeat the same question */
say myquestion

Save something as a locally-available variable (within a step). Does not remain in memory after the step is done.

foreach

use ["a", "b", "c"] as array
foreach (val, index) in array {
  say "at position {{index}} is element with value {{val}}"
}

Iterate over each element of an array.

do

// execute a function
do Fn("someFunc")

// assign new values
do array[3] = "X"
do obj.val.toto = 123

Execute the following expression. Usually used for executing functions without caring for its return value, or for updating values of objects or arrays.

mathematical operators

In CSML, you can use the 4 basic mathematical operators +, -, * and /, as well as the modulo operator %. The regular order of operations applies.

Macros

CSML exposes a number of built-in functions (macros) that make many operations easier. They are what you could call the stdlib of CSML. The number of builtins will grow as the need arises: you can expect some new builtins as part of the upcoming features! All builtins are synchronous, by the way.

You can absolutely nest builtins within builtins. The innermost function will return and make its result as a parameter of the calling function, recursively.

OneOf

say OneOf(["I like you", "You're awesome"])

Returns one the elements of the array passed in the first argument at random.

Useful for randomisation of dialogue parts!

Shuffle

use Button("Blue") as btn1
use Button("Red") as btn2
use Shuffle([btn1, btn2]) as btns

say Question(
  title = "Select a pill",
  buttons = btns
)

Given an array, return it with its elements in a random order.

Especially useful in questions to randomize the available options!

Find

say Find("needle", in="haystack") // false
say Find("yes", in="well yes, I like cheese") // true

Returns whether a string is contained in another string.

Length

say Length("My horse is amazing") // 19

Returns the length of a given string.