Set slots using request metadata

Is it possible to provide context about the conversation/user without them having to say it in an utterance? For example, if a user is logged in to our system, we will have certain metadata about their location/preferences already so can we pass that metadata to rasa and fill those slots in as soon as they start the conversation? For example, if we know from the user’s login metadata that they are in NYC and their first message to Rasa is “What is the weather?” Then Rasa should already have their location slot filled and look up the weather for NYC without asking the user for their location…

Yes, metadata can be accessed from your actions. The action_session_start event can be used to capture this information from the metadata and load it into a slot when the user session begins.

Something like this:

class ActionSessionStart(Action):
    def name(self) -> Text:
        return "action_session_start"

    @staticmethod
    def _slot_set_events_from_tracker(
        tracker: "DialogueStateTracker",
    ) -> List["SlotSet"]:
        """Fetch SlotSet events from tracker and carry over key, value and metadata."""

        from rasa.core.events import SlotSet

        return [
            SlotSet(key=event.key, value=event.value, metadata=event.metadata)
            for event in tracker.applied_events()
            if isinstance(event, SlotSet)
        ]


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

        events = [SessionStarted()]

        # any slots that should be carried over should come after the
        # `session_started` event`
        events.extend(self._slot_set_events_from_tracker(tracker))

        # Grab slots from metadata
        message_metadata = []
        for e in tracker.events[::-1]:
          # Does this tracker event have metadata?
            if "metadata" in e and e["metadata"] != None:
                message_metadata = e["metadata"]
                # Does this metadata have slots?
                if message_metadata and "slots" in message_metadata:
                    for key, value in message_metadata["slots"].items():
                        logger.info(f"{key} | {value}")
                        if value is not None:
                            events.append(SlotSet(key=key, value=value))
                    break
        if len(message_metadata) == 0:
            logger.warn(f"session_start but no metadata, tracker.events: {tracker.events}")

        # an `action_listen` should be added at the end as a user message follows
        events.append(ActionExecuted("action_listen"))

        return events
1 Like

Hi @stephens,

Can you also give an example for filling a slot with a value that is not in metadata by using “action_session_start”? For example, I want to use a slot for “username” and I need to fill this slot when the session started because some of the answers require to know the username. Thank you.

Huseyin,

Here’s an example from our financial bot. In this case it looks back in the tracker store to find a value and sets the slot.

If username is available via an API call, then you would make the call there.

Greg

Hello @stephens - I was using the trigger_intent end point defined here to pass a default intent with the metadata in the entities object. I’m using 1.10.14 currently.

Questions -

  1. Is trigger_intent not the recommended method to pass metadata anymore?
  2. Is trigger_intent deprecated? Trouble is I’m getting a 404 that the trigger_intent is not found.
{"version":"1.10.14","status":"failure","message":"The intent ([Route(handler=<function create_app.<locals>.trigger_intent at 0x7f6e71b28320>, methods=frozenset({'POST'}), pattern=re.compile('^\/conversations\/([^\/]+)\/trigger_intent$'), parameters=[Parameter(name='conversation_id', cast=<class 'str'>)], name='trigger_intent', uri='\/conversations\/<conversation_id>\/trigger_intent'), Route(handler=<function create_app.<locals>.trigger_intent at 0x7f6e71b28320>, methods=frozenset({'POST'}), pattern=re.compile('^\/conversations\/([^\/]+)\/trigger_intent\/$'), parameters=[Parameter(name='conversation_id', cast=<class 'str'>)], name='trigger_intent', uri='\/conversations\/<conversation_id>\/trigger_intent\/')], <function create_app.<locals>.trigger_intent at 0x7f6e71b28320>) does not exist in the domain.","reason":"NotFound","details":{},"help":null,"code":404}[]```

The trigger intent is still supported and passing your metadata as an entity should be no problem. Do you also get a 404 when you hit /?

@stephens Thanks for confirming! I tried a fresh copy of the bot and I got the trigger_intent API working, but there’s something that I’m not quite following.

In the payload for the trigger_intent, it says we pass the name of the intent and the entities to be passed as metadata. However, in the response, under tracker.latest_message, there’s a text key that has the value of an utterance (in the docs and screenshot below it has "text": "Hello!"). We did not pass this in the payload, so how did this appear here? Is there a value that appears by default if we did not pass a message in the payload?

@stephens Looks like the trigger_intent endpoint is failing again for me. Not sure why it worked in the middle and not sure why it’s failing again.

Update - I figured out what you meant by hitting /, and I got a healthy response -

Command

curl -d '{"name":"affirm", "entities":[]}' -H "Content-Type: application/json" -X GET http://localhost:5005/

Output

Hello from Rasa: 1.10.14

But the /trigger_intent endpoint is still failing.

Command -

curl -d '{"name":"affirm", "entities":[]}' -H "Content-Type: application/json" -X POST http://localhost:5005/conversations/test1/trigger_intent

Output

{"version":"1.10.14","status":"failure","message":"The intent ([Route(handler=<function create_app.<locals>.trigger_intent at 0x7efe6c225320>, methods=frozenset({'POST'}), pattern=re.compile('^\/conversations\/([^\/]+)\/trigger_intent$'), parameters=[Parameter(name='conversation_id', cast=<class 'str'>)], name='trigger_intent', uri='\/conversations\/<conversation_id>\/trigger_intent'), Route(handler=<function create_app.<locals>.trigger_intent at 0x7efe6c225320>, methods=frozenset({'POST'}), pattern=re.compile('^\/conversations\/([^\/]+)\/trigger_intent\/$'), parameters=[Parameter(name='conversation_id', cast=<class 'str'>)], name='trigger_intent', uri='\/conversations\/<conversation_id>\/trigger_intent\/')], <function create_app.<locals>.trigger_intent at 0x7efe6c225320>) does not exist in the domain.","reason":"NotFound","details":{},"help":null,"code":404}

Okay, so I think I figured it out (this was probably due to my lack of nous with docker). I had my docker containers running rasa, and then I ran a rasa train command to create a model. But the docker containers running rasa did not immediately load this model. So I had to recompose those containers after training the model and then the trigger_intent endpoint worked.