Trigger a story (or intent) from a custom action?

I have a custom action that calls an api and get a binary response as to whether a user is registered or not.

I need to trigger different stories depending on the case.

So, I have defined two intents in domain.yml - known_user and unknown_user.

How do i trigger one of these intents in the action ?

class ActionGetUser(Action):

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

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

        email = tracker.get_slot("email")
        response = api.get_user({"email" :email})
        if response['status_code'] == 200:
            dispatcher.utter_message(response['data']['name'])
            result = [SlotSet("user_status", "known_user")]
        else :
            dispatcher.utter_message(response['data']['message'])
            result = [SlotSet("user_status", "unknown_user")]

        return result

Update : I am using Slots like how @tyd suggested but wonder if there is an approach without Slots?

Hi @agrawal-mohit! I think what you are looking for are slots.

You can set slots in your custom action and use slots to influence how the dialogue progresses.

Yeah, i am using slots currently and it works in simple cases where i can create stories accordingly. But setting a slot just for this purpose seem unnecessary. I’m trying to see if this can be done without setting a slot.

Are you sure you are using slots like the docs? Your original post did not make that clear.

Why do you think that your slot is unnecessary? Storing whether this user is registered or not seems like exactly the right use case. From the docs:

Slots are your bot’s memory. They act as a key-value store which can be used to store information the user provided (e.g their home city) as well as information gathered about the outside world (e.g. the result of a database query)

Well actually I did after posting the question. For this use case yes, slots are fine.

But i was just wondering if there can be a way to do this without involving slots because the main idea is to trigger a specific intent. If your application warrants this mechanism at many places you will end up creating a lot of flags. It’s not a major issue though. Thanks again for helping me out.

1 Like

Slots are important if you require this information later in the context. Suppose the user asks another question and it requires you to know if he is known or not, then register this information in the slot.

However if you require to trigger a particlar flow given a condition in your custom action, you can do so by triggerring a follow up action using the tracker events - Events from where you can continue your story .

1 Like

Thanks @souvikg10

Hello @souvikg10 thank you for information, but i have one use case when follow up intent makes sense.

I have for example 10 stories about one stuff (eg. ask_weather). Each story starts with intent ask_weather and solves almost every specific case, which can most likely to occur (story for form when user is unhelpful or he wants to stop in the middle of the process, he needs to explain slot, he wants to stop and then changes his mind, he wants to stop and then he really wants to stop, he needs to explain and then he changes his mind, (and more and more combinations and scenarios, which handle different user inputs for one stuff…)).

Then in middle of some other story (different topic, eg chitchat), the chatbot asks eg: “are you going outside tonight?”, user responds with “yes” and at this moment I would like to follow intent ask_weather from my custom action. Otherwise, if I want to handle all 10 cases which can happen after ask_weather intent (and I have them already handled somewhere), I would have to manually concat current story with each of these 10 stories. So I have to write these scenarios again behind current story. I hope it’s clear :slight_smile:

Follow up action is not enough and it doesnt solve this. It is like to call one action and thats all. I need to jump to different stories as his “yes” meant exactly /ask_weather.

Thank you very much if you can help me

I found a tricky way to start another story.

The main idea is…
Rasa core will continue story if I can make the list of Tracker’s events that are same as when the user started.

This is easily possible by returning action_listen and the first user intent of the story sequentially.

action.py example:

class CheckMemberAction(Action):
    def name(self) -> Text:
        return "action_check_member"

    @staticmethod
    def start_story_events(story_intent):
        # type: (Text) -> List[Dict]
        return [ActionExecuted("action_listen")] + [UserUttered("/" + story_intent, {
            "intent": {"name": story_intent, "confidence": 1.0},
            "entities": {}
        })]

    def run(self, dispatcher, tracker, domain):
        if is_unknown_user:
            return self.start_story_events("request.signup")
        else:
            return self.start_story_events("request.form_purchase")
    ...

story.md example:

## some story that ends up with action 
 * ...
 - ...
 - action_check_member

## story1 
 * request.signup
 - utter...
 - ..

## story2
 * request.form_purchase
 - utter_...
 - form_purchase
 - ...

If you want to do this in FormAction class, call self.deactivate() first.

   def start_story_events(self, story_intent):
    # type: (Text) -> List[Dict]
    return self.deactivate() + [ActionExecuted("action_listen")] + [UserUttered("/" + story_intent, {
        "intent": {"name": story_intent, "confidence": 1.0},
        "entities": {}
    })]

It works only when the action is placed at the end of the story.
But, I think it could possible in the middle of story… by adding Restarted() and SetSlot() for preserving slot… (I didn’t tried. :wink: )

This method looks a bit tricky and may not be compatible in the next version, but I actually use this very often when the scenario is complicated and has many user selection steps.

13 Likes

Hi @b1uec0in,

thats awesome nasty trick. It really works!!! You save me a lot of writing. So far I only need it for the classic actions.

Thank you very much.

@tyd please how do i call a different story from an ActionServer using logic control the story that is selected i have tried using UserUttered and SlotSet nothing seems to work.

Screenshot 2021-06-18 at 15.07.11 Screenshot 2021-06-18 at 15.07.33

Screenshot 2021-06-18 at 15.11.37

Hi @oluwoleilesanmi. Why are you trying to control what story is selected?

Hi @tyd Each user that tries out the bot is meant to get a different story from the bot Latin Square Approach. At the end of the cycle of story selection it restarts from the first story and the sequence continues.

I’m interested in this as well - to have the bot be able to switch/trigger intents or fire off a different story based on some outside logic or factors.

Hello @oluwoleilesanmi and @jonathanpwheat

to have the bot be able to switch/trigger intents or fire off a different story based on some outside logic or factors.

To distinguish different stories and “trigger” them with a custom action, you should use a slot. (Definitely do not use FollowupActions - we only have them for backwards compatibility and they break your policies if you put the stories they generate into the training data.) You can then go down different story paths using rules or stories.

For example, your stories could be

stories:
- story: 1
  steps:
  - intent: utter_greet
  - action: action_choose_path
  - slot_was_set:
    - conversation_path: 1
  # The story of the first path
  - action: # ...
  # ...
- story: 2
  steps:
  - intent: utter_greet
  - action: action_choose_path
  - slot_was_set:
    - conversation_path: 2
  # The story of the second path
  - action: # ...
  # ...

Here, the conversation_path slot should be a categorical slot with one category for each path.

Each user that tries out the bot is meant to get a different story from the bot Latin Square Approach. At the end of the cycle of story selection it restarts from the first story and the sequence continues.

This sounds like you want to cycle through the order or questions in a form? To do this, you can use the requested_slots method and have it return the requested slots in an order determined by the number stored in your conversation_path slot. For this application, your conversation_path slot does not need to be featurized (so you can set influence_conversation = false).

@j.mosig thanks, i tried out the story. Slots values are changed but stories never continue from the story that slot triggered. The first selection seems to work but other selections are stuck on the first story despite slots changing. I am using RASA X so i see when the slots are changed. The slot change is done from actions. The slots are never set from entity extraction and the user is not meant to know what story is selected on their behalf.

Secondly is there a way to make a slot global to all users, so that when users try the application from Telegram, they can continue from the state of that slot.

Kindly help i have been on this for a while now.

The first selection seems to work but other selections are stuck on the first story despite slots changing.

This sounds like your slot is not influencing the conversation. Did you set its type to bool and influence_conversation = true?

is there a way to make a slot global to all users

Slots are always specific to a given conversation. I guess you don’t want to share between users, but between devices? Or do you really mean that all users who talk to the bot get updated slot values as soon as one of them does something that updates the slot value?

@j.mosig thanks once again, i finally got the selection to work, the influence_conversation was not set to true. Categorical slots were used and it worked fine.

Slots are always specific to a given conversation. I guess you don’t want to share between users, but between devices? Or do you really mean that all users who talk to the bot get updated slot values as soon as one of them does something that updates the slot value?

Thanks, since slots are always specific to a conversation, I will use a different approach where each user that tries out the system is delivered one of the 3 stories at random.

I have an idea as a way to handle the global values.

You would need to utilize an external database API (or the filesystem) and have a custom action save the global value(s) there, as well as have a custom action pull those values out and set a slot for the user.

An external database such as FireBase could be used to set/get these from any application.

We do something similar, where we fetch some global information from a Postgres database that can be presented to all users in some way.

1 Like

@jonathanpwheat thanks for this suggestion. since the order of story selection is not as important as i initially thought i decided to go with randomness, but i will definitely give it a try if need be.

1 Like