Nothing better to eat your own dog food to understand how you need to improve your tool to better serve your customers. At Xatkit, we have started to create a number of bot projects. This helped us to realize that our current bot definition language had to undergo a major change if we wanted to:
- Simplify the creation of bots requiring complex conversational flows.
- Maximize the reusability opportunities, advancing towards creating bots by reusing building blocks and conversation paths already defined in the past
This wasn’t easy in the “old language version” as the intents definition was mixed with partial order constraints to restrict the freedom in the conversation path. That was enough when dealing with “open” bots (FAQ style bots) where most of the questions are independent and are always available but not when dealing with more “closed” bots where the potential conversations are much more restricted (think of a bot to order food from a website).
To bring the capabilities of our chatbot definition language to the next level we have moved to a DSL closer to state machine semantics and completely decoupled the intent definition from the transition rules that control what intents are available at any given point of the bot execution. This brings the following key benefits:
- You can now reuse previous intents in new bots even if the new bot is implementing different conversation paths.
- You can use all the power of the state machine formalism to define very complex conversation flows while keeping a clear and precise semantics for the bot.
- Create very complex guards in transitions. Move the bot to a new state based on the user input, coming events but also conditions on previous data, parameters of the conversation,…
- Place complex conversation logic to respond to user requests using the state body.
- Modularize the bot. Reuse parts of the state machine in other bots.
- Possibility to define local fallbacks as part of the state behaviour. Beyond a default global fallback, you can now attach local fallbacks to states that let you handle errors within the state context (e.g. to show a message helping the user to respond to the question posed by the bot in the specific state the conversation is in).
Convinced already?. Great!. Let us show a glimpse of what the new language looks like.
Intent definition language
The intent definition remains a separate sublanguage but not even more decoupled from the execution part. Now, for each intent, we just define the set of training sentences for the intent and collect the parameters from the user utterance we want to save with the intent.
See the following example where we have a simple bot that just recognizes two types of user utterances (greetings and stating the name):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | intent Hello { inputs { "Hi" "Hello" } } intent MyNameIs { inputs { "My name is NAME" "I'm NAME" "Call me NAME" } creates context Greetings { set parameter name from fragment "NAME" (entity any) } } |
For each intent, we show a couple of training sentences to teach the bot how to recognize them. Moreover, see how we collect the NAME
parameter in a context to use it later on (e.g. to answer to the user in a more personalized way).
At this point, we are not stating what intent the bot should try to match first, this is something that belongs to the execution DSL we’re going to see just next. This enables us to reuse the intents (for example, in another bot where we may also need to ask for the user’s name but not just after a greeting intent).
Execution definition language
The execution file now defines a state machine that describes both the bot reactions to the intents/events and the potential transitions between them. Bot designers can now look at the execution file and have an idea of the entire conversation flow.
- Body (optional): the bot reaction, executed when entering the state.
- Next (mandatory): the outgoing transitions defined as condition –> State. The conditions are evaluated when an event/intent is received, and if a transition is navigable the execution engine moves to the specified state (and executes its body, etc). Note that transition conditions can be as complex as you want. And they are real guards, meaning that if the entire condition is not true the transition is not navigable, and the engine will stay in the same state.
- Fallback (optional): local fallbacks made easy. This section can contain arbitrary code (as the Body section) that will be executed if the engine cannot find a navigable transition.
- Init: a regular state that is entered when the user session is created. It can contain a Body, Next, and Fallback section.
- Default_Fallback: a state that can only contain a Body section, and cannot be the target of a transition. This state represents the default fallback code to execute when there is no local fallback defined in a state. This state can be used to print a generic error message (“Sorry I didn’t get it“), while local fallback can print a custom message tailored to the current state (“Please reply by yes or no“).
Finally, states can define a single wildcard transition (using the reserved character _ as the transition condition) that will be automatically navigated when the state’s body has been computed. This allows us to modularize the execution logic and reuse the same code in multiple places. See the detailed example below that shows a simple bot that just replies to the Hello intent above, asks for the user name and says hello to her (this example bot is supposed to be displayed via our React-based chat widget):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //We can always have an init state in case we need to initialize some bot parameters (e.g. welcoming message) Init { Next { //Here we state that the bot will first listen for an utterance matching the Hello intent, it will ignore anything else intent == Hello --> HandleHello } } HandleHello { Body { ReactPlatform.Reply("Hi, what's your name?") } Next { //We wait for the user to input the name, no other transition is possible at this point //Obviously, in more complex bots we may have several possible outgoing transitions in a given state intent == MyNameIs --> HandleMyNameIs } } HandleMyNameIs { Body { ReactPlatform.Reply("Hi " + context.get("Greetings").get("name")) } Next { // An automatic transition to the Init state since at this point the conversation is finished and we can start again _ --> Init } } // Default Fallback state could go here |
FNR Pearl Chair. Head of the Software Engineering RDI Unit at LIST. Affiliate Professor at University of Luxembourg. More about me.
Hi Jordi,
how should I start?
OK, to eat one’s own dog food is always a good idea. Also state machine semantic is both very powerful and formal.
No doubt about that.
But, to whom is ChatBot aimed. I am 99.999% sure every single individual in your working group is familiar with thinking in state machines. But what about the aimed audience. I am faced on a daily basis with people being fair engineers, but being completely unable to think in state machines. These people might or not be your aimed audience. I do not know. But nevertheless I advise you to think about this.
/Carsten
You’re absolutely right. But the “regular citizen” is not the audience. In fact, one of the goals of our previous language version was to be more intuitive but we quickly realize that our potential clients couldn’t care less about the chatbot language. They just wanted the chatbot. So at that point we decided to create a chatbot language for “us” and not sell the tool but our services/final bots created with it.
This chatbot plugin for WordPress is a good example of this
Good news to me. Chatbot might get useful for me.
Thanks Jordi,
Carsten