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:
- text - arbitrary text (its content does not affect stories);
- bool - stores one of two values: true, false, which can affect stories;
- float - floating point number; for it you can define a range min_value, max_value;
- categorical - one of the predefined values (can also affect stories);
- list - list of values (array);
- any - value of any type that can be passed in JSON (dictionary, list, etc.)
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 slotIf 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.
- - action: form_person_info is an action, similar to any utter. But at the same time, not one response is output, but a loop is launched in which the bot will try to find out the value of the slots by asking questions and expecting to receive entities from the answers that fill the slots.
- - active_loop: form_person_info - does nothing, but only reports that the form is still active at this point (similarly works slot_was_set in stories).
- - active_loop: null informs that there are no more active forms in the loop.
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)