When building conversational user interfaces, it’s important to think about continued engagement and high-quality interactions. During a recent webinar on advanced voice design techniques, we shared some best practices for applying advanced features like dialog management, entity resolution, and memory to enable customers to engage in multi-turn conversations with Alexa skills.
We used a skill we developed called Pet Match to demonstrate these concepts. Watch the on-demand webinar to see the skill in action. You can also tune into this 2-part blog series where we tear down Pet Match to show you the ins and outs of dialog management and entity resolution. Our first post dives into the dialog management aspects of the skill.
Pet Match uses Dialog Management, which delegates slot collection to Alexa. Pet Match finds the perfect pet for the user by asking a series of questions designed to fill the size, temperament, and energy slots as long as they are looking for a dog. For example, the user can say, "Alexa, tell Pet Match I want family dog that is high energy." However, the same process can be applied to match the user with cats, birds, turtles, books, movies, video games or whatever you'd like to match the user with. Once the 3 required slots are collected, the values are passed to an API through an http get request. The API returns the match in a JSON payload, which the skill unpacks and converts into speech output.
Through Dialog Management, Pet Match gains the flexibility of collecting the slots, all at once in a one-shot utterance and one or many slots in a multi-turn sequence without writing any code to manage slot elicitation. Dialog Management does provide a way to hook into it so you can override the default behavior. This blog post will refer to several blocks of code in Pet Match's dialog model and AWS Lambda Function. The entire codebase is available on github.com.
Pet Match's PetMatchIntent is using Dialog Management. If you look at the Intent Slots for PetMatchIntent, you'll notice that size, temperament, and energy are marked REQ which indicates that they are required slots.
In the interaction-model you should see the following:
"prompts": [
{
"id": "Elicit.Intent-PetMatchIntent.IntentSlot-size",
"variations": [
{
"type": "PlainText",
"value": "There are dogs that are tiny, small, medium, and large. Which would you like?"
},
{
"type": "PlainText",
"value": "What size of a dog would you like?"
}
]
}]
The id Elicit.Intent-PetMatchIntent.IntentSlot-size indicates that Alexa should handle elicitation of the size slot. The variations is a set of prompts that Alexa will use to elicit the size slot from the user. Be sure to take a look at the temperament and energy slots as well.
In the slots array, for the size slot you should see:
"slots": [
//...
{
"name": "size",
"type": "sizeType",
"samples": [
"{I_Want} {article} {size} {pet}",
"{I_Want} {size}",
"{comparison} than a {size}",
"the {size}",
"Something i can {size}",
"{size} size",
"{I_Want} {article} {size} {pet} that {energy}",
"{I_Want} {article} {size} {temperament} {pet}",
"{I_Want} {article} {size} {temperament} to {energy}",
" {temperament} {pet}",
"{energy} energy",
"{size}"
]
},
// ...
The size slot contains an object called samples, which are the utterances that the user will say to fill the slot. You may have noticed that we have defined some additional unrequired slots called I_Want, article, pet, and comparison. These slots will be filled if provided but Dialog Management will not prompt for them because they are optional. One great Dialog Management feature is the ability to provide slots in addition to the one being prompted. This enables the PetMatchIntent to capture additional slots when prompting the user for size.
For example, if Pet Match prompts the user for the size slot by asking, "What size of a dog would you like?" and the user responds, "I want a large guard dog." The {I_Want} {article} {size} {temperament} {pet} utterance will allow PetMatchIntent to capture the temperament in addition to the size. Likewise, if the user responded with, "I want a guard dog" The {I_Want} {article} {size} {temperament} {pet} will a capture the temperament even though the user was prompted for size. The skill would then have to reprompt for the size slot because it's still empty, but we didn't lose the temperament.
Notice at this point we haven't written any additional code to be able handle the variations of conversation. We simply provided training data through our slot-level prompts and utterances and Alexa figured out how to handle the input and deliver it to our skill in a meaningful way.
Now that you have an understanding of how the front-end works. Let's take a look at the back-end.
'PetMatchIntent' : function () {
// delegate to Alexa to collect all the required slots
let isTestingWithSimulator = true; //autofill slots when
using simulator, dialog management is only supported with a device
let filledSlots = delegateSlotCollection.call(this, isTestingWithSimulator);
// Code has been truncated for brevity
}
From the PetMatchIntent we are delegating slot elicitation to Alexa via delegateSlotCollection.call(this, isTestingWithSimulator). Dialog Management has 3 states, STARTED, IN_PROGRESS, and COMPLETED. The delegateSlotCollection function checks the present dialog state, this.event.request.dialogState, and returns the slots when COMPLETED. At any state in Dialog Management we can fill the slots with default data, override the prompts, ask the user to confirm a slot, or re-elicit a slot. In a separate post, we'll cover in detail how to plug into dialog management to disambiguate a slot value that resolved to more than one synonym using Entity Resolution. The disambiguateSlot function is where we identify ambigous slot values and re-elicit the slot.
function delegateSlotCollection(shouldFillSlotsWithTestData) {
console.log("in delegateSlotCollection");
console.log("current dialogState: " + this.event.request.dialogState);
// This will fill any empty slots with canned data provided in defaultData
// and mark dialogState COMPLETED.
// USE ONLY FOR TESTING IN THE SIMULATOR.
if (shouldFillSlotsWithTestData) {
let filledSlots = fillSlotsWithTestData.call(this, defaultData);
this.event.request.dialogState = "COMPLETED";
};
if (this.event.request.dialogState === "STARTED") {
console.log("in STARTED");
console.log(JSON.stringify(this.event));
var updatedIntent=this.event.request.intent;
// optionally pre-fill slots: update the intent object with slot values
// for which you have defaults, then return Dialog.Delegate with this
// updated intent in the updatedIntent property
disambiguateSlot.call(this);
console.log("disambiguated: " + JSON.stringify(this.event));
return this.emit(":delegate", updatedIntent);
console.log('shouldnt see this.');
} else if (this.event.request.dialogState !== "COMPLETED") {
console.log("in not completed");
//console.log(JSON.stringify(this.event));
disambiguateSlot.call(this);
return this.emit(":delegate", updatedIntent);
} else {
console.log("in completed");
//console.log("returning: "+ JSON.stringify(this.event.request.intent));
// Dialog is now complete and all required slots should be filled,
// so call your normal intent handler.
return this.event.request.intent.slots;
}
}
Once this.event.request.dialogState is COMPLETED all of the required slots have been collected and delegateSlotCollection returns them. Then we simplify the slots object by passing filledSlots to getSlotValues which returns the mapping of synonyms to resolved values. This will be covered more in detail in a separate post.
Once the object has been simplified, we generate the http get request to the Pet Match API that returns the match based upon the size, temperament and energy slots.
'PetMatchIntent' : function () {
// delegate to Alexa to collect all the required slots
let isTestingWithSimulator = true; //autofill slots when
using simulator, dialog management is only supported with a device
let filledSlots = delegateSlotCollection.call(this, isTestingWithSimulator);
let petMatchOptions = buildPetMatchOptions(slotValues);
httpGet(petMatchOptions).then(
response => {
if( response.result.length > 0 ) {
this.response.speak("So a "
+ slotValues.size.resolved + " "
+ slotValues.temperament.resolved + " "
+ slotValues.energy.resolved
+ " energy dog sounds good for you. Consider a "
+ response.result[0].breed);
} else {
this.response.speak("I'm sorry I could not find a match for a "
+ slotValues.size.resolved + " "
+ slotValues.temperament.resolved + " "
+ slotValues.energy.resolved
+ " dog");
}
})
).then(() => {
// after we get a result, have Alexa speak.
this.emit(':responseReady');
}
);
httpGet returns a promise and in the then we pass the anonymous function that builds the speech response.
The helper functions delegateSlotCollection, getSlotValues and httpGet, have been written a way that will allow you to paste them directly into your code so you can easily add Dialog Management to your skill.
Now that you have a deeper understanding of Dialog Management, read the developer documentation on Dialog Interface Reference.
Also consider how you would use Dialog Management in your existing and future skills. It’s perfect for multi-turn interactions between the user and your skill and greatly reduces the amount of complex logic you would otherwise have to write on your own to keep track of the data that you have versus what you need, as well as capturing additional slots.
Stay tuned for the next deep dive on Pet Match's implementation of Entity Resolution, which will help make the interaction between the user and your skill more natural in addition to simplifying the normalization of input.
Every month, developers can earn money for eligible skills that drive some of the highest customer engagement. Developers can increase their level of skill engagement and potentially earn more by improving their skill, building more skills, and making their skills available in in the US, UK and Germany. Learn more about our rewards program and start building today.