Context swith inside forms

I have a problem for implementing context switch in rasa forms. Suppose you have a very simple story like this:

## story_sms
* sms
  - sms_form
  - form{"name": "sms_form"}
  - form{"name": null}
  - action_reset_contact_slot

In this story, I want to send a text message to someone. For this story, I defined sms_form and action_reset_contact_slot actions:

class SmsForm(FormAction):
    def name(self) -> Text:
        return "sms_form"

    @staticmethod
    def required_slots(tracker: Tracker) -> List[Text]:
        return ["contact"]

    def slot_mappings(self) -> Dict[Text, Union[Dict, List[Dict]]]:
        return {
            "contact": self.from_entity(entity="contact"),
        }

    def validate_contact(self, value: Text, dispatcher: CollectingDispatcher,
                         tracker: Tracker, domain: Dict[Text, Any], ) -> Any:
        """Validate contact value."""
        if self.get_entity_value('contact', tracker):
            dispatcher.utter_template("utter_sms_slots_values", tracker, contact=value)
            return {"contact": value}
        else:
            dispatcher.utter_template("utter_wrong_contact", tracker)
            # validation failed, set slot to None
            return {"contact": None}

    def submit(
            self,
            dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: Dict[Text, Any],
    ) -> List[Dict]:
        # utter submit template
        dispatcher.utter_template("utter_sms_submit", tracker)
        return []

class ResetContactSlot(Action):
    def name(self):
        return "action_reset_contact_slot"
    def run(self, dispatcher, tracker, domain):
        return [SlotSet("contact", None)]

I can handle the following conversations with my this definition of SmsForm:

conversation 1:
* Send a text message to David
      * Receiver: David (utter_sms_slots_values)
      * Ok! (utter_sms_submit)
conversation 2:
* Send a text message
    * To whom? (utter_ask_contact)
* David
    * Receiver: David (utter_sms_slots_values)
    * Ok! (utter_sms_submit)

In the second conversation, If I don’t enter a valid contact name then utter_wrong_contact show (based on validate_contact). I want to change this part. What if I want to change the flow of conversation like this:

* Send a text message
   * To whom?
* Hello (greet)
   * Hi! (utter_greet)

I want to check if the answer to utter_ask_contact was not a valid contact name then rasa deactivates this form, remove possible slots and answer user message. In this case, I want to get utter_greet as the response. (I can not handle this case using an unhappy story for each possible intent because I have 42 other intents.)

hi @mac_71128 - welcome back to the forum! you definitely want to make sure your form is properly deactivated when the user doesn’t provide a valid name, but that should happen automatically if the extractor can’t find the name slot.

You have a couple of options if you want to handle 42 different intents here. 1. you can define a mapping policy for those 2. you can include them in a single story with OR between each of the intents 3. you can probably merge a bunch of these into a single intent using the supervised response selector here

Hi @amn41. Thanks :slight_smile:

Thank you for your reply. As you can see, my previous implementation of sms_form keeps asking the user until getting a valid contact name. And I don’t want this. I want to exit the story(story_sms) peacefully and start to find another one whenever the answer to utter_ask_contact was about something else(for example another intent like greet).

If I change the implementation of sms_form and story_sms like this:

class SmsForm(FormAction):
    def name(self) -> Text:
        return "sms_form"

    @staticmethod
    def required_slots(tracker: Tracker) -> List[Text]:
        return ["contact"]

    def slot_mappings(self) -> Dict[Text, Union[Dict, List[Dict]]]:
        return {
            "contact": self.from_entity(entity="contact"),
        }

    def submit(
            self,
            dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: Dict[Text, Any],
    ) -> List[Dict]:
        # utter submit template
        dispatcher.utter_template("utter_sms_submit", tracker)
        return []

## story_sms
* sms
  - sms_form
  - form{"name": "sms_form"}
  - form{"name": null}
  - utter_sms_slots_values
  - action_reset_contact_slot

In this way, we don’t have to repeat utter_ask_contact and we can change the context whenever we want but look what will happen:

* Send a text message to David
      * Ok! (utter_sms_submit)
      * Receiver: David (utter_sms_slots_values)
* Send a text message
    * To whom? (utter_ask_contact)
* David
    * Ok! (utter_sms_submit)
    * Receiver: David (utter_sms_slots_values)
* Send a text message
    * To whom? (utter_ask_contact)
* Hello (greet)
   * Hi! (utter_greet)
* Send a text message
    * What? (utter_not_clear_query)
* Send a text message
    * What? (utter_not_clear_query)
* Send a text message
    * What? (utter_not_clear_query)
* Send a text message
    * What? (utter_not_clear_query)

So, there is something wrong here.

By using mapping policy, can we exit sms_form without these problems? If user answer to utter_ask_contact was ‘Hello’ or ‘take me a taxi’ can we change the provide the answer properly for example by utter_greet or taxi_form?

I can not define a single story with OR between each of the intents as an unhappy story because the answer to each one is different. I want to answer Hello(greet) by Hi!(utter_greet) and answer take me a taxi(taxi) by invoking taxi_form. Maybe I didn’t get it. Can you show me an example?

I also tried to solve this problem by changing the request_next_slot function but I couldn’t. Here is my first try:

class SmsForm(FormAction):
    def name(self) -> Text:
        return "sms_form"

    @staticmethod
    def required_slots(tracker: Tracker) -> List[Text]:
        return ["contact"]

    def slot_mappings(self) -> Dict[Text, Union[Dict, List[Dict]]]:
        return {
            "contact": self.from_entity(entity="contact"),
        }

    def request_next_slot(
            self,
            dispatcher,  # type: CollectingDispatcher
            tracker,  # type: Tracker
            domain,  # type: Dict[Text, Any]
    ):
        # type: (...) -> Optional[List[Dict]]
        """Request the next slot and utter template if needed,
            else return None"""

       intent = tracker.latest_message.get("intent", {}).get("name")
       entity = tracker.latest_message.get("entities", {})
       if intent == 'greet':
            return self.deactivate() + ??tracker.get_next_action(intent=intent)??
       else:
            for slot in self.required_slots(tracker):
                if self._should_request_slot(tracker, slot):
                    print("Request next slot '{}'".format(slot))
                    dispatcher.utter_template(
                        "utter_ask_{}".format(slot),
                        tracker,
                        silent_fail=False,
                        **tracker.slots
                    )
                    return [SlotSet('contact', slot)]
            # no more required slots to fill
            return None

    def submit(
            self,
            dispatcher: CollectingDispatcher,
            tracker: Tracker,
            domain: Dict[Text, Any],
    ) -> List[Dict]:
        # utter submit template
        dispatcher.utter_template("utter_sms_submit", tracker)
        return []

    def run(self, dispatcher, tracker, domain):
        # activate the form
        events = self._activate_if_required(dispatcher, tracker, domain)
        # validate user input
        events.extend(self._validate_if_required(dispatcher, tracker, domain))
        # check that the form wasn't deactivated in validation
        if Form(None) not in events:

            # create temp tracker with populated slots from `validate` method
            temp_tracker = tracker.copy()
            for e in events:
                if e["event"] == "slot":
                    temp_tracker.slots[e["name"]] = e["value"]

            next_slot_events = self.request_next_slot(dispatcher, temp_tracker, domain)

            if next_slot_events is not None:
                # request next slot
                events.extend(next_slot_events)
            else:
                # there is nothing more to request, so we can submit
                self._log_form_slots(tracker)
                print("Submitting the form '{}'".format(self.name()))
                events.extend(self.submit(dispatcher, temp_tracker, domain))
                # deactivate the form after submission
                events.extend(self.deactivate())
        return events

I don’t know how can I deactivate the form, get the next action by using intent and/or entity(find another story) and add it as the next event. I would appreciate it if you could help me figure this out. Thanks.

hi @mac_71128 - from your conversation that you post it looks like things work well, and the first incorrect step is the bot asking ‘What?’ utter_not_clear_query. Is the problem just that the formaction is not getting predicted after send a text message the second time it’s sent? What does the output tell you when you run with --debug ?

Hi @amn41 - Yes. After I entered Hello and exit the form, for Send a text message I got utter_not_clear_query response. It seems that the sms_form was not deactivated properly so this happens. Or because I didn’t define any kind of unhappy path to handle these.

The log of rasa shell was:

Your input ->  Send a text message                                                                                                                              
2019-08-10 10:27:59 DEBUG    rasa.core.agent  - Created a new lock for conversation 'default'
2019-08-10 10:27:59 DEBUG    rasa.core.tracker_store  - Recreating tracker for id 'default'
2019-08-10 10:27:59 DEBUG    rasa.core.processor  - Received user message 'Send a text message' with intent '{'name': 'sms', 'confidence': 0.9557045102119446}' and entities '[]'
2019-08-10 10:27:59 DEBUG    rasa.core.processor  - Logged UserUtterance - tracker now has 42 events
2019-08-10 10:27:59 DEBUG    rasa.core.policies.memoization  - Current tracker state [{'intent_sms': 1.0, 'prev_action_listen': 1.0}, {'intent_sms': 1.0, 'prev_sms_form': 1.0, 'active_form_sms_form': 1.0}, {'intent_greet': 1.0, 'active_form_sms_form': 1.0, 'prev_action_listen': 1.0}, {'prev_utter_greet': 1.0, 'intent_greet': 1.0, 'active_form_sms_form': 1.0}, {'intent_sms': 1.0, 'active_form_sms_form': 1.0, 'prev_action_listen': 1.0}]
2019-08-10 10:27:59 DEBUG    rasa.core.policies.memoization  - There is no memorised next action
2019-08-10 10:27:59 DEBUG    rasa.core.policies.fallback  - NLU confidence threshold met, confidence of fallback action set to core threshold (0.3).
2019-08-10 10:27:59 DEBUG    rasa.core.policies.form_policy  - There is an active form 'sms_form'
2019-08-10 10:27:59 DEBUG    rasa.core.policies.ensemble  - Predicted next action using policy_3_FormPolicy
2019-08-10 10:27:59 DEBUG    rasa.core.processor  - Predicted next action 'sms_form' with confidence 1.00.
2019-08-10 10:27:59 DEBUG    rasa.core.actions.action  - Calling action endpoint to run action 'sms_form'.
2019-08-10 10:27:59 DEBUG    rasa.core.actions.action  - Failed to extract slot contact with action sms_form
2019-08-10 10:27:59 DEBUG    rasa.core.policies.memoization  - Current tracker state [{'intent_sms': 1.0, 'prev_action_listen': 1.0}, {'intent_sms': 1.0, 'prev_sms_form': 1.0, 'active_form_sms_form': 1.0}, {'intent_greet': 1.0, 'active_form_sms_form': 1.0, 'prev_action_listen': 1.0}, {'prev_utter_greet': 1.0, 'intent_greet': 1.0, 'active_form_sms_form': 1.0}, {'intent_sms': 1.0, 'active_form_sms_form': 1.0, 'prev_action_listen': 1.0}]
2019-08-10 10:27:59 DEBUG    rasa.core.policies.memoization  - There is no memorised next action
2019-08-10 10:27:59 DEBUG    rasa.core.policies.fallback  - NLU confidence threshold met, confidence of fallback action set to core threshold (0.3).
2019-08-10 10:27:59 DEBUG    rasa.core.policies.form_policy  - There is an active form 'sms_form'
2019-08-10 10:27:59 DEBUG    rasa.core.policies.ensemble  - Predicted next action using policy_2_FallbackPolicy
2019-08-10 10:27:59 DEBUG    rasa.core.processor  - Predicted next action 'utter_not_clear_query' with confidence 0.30.
2019-08-10 10:27:59 DEBUG    rasa.core.processor  - Action 'utter_not_clear_query' ended with events '['BotUttered(text: What?, data: {"elements": null, "quick_replies": null, "buttons": null, "attachment": null, "image": null, "custom": null}, metadata: {})']'
2019-08-10 10:27:59 DEBUG    rasa.core.policies.memoization  - Current tracker state [{'intent_sms': 1.0, 'prev_sms_form': 1.0, 'active_form_sms_form': 1.0}, {'intent_greet': 1.0, 'active_form_sms_form': 1.0, 'prev_action_listen': 1.0}, {'prev_utter_greet': 1.0, 'intent_greet': 1.0, 'active_form_sms_form': 1.0}, {'intent_sms': 1.0, 'active_form_sms_form': 1.0, 'prev_action_listen': 1.0}, {'prev_utter_not_clear_query': 1.0, 'intent_sms': 1.0, 'active_form_sms_form': 1.0}]
2019-08-10 10:27:59 DEBUG    rasa.core.policies.memoization  - There is no memorised next action
2019-08-10 10:27:59 DEBUG    rasa.core.policies.form_policy  - There is an active form 'sms_form'
2019-08-10 10:27:59 DEBUG    rasa.core.policies.mapping_policy  - There is no mapped action for the predicted intent, 'sms'.
2019-08-10 10:27:59 DEBUG    rasa.core.policies.ensemble  - Predicted next action using policy_2_FallbackPolicy
2019-08-10 10:27:59 DEBUG    rasa.core.processor  - Predicted next action 'action_listen' with confidence 1.00.
2019-08-10 10:27:59 DEBUG    rasa.core.processor  - Action 'action_listen' ended with events '[]'
2019-08-10 10:27:59 DEBUG    rasa.core.agent  - Deleted lock for conversation 'default' (unused)
What?

And the following error showed up in the actions server:

2019-08-10 10:01:17 ERROR    rasa_sdk.endpoint  - Failed to extract slot contact with action sms_form