Skip to content

Commit

Permalink
Implement reminderbot demo and add user guide (#5189)
Browse files Browse the repository at this point in the history
Co-authored-by: Tobias Wochinger <[email protected]>
  • Loading branch information
Johannes E. M. Mosig and wochinge authored Feb 25, 2020
1 parent 730fd06 commit 00143ae
Show file tree
Hide file tree
Showing 15 changed files with 511 additions and 19 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ failed_stories.md
errors.json
pip-wheel-metadata/*
events.db
events.db-shm
events.db-wal
rasa.db
rasa.db-shm
rasa.db-wal
*.swp
*.coverage*
env
Expand Down
1 change: 1 addition & 0 deletions changelog/5189.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added user guide for reminders and external events, including ``reminderbot`` demo.
1 change: 1 addition & 0 deletions changelog/5189.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `db-shm` and `db-wal` files to `.gitignore`
169 changes: 169 additions & 0 deletions docs/core/reminders-and-external-events.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
:desc: Learn how to use external events and schedule reminders.

.. _reminders-and-external-events:

Reminders and External Events
=============================

.. edit-link::

The ``ReminderScheduled`` event and the
`trigger_intent endpoint <../../api/http-api/#operation/triggerConversationIntent>`_ 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 <https://github.com/RasaHQ/rasa/tree/master/examples/reminderbot/README.md>`_.

.. contents::
:local:

.. _reminders:

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-guide:

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,

.. code-block:: md
## 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 :ref:`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):

.. literalinclude:: ../../examples/reminderbot/actions.py
:pyobject: ActionSetReminder

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:

.. code-block:: md
- EXTERNAL_reminder:
triggers: action_react_to_reminder
where the ``action_react_to_reminder`` is

.. literalinclude:: ../../examples/reminderbot/actions.py
:pyobject: ActionReactToReminder

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 <https://rasa.com/docs/rasa-x/installation-and-setup/docker-compose-script/>`_ 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 :ref:`callbackInput` channel to send messages to a `webhook <https://en.wikipedia.org/wiki/Webhook>`_.

.. _cancelling-reminders-guide:

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

.. literalinclude:: ../../examples/reminderbot/actions.py
:pyobject: ForgetReminders

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-event-guide:

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 <../../api/http-api/#operation/triggerConversationIntent>`_ 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 :ref:`mapping-policy`.

.. _getting-conversation-id:

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 :ref:`custom-actions`) that displays the ID in the conversation.
For example:

.. literalinclude:: ../../examples/reminderbot/actions.py
:pyobject: ActionTellID

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:

.. code-block:: md
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 :ref:`custom_session_start`).

.. _responding_to_external_events:

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.

.. literalinclude:: ../../examples/reminderbot/actions.py
:pyobject: ActionWarnDry

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

.. code-block:: shell
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!".
21 changes: 2 additions & 19 deletions docs/core/responses.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,22 +166,5 @@ Proactively Reaching Out to the User with External Events
You may want to proactively reach out to the user,
for example to display the output of a long running background operation
or notify the user of an external event.

To do so, you can ``POST`` an intent to the
`trigger_intent endpoint <../../api/http-api/#operation/triggerConversationIntent>`_.
The intent, let's call it ``EXTERNAL_sensor``, will be treated as if the user had sent a message with this intent.
You can even provide a dictionary of entities as parameters, e.g. ``{"temperature": "high"}``.
For your bot to respond, we recommend you use the :ref:`mapping-policy` to connect the sent intent ``EXTERNAL_sensor``
with the action you want your bot to execute, e.g. ``utter_warn_temperature``.
You can also use a custom action here, of course.

Use the ``output_channel`` query parameter to specify which output
channel should be used to communicate the assistant's responses back to the user.
Any messages that are dispatched in the custom action will be forwarded to the specified output channel.
Set this parameter to ``"latest"`` if you want to use the latest input channel that the user has used.

.. 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 :ref:`callbackInput` channel to send messages to a webhook.
To learn more, check out `reminderbot <https://github.com/RasaHQ/rasa/tree/master/examples/reminderbot/README.md>`_ in
the Rasa examples directory, or the `docs page on this topic <../../core/external-events-and-reminders>`_.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Understand messages, hold conversations, and connect to messaging channels and A
core/domains
core/responses
core/actions
core/reminders-and-external-events
core/policies
core/slots
core/forms
Expand Down
40 changes: 40 additions & 0 deletions examples/reminderbot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Reminderbot

The `reminderbot` example demonstrates how your bot can respond to external events or reminders.

## What’s inside this example?

This example contains some training data and the main files needed to build an
assistant on your local machine. The `reminderbot` consists of the following files:

- **data/nlu.md** contains training examples for the NLU model
- **data/stories.md** contains training stories for the Core model
- **config.yml** contains the model configuration
- **domain.yml** contains the domain of the assistant
- **credentials.yml** contains credentials for the different channels
- **endpoints.yml** contains the different endpoints reminderbot can use
- **actions.py** contains the custom actions that deal with external events and reminders

## How to use this example?

To train and chat with `reminderbot`, execute the following steps:

1. Train a Rasa Open Source model containing the Rasa NLU and Rasa Core models by running:
```
rasa train
```
The model will be stored in the `/models` directory as a zipped file.
2. Run a Rasa action server with
```
rasa run actions
```
3. Run a Rasa X to talk to your bot.
If you don't have a Rasa X server running, you can test things with `rasa x` in a separate shell (the action server must keep running).
For more information about the individual commands, please check out our
[documentation](http://rasa.com/docs/rasa/user-guide/command-line-interface/).
## Encountered any issues?
Let us know about it by posting on [Rasa Community Forum](https://forum.rasa.com)!
Empty file.
127 changes: 127 additions & 0 deletions examples/reminderbot/actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# This files contains your custom actions which can be used to run
# custom Python code.
#
# See this guide on how to implement these action:
# https://rasa.com/docs/rasa/core/actions/#custom-actions/


# This is a simple example for an assistant that schedules reminders and
# reacts to external events.

from typing import Any, Text, Dict, List
import datetime

from rasa_sdk import Action, Tracker
from rasa_sdk.events import ReminderScheduled, ReminderCancelled
from rasa_sdk.executor import CollectingDispatcher


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]


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 []


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 []


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 []


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()]
5 changes: 5 additions & 0 deletions examples/reminderbot/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language: en
pipeline: supervised_embeddings
policies:
- name: MemoizationPolicy
- name: MappingPolicy
Loading

0 comments on commit 00143ae

Please sign in to comment.