Warning: This document is for an old version of Rasa Core.

Slot Filling and Common Patterns

Note

Here we go through some typical conversation patterns and explain how to implement them with Rasa Core.

Rasa Core uses ML models to predict actions, and slots provide important information these models rely on to keep track of context and handle multi-turn dialogue. Slots are for storing information that’s relevant over multiple turns. For example, in our restaurant example, we would want to keep track of things like the cuisine and number of people for the duration of the conversation.

Collecting Information to Complete a Request (Slot Filling)

Probably the most common pattern of all 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.

To make this easier, Rasa has a special action class called FormAction. This lets you have a single action for this task, rather than separate actions for each question, e.g. utter_ask_cuisine, utter_ask_numpeople, etc.

You don’t have to use FormAction s to do slot filling, but it can be easier.

A form action has a set of required fields, which you define for the class:

class ActionSearchRestaurants(FormAction):

    RANDOMIZE = False

    @staticmethod
    def required_fields():
        return [
            EntityFormField("cuisine", "cuisine"),
            EntityFormField("number", "people"),
            BooleanFormField("vegetarian", "affirm", "deny")
        ]

    def name(self):
        return 'action_search_restaurants'

    def submit(self, dispatcher, tracker, domain):
        results = RestaurantAPI().search(
            tracker.get_slot("cuisine"),
            tracker.get_slot("people"),
            tracker.get_slot("vegetarian"))
        return [SlotSet("search_results", results)]

The way this works is that every time you call this action, it will pick one of the REQUIRED_FIELDS that’s still missing and ask the user for it. You can also ask a yes/no question with a BooleanFormField.

The story will look something like this:

* request_restaurant
     - action_restaurant_form
     - slot{"requested_slot": "people"}
* inform{"number": 3}
     - action_restaurant_form
     - slot{"people": 3}
     - slot{"requested_slot": "time"}
* inform{"time": "8pm"}
   - action_restaurant_form

Some important things to consider:

  • Your domain needs to have a slot called requested_slot. This can be unfeaturized, but if you want to support contextual questions like “why do you need to know that information?” it will help if you make this a categorical slot.
  • You need to define utterances for asking for each slot in your domain, e.g. utter_ask_{slot_name}.
  • We strongly recommend that you create these stories using interactive learning, because if you type these by hand you will probably forget to add all of the slots.
  • Any slots that are already set won’t be asked for. E.g. if someone says “I’d like a Chinese restaurant for 8 people” the submit function should get called right away.

Validation and Free-text Input

For any subclass of FormField, it’s validate() method will be called before setting it as a slot. By default this just checks that the value isn’t None, but if you want to check the value against a DB, or check a pattern is matched, you can do so by defining your own class like MyCustomFormField and overriding the validate() method.

There is also a FreeTextFormField class which will just extract the user message as a value. However, there is no way to write a ‘wildcard’ intent in Rasa Core stories as of now. Typically your NLU model will assign this free-text input to 2-3 different intents. It’s easiest to add stories for each of these.

Collecting Information using Slots

For collecting a set of preferences, you can use TextSlot s like in the restaurant example:

slots:
  cuisine:
    type: text
 people:
    type: text
 ...

When Rasa sees an entity with the same name as one of the slots, this value is automatically saved. For example, if your NLU module detects the entity people=8 in the sentence “I’d like a table for 8”, this will be saved as a slot,

>>> tracker.slots
{'people': <TextSlot(people: 8)>}

When Rasa Core predicts the next action to take, the only information it has about the TextSlot s is whether or not they are defined. So you have enough information to know that you don’t have to ask for this information again, but the value of a TextSlot has no impact on which actions Rasa Core predicts. This is explained in more detail below.

The full set of slot types and their behaviour is described here: Slots.

Using Slot Values to Influence Which Actions are Predicted

Custom Slots

Maybe your restaurant booking system can only handle bookings for up to 6 people, so the request above isn’t valid. In this case you want the value of the slot to influence the next selected action (not just whether it’s been specified). You can achieve this using a custom slot class.

The way we defined it below, if the number of people is less than or equal to 6, we return (1,0), if it’s more we return (0,1), and if it’s not set (0,0).

Rasa Core can use that information to distinguish between different situations - so long as you have some training stories where the appropriate responses take place, e.g.:

# story1
...
* inform{"people": "3"}
- action_book_table
...
# story2
* inform{"people": "9"}
- action_explain_table_limit
from rasa_core.slots import Slot

class NumberOfPeopleSlot(Slot):

  def feature_dimensionality(self):
      return 2

  def as_feature(self):
      r = [0.0] * self.feature_dimensionality()
      if self.value:
          if self.value <= 6:
              r[0] = 1.0
          else:
              r[1] = 1.0
      return r

If you want to store something like the price range, this is actually a little simpler. Variables like price range usually take on one-of-n values, e.g. low, medium, high. For these cases you can use a categorical slot.

slots:
  price_range:
    type: categorical
    values: low, medium, high

Rasa automatically represents (featurises) this as a one-hot encoding of the values: (1,0,0), (0,1,0), or (0,0,1).

Slot features

When Rasa Core runs trains a dialogue model using your stories the presence of a Slot entry will be used to help determine the next action that should be taken. This works best with CategoricalSlot slot types.

A TextSlot can have any value, but it only has a single feature. It can be set, in which case the feature has a value (1), or if it is not set it will have a value (0).

A CategoricalSlot has a number of values each of which is a feature. Taking the example below, when the restaurant_availability slot is set Rasa Core will be able to determine whether or not the restaurant in question is available and choose radically different actions to perform based on the value.

restaurant_availability:
    type: categorical
    values:
    - unknown
    - booked-out
    - waiting-list
    - available

A Slot will be set by Rasa Core if its name and the name of the entity detected by the NLU module match. The value of the slot will influence the story dialogue if you add the slot to the training stories - this is explained in the examples below. Slots can also be set explicitly from our own custom Action and influence the dialogue based on real-world information.

class ActionMakeBooking(Action):

    def run(self, dispatcher, tracker, domain):
        restaurant_name=tracker.get_slot("restaurant_name")
        location=tracker.get_slot("location")
        num_people=tracker.get_slot("people")
        date=tracker.get_slot("date")
        # this will fetch the availability of the restaurant from your DB or an API
        availability=restaurantService.check_availability(restaurant_name, location, num_people, date)
        return [SlotSet("restaurant_availability", availability)]

The snippet of code above from a hypothetical Action shows that the value of the slot restaurant_availability is determined by querying a database or API. The restaurant availability is not something that is known when we train the dialogue model, the Slot value is the only way we can alter the course of the conversation based on information from the outside world.

The data fetched from an API call can also be stored for later use without altering the outcome of a conversation as detailed in Storing API responses in the tracker.

Slot Features Example

Note

These example stories have been constructed manually for illustrative purposes. While this is a valid approach to training your model the preferred approach is to use interactive learning which generates stories that are much less error-prone.

In this first story we will try and make a booking for 5 people in a restaurant on the night of 21st August 2018. In this case the restaurant is booked out so we want to apologize to the customer and suggest similar restaurants. It is assumed that the Rasa Core model has been trained to recognise a message like “Book Murphys Bistro on August 21 for 5 people”

# restaurant unavailable
* _make_booking{"people":"5", "date":"2018-08-21T19:30:00+00:00", "restaurant_id":"145"}
- slot{"restaurant_availability": "booked-out"}
- utter_sorry_unavailable
- action_show_similar

This second story details the flow when the restaurant is available. We will tell the customer we have booked the restaurant and ask if any further help is required.

# restaurant available
* _make_booking{"people":"5", "date":"2018-08-22T19:30:00+00:00", "restaurant_id":"145"}
- slot{"restaurant_availability": "available"}
- action_make_booking
- utter_restaurant_booked
- utter_anything_more
* _bye
- utter_thank_you

In this last example, the intent make_booking was found but either Rasa Core failed to parse a date or the date was not provided. In this case we would need to ask for more information.

Note: this last story is using the fact that date is a TextSlot and therefore has a single feature that is set or not.

# restaurant request without date
* _make_booking{"people":"5", "restaurant_id":"145"}
- slot{"date": null}
- utter_date_required
* _inform{"date":"2018-08-22T19:30:00+00:00"}
- action_make_booking
- utter_restaurant_booked
- utter_anything_more
* _bye
- utter_thank_you

Storing API responses in the tracker

The result from an API call can be stored in a Slot as explained above. In that case the data is stored in a Slot that is featurized, influencing the flow of the dialogue.

A slot of type unfeaturized can be used to store the results from a database query or API call so that it will not influence the course of a dialogue. An exaple unfeaturized slot defined in a domain file:

slots:
    api_result:
        type: unfeaturized

You can set this value in a custom Action:

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

class ApiAction(Action):
    def name(self):
        return "api_action"

    def run(self, tracker, dispatcher):
        data = requests.get(url).json
        return [SlotSet("api_result", data)]

This is especially useful when you are persisting your tracker in Redis or another data store. You could cache the API or database responses separately, but storing them in the tracker means they will be persisted automatically with the rest of the dialogue state, and will be restored along with the rest of the state should the system require a reboot.

Fallback / Default Actions

Sometimes you want to fall back to a default action like saying “Sorry, I didn’t understand that”. To do this, add the FallbackPolicy to your policy ensemble. The default action will be executed if the intent recognition has a confidence below nlu_threshold or if none of the dialogue policies predict an action with confidence higher than core_threshold

from rasa_core.policies.fallback import FallbackPolicy
from rasa_core.policies.keras_policy import KerasPolicy
from rasa_core.agent import Agent

fallback = FallbackPolicy(fallback_action_name="utter_default",
                          core_threshold=0.3,
                          nlu_threshold=0.3)

agent = Agent("domain.yml",
               policies=[KerasPolicy(), fallback])