Dialogs modeled as finite state machines ?

Update (October, 30th 2020): I opensourced NaifJs, the dialog manager that implement my 4 years ago idea.

Update (October, 30th 2020): I opensourced NaifJs, the dialog manager that implement my 4 years ago idea.

I describe briefly my small study to model chatbot conversations as a finite state machines. I present here a personal project ((dialogs)), and I also mention great aBot open-source project, but eventually concluding with a dialog flow scripting languages apologia.

((dialogs)) — an abandoned experiment

In December 2015 I wrote ((Dialogs)) a very draft Ruby (my beloved programming language) code, with the aim to proof the concept of a conversational framework where any dialog is modeled as a finite state machine. A complex nested dialogs flow is modeled here as passages from a state of a machine to a state of another machine, in a graph of nested dialogs. In my approach each dialog is an instance (in terms of object oriented parlance) of a class (= a dialog template, with an associated DSL — Domain Specific Language).

At first glance all that sound cool, isn’t it ?

online shopping order submission macro dialog example: conversation contains three subdialogs

Nevertheless I abandoned soon my naif gist, and I tell you why in a moment, but it could be still interesting to read introduction/manifesto:

solyaris/dialogsdialogs - A dialog system framework for conversational services.github.com

Abot — a promising open source bot framework

Abot is an open-source beautiful project where again, a dialog is modeled as a state machine; I just recently discovered it after having sketched my own ((dialogs)).

Abot makes it easy and fun to build your own digital assistant, and we include everything you need to get started.

itsabot/abotabot - Abot makes it easy and fun to build your own digital assistant, and we include everything you need to get…github.com

Abot is developed in Go language, based upon the gift of a chatbot engine/framework architecture of third-party plugins, where each plugin represent a service/dialog (with a behaviour modeled as a finite state machine). Here below an extract from the aBot github readme page:

Abot’s core consists of three parts:
1. An API that accepts natural language inputs.
2. A state machine that tracks grammar and context across inputs, enabling the chaining of commands.
3. A router that selects the appropriate plugins to send the input based on the current command and past context.
BTW, I love aBot plugins architecture as a great example of open-souce collaborative directory of resources (general statement), nevertheless I’m still perplex on the dialog modeling as a FSM (this is also a self-criticism to my (dialogs))!)

Why I don’t like FSM approach

Even if I can state without doubts that aBot is a good piece of software, more I think about the approach to model a dialog flow as a chain of dialogs each one rigidly built-upon a finite state machine, more I think this as a wrong way to build complex conversational applications.

There are some reasons of my skepticism and I just mention two:

First, whatever the implementation may be ( my modest ((dialogs)), aBot, or any framework with similar approach) dialogues are anyway “hard-coded” inside source code of a specific programming language, Ruby, Go, NodeJs or any other, and I strongly believe that’s bad, because in this way the dialog flow is drowned in the software.

func kwGetRating(in *dt.Msg) string {
biz, err := getBusiness(in)
if err != nil {
return ""
}
f := fmt.Sprintf("%.1f", biz.Rating)
return "They have a " + f + " star rating on Yelp."
}
Digression: If I can hazard a joking parallelism with web programming … immerge natural language output prints in the above mentioned way, is like times where we built web pages with CGI programming, producing HTML code with print (wrote in some programming language) in a running process spawned by a web server. Nowadays instead, HTML literals are better produced by scripting languages like PHP or Ruby on Rails, that embed programming language functions inside the HTML code… ;-)

Let’s now consider an example with ((dialogs)), of a dialog to collect items in a list: https://github.com/solyaris/dialogs/tree/master/dialogs/list

My gist was to split the FSM logic code (here I report just an extract from list.rb file) …:

class List < Dialog
    def self.initialize_data
[]
end
    #
# state: start
#
def start(text)
go :add, add_o
end
    #
# state: add
#
def add(item)
case item
when yes_i
go :add, add_more_o
      when no_i || exit_i 
if data.empty?
reply aborted_o
return back :finish
end
        # list items
reply_list
go :confirm, confirm_o
      when help_i  
help_add

when list_i
reply_list
go :add, add_more_o
      else
#
# NLP understanding
#
interpret item
        # add line to data text
data.push item
        # back to the same state
go :add, add_more_o
end
end
...

…from the country language literals (by example in Italian, see the list_lang_it.rb here below:

# file: list_lang_it.rb
module Dialogs
class List < Dialog
private

# input methods

def yes_i
/\bsi\b|\bok\b|\bprocedi\b|\bvai\b/i
end

def del_i
/\bdel\b|\btogli\b|\brimuovi\b/i
end

# output methods

def confirm_o
"confermi lista ?"
end

def confirmed_o
"lista confermata!"
end

def del_o(max_num)
[
"quale item vuoi cancellare (1-#{max_num}) ?",
"cosa vuoi togliere (1-#{max_num}) ?"
].sample
end

Let’s consider insted “hello world” example from aBot project. Here below the code:

// Package hello responds to "Say something" with "Hello World".
package hello

import (
"log"

"github.com/itsabot/abot/shared/datatypes"
"github.com/itsabot/abot/shared/nlp"
"github.com/itsabot/abot/shared/plugin"
)

var p *dt.Plugin

func init() {
// Create the plugin, setting it up to communicate with Abot through
// the functions we specified.
var err error
p, err = plugin.New("github.com/itsabot/plugin_hello")
if err != nil {
log.Fatalln("failed to build plugin.", err)
}

// When Abot receives a message, it'll route the message to the correct
// package. Doing that requires a trigger, which tells Abot to send the
// response to this package when Commands include "say" and Objects
// include "something", "hello", etc. Case should always be lowercase,
// and the words will be stemmed automatically, so there's no need to
// include variations like "cat" and "cats". plugin.AppendTrigger is
// optional if you set KeywordHandlers (as described in the Building a
// Plugin guide), but since we only have a state machine, we'll add
// these words as triggers.
plugin.AppendTrigger(p, &nlp.StructuredInput{
Commands: []string{"say"},
Objects: []string{"something", "hello", "hi"},
})

// Abot includes a state machine designed to have conversations. This
// is the simplest possible example, but we'll cover more advanced
// cases with branching conversations, conditional next states, memory,
// jumps and more in other guides.
//
// For more information on state machines in general, see:
// https://en.wikipedia.org/wiki/Finite-state_machine
plugin.SetStates(p, [][]dt.State{[]dt.State{
{
OnEntry: func(in *dt.Msg) string {
return "Hello world!"
},
OnInput: func(in *dt.Msg) {
},
Complete: func(in *dt.Msg) (bool, string) {
return true, ""
},
},
}})
}

A complete code example of a plugin dialog that find restaurants nearby in aBot:

itsabot/plugin_restaurantsplugin_restaurants — Find great restaurants nearby.github.com

Now, my feeling is that the above code is rather hard to read, to maintain, to debug, even if “dialog volleys” are maybe high-performance in terms of CPU (but this have to be measured with metrics to be done).

The basic reason I’m now pretty contrary to the FSM implementation approach is that it become too complex to program really complex (= interesting) real conversations, if any dialog is coded as a finite state machine.

Last but not least, a FSM-driven dialog is probably too “rigid” looking as “robotic” and eventually annoying in machine-to-humans interactions. Nevertheless… FSM-driven protocols could be suitable for machine-to-machine (chatbot-to-chatbot) conversations… but this is a topic I would deepen in a future article (stay tuned)!

Decoupling dialogs scripts from logics behind!

At the end of the day I temporarily concluded that, a part very simple “transactional” applications, a better solution to manage complex chatbot conversations is to use dialog flow scripting engines. In this case there is a precise separation between the dialog script (front-end) and any transaction (done by back-end services). And, above all, scripting techniques allow to build up complex conversations by people that are not programmers or developers! And that’s a huge pro that I deepen in a next article, even if time ago I wrote few ideas about that in few articles:

Decoupling business logic with dialog flow scriptingHi Matthias!medium.com

Supescriptjs as dialog script / knowledge interchange languageHi Rob! I only recently discovered www.superscriptjs.com. I like your tech structured solution(s) and above all I thank…medium.com

Next month I’ll publish my analysis and comparison among some dialog flow scripting languages, on the article: “Chatbot Scripting Engines Comparison”, where I will also details some pros and cons of the rule-based bots engines.

21 October 2016 update

21 October update: I’m late with the “comparison” article, btw, in last months I studied better ChatScript and I wrote a very very simple introduction:
https://medium.freecodecamp.com/chatscript-for-beginners-chatbots-developers-c58bb591da8#.b9fcjpxe1
Now, even if I still hardly believe in chatbot scripting and smart rule-based opensource engines,
I’m thinking again about dialog modeling with finite state machines, with a specific interest in conversational state-full topics and machine to machine (= bots-to-bots) (automatic) (conversational).
In that path I’m re-coding (in Ruby) my previous ugly ((dialogs)) proof of concept, with now a more practical approach, coding a micro framework, codename: naif, maybe released in December 2016. 
Stay tuned :)

23 February 2017 update

GitHub - solyaris/naif: Ruby micro-framework to build dumb chat-machinesnaif - Ruby micro-framework to build dumb chat-machinesgithub.com

What do you think about it ? Please comment!

Please tap or click “︎❤” to help to promote this piece to others.