How can I use FormAction with a list slot?

I want to create a conversation flow in which the user provides some input, and based on that input the bot retrieves a series of yes/no questions (e.g. from some db or api) that it asks consecutively. Once all the questions have been answered the bot then feeds the initial input that generated those questions plus the answers to those yes/no questions into a prediction model, but I don’t know how to get started. Click here for a more detailed description of my project idea.

To my understanding, this dialogue requires FormAction, but I don’t know how to use list slots.

I looked at: Using the affirm intent multiple times and storing the "yes" or "no" values, but the difference in my case is that the yes/no questions refer to the same group of entities. I’m thinking I need a slot that’s a list of the user’s Boolean user responses which can then be passed onto my prediction model, so: how can I fill a slot of type-list with Boolean of previous user responses?

1 Like

Hi @slcheungcasado!

Could you please elaborate where and how are you planning to use ‘list’ slot?

Example conversation:

input> I think I have a cough

output> do you have a fever?

input> yes/no

output> do you feel dizzy?

input> yes/no

output> are your eyes reddened?

input> yes/no

output> predicted diagnoses: (1) bronchitis, (2) benign cough,… etc

I would like to store the yes/no responses to a conversation like the above, except that the questions (‘fever’, ‘dizzy’, etc) are dynamically generated. In the example above, RASA detects the entity ‘cough’ and retrieves what are the similar symptoms from an API, based on these similar symptoms I need to get RASA to output the yes/no questions and store the answers to them so I can then use those answers in some diagnosis prediction model. So the list slot should store the input (yes/no) for each question.

Hope it’s clearer now, @saurabh-m523

How do you plan to output the questions based on API? Do you have the templates already for these questions?

I haven’t decided on that yet, what I’ve implemented so far on that end is given a symptom, output a list of similar symptoms, so I haven’t made the template questions per similar symptom. Wasn’t sure whether it would be smarter to implement it on the API-side or just send out a list of words (symptoms) back to RASA and handle it on that end

Okay! As far as I understand, here is how I would approach this (assuming that the total number of symptoms (and therefore questions) is fixed):

First, I’ll define all these symptoms as unfeaturized slots in my domain and also define the corresponding utter_slot_name templates for asking the questions.

I’ll also keep an unfeaturized slot responses to store the responses (I’ll explain how to use this single slot to store all the responses later.)

Now, when the FormAction activates, we are not sure how many slots we need to fill (because the number of followup questions could vary based on the current symptom). The API could provide us this information.

Now in FormAction’s required_slots method, make a call to your API and get the list of followup symptoms (as slot names) to ask (for a given current symptom) and return that list. This will dynamically tell the FormAction what to ask (it will iterate over this list to ask it by uttering the pre-defined templates).

Now comes the important part of validating these slots. We need to write a validation function for all the symptoms so that the appropriate validation function could be called when required. Inside the validation method of a slot, get the value of that slot and set the slot responses to a dictonary, something like {'cough': 'yes'}. The responses slot can be extracted from the tracker and can be treated like any regular python dictonary. So inside the validation function of each slot, extract the responses slot and append to it the value of the the slot that is currently getting validated.

Finally in the submit method of the FormAction, extract the responses slot and do whatever you want with it.

Hope that helps! Feel free to ask any further questions you may have.

1 Like

Doesn’t this also mean that I would have to make my NLU part be able to discern between every symptom? At the moment I just have two entities symptom and disease. Is this were synonyms could be used?

You don’t actually need to make entities for every symptom. So for you two entities, symptom and disease make two more slots, symptom and disease. Now the symptom slot will be auto-filled by rasa when it gets a user message like “I think I have a cough” (assuming ‘cough’ is a symptom :sweat_smile:). You could extract the value of the symptom slot inside the required_slots method of the FormAction and make your API call based on that.

Alright, I will follow your advice to implement this during the upcoming days. If I encounter any other issues I will try to reach out, otherwise I’ll tag your previous post as the solution.

Thanks for the guidance :smiley:

Suppose I have 250 symptoms, then this means I need to add 250 slots, 250 utter_slot_name, 250 validate_slot_name functions, and then use the validate_slot_name functions to update the responses slot dictionary? Doesn’t that mean that the 250 validation_slot_name functions would all have the same function body (append to responses), the only difference being the slot_name? Or am I misunderstanding something? @saurabh-m523

You are right.

I could think of a workaround, it requires a little bit of hacking. You can avoid writing 250 utter_slot_name by overriding request_next_slot method of the FormAction class inside your own FormAction, here is the original definition:

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

    for slot in self.required_slots(tracker):
        if self._should_request_slot(tracker, slot):
            logger.debug(f"Request next slot '{slot}'")
            dispatcher.utter_message(template=f"utter_ask_{slot}", **tracker.slots)
            return [SlotSet(REQUESTED_SLOT, slot)]

    # no more required slots to fill
    return None

You can customize the dispatcher.utter_message method as per your requirement.

You can also avoid writing 250 validation_slot_name functions by overriding the validate_slots method. Here is the original definition:

async def validate_slots(
    self,
    slot_dict: Dict[Text, Any],
    dispatcher: "CollectingDispatcher",
    tracker: "Tracker",
    domain: Dict[Text, Any],
) -> List[EventType]:
    """Validate slots using helper validation functions.

    Call validate_{slot} function for each slot, value pair to be validated.
    If this function is not implemented, set the slot to the value.
    """

    for slot, value in list(slot_dict.items()):
        validate_func = getattr(self, f"validate_{slot}", lambda *x: {slot: value})
        if utils.is_coroutine_action(validate_func):
            validation_output = await validate_func(
                value, dispatcher, tracker, domain
            )
        else:
            validation_output = validate_func(value, dispatcher, tracker, domain)
        if not isinstance(validation_output, dict):
            logger.warning(
                "Returning values in helper validation methods is deprecated. "
                + f"Your `validate_{slot}()` method should return "
                + "a dict of {'slot_name': value} instead."
            )
            validation_output = {slot: validation_output}
        slot_dict.update(validation_output)

    # validation succeed, set slots to extracted values
    return [SlotSet(slot, value) for slot, value in slot_dict.items()]

As you can see, this function calls the corresponding validate_slot_name function for a given slot. You can customize this to do the appending here itself for a given slot.

I couldn’t think of a way to avoid writing 250 slots though.

Hope that helps.

1 Like

Hm, the idea of making a FormAction subclass hadn’t occurred to me. I’ll admit, I don’t feel like this is supposed to be the intended approach to design this type of conversation. The idea of having to overwrite code that is functionally intended to validate into something that mutates the data (by appending slots together) bothers me.

I’ll see if I can come up with a different approach, otherwise I will follow your idea. Thanks again.

Please share your findings and approaches :slightly_smiling_face:

It seems that your idea is the only way to accomplish this based on this post. :slightly_frowning_face: (As of now)