Slot-filling

Slot-filling is a common design-pattern in spoken design, where you typically want to allow users to more freely answer a broad question instead of directly asking many detailed questions. For example, a travel agent bot asking "How can I help you?" is typically a better user-experience than initiating a sequence of questions "Do you want to book a trip?" -> "What city is your destination?" -> "Where do you want to go from?" etc. To this more broad question "How can I help you", users may for example say "I want to book a trip", but they may also provide more information right away - for example "I want to book a trip to Stockholm from New York for two people". In the first example, we have to ask followup questions since we have slots unfilled - i.e. we have to know what destination, departure city and how many people are travelling. For the second example, we could instead proceed to the next step of the dialog since these "slots are already filled.

To accomplish slot-filling in Narratory, you combine the two concepts discussed above: optional entities and conditionals. An example follows:

First, we define our bookingIntent to have examples using a variety of our three entities, and a simple BotTurn where we expect an answer matching this intent:

export const bookingIntent: Intent = {
examples: [
"I want to book a trip",
"I want to book tickets",
"I want to book a trip to _toCity",
"I want to book a trip to _toCity from _fromCity",
"I want to book _numPeople tickets to _toCity",
"I want to book _numPeople tickets from _fromCity to _toCity",
],
entities: {
toCity: entities.geoCity,
fromCity: entities.geoCity,
numPeople: entities.numberInteger
}
}
const openQuestion: BotTurn = {
say: "What can I help you with?", user: [
{ intent: bookingIntent, bot: "Excellent"}
]
}

This means that the slots toCity, fromCity and numPeople may or may not have been set after this. No matter what, we reply "Excellent". If these slots have not been filled, we want to ask following questions to fill these slots. To do this, we create 3 new intents, one for each slot and then three following BotTurns each with a condition:

// Intents
const numPeopleIntent: Intent = {
examples: ["_tickets", "_tickets people", "_tickets tickets"],
entities: {
numPeople: entities.numberInteger
}
}
const travelFromIntent: Intent = {
examples: ["I want to go from _fromCity", "I want to fly from _fromCity", "_fromCity", "from _fromCity"],
entities: {
fromCity: entities.geoCity,
}
}
const travelToIntent: Intent = {
examples: ["I want to go to _toCity", "I want to fly to _toCity", "_toCity", "to _toCity"],
entities: {
toCity: entities.geoCity,
}
}
// Turns
const askTickets: BotTurn = {
cond: { numPeople: null },
say: "How many people are travelling?", user: [
{ intent: numPeopleIntent, bot: `How nice, _${user.numPeople}_ people.` }
]
}
const askTo: BotTurn = {
cond: { "toCity": null },
say: "Where do you wanna go to?", user: [
{ intent: travelToIntent, bot: "To _toCity, got it" }
]
}
const askFrom: BotTurn = {
cond: { "fromCity": null },
say: "Where do you wanna go from?", user: [
{ intent: travelFromIntent, bot: "From _fromCity, got it" }
]
}

Here, we see that each of these BotTurns have a cond {} parameter, with each one checking if a variable is null (which we learned in logic & conditionals means the variable is not set).

Note that different syntax is used in each cond and in the followups to repeat the entity value the user said. This is to show that there are different ways of doing it, based on preference. The ${user.numPeople} syntax is an experimental feature that requires you to run npm run watch while building your app, but it gives you autocomplete for all variables. Also note that above it might make sense to use the entities instead of creating individual intents for each "slot-filling" intent. This will be supported shortly.

Finally, it is a good practise to confirm that we filled all the slots properly. This can be done as shown below, also allowing users to repair the turn (as shown in repair in bulding blocks). An example of this below:

const confirm: BotTurn = {
say: "So, to confirm, _numPeople going from _fromCity to _toCity. Shall we proceed with the booking?", user: [
{ intent: ["It is ok", "OK", "great", "yes"], bot: "Great, let's proceed" },
{ intent: ["No", "It is not good", "wrong"], bot: {
say: "Okay. What do you want to correct?",
repair: true
}},
{ intent: travelFromIntent, bot: {
say: "Ok, sorry. _fromCity it is. Otherwise we are good?",
repair: true
}},
{ intent: travelToIntent, bot: {
say: "Ok, sorry. Going to _toCity. Otherwise we are good?",
repair: true
}},
{ intent: numPeopleIntent, bot: {
say: "Alright. _numPeople people. Does it sound good otherwise?"
repair: true
}},
{ intent: ["Abort"], bot: "Okay, aborting" }
]
}

As you see here, the same slot-filling intents are used again, here combined with repair which makes Narratory stay in the same BotTurn. If the user finally confirms that the details are correct, we move on with a followup - which takes us to the next turn in the narrative.

IMPORTANT: Don't forget to add all turns together to a narrative:

const narrative = [openQuestion, askTickets, askTo, askFrom, confirm]