Custom Slot extraction and validation fails using custom actions

Hi, I am new to Rasa development and I haven’t quite understood the concept of custom Slot extraction and validation through custom actions since I’m having some issues, for example, I am working right now on a project where I will display a carousel of Smartphone brands and models for the user to select. I have created two slots brand and model with custom mapping, a form called purchase_form that gets activated when the intent of the user is to purchase a phone. For the display of the carousel I have implemented a custom action “action_ask_brand” and “action_ask_model” that dispatches to the webchat widget an attachment to display the brand and model carousel. Once the user clicks on a button to select a brand , the brand slot should be set using a custom action method “extract_brand” that takes the last user message and fills the slot and then should be validated in the “validate_brand”. I’m having an issue with this as the slots get filled, but then reset to None and I’m stuck in a loop.

image

rasa run actions --debug

  • domain.yml :

forms:

purchase_form:

required_slots:

- brand

- model

slots:

brand:

type: text

mappings:

- type: custom

model:

type: text

mappings:

- type: custom
  • actions.py

brands=[“SAMSUNG”,“APPLE”]

apple_models=[“iPhone 13”,“iPhone 13 Pro”,“iPhone SE”]

samsung_models=[“Galaxy Z”,“Galaxy S”,“Galaxy Note”]

class AskForSlotAction(Action):

def name(self) -> Text:

    return "action_ask_brand"

   

def run(self,dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict

) -> List[EventType]:

   

    message = {

        "type": "template",

        "payload": {

            "template_type": "generic",

            "elements": []

            }

        }

       

    for brand in brands:

        element={

                            "title": "Brand",

                            "subtitle": brand,

                            "image_url": f"./brands/{brand}.png",

                            "buttons": [

                                {

                                "title": brand,

                                "payload": brand,

                                "type": "postback"

                                },

                   

                            ]

                        }

        message["payload"]["elements"].append(element)        

    dispatcher.utter_message(text= "Choose a smartphone brand :" )

    dispatcher.utter_message(attachment=message)

    return []

class AskForSlotAction(Action):

def name(self) -> Text:

    return "action_ask_model"

   

def run(self,dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict

) -> List[EventType]:

    brand=tracker.get_slot("brand")

    if brand == "SAMSUNG":

        models=samsung_models

    elif brand== "APPLE":

        models=apple_models  

    message = {

        "type": "template",

        "payload": {

            "template_type": "generic",

            "elements": []

            }

        }

       

    for model in models:

        element={

                            "title": "Brand",

                            "subtitle": brand,

                            "image_url": f"./models/{model}.png",

                            "buttons": [

                                {

                                "title": model,

                                "payload": brand,

                                "type": "postback"

                                },

                   

                            ]

                        }

        message["payload"]["elements"].append(element)        

    dispatcher.utter_message(text= "Choose a model :" )

    dispatcher.utter_message(attachment=message)

    return []

class ValidateSimulationForm(FormValidationAction):

def name(self) -> Text:

    return "validate_purchase_form"



async def required_slots(

        self,

        domain_slots: List[Text],

        dispatcher: "CollectingDispatcher",

        tracker: "Tracker",

        domain: "DomainDict",

    ) -> List[Text]:

        slots = ["brand","model"]

        return slots

async def extract_brand(

         self,

         

         dispatcher:CollectingDispatcher,

         tracker: Tracker,

         domain: DomainDict,

     ) -> Dict[Text, Any]:

    text_of_last_user_message = tracker.latest_message.get("text")

    brand=text_of_last_user_message

    if brand  not in brands :      

        return {"brand": None}        

    return  {"brand": brand}

async def extract_model(

         self,

         dispatcher:CollectingDispatcher,

         tracker: Tracker,

         domain: DomainDict,

     ) -> Dict[Text, Any]:

    text_of_last_user_message = tracker.latest_message.get("text")

    model=text_of_last_user_message

    if model  not in apple_models or samsung_models:      

        return {"model": None}        

    return  {"model": model}

def validate_brand(

            self,

            slot_value: Any,

            dispatcher: CollectingDispatcher,

            tracker: Tracker,

            domain: DomainDict,

    ) -> Dict[Text, Any]:

       

    if slot_value not in brands:

        dispatcher.utter_message(text=f"You have to choose a brand!")

        return {"brand" , None}        

    dispatcher.utter_message(text=f"OK! You have chosen the brand: {slot_value}.")

    return {"brand": slot_value}



def validate_model(

            self,

            slot_value: Any,

            dispatcher: CollectingDispatcher,

            tracker: Tracker,

            domain: DomainDict,

    ) -> Dict[Text, Any]:

    if slot_value not in apple_models or samsung_models :

        dispatcher.utter_message(text=f"You have to choose a model")

        return {"model" , None}        

    dispatcher.utter_message(text=f"OK! You have chosen: {slot_value}.")

    return {"model": slot_value}

@kassem404 First of all the error you get when running the slot validation method validate_model is caused by a typo in your return value: it should be using a colon {model: None} instead of a comma.

Secondly I’d suggest investigating your if logic - perhaps have a merged list of apple and samsung models and rewrite this to if slot_value not in merged_list: At the moment you have a bug there because of the use of or, since samsung_models evaluates to True. Hope this helps!

1 Like

Hi, thank you so much for responding to my topic. Indeed I’ve noticed those two mistakes but somehow it didn’t fix my other issue, so I did more documentation reading and research and found out that the custom slot extract actions are always run on the background to keep the slots updated.

Here’s the action server log after fixing the two mistakes (I added a print in all methods for debugging):

(venv) PS D:\chatbotTest> rasa run actions --debug D:\FinanceBot\venv\lib\site-packages\sanic_cors\extension.py:39: DeprecationWarning: distutils Version classes are deprecated. Use packaging.version instead. SANIC_VERSION = LooseVersion(sanic_version) 2022-04-29 13:36:37 INFO rasa_sdk.endpoint - Starting action endpoint server… 2022-04-29 13:36:37 INFO rasa_sdk.executor - Registered function for ‘action_ask_brand’. 2022-04-29 13:36:37 INFO rasa_sdk.executor - Registered function for ‘action_ask_model’. 2022-04-29 13:36:37 INFO rasa_sdk.executor - Registered function for ‘validate_purchase_form’.
2022-04-29 13:36:37 INFO rasa_sdk.endpoint - Action endpoint is up and running on http://0.0.0.0:5055 2022-04-29 13:36:37 DEBUG rasa_sdk.utils - Using the default number of Sanic workers (1). 2022-04-29 13:37:14 DEBUG rasa_sdk.executor - Received request to run ‘action_ask_brand’ Ask brand 2022-04-29 13:37:14 DEBUG rasa_sdk.executor - Finished running ‘action_ask_brand’ 2022-04-29 13:37:19 DEBUG rasa_sdk.executor - Received request to run ‘validate_purchase_form’
Extract brand Extract model Validate brand Validate model 2022-04-29 13:37:19 DEBUG rasa_sdk.executor - Finished running ‘validate_purchase_form’ 2022-04-29 13:37:22 DEBUG rasa_sdk.executor - Received request to run ‘action_ask_model’ Ask model 2022-04-29 13:37:22 DEBUG rasa_sdk.executor - Finished running ‘action_ask_model’ 2022-04-29 13:37:42 DEBUG rasa_sdk.executor - Received request to run ‘validate_purchase_form’
Extract brand Extract model Validate brand Validate model 2022-04-29 13:37:42 DEBUG rasa_sdk.executor - Finished running ‘validate_purchase_form’ 2022-04-29 13:37:44 DEBUG rasa_sdk.executor - Received request to run ‘action_ask_model’ Ask model 2022-04-29 13:37:44 DEBUG rasa_sdk.executor - Finished running ‘action_ask_model’

I later found someone with the same issue, NoxDv on Github suggested to add a conditional statement in each extract method if tracker.slots[“requested_slot”] == “model” and it worked :+1:.

Here is the extract method for model after adding that conditional statement:

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

     print("Extract model")
    print(tracker.slots["requested_slot"] == "model")
    if tracker.slots["requested_slot"] == "model":
        text_of_last_user_message = tracker.latest_message.get("text")
        model= text_of_last_user_message
    
        if model not in models:
            print(model)
            
            print(tracker.get_slot("model"))
            return {"model": None}
           
        print(tracker.get_slot("model"))
        return  {"model": model}
        
    print(tracker.get_slot("model")) 
    return {"model": tracker.get_slot("model")}

Thank you all for the help!