Using Duckling to fill multiple time slots in rasa form

Hi, I am new to using rasa. I am trying to build a flight search and booking bot. In order to extract a users flight departure and return date I am using DucklingHTTPExtractor in the pipeline. The user can ask a question like: I would to travel from London to Paris. This would activate a form and questions will be asked to fill the following required slots:

  • depart_date
  • no_of_adults
  • no_of_children
  • no_of_infants
  • class_type
  • currency_code
  • round_trip (“round trip or one way”)
  • return_date (is required only if round_trip slot set to “round trip”)

If user chooses to book a round trip then I want to accept 2 dates from the user: departure and return date. But I am not sure how I can go about implementing this as duckling lets me set only one time slot. The chatbot works perfectly when I capture only the departure date as I create an entity for departure date with the same name as the duckling entity i.e. ‘time’ . But when I introduce depart_date and return_date as separate entities under required slots and in the slot mappings it doesn’t map to time entity. How do I go about doing this? Here is a screenshot of the slots that have been set before form submission on rasa interactive shell

I have also attached the files I am using. domain.yml (7.6 KB) actions.py (12.9 KB) stories.md (14.4 KB) nlu.md (737.2 KB)

Hi David and welcome to the forum.

When you run the action, you’ll want to pull out the time entity, something like this:

duckling_time = tracker.get_slot("time")

and then parse the duckling_time value which may contain the depart or return date depending on the intent.

Greg

Hi @stephens got that to work, thanks. I am trying to extract the departure date from the flight and inform_depart_date intent and return date from the inform_return_date intent. Since depart date and return date are set separately through two different intents the duckling time entity overwrites the previous time value, say i specify I want to depart on August 21st then time slot gets set to 2020-08-21T00:00:00 and in another intent (inform_return_date) I say I want to return on August 25th then time gets overwritten to 2020-08-25T00:00:00. To fix this I created two extra text slots depart_date and return_date which map to the duckling time entity. I also wrote validate functions for both departure date and return date that set these dates from the time entity. Here are the validate functions I have written

    def validate_return_date(self,
                      value: Text,
                      dispatcher: CollectingDispatcher,
                      tracker: Tracker,
                      domain: Dict[Text, Any],
                      ) -> Dict[Text, Any]:
        """Validate return date."""

        time_entities = [e for e in tracker.latest_message["entities"] if e["entity"] == "time"]
        if tracker.latest_message['intent'].get('name') == "flight" and len(time_entities) > 1:
            [return_date, return_time, *extra] =  re.split(r"[T,+,.]",time_entities[-1]["value"])
            depart_date = datetime.datetime.strptime(tracker.get_slot("depart_date"),"%Y-%m-%d").date()
            return_date = datetime.datetime.strptime(return_date,"%Y-%m-%d").date()

            if return_date < depart_date:
                dispatcher.utter_message("Return date should be after the departure date")
                SlotSet("return_date",None)
                SlotSet("time",None)
                return {"return_date": None}
            else:
                SlotSet("return_date", str(return_date))
                return {"return_date": str(return_date)}

        else:
            [return_date, return_time, *extra] =  re.split(r"[T,+,.]",tracker.get_slot("time"))
            return_date = datetime.datetime.strptime(return_date,"%Y-%m-%d").date()
            depart_date = datetime.datetime.strptime(tracker.get_slot("depart_date"),"%Y-%m-%d").date()
            if return_date < depart_date:
                dispatcher.utter_message("You need to choose a return date after the departure date")
                SlotSet("return_date",None)
                SlotSet("time",None)
                return {"return_date": None}
            else:
                SlotSet("return_date", str(return_date))
                return {"return_date": str(return_date)}

    def validate_depart_date(self,
                      value: Text,
                      dispatcher: CollectingDispatcher,
                      tracker: Tracker,
                      domain: Dict[Text, Any],
                      ) -> Dict[Text, Any]:
        """Validate departure date."""

        [depart_date, depart_time, *extra] =  re.split(r"[T,+,.]",tracker.get_slot("time"))
        depart_date = datetime.datetime.strptime(depart_date,"%Y-%m-%d").date()
        now = datetime.datetime.now().date()

        if depart_date < now:
            dispatcher.utter_message("Sorry you need to choose date in the present for me to search for flights")
            SlotSet("depart_date",None)
            SlotSet("time",None)
            return {"depart_date": None}
        else:
            SlotSet("depart_date", str(depart_date))
            return {"depart_date": str(depart_date)}

In the slot mappings function I have defined the following

"depart_date": self.from_entity(entity="time",intent=["flight","inform_departure_date"]),

"return_date": self.from_entity(entity="time", intent=["inform_return_date"])

This fixed the 2 time slots problem but now there is another problem. Only the time slot gets set when the entity is detected in the flight intent, the depart_date doesn’t get set. This leads to slot showing as missing and the utter_ask_depart_date question is asked (even though departure date is already specified) when the form action is activated. I tried creating a validate function for time but it didn’t work and the same problem persists.

def validate_time(self,
                      value: Text,
                      dispatcher: CollectingDispatcher,
                      tracker: Tracker,
                      domain: Dict[Text, Any],
                      ) -> Dict[Text, Any]:
        """Validate duckling time entity"""

        latest_intent = tracker.latest_message['intent'].get('name')

        if latest_intent == "flight" or latest_intent == "inform_departure_date":
            [depart_date, depart_time, *extra] =  re.split(r"[T,+,.]",tracker.get_slot("time"))
            depart_date = datetime.datetime.strptime(depart_date,"%Y-%m-%d").date()
            now = datetime.datetime.now().date()

            if depart_date < now:
                dispatcher.utter_message("Sorry you need to choose date in the present for me to search for flights")
                SlotSet("depart_date",None)
                SlotSet("time",None)
                return {"depart_date": None}
            else:
                SlotSet("depart_date", str(depart_date))
                return {"depart_date": str(depart_date)}

Is there something you can suggest to fix this?

In general, we recommend using a single inform intent to capture the entities. Try getting the duckling time entity in your validate_time function and set the slots based on what you see there. Depending on what the user said, you could have both departure and return dates.

I have a bot this does something like this:

        duckling_time = tracker.get_slot("time")

        logger.info(f"duckling_time: {type(duckling_time)}, value: {duckling_time}, to_time: {to_time}, from_time: {from_time}")
        # do we have two dates
        if type(duckling_time) is dict:
            from_time = tracker.get_slot("time")['from'][:19]
            to_time = tracker.get_slot("time")['to'][:19]
        else:
             # process time and determine if this return or departure based on current slot settings
2 Likes

Thanks @stephens it works now

Could please share your files

hi @stephens, I want ask the user separately about departure date and return date.
When im giving departure date . It automatically filing to return date slot also. Due to return date pre filled. form not asking the return date slot question.

How can i solve this

You could create a separate slots for the dates. A set of slots filled by the entity extraction (depart_date & return_date) and then a set validated slots that are only filled by your validate function (depart_date_validated).

thank you @stephens