How to repeat a story ,to repeat itself until satisfied for user

rasa core version: 0.12.3 python version : 3.6.7 64 bit os : windows 10

Scenario : My bot must suggest a nearby parking location .

		BOT                                                    User

	Hello, how can i help
											      i want to know near by parking places
   OK sure!
   Please share the location
											       "user selects location in map"                      
	Sorry, No parking places available here,
	Do, you want to search in different location
											         Yes
	OK sure!
	Please share the location
										            "user selects location in map"                      
	sorry, No parking places available here,
	Do, you want to search in different location
											         Yes
	OK sure!
	Please share the location
														"user selects location in map"                      
	Parking available in half Km 
	from your location

	thanks for contacting       

=========================================================================

here the story has to repeat same from asking location( Please share the location ), how can i achieve this with rasa stack. bot has to repeat the steps n number of time until a valid parking spot is obtained please help me .

Sounds like FormAction should work well. Documentation of FormAction is located here: Slot Filling You can check out Example bot here: rasa_core/examples/formbot at master · RasaHQ/rasa_core · GitHub

FormAction allow you to do fine control of the flow as long as you are in the FormAction. I would create 2 slots parking_location and user_selected_location and set as required_fields in FormAction.

Hi Naoko, thanks for the reply .

Actually here in the flow slots are filled and processed and given Output to the user. but here i want to repeat same story again with new values in the slots n Number of times based on the user decision.

You can repeat as long as you want using FormAction because as long as all required_field is filled, FormAction will continue to ask questions. So here is what I would do. As I noted I will declare 2 slots + user_select_different_location. If user_select_different_location is No, then I will set slot value of parking_location to None. then form will ask parking_location again. Do you think that would work?

Hi @naoko, I am new to this and am also wanting to do the same thing. Which is to loop through the stories until the user is satisfy with all the inputs he has make. I have look through your example and it seems like it will work.( Have not tested it out yet ) But am I able to check what Intent it is? Like user is able to reply “no”, “don’t want” and all of this is under the intent “deny”. So if user_select_different_location is under intent “deny” then i will reset the first 2 slots so that the bot will reask this question. How would I be able to do this?

Hello @enzechua, So after I went step by step, no 2 slot is required. Just one. Here is the flow I think it should work…

FormAction: require parking_location

BOT says: Please share your location

USER: select location on map

FormAction SET parking_location and Check Parking availability

BOT says: Sorry no parking places available. do you want to search in different location?

USER says: Okay, sure (intent AFFIRM) or No thanks (intent DENY)

IN FORM ACTION: if user intent is AFFIRM then: reset parking_location in FormAction func validate. return [SlotSet('parking_location', None)] Once the parking_location is reset, the BOT should ask user “Please share your location”

If user intent is DENY then: in request_next_slot you add something like

	intent = tracker.latest_message.get("intent", {}).get("name")
    if intent == 'DENY':
        return self._deactivate()

to exit FormAction.

1 Like

@naoko Thank you so much for the reply I have tried it at the validate function of Form Action and it seems to go well. Just that I have a slight problem. So let’s say,

BOT says: Do you want to confirm this parking location?

USER says: No (intent DENY)

And therefore I would reset the SLOT(parking_location, None). So base on your reference, it will be something like this.

intent = tracker.latest_message.get("intent", {}).get("name")
if intent == 'DENY':
    return [SlotSet('parking_location', None)]

return [] # does not save slot values. 

It seems like return[] does not save my slot values. Therefore bot would keep ask the question regardless the answer is. So how do I return back all the values if the intent is not DENY.

PS. Thanks so much once again, your answer has really helped me alot! And the way you format your answers makes it so much easier for me to understand which is why I am trying to do it like yours. haha.

1 Like

To save the Slot value, you want to return

return [SlotSet('parking_location', "value of parking location")]

See if that helps!

1 Like

Hello @naoko didn’t see this but yes it works, thank you so much once again! :smiley:

can i see your whole actions file because i have the same issue but i dont know how to apply your solution into my project

Hi there, may I know what is your issue on? Is it referring to the question itself? So let’s say I have a slot that is needed to be filled up

def slot_mappings(self):
        return {
            "outlet_menu_question": self.from_text(),
        }

If I want the story to repeat itself, in my validation function, I will do this:

def validate(self, dispatcher, tracker, domain):
        value = int(tracker.latest_message.get("text"))
        if value > 0:
            return[
                SlotSet("outlet_menu_question", value)
            ]
        dispatcher.utter_template('utter_wrong_command', tracker)
        return [SlotSet("outlet_menu_questions", None)]

What this function does is that it checks the value that the user had entered. Base on the condition given, if the value is not what I want, I will set the slot to be none. When a slot is not filled up, the question would then be repeated once again. However, if I meet the condition, I will return back a value to the slot and thus the validation function would be completed. The question would not be repeated anymore since the slot is filled up.

Btw, I am not sure how much Rasa had change. It had been 1 year since I last touch it.

Thanks for the reply.My situation is that i want to ask my user name and ask them to confirm it.The story will not end until users confirm it’s true.Thus,I creat a form with 2 slot.One is for the name and the other is for the comfirmation.My validate func look like this:

def validate(self, dispatcher, tracker, domain):
    value = tracker.get_slot("name1")
    intent=tracker.get_slot("user_confirm")
    if intent == True:
        return [SlotSet("name1",value)]
    return [SlotSet("name1",None)] 

but eventually it cant save the name so it keep asking.Please help,i’m just a beginner

No worries, looking from your codes, it just mean that you did not enter the conditional statement.

  1. First of all, may I look at your slot_mappings? If the slot returns self.from_text() then it makes sense why the conditional statement did not work.

  2. To emphasise on the first point, you are looking for intent and not the value of user_confirm itself. I assume they could type “yes”, “confirm” etc, and therefore when you use tracker.get_slot(“user_confirm”), you are actually getting the pure value of that slot. One way to confirm this is to print(intent) and see what you get.

To be honest, I kind of forgot how to do it since it’s been 1 year. But as shown above, I think you could do this

def validate(self, dispatcher, tracker, domain):
    value = tracker.latest_message.get("text")
    slot_to_fill = tracker.get_slot("requested_slot")
    intent = tracker.latest_message.get("intent", {}).get("name")
    if slot_to_fill == "user_confirm": 
        if intent =="true":
            return [SlotSet("user_confirm", value)]
        else:
            return [SlotSet("user_confirm", None)]
    return [SlotSet(slot, value) for slot, value in slot_values.items()]

From what I recall, the validate function is called for every single slot. Which is why I use tracker.latest_message.get(“text”) to get the last message from the slot.

  1. I think your name1 question gets called first. Therefore, since none of the if statement is true, it will return its own value.

  2. Since the next slot to fill with be “user_confirm”, check the intent of it. If it is not true, set “user_confirm” slot to be none and have the question repeat again till the intent is true.

God bless you sir! Thanks to your instructions,finally my bot can ask the same questions until user confirm their name.However,following your code make my bot keep asking for user_confirm instead of name1 thus i changed it a little bit.Thanks for your help!

Glad to know it works! Oh, haha I might have read your previous question wrongly but am glad you have manage to change and make it work :slight_smile: Cheers!

1 Like

HI @littlerain2310, I have the same issue and was wondering if you can help? I am creating a loop based on whether the user would like to pay or order more. This is my function:

class FormGetRestaurantMenuCategoryOrderMoreDishes_en(FormValidationAction):
	
	
    def name(self):
        return 'form_get_restaurant_menu_category_order_more_dishes_en'
		
    @staticmethod
    def required_slots(tracker: Tracker) -> List[Text]:
        """A list of required slots that the form has to fill"""
        return ["order_more_or_pay_en" ]

    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 {"order_more_or_pay_en":[self.from_text()]}
    
    def validate(self, dispatcher, tracker, domain):
        value = tracker.latest_message.get("text")
        slot_to_fill = tracker.get_slot("requested_slot")
        intent = tracker.latest_message.get("intent", {}).get("order_more_or_pay_en")
        if slot_to_fill == "order_more_or_pay_en": 
            if intent =="pay":
                return [SlotSet("order_more_or_pay_en", value)]
            else:
                return [SlotSet("order_more_or_pay_en", None)]
        return [SlotSet(slot, value) for slot, value in slot_values.items()]
   
    #def submit(self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> List[Dict]:

    #    if tracker.get_slot('order_more_or_pay_en') == 'Order More':
    #        dispatcher.utter_message(text="Choose wisely...")

    #    elif tracker.get_slot('order_more_or_pay_en') == 'Pay':
    #        dispatcher.utter_message(text="Please be patient you will need to answer more questions before you can pay :)")

    #    return []

I want it to repeat if the user chooses from a button that he/she wants to order more but it continues with the story for if the user wants to pay. Can you please tell me what I did wrong?