Conversations are dynamic and do not follow a straight line. Have you ever been in a conversation with someone where you asked a question and instead of the answer they reply with a question? In this case, the person on the other end was missing some important information that they needed in order to make a decision. In conversation, it's perfectly normal for someone to answer with follow-up questions until they've obtained all the information that they need in order to provide an answer. When your conversational partner asks a follow-up question, you will need to:
- Momentarily set aside the context of the original question
- Enter the context of the new question asked
- Assist in finding the answer to the new question
- Re-enter the context of the original question
- Prompt and listen for the answer to the original question
When you break it down it's pretty complicated and it's amazing that we humans can do that without much effort.
In order to create a conversational skill, you'll need to mimic that behavior. First you should write a happy path script, which is the simplest conversation that your customers will have with your skill. After that you should try to anticipate how users will deviate from your happy path. Doing so will help you to anticipate areas where they might ask a follow-up question. You can then build in answers to those questions. If you’d like to dive deeper into designing a happy path script, check out the script it out section of the Designing for Conversation online course.
One important thing that your skill will need to do is keep track of the information that it has been collected. Today's blog post will share how you can enable your Alexa skill to capture important information using context switching.
Context Switching with The Foodie
The Foodie is an example skill that we created for our Designing for Conversation online course. It's a skill that recommends meals and recipes to it's customers based on a set of questions. When the customer expresses a desire to cook, The Foodie tailors its questions to figure out what kind of recipe to recommend. "There are small medium and large portions, which would you like?" is one of those questions.
Some people might be just fine answering, small or medium while others might be watching their caloric intake and would like to know how many calories are in a large portion before they decide. To do this, the Foodie uses two intents.
- RecommendationIntent: Uses dialog management to track the conversation and automatically ask questions to fill a set of required slots.
- ExplainPortionSizeIntent: Recieves the portion slot, tells the customer how many calories are in the requested portion, and prompts the customer to choose a portion size.
When the customer chooses a portion size, the skill will re-enter the RecommendationIntent and we'll need to restore the context manually otherwise the skill wont remember what has already been collected.
Failing to do so will result in a frustrating experience and our customers will not be pleased. Take a minute to watch this video to see how frustrating that would be:
Wasn’t that awkward? You shouldn't expect your customers to have keep repeating previously provided information over and over. One of the great things about conversation is that it's reactive and conversational partners can work together to communicate their thoughts and feelings.
Let's take a look at how we are remembering context with The Foodie.
Remembering and Restoring Context with Session Attributes
The Alexa Skills Kit Software Development Kit (SDK) includes a lot of great features. One great feature is called session attributes. We can use these attributes to store important information that our skill need not forget. If you’re unfamiliar with session attributes and are interested in learning more, take a look at Alexa Skill Recipe: Using Session Attributes to Enable Repeat Responses.
The Foodie, just like its conversational human counterpart, uses five steps to context switch:
- Get session attributes
- Check those session attributes for previously collected slot values
- Restore the slots if we have the values available
- Set any newly acquired session attributes
- Save/Sync
Let’s take a look at how these 5 steps translate to code.
Step 1: Get
To get the session attributes, we will make use of the SDK’s AttributesManager. It includes a function called getSessionAttriutes which reads the session attributes from the response to our skill code. We’ll need to access the attributes later so we will save them into a variable called sessionAttributes.
const session handlerInput.attributesManager.getSessionAttributes();
Now let's check them for previously collected slot values.
Step 2: Check
Using the sessionAttributes, we’ll check to see if we’ve previously saved the current intent’s slots. If they aren’t there then it’s the first time our user’s experienced this intent during this interaction (session).
If, however, we’ve already collected some of the slots, then we have some slots to restore. In this case, Alexa is either continuing where the dialog management flow previously left off, or returning from a context switch. To check the later case, our if statement would look like:
if (sessionAttributes[currentIntent.name]) {
// We have slots to restore!
}
Now that we’ve identified something to restore, let’s restore them.
Step 3: Restore
To restore our slots, we need to identify slots that are filled vs. those that are empty. This will help us determine where the conversation needs to pick up. We don’t want to have our skill ask again for information previously given.
To do this we will loop through the entire set of slots for the sessionAttributes and add them to the currentIntent’s slot values. In code that would look like:
const savedSlots = sessionAttributes[currentIntent.name].slots;
foreach(let key in savedSlots) {
if (!currentIntent.slots[key].value && savedSlots[key].value) {
currentIntent.slots[key] = savedSlots[key];
}
}
The if statement ensures that we are only restoring slots values when we:
- Have a saved slot value in sessionAttributes
- The currentIntent’s slot doesn’t have a value
We are doing this to allow the user to override their choice. For example, if they asked for a large portion and then changed their mind asking for a medium portion, the code will allow them to do so.
We’ve restored our slots. Let’s move on to setting our session attributes slots to match with those of currentIntent.
Step 4: Set
This is an important step. We don’t want Alexa to forget the slots especially after all that hard work that we did. So to do that our code will update the sessionAttributes to be the currentIntent that we just finished updating in our loop.
sessionAttributes[currentIntent.name] = currentIntent;
attributesManager.setSessionAttributes(sessionAttributes);
Now let’s move onto the final step.
Step 5: Sync/Save
We’re not out of the woods yet. We still need to tell the Alexa Service that we’ve changed the currentIntent’s slot values. To do so, we need to send our modified currentIntent along with the Dialog.Delegate that we pass back to the Alexa Service to have dialog management automatically prompt for our missing required slots. The SDK makes it easy for us. We simply need to pass currentIntent to the addDelegateDirective() function.
return handlerInput.responseBuilder
.addDelegateDirective(currentIntent);
The Code at a Glance
Now that we’ve seen the code piece by piece, let’s take a look at what it looks like all together.
const DialogManagementStateInterceptor = {
process(handlerInput) {
const currentIntent = handlerInput.requestEnvelope.request.intent;
if (handlerInput.requestEnvelope.request.type === "IntentRequest"
&& handlerInput.requestEnvelope.request.dialogState !== "COMPLETED") {
const attributesManager = handlerInput.attributesManager;
const sessionAttributes = attributesManager.getSessionAttributes();
// If there are no session attributes we've never entered dialog management
// for this intent before.
if (sessionAttributes[currentIntent.name]) {
let savedSlots = sessionAttributes[currentIntent.name].slots;
for (let key in savedSlots) {
// we let the current intent's values override the session attributes
// that way the user can override previously given values.
// this includes anything we have previously stored in their profile.
if (!currentIntentSlots[key].value && savedSlots[key].value) {
currentIntent.slots[key] = savedSlots[key];
}
}
}
sessionAttributes[currentIntent.name] = currentIntent;
attributesManager.setSessionAttributes(sessionAttributes);
}
}
};
This code will go a long way to ensure that your customers aren’t forced to repeating themselves when the conversation caues your skill to switch back and forth between contexts.
Did you notice that we wrapped all of code in DialogManagementStateInterceptor? Interceptors run at either every request or response. The if statement checks to see whether the request is an intent request where the dialogState isn’t completed. This allows our code to support context-switching from any intent where dialog management is activated.
If you’ve never used an interceptor you’ll need to register it. Failing to do so will leave your interceptor inoperative and thus sad and lonely. Request interceptors are processed before our handlers while response interceptors are processed after. Since our handlers need the most up to date synced slots, we need a request interceptor.
We’ll pass our DialogManagementStateInterceptor to addRequestInterceptors() to do so:
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
// ... handlers
)
.addRequestInterceptors(
DialogManagementStateInterceptor
)
.addErrorHandlers(ErrorHandler)
.withTableName("theFoodie")
.lambda();
With this code, our skill can switch contexts without forgetting anything.
Don't Lose Context
Conversations can take on many branching paths. For example, a question can be answered with a question. Once the answer to the second question has been provided, it's up to your skill to prompt your customer to answer the original one. To avoid frustration and to build a more conversational skill, your skill should remember the information previously collected so it doesn't ask for it over an over. Being able to support a context switch will go a long way into building a conversational skill.
Now that you've read through this post, try to think about how you can put these techniques and features to use in your own skills. Let's continue the discussion online! You can find me on Twitter @SleepyDeveloper.
Related Content
- Build for Memory: Keep Track of Important Information When Designing Conversational Alexa Skills
- Build for Dialog: Solving the Order Problem When Designing Conversational Alexa Skills
- A Primer on Communication: Creating Conversational Transactions When Designing for Voice
- Why Conversational Design Matters: 4 Hallmarks of Conversational Skills
- How to Design with Users in Mind When Building for Voice
- Why Writing a Script Is the Best Way to Design for Conversation
- The Foodie Source Code on GitHub
Source: Alexa Developer Blog