Conditional logic in rasa forms

Hi!

I am trying to make a bot that takes a budget from a user and then certain requirements before suggesting a product from a database.

I want to avoid the bot asking a certain question when the budget given by the user is below a certain threshold. What should i do to achieve this ?

` async def required_slots(
        self,
        slots_mapped_in_domain: List[Text],
        dispatcher: "CollectionDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",

    ) -> Optional[List[Text]]:
         
        
        if tracker.get_slot('category') == 'phone':
            required_slots = ['price_code','display', 'camera', 'storage', 'ram', 'battery']
        elif tracker.get_slot('category') == 'tablet':
            required_slots = ['price_code', 'storage', 'ram']
        
        return required_slots

Hi @sealedhermit, welcome to the Rasa forum :slight_smile:

Could you please provide some more information on your use case, e.g. some sample conversation you’d like the bot to follow and where the conditional logic from the budget should take effect?

From your example above, if I understand correctly there’s different categories of products. Should those be set automatically by based on the budget the user submits? Or would they be triggered by an intent, like buy_phone and buy_tablet?

1 Like

Hi @MatthiasLeimeister Thanks for the reply!

I am sorry i couldn’t make my question clear. I have a few slots to be filled with user input such as category, price, display, storage and battery. If the user enters a price below say, 200usd, i want my bot to drop the slot display from the form and shouldn’t ask the utter_ask_display. The form is triggered by an intent buy_product. Thank you!

 async def required_slots(
        self,
        slots_mapped_in_domain: List[Text],
        dispatcher: "CollectionDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",

    ) -> Optional[List[Text]]:
         
        
        if tracker.get_slot('category') == 'phone':
            required_slots = ['price','display', 'camera', 'storage', 'ram', 'battery']
        elif tracker.get_slot('category') == 'tablet':
            required_slots = ['price', 'storage', 'ram']
        
        return required_slots
    

I tried using the required slots method, that worked out for different product types, but say the user want a phone and he enters a price below 200usd, i want to skip the display from the required slot for phones. Here is where i am stuck at!

Ah I see. In that case, if the price slot is already set, you could modify the required slots based on the value. Could something like the following work?

async def required_slots(
        self,
        slots_mapped_in_domain: List[Text],
        dispatcher: "CollectionDispatcher",
        tracker: "Tracker",
        domain: "DomainDict",

    ) -> Optional[List[Text]]:
         
    required_slots = slots_mapped_in_domain.copy()
    price = tracker.get_slot('price')
    if price is not None and price < THRESHOLD:
        required_slots.remove('display')
    
    return required_slots

Then the form should not ask for the display information anymore and continue to fill the other slots.

Another thing I noticed, since your required slots change considerably based on the product category, an alternative could also be to split those into separate forms, and trigger them with individual intents, like buy_phone, buy_tablet, etc. This would lift some logic from the validation action.

1 Like

Hey @MatthiasLeimeister !

Thanks for the heads up. I had tried this before but ended up getting errors such as can’t use operators like > between string and int.

So i changed my code like this `

required_slots = slots_mapped_in_domain.copy()
    price = tracker.get_slot('price')
    price_int = int(re.findall(r'[0-9]+',price)[0])
    if price_int is not None and price _int <THRESHOLD:
        required_slots.remove('display')

This now returns me TypeError: expected string or bytes-like object.

Hi @sealedhermit, the TypeError seems likely to come from price being None, which doesn’t work with re.findall. You should check before that step if the value is actually set.

However, the regex matching seems a bit odd. Ideally the slot should only be filled with the number, so it can be converted from the string returned by get_slot (it might be a float though, depending on user input?). If the slot contains more text than just the number, this rather suggests an issue with the entity extraction.

@MatthiasLeimeister Thank you. I ran rasa shell --debug to ensure that the price value is indeed an digit without any string. Even then with the tracker.get_slot(‘price’) and the subsequent if condition, i am getting the error which says operator such as < can’t be used with string and int type. Maybe i am missing something here.

Ah, sorry if I was unclear. Yes, you would still need to convert the string to a float or int number, but if the slot is only digits, you should not need the regex matching. If trying this:

required_slots = slots_mapped_in_domain.copy()
price = tracker.get_slot('price')
if price is not None:
    price_int = int(price)
    if price_int < THRESHOLD:
        required_slots.remove('display')

Does that work for you?