Dynamically added slots aren't waiting for user input

I have noticed repeatedly that the required_slots method does not behave as I expect it to. Suppose I have a form (greeting_form), with one slot (how_are_you). In the required_slots method of the validation action for this form, I dynamically add a second slot (tell_me_more) if the latest user intent was mood_unhappy. Now, I expect the form to ask for this new slot (tell_me_more), then wait for user input to fill this slot with. Instead, Rasa automatically fills this slot with the value of how_are_you. I believe this is a bug, but wanted to double check that my understanding isn’t flawed before filing a bug report.

Hope this makes sense. I’ve also attached the code for testing this. By running rasa shell --debug, you will see that Rasa skips asking utter_ask_tell_me_more, and fills the slot tell_me_more with the same value as how_are_you.

domain.yml (864 Bytes)

stories.yml (176 Bytes)

actions.py (1.5 KB)

Hey @alexyuwen,

what happens in your case: after the form receives a mood_unhappy utterance, it adds tell_me_more to the list of required slots. Then, the form proceeds with extracting any slots (the order can be seen in the Rasa SDK code on a few lines starting here). And since tell_me_more is defined to be always extracted, no matter what the intent or entities, it gets extracted from the same utterance and the form doesn’t need to ask a follow-up question just to fill that slot.

I think this behaviour is actually desirable. The user can have the option of providing all the necessary information in their first sentence. This is also why Rasa SDK tries to extract all required slots every time it receives an utterance. In your case, you may need to change the extractor method for tell_me_more such that it only extracts something from some specific intents or looks for some specific entities. Or perhaps you want to ignore the current utterance if its intent is mood_unhappy but the how_are_you slot is not set yet? (This trick should work because all freshly extracted slots get set only after they’ve all been extracted, see here.)

By the way, I realise there are bits which I explained here but aren’t well covered in the docs. I encourage you to contribute some changes yourself if you see bits that need improvements :slight_smile:

1 Like

I now realised that my suggested trick

perhaps you want to ignore the current utterance if its intent is mood_unhappy but the how_are_you slot is not set yet?

doesn’t work in this specific case because how_are_you is a slot mapped in the domain (i.e. not using a custom extractor function). All currently required slots that are mapped in the domain get extracted (and put into the tracker) before any logic in the form validation action starts (i.e. before custom slots are extracted).

I think this is (from a user’s point of view) a peculiar inconsistency between slots mapped in the domain and custom slots. Basically, if in some custom extractor code you want to condition on other slots being set or not, you have to be very careful to take into account that domain-mapped slots from the current utterance will already be set but custom-mapped slots will not. I’m curious what @Tobias_Wochinger thinks about this :thinking:

1 Like

Hi @SamS, thank you for this explanation. I tried conditioning on entities, slot values, and intents, none of which changed the control flow. Just as an example, if, inside the extractor method, I write:

latest_intent = tracker.latest_message.get("intent")["name"]
if latest_intent == "affirm": # arbitrary choice for debugging
    return {"tell_me_more": None}
else:
    return {"tell_me_more": tracker.latest_message.get("text")}

then Rasa still uses the most recent user utterance to fill the slot, instead of asking utter_ask_tell_me_more. I finally had some success with

print("I'M INSIDE THE EXTRACTOR METHOD!") # debugging line
last_event = tracker.events[-1]

if last_event["event"] == "slot" and last_event["name"] == "how_are_you":
    return {"tell_me_more": None}
return {"tell_me_more": tracker.latest_message.get("text")}

which caused Rasa to run utter_ask_tell_me_more, as desired. However, if the intent of the next user response is not mood_unhappy, then Rasa “fails to extract requested slot ‘tell_me_more.’” Using a debugging line, I determined that extract_tell_me_more wasn’t even being run, so it seems the issue is with the required_slots method. However I see nothing inside of my required_slots method that would cause extract_tell_me_more to not run. You said that

which seems to contradict the fact that extract_tell_me_more isn’t being run.

Here is my new actions file: actions.py (2.0 KB)

Hey @alexyuwen, I’ll try to break down this knot because it’s getting pretty hairy :slight_smile:

Which custom extractors are run gets determined by the contents of the required_slots list. (This is what I meant by “Rasa SDK tries to extract all required slots”.) This list is created after all slots mapped in the domain (in this case how_are_you) are extracted.

In your code:

if latest_intent == "mood_unhappy":
    slots_mapped_in_domain = ["tell_me_more"] + slots_mapped_in_domain

the list is defined such that it contains tell_me_more only right after the specific intent, while the utterance corresponding to this intent is being processed. That’s why, if the intent of the following utterance is not mood_unhappy, your slot won’t make it to the list and the extractor won’t get executed.

To be honest, I don’t understand the first code example (the one that conditions on affirm). I’d expect to return {"tell_me_more": None} for all intents other than some tell_more intent :thinking: The second code example makes sense and it correctly delays extracting some actual value for tell_me_more until the next turn. However, in that next turn, you have to make sure that the extractor gets run again, e.g. by changing the code in required_slots() to:

if latest_intent in ["mood_unhappy", "tell_more"]:
    slots_mapped_in_domain = ["tell_me_more"] + slots_mapped_in_domain

Let me know if something’s still unclear :wink:

1 Like

@SamS Thanks for pointing me to this thread. I’m still confused. What in the actions.py code tells Rasa to look at the next slot? I pasted his actions.py code below.

he has a forms in the domain that ask these questions in sequence, but absent that detailed in the domain file, how would actions.py instruct questions to be asked in sequence?

or,. if its just modifying a form, what in the actions.py tells it to ask a certain question in a particular order?

in my case, I want two questions asked in sequence (or in an order I specify) and then take open ended responses. I’d prefer to be entirely instructed from actions.py. Thanks!

async def required_slots(
        self,
        slots_mapped_in_domain: List[Text],
        dispatcher: "CollectingDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",
) -> Optional[List[Text]]:

    latest_intent = tracker.latest_message.get("intent")["name"]
    if latest_intent == "mood_unhappy":
        return ["tell_me_more"] + slots_mapped_in_domain
    return slots_mapped_in_domain

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

    text_of_last_user_message = tracker.latest_message.get("text")
    return {"tell_me_more": text_of_last_user_message}

@travelmail26 I’ll assume that you are confident about the way that slots listed for a form in the domain influence how the form asks questions. Slots not listed for the form in the domain, but instead added dynamically from within a form validation action, behave similarly. The form fetches required slots here and treats the list of slots as if they came from the domain – it picks the first one that is not filled and asks the user about the slot.