Frame-based Dialogue Management

In this tutorial, we aim at building a simple train booking application using the Snips console and the hermes-javascript library. For the purpose of simplicity, we'll cover only one way ticket.

A ticket booking can be completed if and only if:

  • The user has expressed the intention of booking a train ticket that will be covered by the BookTrainTicket intent

  • The user has provided the following attributes:

    • origin: the train station the user is travelling from

    • destination: the train station the user is travelling to

    • departure_datetime: the datetime at which the user wants to leave

Now let's get started and create this Train booking application that contains a BookTrainTicket intent. You can refer this article if you want to learn more about intent creation in the console.

Preliminary: Frame-based dialogue management

In dialogue management, filling such a contract is called frame-based dialog management - the dialogue is successful when the contract is fulfilled, in the same way a web form can be validate once all required fields are specified. Despite the simplicity of the contract in our case, covering all interaction scenarios is actually trickier that it seems in the context of voice interfaces.

Slot elicitation and collaboration

In most cases, only a subset of the required attributes will be provided with the initial user intention. In certain cases such as "Book me a train ticket", none of the required attributes is actually specified. You'll therefore have to support multi-turn conversations to elicit the missing slots.

The user can be collaborative (or not) and the interaction flow should account for such cases. Slot-by-slot elicitation is often not satisfactory from a user experience standpoint. Let's consider the following interaction flow:

- User: Book me a train ticket
- Assistant: Sure, where will you be leaving from ?
- User: Going from San Diego to L.A
- Assitant: Leaving San Diego, got it. Where are you heading ?
- User: Already told you, I'm going to L.A
- Assistant: When will you be leaving ?
- User: Tomorrow at 5

Despite the fact that the contract is fulfilled, the user would have expected the assistant to be more collaborative in the sense that it could have detected both the origin and the destination slots from the user second turn.

Confirmation

For an action involving payment such as ticket booking, the user also expects to be asked for confirmation before the payment is made, hence the need for more interaction turns between the assistant and the user.

Early termination

At any time, the user should be able to leave the conversation, either by not answering a specific question or by simply mentioning that the current interaction should stop.

Intents, slots and enabled/disabled by default status

Role

Enabled by default

Intent and Slots

Training examples

Begin interaction

BookTrainTicket

  • origin

  • destination

  • departure_datetime

  • Book me a train ticket

  • I want a train ticket to San Diego (destination)

  • I want a ticket from Chicago (origin) to Philiadelphia (destination) leaving tomorrow (departure_datetime)

  • Book me a train from Los Angeles (origin) to New York City (destination)

Elicitation

ElicitItinerary

  • origin

  • destination

  • Leaving from Los Angeles (origin) and heading to San Francisco (destination)

  • From Boston (origin) to Chicago (destination)

  • Heading to Boston (destination) from Chicago (origin)

  • Leaving Boston (origin), heading to New York City (destination)

Elicitation

ElicitLocation

  • location

  • Detroit (location) please

  • New York City (location)

  • I'll be heading to New York City (location)

  • Leaving from Chicago (location)

Elicitation

ElicitDatetime

  • datetime

  • on friday the second (datetime) please

  • Leaving tomorrow morning (datetime)

  • I'll be leaving on Thursday (datetime)

Confirmation

Yes

  • Yes

  • Sure, go ahead

  • Yep

Confirmation

No

  • No

  • Nope

  • Certainly not

Early termination

Stop

  • Stop

  • Cancel

  • Cancel please

In order to cover for elicitation in a collaborative way, confirmation and early termination, we're creating dedicated intents. It is important to note that none of these intents should be enabled by default.

Implementing the dialogue flow

The interaction flow chart is provided below and showcases how intents are used depending on the state of the dialogue.

Sounds fairly complicated no ? The good news is that the Dialogue API allows us to implement such a flow using the hermes/dialogueManager/continueSession message (see the reference here and related tutorial), and the hermes-javascript client provides the right interfaces that makes it even much easier.

const { withHermes } = require('hermes-javascript')
const getSlotByName = (msg, name) => msg.slots.find(slot => slot.slotName === name)
const intentPrefix = 'DavidSnips:'
withHermes(hermes => {
const dialog = hermes.dialog()
const breakDialogue = flow => {
/* Break if the Stop intent is detected */
flow.continue(intentPrefix + 'Stop', (_, flow) => {
flow.end()
})
}
const bookTrainHandler = (message, flow, { foundSlots = {} } = {}) => {
breakDialogue(flow)
foundSlots.origin =
foundSlots.origin ||
getSlotByName(message, 'origin')
foundSlots.destination =
foundSlots.destination ||
getSlotByName(message, 'destination')
foundSlots.departure =
foundSlots.departure ||
getSlotByName(message, 'departure_datetime')
if(!foundSlots.origin && !foundSlots.destination) {
// Both location slots are missing
flow.continue(intentPrefix + 'ElicitItinerary', (message, flow) =>
bookTrainHandler(message, flow, { foundSlots })
)
return 'Where are you leaving from and heading to?'
} else if(!foundSlots.origin || !foundSlots.destination){
// A single location is missing
const tts = foundSlots.origin ?
'Got it. And where are you heading to?' :
'Sure. Where are you leaving from?'
flow.continue(intentPrefix + 'ElicitLocation', (message, flow) => {
const locationSlot = getSlotByName(message, 'location')
if(locationSlot) {
if(!foundSlots.origin) {
foundSlots.origin = locationSlot
} else {
foundSlots.destination = locationSlot
}
}
return bookTrainHandler(message, flow, { foundSlots })
})
return tts
}
if(!foundSlots.departure) {
// Departure time is missing
flow.continue(intentPrefix + 'ElicitDatetime', (message, flow) => {
const datetimeSlot = getSlotByName(message, 'datetime')
if(datetimeSlot)
foundSlots.departure = datetimeSlot
return bookTrainHandler(message, flow, { foundSlots })
})
return 'Sure. And when are you leaving ?'
}
flow.continue(intentPrefix + 'No', (msg, flow) => flow.end())
flow.continue(intentPrefix + 'Yes', (msg, flow) => {
flow.end()
return 'Booking confirmed'
})
return `To sum things up, you want me to book a train ticket to ${foundSlots.destination.rawValue}
leaving ${foundSlots.origin.rawValue} ${foundSlots.departure.rawValue}. Do you confirm ?`
}
// Register dialogue handler
dialog.flow(intentPrefix + 'BookTrainTicket', bookTrainHandler)
})