Skip to content

April 21st, 2022

3 ways to use the new global slot mappings in your chatbot development

  • portrait of Johannes Mosig

    Johannes Mosig

  • portrait of Rachael Tatman

    Rachael Tatman

With Rasa 3.0 we enabled “global slot mappings”, which gives you more control over information flow in your chatbot. This blog post will go over three new approaches unlocked by this.

Slots let you store information over the course of a conversation, like a user's name, account number, and whether they're booking a flight or train. Slot mapping is the process of gathering and preparing this information so that the dialogue policy can use it to choose the next action or insert it in the bot’s response templates. With Rasa 3.0 we enabled “global slot mappings”, which gives you more control over this information flow.

In this blog post, we'll show you three new ways to use global slot mappings in your Rasa assistant to solve common problems and unlock new funcionality.

1. Decide what your bot should do based on external information

Let’s say we want our bot to behave differently depending on the time of day. For example, if the user asks to chat with a human and it’s outside of office hours. The bot should tell the office hours and continue to try fixing the user’s problem. But if it’s within office hours, the bot should hand the conversation over to a human operator.

This is an example of a high-level decision, so we want to use a featurized slot that influences the policy. Hence, we define an office_open slot that should be True whenever the office is open, and False otherwise.

YAML
slots:
office_open:
type: bool
influence_conversation: true
mappings:
- type: custom
action: action_set_office_open

The value of the slot is set in the action_set_office_open, which we need to define in the action server:

Python
class ActionSetOfficeOpen(Action):
def name(self) -> Text:
return "action_set_office_open"
def run(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any]
) -> List[Dict[Text, Any]]:
office_open = random.choice([True, False]) # just for debugging
return [
SlotSet("office_open", office_open)
]

This code will now run after every user message, so we have a guarantee that the office_open stays up to date. Note: the name “action” may be confusing. This is not an action that the policy would see or predict. It is just a piece of code that is run at the end of the NLU pipeline.

With the office_open slot in place, we can now define the bot’s behavior in two short rules***:

YAML
- rule: Deal with request_human at office hours
steps:
- intent: request_human
- slot_was_set:
- office_open: true
- action: utter_handover_to_human # Just an example for the demo
- rule: Deal with request_human outside office hours
steps:
- intent: request_human
- slot_was_set:
- office_open: false
- action: utter_office_hours

Before we introduced global slot mappings in Rasa, this was not possible. You either had to train the policy to execute a custom action that would fill the office_open slot, or you had to handle the decision within a custom action that either hands over to a human or prints the office hours.

***Rules are written in the same way as stories, but they typically represent only pieces of a conversation. Whenever the bot encounters a situation where the conversation matches the rule, RulePolicy executes the next action defined by the rule.

2. Use a dummy slot to fill multiple slots at the same time

The usual YAML format for slot mappings suggests that all slots are independently filled and you have one mapping (custom slot filling action) per slot. However, for most applications the slot values are interdependent and it is better to declare a single function that does all the mapping.

To do this, you define a dummy slot with a custom mapping

YAML
slots:
dummy:
type: any
influence_conversation: false
mappings:
- type: custom
action: global_slot_mapping

and fill all slots from within the global_slot_mapping function using a custom action you've written for that purpose.

Example: Condition the policies on low entity extraction scores

With the office_open slot we let the bot react to external information (the time of day). But since the slot mapping actions have access to the conversation history (the tracker object), we can also set slots based on properties of the dialogue, including information that usually does not enter the policy such as entity extraction confidence scores.

Let’s say we add a form to our bot, where the bot asks for the item type and it’s last known location when the user has lost something. Now the slots and forms are defined in the domain as

YAML
slots:
dummy:
type: any
mappings:
- type: custom
action: global_slot_mapping
# Featurized slots for policies
office_open:
type: bool
influence_conversation: true
mappings:
- type: custom
low_entity_score:
type: bool
influence_conversation: true
initial_value: false
mappings:
- type: custom
# Unfeaturized slots for forms
lost_item_type:
type: text
influence_conversation: false
mappings:
- type: custom
last_known_item_location:
type: text
influence_conversation: false
mappings:
- type: custom
# Unfeaturized slots for NLG
unclear_entity_value:
type: text
influence_conversation: false
mappings:
- type: custom
forms:
main_form:
required_slots:
- lost_item_type
- last_known_item_location

You see that only the dummy slot has a slot mapping action. This action now takes care of the entire mapping process:

Python
class GlobalSlotMapping(Action):
def name(self) -> Text:
return "global_slot_mapping"
def run(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any]
) -> List[Dict[Text, Any]]:
new_slot_values: Dict[Text, Any] = dict()
# Office hours
new_slot_values["office_open"] = random.choice([True, False])
# Entity mapping and low entity score handling
low_entity_score: bool = False
for entity_type, value, score in get_entity_data(tracker.latest_message):
print(f"{entity_type}: {value} ({score})")
if score < 0.98:
low_entity_score = True
new_slot_values["unclear_entity_value"] = value
else:
if entity_type == "item":
new_slot_values["lost_item_type"] = value
elif entity_type == "location":
new_slot_values["last_known_item_location"] = value
new_slot_values["low_entity_score"] = low_entity_score
return [
SlotSet(name, value)
for name, value in new_slot_values.items()
]

Now we can define a rule to handle the low extraction score:

YAML
- rule: Handle low entity score in main form
condition:
- active_loop: main_form
steps:
- intent: inform
- slot_was_set:
- low_entity_score: true
- action: utter_entity_unclear
- action: main_form
- active_loop: main_form

And with this, our bot can have the following conversation:

Your input ->  hi                                                                                                                                                                                      
Hello! How can I help you?
I am Lost & Found Bot and can help you find things.
Your input ->  i lost my umbrella                                                                                                                                                                      
Where did you last see your item?
Your input ->  on the tran                                                                                                                                                                             
I'm not sure what you mean by 'tran'.
Where did you last see your item?
Your input ->  i mean, on the train                                                                                                                                                                    
You are looking for 'umbrella', last seen at 'train'

Note, however, that the confidence scores of entity extractors are not necessarily reliable. It can easily happen that an entity is wrongly extracted and still has a very high score. Re-training your model might also drastically change confidence scores.

3. Use slots for response generation

We just saw how you can define global slot mappings to influence the policy’s decisions via featurized slots. Alternatively, we can skip the policy in the information flow diagram and only influence the response generation via unfeaturized slots.

Given the response template (utter_*), slots can influence the bot’s response in two ways: be used in response conditions or serve as variables.

Example: Deal with repeated questions

You may want your bot to give ever more detailed answers if the user keeps asking the same question again and again. For example, if the user keeps asking about the bot’s abilities and the bot replies with utter_abilities, you don’t want the bot to always say the exact same thing. But you also don’t want to choose randomly between response templates. With global slot mappings, you can do this by keeping track of how often the bot executed utter_abilities, putting this information into a slot num_utter_abilities, and defining this response template such that it is conditioned on this slot. The utter_abilities response could be

YAML
responses:
utter_abilities:
- text: "I am Lost & Found Bot and can help you find things."
condition:
- type: slot
name: num_utter_abilities
value: 0
- text: "I can help you find things that you've lost either on a train or some other place in town."
condition:
- type: slot
name: num_utter_abilities
value: 1
- text: "Actually, I'm just a demo, so don't expect me to really find something."
condition:
- type: slot
name: num_utter_abilities
value: 2
- text: "I can't do anything beyond what I already mentioned, sorry."

and we keep the num_utter_abilities slot up to date with the following code in the slot mapping function:

Python
class GlobalSlotMapping(Action):
def name(self) -> Text:
return "global_slot_mapping"
def run(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any]
) -> List[Dict[Text, Any]]:
new_slot_values: Dict[Text, Any] = dict()
# ...
# Count how often the bot executed `utter_abilities`
num_utter_abilities = 0
for event in tracker.applied_events():
if event_is_action(event, "utter_abilities"):
num_utter_abilities += 1
new_slot_values["num_utter_abilities"] = num_utter_abilities
return [
SlotSet(name, value)
for name, value in new_slot_values.items()
]

Finally, we have to define some new intents and the appropriate rule such that your bot responds correctly:

YAML
- rule: Respond to ask_what_can_do
steps:
- intent: ask_what_can_do
- action: utter_abilities
- intent: tell_me_more # for this to work, set `restrict_rules: false` for the RulePolicy
- action: utter_abilities
- intent: tell_me_more
- action: utter_abilities

And with this, our bot can have the following conversation:

Your input ->  hi                                                                                      
Hello! How can I help you?
I am Lost & Found Bot and can help you find things.
Your input ->  what can you do?                                                                        
I can help you find things that you've lost either on a train or some other place in town.
Your input ->  what else?                                                                              
Actually, I'm just a demo, so don't expect me to really find something.
Your input ->  tell me more                                                                            
I can't do anything beyond what I already mentioned, sorry.

This is a very simplistic application. But in principle, you can use the slot mapping to condition your response template on any information that you can extract from the conversation history (or external sources). For example, you could add a light-weight classifier to the NLU pipeline and adjust your responses according to the user’s style of writing. Remember, however, to only condition your bot’s responses on things that you know for sure about the user.

Conclusion

As you can see, global slot mappings introduce a lot of new functionality to Rasa Open Source that may not be immediately apparent when you first learn about them. They're a flexible and powerful new feature and we look forward to seeing what other problems they can solve for you.