Issue with handling conditional slot logic and validations in the same action in Rasa 2.0

@JialiuXu I’m afraid your use case is starting to grow quite hairy here… The current behaviour, I think, is the desired one – a validate_{slot} function should update the tracker. If there’s a reason why you don’t want to update the tracker, then maybe you can pass a new tracker (or a copy of the current tracker) to validate()? By the way, if you don’t want to modify the code of FormValidationAction, you can always inherit from Action. That way, you can create a class that will be almost like FormValidationAction but some parts of it will be customized. If you look at what code FormValidationAction adds on top of Action, it should be possible to adapt these for your purposes…

Thanks for your reply, Sam @SamS Sorry, I didn’t make myself clear, I didn’t mean validate_{slot} function not update the tracker, but it shouldn’t validate the slots I set from previous actions and already in the tracker, what happens here is:

  1. The bot make an API call and get the user name: name/guest user
  2. use SlotSet update tracker: name/None if guest user
  3. I want to use this form collect user info,
def required_slots(self, tracker: Tracker) -> List[Text]:
       name = tracker.get_slot("name")
       name1 = tracker.get_slot("name1")     
        if name and (not name1):
            return ["name1"]
        elif name and name1:
            return ["gender", "ethnic" ,"DOB", "region"]
        else:
            return ["name", "gender", "ethnic", "DOB", "region"]

so when I already have the name slot filled, instead of validate, the form should direct ask name1 and then gender and ethnic, etc, but the form still trying to validate the name slot which cause the wrong logic I don’t think my user case is hairy, it’s a very simple logic and make sense to me, it works well all the time in rasa 1.x, but become so hard to achieve in rasa 2.0 I think what I’ll do is just find the 1.x SDK and compare old formaction and FormValidationAction, and revert the FormValidationAction function to 1.x formaction

Alright @JialiuXu I think I understand now. You can validate just the recently set slots if you follow the code of FormValidationAction: in validate(), it iterates only over the recent SlotSet events because only those are returned by tracker.slots_to_validate().

In Rasa 2.1 there’s now an updated version of FormValidationAction that should make many of the issues tackled above easy. See in particular the docs on advanced usage of forms.

@SamS I’m still really confused about dynamic form behavior. Is there any examples of bots that use actions to fill multiple slots? The documentation doesn’t really lay out what that looks like. Thanks!

Hey @travelmail26, I get you, this should be documented better. When you say:

bots that use actions to fill multiple slots

what exactly do you mean? A form that fills multiple slots – possibly some of them with mappings defined in the domain and some others added dynamically in a FormValidationAction? Or something else?

@SamS yes! and thanks for your quick response. It’s very helpful. So, I have a bot that asks two questions

  1. How long did you sleep?
  2. How would you describe your sleep?

For a lot of reasons, I want to do this as a custom action and not the normal form slots, partly because I need an open ended response option (among others). The documentation doesn’t lay out how to ask successive questions to gather data in a custom action. I’d love to seen an example.

Thanks!

Ah, I see! Well, as a matter of fact, custom actions are a tool that we give to folks with only the essential guidance, without many examples of how to accommodate specific conversational patterns. One reason for this is that custom actions are meant for advanced use cases and for advanced developers.

This being said, to:

I want to do this as a custom action and not the normal form slots

I can only say: What if I told you that forms were also just custom actions? :wink: I’m talking about FormAction and FormValidationAction in Rasa SDK. These custom actions take care of forms and you can tweak them to achieve your own form behaviour :slight_smile:

@SamS Got it. I guess id like help for a few reasons. 1. I did try with the conventional Forms/Slots method and ran into a bunch of issues. I left some questions on the forum and never heard back. 2. I’d like greater control over my options, so when I run into a pre-built feature that needs an exception, I can at least code my own way to a solution. So, if its not too much trouble, id really love to see an example. i’d like to use Rasa for my project and the community is still a big reason im trying to stick with it despite the complications :). Thanks

I get you.

I left some questions on the forum and never heard back.

Ideally, that shouldn’t happen… Feel free to point me towards any which are still unanswered and really relevant to this thread.

I’d like greater control over my options, so when I run into a pre-built feature that needs an exception, I can at least code my own way to a solution. So, if its not too much trouble, id really love to see an example.

It’s still not entirely clear to me what kind of example you imagine. Could you be more specific?

To trigger your action instead of the default form, just give your action the same name as the form’s name (e.g. restaurant_form). If you want to achieve form behaviour with a custom action, look at FormAction in the Rasa SDK code. Rasa is an open-source project and, ideally, the code should serve as an additional source (to complement the docs, which will never be exhaustive). If the code is unclear or not well documented, then let’s work on it, but we’ll never be able to describe in the docs the many ways in which each piece of code can be tweaked…

1 Like

@SamS Thanks for the reply! I posted an question here and here and it was (eventually) responded to but didn’t really help me with an example. i’ve pasted it here if it helps. The main thing I need help with is how to code the action.py so that it asks sequential questions and then takes in any text input:

I’ve been having difficulty coming up with a custom action to fill slots with open ended texts (after reading all the documentation and looking at the working examples on github). Below is my code. I’d really like some help. Here, I want the bot to ask two questions in order (and I would like it to do it in custom actions).

  1. Would you like to talk about your sleep?
  2. How would you describe your sleep?

I want the second question to capture any text, no matter how long, and return it in a variable that I can dump in a spreadsheet later. I’m really confused how I ask both questions in the action.py file.

The form is triggered by this intent in the NLU

- intent: sleep_log
  examples: |
    - I'd like to log my sleep

The domain of the form is:

responses:
  utter_confirm_sleep:
  - text: Do you want to talk about how you slept?

forms:
  sleep_form:
    sleep_confirm:
      - type: from_intent
        intent: affirm
        value: True
      - type: from_intent
        intent: deny
        value: False
      - type: from_intent
        intent: inform
        value: True

And, STORIES:

- story: sleep happy path
  steps:
  - intent: sleep_log
  - action: utter_confirm_sleep
  - intent: affirm
  - action: sleep_form
  - active_loop: sleep_form

ACTIONS

class ValidateSleep(FormValidationAction):
    print ("sleep form triggered")
    def name(self) -> Text:
        print ('validation run sleep')
        return "validate_sleep_form"

    
    async def validate_sleep_confirm(
        self,
        value: Text,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> Dict[Text, Any]:
        if value:
            print ('true value of sleep confirmed')
            return {"sleep_confirm": True}
        else:
            print ('false value sleep confirmed')
            return {"sleep_confirm": False }
    
    async def required_slots(
        self,
        slots_mapped_in_domain: List[Text],
        dispatcher: "CollectingDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",
    ) -> Optional[List[Text]]:
        required_slots = slots_mapped_in_domain + ["sleep_description"]
        print ('required slot triggered')
        return required_slots

    async def extract_sleep_description(
        self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict
    ) -> Dict[Text, Any]:
        text_of_last_user_message = tracker.latest_message.get("text")
        sleep_description = "slept" in text_of_last_user_message
        dispatcher.utter_message(text="please describe your sleep")

        return {"sleep_description": sleep_description}

Please let me know how I can make this question any more clear. I’m new but eager to learn how to do this. Thank you!!

@travelmail26 alright, now I see what you’re trying to achieve! I thought you wanted to create a custom action that would replace a form, but you are not trying to replace a form, only change an existing form a bit. It looks to me that you don’t have to use any validation action or write any other action code, it should be enough to add in the domain the 2nd slot (sleep_description) and have it extracted as from_text :thinking:

Well, now I’m realising that it won’t be as simple as I’ve described. Why? Because if you add sleep_description for the form into your domain as I described, this slot will get filled from whatever user utterance comes first, i.e. from the user’s sleep-confirming answer. You probably want to wait and only fill sleep_description from the utterance that follows. For that, it’s true that you’ll have to add sleep_description dynamically to the form only after sleep_confirm is filled. Have a look at this thread which deals exactly with this issue :slightly_smiling_face:

@SamS Thanks. I replied on the other thread too. So, I think what I’d like to do is figure out how to do this entirely through actions.py, and then maybe later simply modify a form.

the major problem I’m having is that when I try to fill the slot for sleep_description, rasa is processing an intent. I don’t want it to have an intent. I just want it to take whatever is said (no matter how long the reply is). I figured this might be easier if i just created a set of questions purely in actions.py.

i was getting a lot of issues when I tried to ask a question that took input of any text and length.

@travelmail26 can you clarify what you mean by “through actions.py”? You mean a FormValidationAction, or a custom action written entirely from scratch, or maybe a tweaked FormAction?

when I try to fill the slot for sleep_description, rasa is processing an intent. I don’t want it to have an intent.

In Rasa NLU, every user utterance gets assigned an intent (if you have an intent classifier in the pipeline). There’s no way around it… But you can still access the raw text – that’s exactly what from_text slot mapping does.

i was getting a lot of issues when I tried to ask a question that took input of any text and length.

Can you be more specific? What do you mean by asking a question that takes input of any text and length?

Happy to be more specific (and an example would very much help :wink: ). I’ve read this doc a bunch of times, but if there is better documentation, I’m happy to read it. Let me spell things out a bit more

  1. I have a bot that will be taking very long pieces of text from users. It can be several paragraphs for some questions. I want the bot to offload that text anywhere I tell it (like a spreadsheet). I don’t expect that Rasa will be able to infer an intent from some of these long texts and don’t need it to. I just want to capture the information.

  2. one of the issues I was running into i listed here (but noone answered). I have texts that may be very close to different types of intents, and was trying to just capture whatever text came in.

  3. As you mentioned in another thread: “Slots not listed for the form in the domain, but instead added dynamically from within a form validation action, behave similarly”. I’m not really comfortable with how action validations and required_slots pull in slots from the domain. When I see “required_slots”, I don’t entirely understand what’s going on but would love to learn. I haven’t found documentation that’s helpful yet.

  4. What would be most helpful, if its possible, is to just help me create (or point to an example) of where a FormValidationAction or something in actions.py creates a form entirely within actions.py for more than one slot. I’ve yet to see an example in Rasa documentation that shows how custom slot mappings can be made that point to more than one slot and how each slot would be filled in a certain sequence and take whatever specified text i need. The only thing i’m going off of is here and that only shows how to create a single slot modified form an existing form. Maybe that’s the way to go, but if it is, i still don’t know how to take raw text from this question no matter how long.

Thanks again for all your help!

Right, as I said before, the docs for forms could be improved, but I think here the main issue lies somewhere else…

I don’t expect that Rasa will be able to infer an intent from some of these long texts and don’t need it to.

Unfortunately, intents are (for a good reason) at the very heart of Rasa. Every message will get an intent assigned. If you use the fallback classifier, I suggest you remove it – in order for your NLU pipeline to never fail to assign an intent. (Normally, this is a bad practice, but here it could help.)

I’m not really comfortable with how action validations and required_slots pull in slots from the domain. When I see “required_slots”, I don’t entirely understand what’s going on but would love to learn.

Even though the docs should be clearer on this, I’d still say that your best bet is to actually read the code for FormValidationAction (maybe start on this line) and play with the code. I could tell you a simplified explanation of what required_slots does, but that’s already in the docs. (In short, whatever list you construct and return from required_slots, replaces the list of slots defined for the form in the domain.) For the details, you’d still need to see the actual implementation… As for how slots are pulled in from the domain: It’s just a matter of having access to a Domain object inside FromValidationAction and getting the slots from there, no further magic.

an example of where a FormValidationAction or something in actions.py creates a form entirely within actions.py for more than one slot

Right, now this is something interesting! Unfortunately, as far as I know, there’s currently no way to have a form that has no slots listed in the domain (i.e. all slots are extracted in a custom way). There has to be one slot in the domain – so that the form can actually start by asking about that slot. @Tobias_Wochinger this sounds like a bug, right? But I guess in your case @travelmail26 you can have the first slot listed in the domain and only the rest of your slots will be custom-mapped slots (added dynamically from within FormValidationAction), or not? :thinking:

This has actually been fixed: optional slot mappings by wochinge · Pull Request #7171 · RasaHQ/rasa · GitHub

1 Like

Thanks tobias and @SamS i had a look at the github ticket and form validation code. But, im still (really) confused on how to set up a slot form, or something custom actions in actions.py, that can take as input text of any length and content. My attempt fails because Rasa tries to infer an intent from a long piece of text, inevitably gets it wrong and then doesnt move on to the next appropriate slot.

So, how i do set up something (ideally all in actions.py) that asks at least two questions, in sequence, that can take any text input? Maybe im reading something wrong and im happy to read some documentation or look at an example but. But, im very confused. Thanks for helping me out :slight_smile:

@travelmail26 have you tried removing the NLU fallback classifier (or setting the threshold extremely low), as I suggested above?

1 Like