Form - wrong intent detection

Hi,

We have following RASA Forms documentation (link), and several relevant forum discussions, in order to ramp-up a simple for which ask the user for his gender, age, weight and height.

For that end, we’ve defined 4 corresponding slots, entities and responses in the domain file:

slots:
  gender:
    type: unfeaturized
    auto_fill: false
  age:
    type: unfeaturized
    auto_fill: false
  weight:
    type: unfeaturized
    auto_fill: false
  height:
    type: unfeaturized
    auto_fill: false
  requested_slot:
    type: unfeaturized

entities:
  - gender
  - age
  - weight
  - height

responses:
  utter_ask_gender:
    - buttons:
        - payload: '/inform{"gender": "male"}'
          title: זכר
        - payload: '/inform{"gender": "female"}'
          title: נקבה
      text: are you a male or a female?
  utter_ask_age:
    - text: how old are your?
  utter_ask_weight:
    - text: what is your weight?
  utter_ask_height:
    - text: what is your height?

We also came up with corresponding NLU intents:

## intent:request_profile_info
- I want to provide my profile details
- Do you know who am I

## intent:slot_getter_gender
- [male](gender)
- [female](gender)

## intent:slot_getter_age
- [10](age)
- [37](age)
- [5](age)
- [75](age)
- [18](age)
- [38](age)

## intent:slot_getter_weight
- [23](weight)
- [74](weight)
- [87](weight)
- [57](weight)
- [62](weight)

## intent:slot_getter_height
- [124](height)
- [175](height)
- [188](height)
- [158](height)
- [163](height)

And with a corresponding NLU story:

## user information
* request_profile_info
  - profile_form
  - form{"name": "profile_form"}
  - form{"name": null}

Finally, we’ve added a name: “FormPolicy” policy in the configurations yaml, and implemented the action form class:

class ProfileForm(FormAction):
    """ Profile custom form action"""

    def name(self) -> Text:
        """Unique identifier of the form"""

        return "profile_form"

    @staticmethod
    def required_slots(tracker: Tracker) -> List[Text]:
        """A list of required slots that the form has to fill"""

        return ["gender", "age", "weight", "height"]
    
    def submit(
        self,
        dispatcher: CollectingDispatcher,
        tracker: Tracker,
        domain: Dict[Text, Any],
    ) -> List[Dict]:
        """ Define what the form has to do after all required slots are filled"""

        # utter submit template
        dispatcher.utter_message(text="we got it all, thanks!")
        return []

Now the problem is that the intents are not being detected correctly.
For example, we were expecting RASA to “know” that the a slot_getter_age intent will be followed an utter_ask_age question. Unfortunately, that’s not the case, and wrong intents detected.

Can you please guide us how to make it right? Have we did something wrong in the above implementation?

Thanks ahead, Newt Team

Hello @sgino209

The way you define the intents Rasa has no way of distinguishing them. It may be learning that “75” is an age and “74” is a weight, but that is not what you want. Instead, you could just define a single intent inform and a single entity integer, but you keep the different slots for the different fields. Then you give some examples for the inform intent using entity roles (see https://rasa.com/docs/rasa/nlu/entity-extraction/#entities-roles-groups). E.g.

## intent:inform
- I am [10]{"entity": "integer", "role": "age"} years old
- [42](integer)
- I'm [163]{"entity": "integer", "role": "height"} cm tall
- I am [32]{"entity": "integer", "role": "age"} years old and [163]{"entity": "integer", "role": "height"} cm tall
...

Then you tell your form to fill the slots with from_entity with the specific role as first priority, and from_entity without role specification as second option.

Hi Johannes,

Thanks for your detailed reply.
We have followed your exact guidelines, but the new trained-model appears to detect only the intent (‘integer’) and not the role itself.
As a result, all the slots (age, weight, height) are set with the same value (age):

  • utter_ask_age: “how old are you?”
  • user responds with an answer, e.g. “37”
  • intent is detected as ‘integer’ (no role)
  • age, weight and height are set to “37” (second from_entity mapper, without role)

We are currently mainly using the RasaX edition. Could it be that it’s a RasaX issue, i.e. roles are not supported there (we got this impression from this discussion)?

We were looking for a way to tell RASA that age shall be set after utter_ask_age, weight shall be set after utter_ask_weight and finally height shall be set after utter_ask_height.

We eventually ended-up with getting the desired behavior with requested_slot and with validate methods:

def slot_mappings(self) -> Dict[Text, Union[Dict, List[Dict]]]:
    """A dictionary to map required slots to
        - an extracted entity
        - intent: value pairs
        - a whole message
        or a list of them, where a first match will be picked"""

    return {
        "age": [
            self.from_entity(entity="integer", role="age"),
            self.from_entity(entity="integer"),
        ],
        "weight": [
            self.from_entity(entity="integer", role="weight"),
            self.from_entity(entity="integer"),
        ],
        "height": [
            self.from_entity(entity="integer", role="height"),
            self.from_entity(entity="integer"),
        ],
    }
   
def validate_age(
    self,
    value: Text,
    dispatcher: CollectingDispatcher,
    tracker: Tracker,
    domain: Dict[Text, Any],
) -> Dict[Text, Any]:
    """Validate age value."""
    
    requested_slot = tracker.get_slot("requested_slot")
    age_slot = tracker.get_slot("age")

    if requested_slot == "age":
        return {"age": value}
    elif age_slot:
        return {"age": age_slot}
    else:
        return {"age": None}

def validate_weight(
    self,
    value: Text,
    dispatcher: CollectingDispatcher,
    tracker: Tracker,
    domain: Dict[Text, Any],
) -> Dict[Text, Any]:
    """Validate weight value."""
    
    requested_slot = tracker.get_slot("requested_slot")
    weight_slot = tracker.get_slot("weight")

    if requested_slot == "weight":
        return {"weight": value}
    elif weight_slot:
        return {"weight": weight_slot}
    else:
        return {"weight": None}

def validate_height(
    self,
    value: Text,
    dispatcher: CollectingDispatcher,
    tracker: Tracker,
    domain: Dict[Text, Any],
) -> Dict[Text, Any]:
    """Validate height value."""
    
    requested_slot = tracker.get_slot("requested_slot")
    height_slot = tracker.get_slot("height")

    if requested_slot == "height":
        return {"height": value}
    elif height_slot:
        return {"height": age_slot}
    else:
        return {"height": None}

With the following story:

## Introduction
* Bot_introduction
    - profile_form
    - form{"name":"profile_form"}
    - slot{"requested_slot":"gender"}
* inform{"gender":"male"}
    - profile_form
    - slot{"gender":"male"}
    - slot{"requested_slot":"age"}
* inform{"integer":"38"}
    - profile_form
    - slot{"age":"38"}
    - slot{"requested_slot":"weight"}
* inform{"integer":"72"}
    - profile_form
    - slot{"weight":"72"}
    - slot{"requested_slot":"height"}
* inform{"integer":"172"}
    - profile_form
    - slot{"height":"172"}
    - slot{"requested_slot":null}
    - form{"name":null}
    - utter_slot_getter_username

This appears to be a proper workaround for our end.

Thanks,
Newt Team

1 Like

Hi @sgino209

Sorry for the late reply. Yes, using the validate function is a good way to go. Glad it works now :slight_smile:

1 Like