Building Stateless Bots using Rasa Stack

This blog aims at exploring the Rasa Stack to create a stateless chat-bot. We will look into how, the recently released Rasa Core, which provides machine learning based dialogue management, helps in maintaining the context of conversations using machine learning in an efficient way.

If you have developed chatbots, you would know how hopelessly bots fail in maintaining the context once complex use-cases need to be developed. There are some home-grown approaches that people currently use to build stateful bots. The most naive approach is to create the state machines where you create different states and based on some logic take actions. As the number of states increases, more levels of nested logic are required or there is a need to add an extra state to the state machine, with another set of rules for how to get in and out of that state. Both of these approaches lead to fragile code that is harder to maintain and update. Anyone who’s built and debugged a moderately complex bot knows this pain.

After building many chatbots, we have experienced that flowcharts are useful for doing the initial design of a bot and describing a few of the known conversation paths, but we shouldn’t hard-code a bunch of rules since this approach doesn’t scale beyond simple conversations.

Thanks to the Rasa guys who provided a way to go stateless where scaling is not at all a problem. Let's build a bot using Rasa Core and learn more about this.

Rasa Core: Getting Rid of State Machines

The main idea behind Rasa Core is that thinking of conversations as a flowchart and implementing them as a state machine doesn’t scale. It’s very hard to reason about all possible conversations explicitly, but it’s very easy to tell, mid-conversation, if a response is right or wrong. For example, let’s consider a term insurance purchase bot, where you have defined different states to take different actions. Below diagram shows an example state machine:

image-1.png

Let’s consider a sample conversation where a user wants to compare two policies listed by policy_search state.

In above conversation, it can be compared very easily by adding some logic around the intent campare_policies. But real life is not so easy, as a majority of conversations are edge cases. We need to add rules manually to handle such cases, and after testing we realize that these clash with other rules we wrote earlier.

Rasa guys figured out how machine learning can be used to solve this problem. They have released Rasa Core where the logic of the bot is based on a probabilistic model trained on real conversations.
 

Structure of a Rasa Core App

Let’s understand few terminologies we need to know to build a Rasa Core app:

1. Interpreter: An interpreter is responsible for parsing messages. It performs the Natural Language Understanding and transforms the message into structured output i.e. intent and entities. In this blog, we are using Rasa NLU model as an interpreter. Rasa NLU comes under the Rasa Stack. In Training section, it is shown in detail how to prepare the training data and create a model.
 

2. Domain: To define a domain we create a domain.yml file, which defines the universe of your bot. Following things need to be defined in a domain file:

  • Intents: Things we expect the user to say. It is more related to Rasa NLU.
  • Entities: These represent pieces of information extracted what user said. It is also related to Rasa NLU.
  • Templates: We define some template strings which our bot can say. The format for defining a template string is utter_<intent>. These are considered as actions which bot can take.
  • Actions: List of things bot can do and say.  There are two types of actions we define one those which will only utter message (Templates) and others some customised actions where some required logic is defined. Customised actions are defined as Python classes and are referenced in domain file.
  • Slots: These are user-defined variables which need to be tracked in a conversation. For e.g to buy a term insurance we need to keep track of what policy user selects and details of the user, so all these details will come under slots. 

3. Stories: In stories, we define what bot needs to do at what point in time. Based on these stories, a probabilistic model is generated which is used to decide which action to be taken next. There are two ways in which stories can be created which are explained in next section.
 

Let’s combine all these pieces together. When a message arrives in a Rasa Core app initially, interpreter transforms the message into structured output i.e. intents and entities. The Tracker is the object which keeps track of conversation state. It receives the info that a new message has come in. Then based on dialog model we generate using domain and stories policy chooses which action to take next. The chosen action is logged by the tracker and response is sent back to the user.
 

Training and Running A Sample Bot

We will create a simple Facebook chat-bot named Secure Life which assists you in buying term life insurance. To keep the example simple, we have restricted options such as age-group, term insurance amount, etc.

There are two models we need to train in the Rasa Core app:

Rasa NLU model based on which messages will be processed and converted to a structured form of intent and entities. Create following two files to generate the model:

data.json: Create this training file using the rasa-nlu trainer. Click here to know more about the rasa-nlu trainer.

nlu_config.json: This is the configuration file.

{
  "pipeline": "spacy_sklearn",
  "path" : "./models",
  "project": "nlu",
  "data" : "./data/data.md"
}

Run below command to train the rasa-nlu model:-

$ python -m rasa_nlu.train -c nlu_model_config.json --fixed_model_name current

Dialogue Model: This model is trained on stories we define, based on which the policy will take the action. There are two ways in which stories can be generated:

  • Supervised Learning: In this type of learning we will create the stories by hand, writing them directly in a file. It is easy to write but in case of complex use-cases it is difficult to cover all scenarios.
  • Reinforcement Learning:  The user provides feedback on every decision taken by the policy. This is also known as interactive learning. This helps in including edge cases which are difficult to create by hand. You must be thinking how it works? Every time when a policy chooses an action to take, it is asked from the user whether the chosen action is correct or not. If the action taken is wrong, you can correct the action on the fly and store the stories to train the model again.

Since the example is simple, we have used supervised learning method, to generate the dialogue model. Below is the stories.md file.

## All yes
* greet
    - utter_greet
* affirm
    - utter_very_much_so
* affirm
    - utter_gender
* gender
    - utter_coverage_duration
    - action_gender
* affirm
    - utter_nicotine
* affirm
    - action_nicotine
* age
    - action_thanks

## User not interested
* greet
    - utter_greet
* deny
    - utter_decline

## Coverage duration is not sufficient
* greet
    - utter_greet
* affirm
    - utter_very_much_so
* affirm
    - utter_gender
* gender
    - utter_coverage_duration
    - action_gender
* deny
    - utter_decline

Run below command to train dialogue model :

$ python -m rasa_core.train -s <path to stories.md file> -d <path to domain.yml> -o models/dialogue --epochs 300

Define a Domain: Create domain.yml file containing all the required information. Among the intents and entities write all those strings which bot is supposed to see when user say something i.e. intents and entities you defined in rasa NLU training file.

intents:
 - greet
 - goodbye
 - affirm
 - deny
 - age
 - gender

slots:
  gender:
    type: text
  nicotine:
    type: text
  agegroup:
    type: text

templates:
  utter_greet:
    - "hey there! welcome to Secure-Life!\nI can help you quickly estimate your rate of coverage.\nWould you like to do that ?"

  utter_very_much_so:
    - "Great! Let's get started.\nWe currently offer term plans of Rs. 1Cr. Does that suit your need?"

  utter_gender:
    - "What gender do you go by ?"

  utter_coverage_duration:
    - "We offer this term plan for a duration of 30Y. Do you think that's enough to cover entire timeframe of your financial obligations ?"

  utter_nicotine:
    - "Do you consume nicotine-containing products?"

  utter_age:
    - "And lastly, how old are you ?"

  utter_thanks:
    - "Thank you for providing all the info. Let me calculate the insurance premium based on your inputs."

  utter_decline:
    - "Sad to see you go. In case you change your plans, you know where to find me :-)"

  utter_goodbye:
    - "goodbye :("

actions:
  - utter_greet
  - utter_goodbye
  - utter_very_much_so
  - utter_coverage_duration
  - utter_age
  - utter_nicotine
  - utter_gender
  - utter_decline
  - utter_thanks
  - actions.ActionGender
  - actions.ActionNicotine
  - actions.ActionThanks

Define Actions: Templates defined in domain.yml also considered as actions. A sample customized action is shown below where we are setting a slot named gender with values according to the option selected by the user.

from rasa_core.actions.action import Action
from rasa_core.events import SlotSet

class ActionGender(Action):
    def name(self):
        return 'action_gender'
    def run(self, dispatcher, tracker, domain):
        messageObtained = tracker.latest_message.text.lower()

        if ("male" in messageObtained):
          return [SlotSet("gender", "male")]
        elif ("female" in messageObtained):
          return [SlotSet("gender", "female")]
        else:
          return [SlotSet("gender", "others")]

Running the Bot

Create a Facebook app and get the app credentials. Create a bot.py file as shown below:

from rasa_core import utils
from rasa_core.agent import Agent
from rasa_core.interpreter import RasaNLUInterpreter
from rasa_core.channels import HttpInputChannel
from rasa_core.channels.facebook import FacebookInput

logger = logging.getLogger(__name__)

def run(serve_forever=True):
    # create rasa NLU interpreter
    interpreter = RasaNLUInterpreter("models/nlu/current")
    agent = Agent.load("models/dialogue", interpreter=interpreter)

    input_channel = FacebookInput(
        fb_verify="your_fb_verify_token",  # you need tell facebook this token, to confirm your URL
        fb_secret="your_app_secret",  # your app secret
        fb_tokens={"your_page_id": "your_page_token"},   # page ids + tokens you subscribed to
        debug_mode=True    # enable debug mode for underlying fb library
    )

    if serve_forever:
        agent.handle_channel(HttpInputChannel(5004, "/app", input_channel))
    return agent

if __name__ == '__main__':
    utils.configure_colored_logging(loglevel="DEBUG")
    run()

Run the file and your bot is ready to test. Sample conversations are provided below:

iamge 2.png
image 3.png


Summary

You have seen how Rasa Core has made it easier to build bots. Just create few files and boom! Your bot is ready! Isn't it exciting? I hope this blog provided you some insights on how Rasa Core works. Start exploring and let us know in the comments if you need any help in building chatbots using Rasa Core.