ML: RASA Chatbot: Slots and Forms


Introduction

Any decent bot must have memory. In RASA memory function, in addition to stories, is performed by slots (slots). They can be filled automatically (when extracting entities from intents), in forms or actions. Slots have different types, and their set is called a form (form). For example, data about a person (name, age, gender) can be combined into a form. A form is an active object that, after launch, tries to fill all its slots itself by persistently asking the person corresponding questions.


Slot Types

The following slot types are possible:

The used slots are listed in the slots section of the domain.yml file:
slots:                                      # domain.yml
  TEMPERATURE:
    type: float
    min_value: -100.0                       # minimum value
    max_value:  100.0                       # maximum value
    initial_value: 0                        # initial value

  RISK:
    type: categorical
    values:                                 # can take the following values:
      - low
      - medium
      - high
          
  SHOPPING_ITEMS:
    type: any    
    influence_conversation: false           # does not affect dialogue logic

If a slot has the property "influence_conversation: true" (by default), then such slots can affect the flow of the dialogue in stories (using slot_was_set sections). If the value of this property is false, then the slots only store information and do not affect dialogues. If desired, you can also create customized slot types.

There is an opinion, that for slots that affect stories (influence_conversation: true), it is not worth setting an initial value (initial_value). RASA during training sees the slot value set everywhere, and thinks that this is a "normal" case that should always be, which distorts training.

It is important not to forget that slot_was_set in stories and rules considers identical text slots with different values. If similar concretizations are needed, categorical slots should be used.


Filling Slots

By default, if the slot name (slot) and the entity name (entity) match, then the slot is filled with the value when extracting the same-named entity from the text. So, suppose in domain.yml it is defined:

entities:                                   # domain.yml
  - PERSON

slots:
  PERSON:
    type: text
In data/nlu.yml we will give examples for the user's intent to tell their name:
- intent: my_name                           # data/nlu.yml
  examples: |
    - my name is [Nastya](PERSON)
    - my name is [Anastasia](PERSON)
    - my name is [Timur](PERSON)
    - [Masha](PERSON)

When this intent is recognized, the entity PERSON will be extracted and placed in the slot with the same name PERSON, which can be used, for example, in responses:

                    
responses:                                  # domain.yml
  utter_glad_to_meet_you:
  - text: "Glad to meet you {PERSON}."
An example of such a bot is in the project Bot02_PERSON.zip.


For complex bots, the same entity (entity) may appear in different intents (intent). Therefore, automatic identification of the slot name (slot) and the entity name is not always convenient. To prohibit such identification, in the file domain.yml it is necessary to add the line:

config:                                     # domain.yml
  store_entities_as_slots: false            # no entity will be linked to a slot
If identification needs to be prohibited only for one slot, this is done in the list of slots (property auto_fill: false):
slots:                                      # domain.yml
  ORDER:                                    # slot name
    type: text                              # slot type
    auto_fill: false                        # not filled automatically


Forms

Entities and slots with different names (or with the property auto_fill: false) can be linked using forms. They are placed in the file domain.yml. The form name is simultaneously the name of the action, which can be used in stories (story) or rules (rule). When a form with several slots is activated, RASA starts repeating questions until all form slots are filled. As with rules, for forms to work in loops, in "config.yml" you need to specify:

policies:                                   # config.yml
- name: RulePolicy

Suppose there is a form named form_person_info with two slots (name and age):

forms:                                   
   form_person_info:                        # form name   
      required_slots:                       # the following slots are required for full filling:
         NAME:                              # slot name
            - type: from_entity             # filled from entity,
              entity: PERSON                # which has the name PERSON
         AGE:                               # slot name
            - type: from_entity             # filled from entity,
              entity: NUMBER                # which has the name NUMBER
              not_intent:                   # where to ignore slot filling
              - number_of_items           
To form the corresponding questions it is necessary to define bot responses in domain.yml. They begin with the prefix utter_ask_... Then comes _<form name>_<slot name> (the form name can be omitted). In our case these can be responses: utter_ask_form_person_info_NAME and utter_ask_form_person_info_AGE. In the entities section, as usual, we add PERSON, NUMBER, and in data/nlu.yml we write a sufficient number of examples for these entities.


Form Activation

To launch the form, in the stories we will write an example dialogue in which, when the form is activated, the bot "falls" into filling its slots:
stories:                                  # data/stories.yml  
- story: 1. hello, finding out name and age with successful completion
  steps:
  - intent:      greet                    # 🙎 hello
  - action:      utter_greet              # 💻 Glad to see you
  
  - action:      form_person_info         # activate the form 
  - active_loop: form_person_info         # the event Loop(form_person_info) occurs
  
  - active_loop: null                     # form filled, no active forms  
  - slot_was_set:                         # all slots were defined
    - NAME 
    - AGE
  - action:      utter_you_NAME_AGE       # 💻 Your name is Nastya and you are 16 years old (at the end)

Let's analyze the history flow now.

It is useful to analyze the events that occur when filling this form (command "=e" in !agent.py):

- intent: greet [1.00]
    hello
- action: utter_greet                       < [1.00] policy_0_MemoizationPolicy
    How glad I am to see you!
- action: form_person_info                  < [1.00] policy_0_MemoizationPolicy
- action: Loop(form_person_info)
- action: SlotSet(key: requested_slot, value: NAME)
    What is your name?
- intent: my_name [1.00]
    Masha
        - PERSON         [1.00] pos:[ 0, 4] value: Masha
- action: form_person_info                  < [1.00] policy_1_RulePolicy
- action: SlotSet(key: NAME, value: Masha)
- action: SlotSet(key: requested_slot, value: AGE)
    How old are you?
- intent: my_age [1.00]
    16
        - NUMBER         [1.00] pos:[ 0, 2] value: 16
- action: form_person_info                  < [1.00] policy_1_RulePolicy
- action: SlotSet(key: AGE, value: 16)
- action: SlotSet(key: requested_slot, value: None)
- action: Loop(None)
- action: utter_you_NAME_AGE                < [0.90] policy_2_TEDPolicy
    Your name is Masha and you are 16 years old.
In principle, the form will launch and work correctly if the event active_loop: form_person_info is removed from the story. It will be activated by RASA anyway. However, its presence in the training sequence is useful. Pay attention to the policy policy_1_RulePolicy which is applied when the form is activated. Similarly to other rules (data/rules.yml), launching the form does not "spoil" stories, no matter what happens inside the form filling (it is like a separate procedure).


Unhappy Paths

Sometimes the user starts talking nonsense or shows a desire to interrupt filling the slots during form filling, for example, with this intent:

nlu:                                        # data/nlu.yml
  - intent: irritation 
    examples: |   
    - what are you bothering me with your questions
    - go away
    - leave me alone
Let's add stories that handle this situation. The first story when the irritation intent appears asks again, and upon receiving the intent to continue, continues filling the slots:
- story: 2. hello, finding out name and age with interruption
  steps:
  - intent:      greet                    # 🙎 hello
  - action:      utter_greet              # 💻 Glad to see you
  
  - action:      form_person_info         # activate the form and start the loop  
  - active_loop: form_person_info         # form is currently active (being filled)  
  
  - intent:      irritation               # 🙎 go away
  - action:      utter_not_understand     # 💻 I didn't understand you. Shall we continue?
  - intent:      affirm                   # 🙎 yes
    
  - action:      form_person_info         # activate the form     
  - active_loop: form_person_info         # form is currently active (being filled)  
  
  - active_loop: null                     # form filled, no active forms
  - slot_was_set:                         # all slots were defined
    - NAME 
    - AGE
  - action:      utter_you_NAME_AGE       # 💻 Your name is Nastya and you are 16 years old (at the end)  
The second story terminates the form filling loop by calling the action_deactivate_loop action:
- story: 3. hello, finding out name and age with interruption
  steps:
  - intent:      greet                    # 🙎 hello
  - action:      utter_greet              # 💻 Glad to see you
  
  - action:      form_person_info         # activate the form and start the loop  
  - active_loop: form_person_info         # form is currently active (being filled)  
  
  - intent:      irritation               # 🙎 go away
  - action:      utter_not_understand     # 💻 I didn't understand you. Shall we continue?
  - intent:      deny                     # 🙎 no    
  - action:      action_deactivate_loop   # forcefully terminate the form
  - active_loop: null
  - action:      utter_goodbye            # See you soon

You can also handle interruptions of form filling in rules:

rules:
- rule: Example of an unhappy path
  condition:  
  - active_loop: restaurant_form           # Condition - form is active
  steps:  
  - intent: chitchat                       # Unhappy interruption with chatter `chitchat`.
  - action: utter_chitchat  
  - action: restaurant_form                # Return to filling the form
  - active_loop: restaurant_form
  


Form Testing Without Interruption

For testing form filling in a situation where the person "obediently" answers, in tests/test_stories.yml we will create the following story example:
stories:                                    # tests/test_stories.yml

- story: 1. hello, finding out name and age with successful completion
  steps:
    - user: |                               # 🙎 hello
        hello                           
      intent:      greet                 
    - action:      utter_greet              # 💻 Glad to see you    
    - action:      form_person_info         # the form should be activated
    - active_loop: form_person_info         # and enter the loop        
    
    - user: |                               # 🙎 Nastya
        [Nastya](PERSON)      
      intent:      my_name  
    - action:      form_person_info
    - active_loop: form_person_info         # continue the loop        
    
    - user: |                               # 🙎 16
        [16](NUMBER)         
      intent:      my_age          
    - action:      form_person_info      
    - active_loop: null                     # should be deactivated            
    - action:      utter_you_NAME_AGE       # 💻 Your name is Nastya and you are 16 years old (at the end)

Form Testing With Interruption

Suppose the person starts behaving disobediently at the very beginning of the dialogue. This corresponds to the following test:
- story: 2. hello, finding out name and age with interruption at the beginning
  steps:
    - user: |                               # 🙎 hello
        hello                           
      intent:      greet                 
    - action:      utter_greet              # 💻 Glad to see you    
    - action:      form_person_info         # the form should be activated
    - active_loop: form_person_info         # form is currently active (being filled)    
    
    - user: |                               # 🙎 go away 
        go away      
      intent:      irritation  
    - action:      utter_not_understand     # 💻 I didn't understand you. Shall we continue?

    - user: |                               # 🙎 yes
        well let's go          
      intent:      affirm                   # 🙎 well let's go             
    - action:      form_person_info         # the form should be activated
    - active_loop: form_person_info         # and enter the loop again        
    
    - user: |                               # 🙎 Nastya
        [Nastya](PERSON)      
      intent:      my_name  
    - action:      form_person_info
    - active_loop: form_person_info         # continue the loop        
    
    - user: |                               # 🙎 16
        [16](NUMBER)         
      intent:      my_age          
    - action:      form_person_info      
    - active_loop: null                     # should be deactivated             
    - action:      utter_you_NAME_AGE       # 💻 Your name is Nastya and you are 16 years old (at the end)
Similarly, a person could try to interrupt form filling in the middle. For this, we will write one more test:
- story: 3. hello, finding out name and age with interruption in the middle
  steps:
    - user: |                               # 🙎 hello
        hello                           
      intent:      greet                 
    - action:      utter_greet              # 💻 Glad to see you    

    - action:      form_person_info         # the form should be activated
    - active_loop: form_person_info         # form is currently active (being filled)    
                      
    - user: |                               # 🙎 Nastya
        [Nastya](PERSON)      
      intent:      my_name  
    - action:      form_person_info
    - active_loop: form_person_info         # continue the loop        
    
    - user: |                               # 🙎 go away
        go away      
      intent:      irritation  
    - action:      utter_not_understand     # 💻 I didn't understand you. Shall we continue?
    - user: |                               # 🙎 yes
        well let's go          
      intent:      affirm                   # 🙎 well let's go       
      
    - action:      form_person_info         # the form should be activated
    - active_loop: form_person_info         # and enter the loop again        
    
    - user: |                               # 🙎 16
        [16](NUMBER)         
      intent:      my_age          
    - action:      form_person_info      
    - active_loop: null                     # should be deactivated
             
    - action:      utter_you_NAME_AGE       # 💻 Your name is Nastya and you are 16 years old (at the end)