Rasa Quizbot - Adding Interaction with Forms (Part 2)
Storing user responses to certain questions or statements can help a chatbot provide users with a better experience. In Rasa, forms collect user responses and save them into slots. The chatbot can then use slot values throughout the conversation. Future questions and statements can incorporate a slot value, such as the user’s name. Even better, the conversation can adapt to a user’s responses, giving users a more personalized experienced.
So far in our Addy quizbot, we’ve set up the basic responses the bot will give and a basic form to collect the user’s name. Now, we’ll add four more forms. The first will be to ask the user what quiz level they want to attempt. The second and third will be our actual quizzes. The last will be to ask the user whether they want to take another quiz. These quizzes will focus on addition problems. We’ll work backwards from the end of our conversation to the beginning as we add these forms.
You may want to review the previous articles if you have not done those yet.
1. Rasa Development: Introduction to Rasa
2. Rasa Quizbot: Setting Up Responses (Part 1)
If you get stuck or want to preview what we’ll do, take a look at the source code for this article.
After taking a quiz, we would like to ask a user if they’d like to take another quiz. We’ll follow similar steps as with the name_form we built in the previous article. First, though, we start off by adding the slot quiz_retake to the slots section in domain.yml. There are two key differences with the quiz_retake slot. We need to use a Boolean since we are expecting a user to answer “yes” or “no” to taking a quiz again. Also, this slot will influence the conversation. If a user types “yes,” we will send them back to choose another quiz. If a user types “no,” then we will send them to the goodbye utterance. Thus, we need to set influence_conversation to true.
quiz_retake:
type: rasa.shared.core.slots.BooleanSlot
initial_value: null
auto_fill: true
influence_conversation: true
Next, we need to add the quiz_retake_form to the forms section of domain.yml. You’ll notice that quiz_retake_form has more settings than name_form. Boolean slots use true or false values. However, our user will probably respond to our question with “yes” or “no.” Even more complicated, a user could response with varying alternatives, such as “sure,” “y”, “nope”, or “not a chance.” In order to map these values to the slots, we can connect the slot to an intent. We already have affirm and deny intents in nlu.py. These intents don’t have exhaustive lists for “yes” and “no” responses. After you add quiz_retake_form, try to add more examples to the affirm and deny intents in nlu.py.
forms:
name_form:
required_slots:
name:
- type: from_text
quiz_retake_form:
required_slots:
quiz_retake:
- type: from_intent
value: true
intent: affirm
- type: from_intent
value: false
intent: deny
We also need to modify utter_quiz_retake. Remember that “ask” specifies the form; quiz_retake is the variable. Our utterance name needs to be utter_ask_quiz_retake.
utter_ask_quiz_retake:
- text: "Would you like to take another quiz?"
Now we need to update our stories.yml to use quiz_retake_form. We need to replace utter_quiz_retake with the following:
- action: quiz_retake_form
- active_loop: quiz_retake_form
- active_loop: null
- slot_was_set:
- quiz_retake: "false"
The slot_was_set may have been surprising. That addition tells Rasa that we expect the user to say “false” (ie. the deny intent in nlu.py) in this story (expected conversation path). What about if a user says “yes?”
Since our quiz_retake slot can influence the conversation, we have a new step to complete. We have to incorporate the different outcomes into our stories to help Rasa understand how to adjust the conversation based on the responses. Add the retake quiz path story below the happy path story.
- story: retake quiz path
steps:
- intent: greet
- action: utter_greet
- action: utter_purpose
- action: name_form
- active_loop: name_form
- active_loop: null
- action: utter_quiz_level
- action: utter_quiz_finished
- action: quiz_retake_form
- active_loop: quiz_retake_form
- active_loop: null
- slot_was_set:
- quiz_retake: "true"
- action: utter_quiz_level
Now we need to add in the quizzes themselves. Our users can choose between a beginner and an advanced quiz. The beginner quiz will focus on single-digit addition problems. The advanced quiz will focus on double and triple-digit problems. We need a separate form for each quiz. Add the two entries below to the forms section in domain.yml.
beginner_quiz_form:
required_slots:
beginnerQ1:
- type: from_text
beginnerQ2:
- type: from_text
advanced_quiz_form:
required_slots:
advancedQ1:
- type: from_text
advancedQ2:
- type: from_text
Then, we’ll add the questions for the beginner quiz. Add these in the responses section in the domain.yml file.
utter_ask_beginnerQ1:
- text: "What is 1 + 1?"
utter_ask_beginnerQ2:
- text: "What is 2 + 9?"
utter_ask_advancedQ1:
- text: "What is 44 + 829?"
utter_ask_advancedQ2:
- text: "What is 404 + 40?"
The form will use slots to hold the answers users give. Update the slots section in the domain.yml file.
beginnerQ1:
type: rasa.shared.core.slots.TextSlot
initial_value: null
auto_fill: true
influence_conversation: false
beginnerQ2:
type: rasa.shared.core.slots.TextSlot
initial_value: null
auto_fill: true
influence_conversation: false
advancedQ1:
type: rasa.shared.core.slots.TextSlot
initial_value: null
auto_fill: true
influence_conversation: false
advancedQ2:
type: rasa.shared.core.slots.TextSlot
initial_value: null
auto_fill: true
influence_conversation: false
Before we update the stories.yml file, we are going to make one more form. We are trying to let users choose the quiz that best suits their educational needs. As a result, we need another form to let students set the quiz level. In the forms section of the domain.yml file, make the quiz_level_form. Notice that the type of slot we are using is from_entity. Entities are data that we want to extract from people’s answers. We need to use this because users may provide their answers embedded in a sentence, and different language might map to different values. For example, a user could say “Hardest” or “Give me the toughest quiz.”
quiz_level_form:
required_slots:
quiz_level:
- entity: quiz_level
type: from_entity
We already have the prompt for this form, but it’s not set up correctly. Can you think of how we need to change utter_quiz_level to make it the prompt for our form?
utter_quiz_level:
- text: "Thanks, {name}. What level quiz would you like to take - beginner or advanced?"
We need to use the “ask” keyword and then match the utterance name to the form slot. Make sure to change utter_quiz_level to utter_ask_quiz_level. You will need to make this change in other places we have used this name, like stories.yml.
utter_ask_quiz_level:
- text: "Thanks, {name}. What level quiz would you like to take - beginner or advanced?"
Now, we need to add a new section to the domain.yml file. We need to add an entities section with the slot for the form.
entities:
- quiz_level
We also need to add the quiz_level slot to our slots section. The quiz_level value will influence the conversation because it will direct our user to one quiz or the other, so we should set influence_conversation to true.
quiz_level:
type: rasa.shared.core.slots.CategoricalSlot
initial_value: null
auto_fill: true
influence_conversation: true
values:
- beginner
- advanced
Entities extract values from phrases. Slots store values. Right now, though, our slot and entity are not connected. We want them to be connected because we are using the values to influence our conversation. To make the entities map to the slots, you need to add a config section to domain.yml as below:
config:
store_entities_as_slots: true
Because we are looking for entities, we need to add a user intent that tracks entities. This intent is going to be the user’s reply to the utter_ask_quiz_level utterance in domain.yml. The user replies could have a lot of variety. We need to assign these possible user replies to either “beginner” or “advanced” – one of the two possible slot values quiz_level could have. In the nlu.yml file, add the quiz_level intent.
- intent: quiz_level
examples: |
- Give me the [easy]{"entity": "quiz_level", "value": "beginner"}
- Let me test my knowledge with the [beginner](quiz_level)
- I'm ready to take the [beginner](quiz_level) quiz.
- Can I take a [simple]{"entity": "quiz_level", "value": "beginner"} difficulty quiz on bash?
- I want to take the [average]{"entity": "quiz_level", "value": "advanced"} difficulty quiz.
- [advanced](quiz_level) quiz, please.
- Let me try the [advanced](quiz_level) quiz.
- Give me the [hardest]{"entity": "quiz_level", "value": "advanced"} one, please.
Notice that there are two ways to set entity values. The first way is to designate the entity value with brackets [ ] and connect it to the entity name in parentheses ( ).
[advanced](quiz_level) quiz, please.
The second way is to use another term, not associated with the slot values. You will need to explicitly connect the term to one of the slot values by specifying the entity name (ie. quiz_level) and the value (ie. beginner or advanced).
Give me the [hardest]{"entity": "quiz_level", "value": "advanced"} one, please.
Can you think of two more ways the user might respond? Practice writing out more possibilities for replies.
We need to add the quiz_level intent to our intents section in domain.yml
intents:
- greet
- quiz_level
- goodbye
- affirm
- deny
Since we have the quiz_level slot, we can use the value in our conversation just like we did with name value. In domain.yml, add the quiz_level slot value to utter_quiz_finished.
utter_quiz_finished:
- text: "Great, {name}! You've answered all the questions in the {quiz_level} quiz"
In stories.yml, we need to incorporate all the work we’ve done. First, it’s important to understand how we will be using the quiz_level_form. We don’t need to set up an active_loop. Since we are getting the value from an entity, we will call our prompt and then listen for an intent.
- action: utter_ask_quiz_level
- intent: quiz_level
We’ll create two stories for each level of quiz – Beginner / retakes a quiz, Beginner / doesn’t retake a quiz, Advanced / retakes a quiz, and Advanced / doesn’t retake a quiz.
stories:
- story: happy path beginner quiz
steps:
- intent: greet
- action: utter_greet
- action: utter_purpose
- action: name_form
- active_loop: name_form
- active_loop: null
- action: utter_ask_quiz_level
- intent: quiz_level
- slot_was_set:
- quiz_level: "beginner"
- action: beginner_quiz_form
- active_loop: beginner_quiz_form
- active_loop: null
- action: utter_quiz_finished
- action: quiz_retake_form
- active_loop: quiz_retake_form
- active_loop: null
- slot_was_set:
- quiz_retake: "false"
- action: utter_goodbye
- story: retake quiz path beginner quiz
steps:
- intent: greet
- action: utter_greet
- action: utter_purpose
- action: name_form
- active_loop: name_form
- active_loop: null
- action: utter_ask_quiz_level
- intent: quiz_level
- slot_was_set:
- quiz_level: "beginner"
- action: beginner_quiz_form
- active_loop: beginner_quiz_form
- active_loop: null
- action: utter_quiz_finished
- action: quiz_retake_form
- active_loop: quiz_retake_form
- active_loop: null
- slot_was_set:
- quiz_retake: "true"
- action: utter_ask_quiz_level
- story: happy path advanced quiz
steps:
- intent: greet
- action: utter_greet
- action: utter_purpose
- action: name_form
- active_loop: name_form
- active_loop: null
- action: utter_ask_quiz_level
- intent: quiz_level
- slot_was_set:
- quiz_level: "advanced"
- action: advanced_quiz_form
- active_loop: advanced_quiz_form
- active_loop: null
- action: utter_quiz_finished
- action: quiz_retake_form
- active_loop: quiz_retake_form
- active_loop: null
- slot_was_set:
- quiz_retake: "false"
- action: utter_goodbye
- story: retake quiz path advanced quiz
steps:
- intent: greet
- action: utter_greet
- action: utter_purpose
- action: name_form
- active_loop: name_form
- active_loop: null
- action: utter_ask_quiz_level
- intent: quiz_level
- slot_was_set:
- quiz_level: "advanced"
- action: advanced_quiz_form
- active_loop: advanced_quiz_form
- active_loop: null
- action: utter_quiz_finished
- action: quiz_retake_form
- active_loop: quiz_retake_form
- active_loop: null
- slot_was_set:
- quiz_retake: "true"
- action: utter_ask_quiz_level
Now, let’s test our bot again.Type “rasa train” to update the model with all the work we’ve done.Then, type “rasa shell.”Addy is working pretty well.Aside from collecting the user’s name, our bot lets users choose a quiz, delivers the quiz, and allows them to practice more. It’s working pretty well.
To practice these skills more, try adding another quiz level- intermediate. Or, you could repurpose the bot into quizzing users on different math operations (addition, subtraction, multiplication, division) instead of focusing on one.
Our bot is coming along nicely, but our user has no way of knowing if they were right or wrong. Addy doesn’t provide a score or feedback based on the correctness. Also, the conversation experience breaks down when a user wants to take another quiz because the variables do not reset and Addy just reuses the existing values. In the next section, we will look at how to add in custom actions to provide this functionality.
Project Code - Addy Quizbot - Step 3