notice

This is unreleased documentation for Rasa Open Source Documentation Master/Unreleased version.
For the latest released documentation, see the latest version (2.0.x).

Version: Master/Unreleased

Forms

One of the most common conversation patterns is to collect a few pieces of information from a user in order to do something (book a restaurant, call an API, search a database, etc.). This is also called **slot filling**.

Usage

To use forms with Rasa Open Source you need to make sure that the Rule Policy is added to your policy configuration. For example:

policies:
- name: RulePolicy

Defining a Form

Define a form by adding it to the forms section in your domain. The name of the form is also the name of the action which you can use in stories or rules to handle form executions. You also need to define slot mappings for each slot which your form should fill. You can specify one or more slot mappings for each slot to be filled.

The following example form restaurant_form will fill the slot cuisine from an extracted entity cuisine and slot num_people from entity number.

forms:
restaurant_form:
cuisine:
- type: from_entity
entity: cuisine
num_people:
- type: from_entity
entity: number

Once the form action gets called for the first time, the form gets activated and will prompt the user for the next required slot value. It does this by looking for a response called utter_ask_{form_name}_{slot_name} or utter_ask_{slot_name} if the former isn't found. Make sure to define these responses in your domain file for each required slot.

Activating a Form

To activate a form you need to add a story or rule, which describes when the assistant should run the form. In the case a specific intent triggering a form, you can for example use the following rule:

rules:
- rule: Activate form
steps:
- intent: request_restaurant
- action: restaurant_form
- active_loop: restaurant_form
note

The active_loop: restaurant_form step indicates that the form should be activated after restaurant_form was run.

Deactivating a Form

A form will automatically deactivate itself once all required slots are filled. You can describe your assistant's behavior for the end of a form with a rule or a story. If you don't add an applicable story or rule, the assistant will automatically listen for the next user message after the form is finished. The following example runs the utterance utter_all_slots_filled as soon as the form your_form filled all required slots.

rules:
- rule: Submit form
condition:
# Condition that form is active.
- active_loop: restaurant_form
steps:
# Form is deactivated
- action: restaurant_form
- active_loop: null
- slot_was_set:
- requested_slot: null
# The actions we want to run when the form is submitted.
- action: utter_submit
- action: utter_slots_values

Users might want to break out of a form early. Please see Writing Stories / Rules for Unhappy Form Paths on how to write stories or rules for this case.

Slot Mappings

Rasa Open Source comes with four predefined mappings to fill the slots of a form based on the latest user message. Please see Custom Slot Mappings if you need a custom function to extract the required information.

from_entity

The from_entity mapping fills slots based on extracted entities. It will look for an entity called entity_name to fill a slot slot_name. If intent_name is None, the slot will be filled regardless of intent name. Otherwise, the slot will only be filled if the user's intent is intent_name.

If role_name and/or group_name are provided, the role/group label of the entity also needs to match the given values. The slot mapping will not apply if the intent of the message is excluded_intent. Note that you can also define lists of intents for the parameters intent and not_intent.

forms:
your_form:
slot_name:
- type: from_entity
entity: entity_name
role: role_name
group: group name
intent: intent_name
not_intent: excluded_intent

In from_entity mapping, when an extracted entity uniquely maps onto a slot, the slot will be filled even if this slot wasn't requested by the form. The extracted entity will be ignored if the mapping is not unique.

forms:
your_form:
departure_city:
- type: from_entity
entity: city
role: from
- type: from_entity
entity: city
arrival_city:
- type: from_entity
entity: city
role: to
- type: from_entity
entity: city
arrival_date:
- type: from_entity
entity: date

In the example above, an entity date uniquely sets the slot arrival_date, an entity city with a role from uniquely sets the slot departure_city and an entity city with a role to uniquely sets the slot arrival_city, therefore they can be used to fit corresponding slots even if these slots were not requested. However, entity city without a role can fill both departure_city and arrival_city slots, depending which one is requested, so if an entity city is extracted when slot arrival_date is requested, it'll be ignored by the form.

from_text

The from_text mapping will use the text of the next user utterance to fill the slot slot_name. If intent_name is None, the slot will be filled regardless of intent name. Otherwise, the slot will only be filled if the user's intent is intent_name.

The slot mapping will not apply if the intent of the message is excluded_intent. Note that you can define lists of intents for the parameters intent and not_intent.

forms:
your_form:
slot_name:
- type: from_text
intent: intent_name
not_intent: excluded_intent

from_intent

The from_intent mapping will fill slot slot_name with value my_value if user intent is intent_name or None. The slot mapping will not apply if the intent of the message is excluded_intent. Note that you can also define lists of intents for the parameters intent and not_intent.

note

The from_intent slot mapping will not apply during the initial activation of the form. To fill a slot based on the intent that activated the form, use the from_trigger_intent mapping.

forms:
your_form:
slot_name:
- type: from_intent
value: my_value
intent: intent_name
not_intent: excluded_intent

from_trigger_intent

The from_trigger_intent mapping will fill slot slot_name with value my_value if the form was activated by a user message with intent intent_name. The slot mapping will not apply if the intent of the message is excluded_intent. Note that you can also define lists of intents for the parameters intent and not_intent.

forms:
your_form:
slot_name:
- type: from_trigger_intent
value: my_value
intent: intent_name
not_intent: excluded_intent

Writing Stories / Rules for Unhappy Form Paths

Your users will not always respond with the information you ask of them. Typically, users will ask questions, make chitchat, change their mind, or otherwise stray from the happy path.

While a form is active, if a user's input does not fill the requested slot, the execution of the form action will be rejected i.e. the form will automatically raise an ActionExecutionRejection. These are the specific scenarios in which a form will raise an ActionExecutionRejection:

To intentionally reject the form execution, you can also return an ActionExecutionRejected event as part of your custom validations or slot mappings.

To handle situations that might cause a form's execution to be rejected, you can write rules or stories that include the expected interruptions. For example, if you expect your users to chitchat with your bot, you could add a rule to handle this:

rules:
- rule: Example of an unhappy path
condition:
# Condition that form is active.
- active_loop: restaurant_form
steps:
# This unhappy path handles the case of an intent `chitchat`.
- intent: chitchat
- action: utter_chitchat
# Return to form after handling the `chitchat` intent
- action: restaurant_form
- active_loop: restaurant_form

In some situations, users may change their mind in the middle of the form action and decide not to go forward with their initial request. In cases like this, the assistant should stop asking for the requested slots.

You can handle such situations gracefully using a default action action_deactivate_loop which will deactivate the form and reset the requested slot. An example story of such conversation could look as follows:

stories:
- story: User interrupts the form and doesn't want to continue
steps:
- intent: request_restaurant
- action: restaurant_form
- active_loop: restaurant_form
- intent: stop
- action: utter_ask_continue
- intent: stop
- action: action_deactivate_loop
- active_loop: null

It is strongly recommended that you build these rules or stories using interactive learning. If you write these rules / stories by hand you will likely miss important things.

Advanced Usage

Forms are fully customizable using Custom Actions.

Validating Form Input

After extracting a slot value from user input, you can validate the extracted slots. By default Rasa Open Source only validates if any slot was filled after requesting a slot.

You can implement a Custom Action validate_{form_name} to validate any extracted slots. Make sure to add this action to the actions section of your domain:

actions:
- validate_restaurant_form

When the form is executed it will run your custom action.

This custom action can extend FormValidationAction class to simplify the process of validating extracted slots. In this case, you need to write functions named validate_<slot_name> for every extracted slot.

The following example shows the implementation of a custom action which validates that the slot named cuisine is valid.

from typing import Text, List, Any, Dict
from rasa_sdk import Tracker, FormValidationAction
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.types import DomainDict
class ValidateRestaurantForm(FormValidationAction):
def name(self) -> Text:
return "validate_restaurant_form"
@staticmethod
def cuisine_db() -> List[Text]:
"""Database of supported cuisines"""
return ["caribbean", "chinese", "french"]
def validate_cuisine(
self,
slot_value: Any,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: DomainDict,
) -> Dict[Text, Any]:
"""Validate cuisine value."""
if slot_value.lower() in self.cuisine_db():
# validation succeeded, set the value of the "cuisine" slot to value
return {"cuisine": slot_value}
else:
# validation failed, set this slot to None so that the
# user will be asked for the slot again
return {"cuisine": None}

You can also extend the Action class and retrieve extracted slots with tracker.slots_to_validate to fully customize the validation process.

Custom Slot Mappings

If none of the predefined Slot Mappings fit your use case, you can use the Custom Action validate_{form_name} to write your own extraction code. Rasa Open Source will trigger this function when the form is run.

Make sure your custom action returns SlotSet events for every extracted value. The following example shows the implementation of a custom slot mapping which sets a slot based on the length of the last user message.

from typing import Dict, Text, List
from rasa_sdk import Tracker
from rasa_sdk.events import EventType
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk import Action
from rasa_sdk.events import SlotSet
class ValidateRestaurantForm(Action):
def name(self) -> Text:
return "validate_restaurant_form"
def run(
self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
) -> List[EventType]:
text_of_last_user_message = tracker.latest_message.get("text")
return [SlotSet("user_message_length", len(text_of_last_user_message))]

Requesting Extra Slots

If you make frequent changes to the required slots and don't want to retrain your assistant when your form changes, you can also use a Custom Action validate_{form_name} to define which slots should be requested. Rasa Open Source will run your custom action whenever the form validates user input. Set the slot requested_slot to the name of the slot which should be extracted next. If all desired slots are filled, set requested_slot to None.

The following example shows the implementation of a custom action which requests the three slots cuisine, num_people, and outdoor_seating.

from typing import Dict, Text, List
from rasa_sdk import Tracker
from rasa_sdk.events import EventType
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk import Action
from rasa_sdk.events import SlotSet
class ValidateRestaurantForm(Action):
def name(self) -> Text:
return "validate_restaurant_form"
def run(
self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
) -> List[EventType]:
required_slots = ["cuisine", "num_people", "outdoor_seating"]
for slot_name in required_slots:
if tracker.slots.get(slot_name) is None:
# The slot is not filled yet. Request the user to fill this slot next.
return [SlotSet("requested_slot", slot_name)]
# All slots are filled.
return [SlotSet("requested_slot", None)]

The requested_slot slot

The slot requested_slot is automatically added to the domain as a slot of type text. The value of the requested_slot will be ignored during conversations. If you want to change this behavior, you need to add the requested_slot to your domain file as a categorical slot with influence_conversation set to true. You might want to do this if you want to handle your unhappy paths differently, depending on what slot is currently being asked from the user. For example, if your users respond to one of the bot's questions with another question, like why do you need to know that? The response to this explain intent depends on where we are in the story. In the restaurant case, your stories would look something like this:

stories:
- story: explain cuisine slot
steps:
- intent: request_restaurant
- action: restaurant_form
- active_loop: restaurant
- slot_was_set:
- requested_slot: cuisine
- intent: explain
- action: utter_explain_cuisine
- action: restaurant_form
- active_loop: null
- story: explain num_people slot
steps:
- intent: request_restaurant
- action: restaurant_form
- active_loop: restaurant
- slot_was_set:
- requested_slot: cuisine
- slot_was_set:
- requested_slot: num_people
- intent: explain
- action: utter_explain_num_people
- action: restaurant_form
- active_loop: null

Again, it is strongly recommended that you use interactive learning to build these stories.

Using a Custom Action to Ask For the Next Slot

As soon as the form determines which slot has to be filled next by the user, it will execute the action utter_ask_{form_name}_{slot_name} or utter_ask_{slot_name} to ask the user to provide the necessary information. If a regular utterance is not enough, you can also use a custom action action_ask_{form_name}__{slot_name} or action_ask_{slot_name} to ask for the next slot.

from typing import Dict, Text, List
from rasa_sdk import Tracker
from rasa_sdk.events import EventType
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk import Action
class AskForSlotAction(Action):
def name(self) -> Text:
return "action_ask_cuisine"
def run(
self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
) -> List[EventType]:
dispatcher.utter_message(text="What cuisine?")
return []