Multiple entities, or single question, to both answer form question

I have a situation in which I want to answer questions that require multiple entities to be answered.

I can put in place a custom action that looks for slot values and maps them to a question answer, but I’m not sure if there’s a way of achieving this without the number of entities and slots getting out of hand?

Here if the user mentions that they have “a fat dog” in their initial chat, then both the entities fat and dog could be set to true. With some processing behind a custom action I could set the answer to the question on the standard_form - “Do you have a fat dog?” - as “yes”. The form would not need to be triggered and the direct question would need to be answered.

Is there a way of setting this up so that I don’t have to have fat_dog as another entity or slot? I think at the moment that the only way to trigger utter_ask_fat_dog is to have a slot called fat_dog (rather than the two separate slots)

In the situation in which the user says they have a dog, I would not be able to set “Do you have a fat dog?” to “yes”, and so the form and associated question would be triggered.

Grateful for any help. Hopefully my explanation makes some sense!

entities:
- dog
- fat
forms:
- standard_form
slots:
  dog:
    auto_fill: true
    type: unfeaturized
  fat:
    auto_fill: true
    type: unfeaturized
templates:
  utter_ask_fat_dog:
  - buttons:
    - payload: '/choose{"fat": "yes", "dog": "yes}'
      title: 'yes'
    - payload: '/choose{"dog": "no"}'
      title: 'no'
    text: Do you have a fat dog?

Any ideas or examples on how to handle this sort of case?

I’ve got slots dog and fat and a form question do you have a fat dog? . I need to be handle the following conditions:

Did you find a good solution? I have to handle a similar problem in my project :slight_smile:

I’m not sure I follow the entire flow of what you are doing but this seems like this would work fine in the normal FormAction with how you setup your custom action server. @liaeh can you explain your use case maybe I can help both of you.

Sure @btotharye ! My situation is different but was hoping to get some ideas from this original post :slight_smile: . Here’s the rundown of my project.

I am designing a chatbot that uses FormAction to search for an appropriate recipe for a user.

My domain file includes this:

entities:
- diet
- food_keyword
- meal_type
- recipe_type
forms:
- recipe_form
intents:
- surprise_me
- goodbye
- inform_dislikes
- deny
- greet
- inform
- affirm
- inform_allergen
- search_recipe:
    use_entities: []
- stop
slots:
  allergen:
    auto_fill: false
    type: unfeaturized
  diet:
    auto_fill: false
    type: unfeaturized
  dislike_ingr:
    auto_fill: false
    type: unfeaturized
  preference:
    auto_fill: false
    type: unfeaturized
  meal_type:
    auto_fill: false
    type: unfeaturized
  name:
    initial_value: human
    type: unfeaturized
  requested_slot:
    type: unfeaturized
templates:
  utter_ask_allergen:
  - text: Do you have any allergies I should know about?
  - text: What allergies should I keep in mind?
  - ...

My nlu file looks like this:

## intent:inform_allergen
    - allergic to [ingredient](food_keyword)
    - i am allergic to [soy](food_keyword)
    - I unfortunately have a [sesame](food_keyword) allergy
    - I have a [chicken](food_keyword) allergy
    - ...

    ## intent:inform_dislikes
    - I don't like [cilantro](food_keyword)
    - [garlic](food_keyword) is disgusting
    - [radish](food_keyword) is gross
    - I dislike [salmon](food_keyword)
    - i don't like [olives](food_keyword) nor [cilantro](food_keyword)
    - ....

    ## intent:inform
    - i am [vegan](diet)
    - [vegetarian](diet) recipes
    - [gluten-free](diet) diet
    - I like [garlic](food_keyword)
    - I love [tofu](food_keyword)
    - I love [anchovies](food_keyword)
    - [salmon](food_keyword) is delicious
    - [radishes](food_keyword) are delicious
    - I want something with [olives](food_keyword)
    - I need something for [lunch](meal_type)
    - I want a recipe for [dinner](meal_type)
    - I want to make [dessert](meal_type)
    - how about something with [ginger](food_keyword)
    - ... 

My actions.py file includes the following:

 class RecipeForm(FormAction):
 ...
 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 {
        "allergen": [ # THIS IS WHERE I'M GETTING STUCK
            self.from_entity(entity="food_keyword", intent="inform_allergen"),
            self.from_entity(entity="food_keyword"), # with/without this doesn't change mapping
            self.from_intent(intent="deny", value=False)
        ],
        "meal_type": self.from_entity(entity="meal_type", not_intent="chitchat"),
        "diet": [
            self.from_entity(entity="diet"),
            self.from_intent(intent="deny", value=False),
        ],
        "dislike_ingr": [ # here too
            self.from_entity(entity="food_keyword", intent="inform_dislikes"), # same as w/ allergen
            self.from_intent(intent="deny", value=False),
        ],
        "preference": self.from_entity(entity="food_keyword", intent=["search_recipe", "inform"]),
    }

The problem: I am not sure how to distinguish between allergens, disliked_ingr(edients), and preference. It isn’t effective to have multiple entities to represent these, because they are all food keywords/ingredients.

So, I made multiple intents (e.g. specific intents for inform_allergen and inform_dislikes) and had some success. If the chatbot runs utter_ask_allergen and the user answers with “I am allergic to eggs”, then eggs is marked as a food_keyword entity currently fills the allergen slot. Same if user replies with “no”, then allergen slot is set to false.

However… when the chatbot runs utter_ask_allergen (“Do you have any allergies I should know about?”) and the user simply answers with “yeah eggs”, then eggs is still marked as a food_keyword entity but fills (replacing whatever was already there) the general preferences slot since the intent is mapped to inform, not inform_allergen. How can the chatbot consider the question they just asked in order to correctly fill the allergen slot? I tried adding self.from_entity(entity="food_keyword") to the slot mapping but this didn’t work.

Any advice is much appreciated :smiley:

I guess I need a way to check tracker.get_slot('requested_slot')=='allergen'. Problem is, the implemented slot_mappings(self) method does not have access to the tracker. I’ve tried to then override the run method in:

class RecipeForm(Form)
def run(
    self,
    dispatcher: "CollectingDispatcher",
    tracker: "Tracker",
    domain: Dict[Text, Any],
) -> List[EventType]:
    self.tracker = tracker
    return super().run(dispatcher, tracker, domain)

When executing the code in cmd with rasa shell, the following error is produced after the FormAction is launched:

Traceback (most recent call last):
  File "c:\users\li\anaconda3\envs\rasaenv\lib\site-packages\rasa\core\run.py", line 128, in run_cmdline_io
    server_url=constants.DEFAULT_SERVER_FORMAT.format("http", port)
  File "c:\users\li\anaconda3\envs\rasaenv\lib\site-packages\rasa\core\channels\console.py", line 140, in record_messages
    async for response in bot_responses:
  File "c:\users\li\anaconda3\envs\rasaenv\lib\site-packages\rasa\core\channels\console.py", line 106, in send_message_receive_stream
    async for line in resp.content:
  File "c:\users\li\anaconda3\envs\rasaenv\lib\site-packages\aiohttp\streams.py", line 39, in __anext__
    rv = await self.read_func()
  File "c:\users\li\anaconda3\envs\rasaenv\lib\site-packages\aiohttp\streams.py", line 328, in readline
    await self._wait('readline')
  File "c:\users\li\anaconda3\envs\rasaenv\lib\site-packages\aiohttp\streams.py", line 296, in _wait
    await waiter
  File "c:\users\li\anaconda3\envs\rasaenv\lib\site-packages\aiohttp\helpers.py", line 596, in __exit__
    raise asyncio.TimeoutError from None
concurrent.futures._base.TimeoutError
Transport closed @ ('127.0.0.1', 50059) and exception experienced during error handling

I’m not sure why this doesn’t work: I’m literally just forwarding the parameters to the overwritten method :smiley: Thanks in advance if anyone can help.

Could you just handle this in the form and have 1 inform intent and validate and set the allergen in the action form?

From what you have here @liaeh I think this should all work. I think 1 intent and 1 entity would work here. If it already is predicting it as food just use that and set the slots with it.