Reminders and External Events

The ReminderScheduled event and the trigger_intent endpoint let your assistant remind you about things after a given period of time, or to respond to external events (other applications, sensors, etc.). You can find a full example assistant that implements these features here.

Reminders

Instead of an external sensor, you might just want to be reminded about something after a certain amount of time. For this, Rasa provides the special event ReminderScheduled, and another event, ReminderCancelled, to unschedule a reminder.

Scheduling Reminders

Let’s say you want your assistant to remind you to call a friend in 5 seconds. (You probably want some longer time span, but for the sake of testing, let it be 5 seconds.) Thus, we define an intent ask_remind_call with some NLU data,

## intent:ask_remind_call
- remind me to call [Albert](name)
- remind me to call [Susan](name)
- later I have to call [Daksh](name)
- later I have to call [Anna](name)
...

and connect this intent with a new custom action action_set_reminder. We could make this connection by providing training stories (recommended for more complex assistants), or using the Mapping Policy.

The custom action action_set_reminder should schedule a reminder that, 5 seconds later, triggers an intent EXTERNAL_reminder with all the entities that the user provided in his/her last message (similar to an external event):

class ActionSetReminder(Action):
    """Schedules a reminder, supplied with the last message's entities."""

    def name(self) -> Text:
        return "action_set_reminder"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:

        dispatcher.utter_message("I will remind you in 5 seconds.")

        date = datetime.datetime.now() + datetime.timedelta(seconds=5)
        entities = tracker.latest_message.get("entities")

        reminder = ReminderScheduled(
            "EXTERNAL_reminder",
            trigger_date_time=date,
            entities=entities,
            name="my_reminder",
            kill_on_user_message=False,
        )

        return [reminder]

Note that this requires the datetime and rasa_sdk.events packages.

Finally, we define another custom action action_react_to_reminder and link it to the EXTERNAL_reminder intent:

- EXTERNAL_reminder:
  triggers: action_react_to_reminder

where the action_react_to_reminder is

class ActionReactToReminder(Action):
    """Reminds the user to call someone."""

    def name(self) -> Text:
        return "action_react_to_reminder"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:

        name = next(tracker.get_latest_entity_values("name"), "someone")
        dispatcher.utter_message(f"Remember to call {name}!")

        return []

Instead of a custom action, we could also have used a simple response template. But here we want to make use of the fact that the reminder can carry entities, and we can process the entities in this custom action.

Warning

Reminders are cancelled whenever you shutdown your Rasa server.

Warning

Reminders currently (Rasa 1.8) don’t work in rasa shell. You have to test them with a running Rasa X server instead.

Note

Proactively reaching out to the user is dependent on the abilities of a channel and hence not supported by every channel. If your channel does not support it, consider using the CallbackInput channel to send messages to a webhook.

Cancelling Reminders

Sometimes the user may want to cancel a reminder that he has scheduled earlier. A simple way of adding this functionality to your assistant is to create an intent ask_forget_reminders and let your assistant respond to it with a custom action such as

class ForgetReminders(Action):
    """Cancels all reminders."""

    def name(self) -> Text:
        return "action_forget_reminders"

    async def run(
        self, dispatcher, tracker: Tracker, domain: Dict[Text, Any]
    ) -> List[Dict[Text, Any]]:

        dispatcher.utter_message(f"Okay, I'll cancel all your reminders.")

        # Cancel all reminders
        return [ReminderCancelled()]

Here, ReminderCancelled() simply cancels all the reminders that are currently scheduled. Alternatively, you may provide some parameters to narrow down the types of reminders that you want to cancel. For example,

  • ReminderCancelled(intent="greet") cancels all reminders with intent greet
  • ReminderCancelled(entities={...}) cancels all reminders with the given entities
  • ReminderCancelled("...") cancels the one unique reminder with the given name “...” that you supplied during its creation

External Events

Let’s say you want to send a message from some other device to change the course of an ongoing conversation. For example, some moisture-sensor attached to a Raspberry Pi should inform your personal assistant that your favourite plant needs watering, and your assistant should then relay this message to you.

To do this, your Raspberry Pi needs to send a message to the trigger_intent endpoint of your conversation. As the name says, this injects a user intent (possibly with entities) into your conversation. So for Rasa it is almost as if you had entered a message that got classified with this intent and these entities. Rasa then needs to respond to this input with an action such as action_warn_dry. The easiest and most reliable way to connect this action with the intent is via the Mapping Policy.

Getting the Conversation ID

The first thing we need is the Session ID of the conversation that your sensor should send a notification to. An easy way to get this is to define a custom action (see Custom Actions) that displays the ID in the conversation. For example:

class ActionTellID(Action):
    """Informs the user about the conversation ID."""

    def name(self) -> Text:
        return "action_tell_id"

    async def run(
        self, dispatcher, tracker: Tracker, domain: Dict[Text, Any]
    ) -> List[Dict[Text, Any]]:

        conversation_id = tracker.sender_id

        dispatcher.utter_message(
            f"The ID of this conversation is: " f"{conversation_id}."
        )

        dispatcher.utter_message(
            f"Trigger an intent with "
            f'curl -H "Content-Type: application/json" '
            f'-X POST -d \'{{"name": "EXTERNAL_dry_plant", '
            f'"entities": {{"plant": "Orchid"}}}}\' '
            f"http://localhost:5005/conversations/{conversation_id}/"
            f"trigger_intent"
        )

        return []

In addition, we also declare an intent ask_id, define some NLU data for it, and add both action_tell_id and ask_id to the domain file, where we specify that one should trigger the other:

intents:
  - ask_id:
    triggers: action_tell_id

Now, when you ask “What is the ID of this conversation?”, the assistant replies with something like “The ID of this conversation is: 38cc25d7e23e4dde800353751b7c2d3e”.

If you want your assistant to link to the Raspberry Pi automatically, you will have to write a custom action that informs the Pi about the conversation id when your conversation starts (see Customising the session start action).

Responding to External Events

Now that we have our Session ID, we need to prepare the assistant so it responds to messages from the sensor. To this end, we define a new intent EXTERNAL_dry_plant without any NLU data. This intent will later be triggered by the external sensor. Here, we start the intent name with EXTERNAL_ to indicate that this is not something the user would say, but you can name the intent however you like.

In the domain file, we now connect the intent EXTERNAL_dry_plant with another custom action action_warn_dry, e.g.

class ActionWarnDry(Action):
    """Informs the user that a plant needs water."""

    def name(self) -> Text:
        return "action_warn_dry"

    async def run(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict[Text, Any]]:

        plant = next(tracker.get_latest_entity_values("plant"), "someone")
        dispatcher.utter_message(f"Your {plant} needs some water!")

        return []

Now, when you are in a conversation with id 38cc25d7e23e4dde800353751b7c2d3e, then running

curl -H "Content-Type: application/json" -X POST -d '{"name": "EXTERNAL_dry_plant", "entities": {"plant": "Orchid"}}' http://localhost:5005/conversations/38cc25d7e23e4dde800353751b7c2d3e/trigger_intent

in the terminal will cause your assistant to say “Your Orchid needs some water!”.