Defining the Conversation Model

The core component of all conversation models is the dialog. Dialogs capture one or more sample conversations that will take place between Alexa and a user.

In ACDL, dialogs are declared with the dialog keyword, followed by the dialog’s type and name. The beginnings of a dialog declaration will look like this:

 dialog Nothing ScheduleTripDialog {
  // dialog body goes here
 }

As shown here, the name of the dialog is ScheduleTripDialog and the type is Nothing. At this time, Nothing is the only valid type for dialogs, although that could change in the future as Alexa Conversations and ACDL evolve.

Within the body of the dialog, you’ll define one or more sample conversations using the sample keyword:

 dialog Nothing ScheduleTripDialog {
  sample {
  // dialog sample 1
  }
 
  sample {
  // dialog sample 2
  }
 }

Each sample models a possible conversation between Alexa and the user. For example, consider the following conversation about planning a trip to Jupiter:

User: Let’s plan a trip

Alexa: Where do you want to go?

User: Jupiter

Alexa: When will your trip start?

User: June 9th

Alexa: When will your trip end?

User: June 16th

Alexa: I’ve got you down for a trip to Jupiter between June 9th and June 16th. Is that correct?

User: Yes

Alexa: Great! Enjoy your trip to Jupiter!

As you can infer from this example, a conversation always starts with the user saying something that initiates the conversation and ends with Alexa having the final words. With that in mind, let’s write some ACDL that defines a sample dialog that models this kind of conversation.

Initiating the Conversation

The first element of any dialog is an expectation of an Invoke request. Invoke is one of a handful of request acts that may take place in a dialog. The following snippet shows how a trip-scheduling conversation might get started.

  sample {
  scheduleTripRequest = ​expect​(Invoke, ScheduleTripEvent)
  ...
 }

Here, the sample dialog begins when the user speaks an utterance from the set of utterances defined in the event named ScheduleTripEvent. ScheduleTripEvent is defined in ACDL like this:

 namespace​ ​com.starport75.events
 
 import com.amazon.alexa.ask.conversations.*
 import com.starport75.types.TripDetails
 
 ScheduleTripEvent = utterances<TripDetails>(
  [
 "plan a trip"​,
 "schedule a trip to {destination}"​,
 "book a trip to {destination}"​,
 "i want to go to {destination}"​,
 "schedule a trip to {destination} starting {departureDate}"​,
 "plan a trip to {destination} between {departureDate} and {returnDate}"​,
 "schedule a trip for {departureDate} to {returnDate}"
  ]
 )

ScheduleTripEvent is essentially a set of utterances that, when spoken by the user in the context of the skill, trigger the event, thus kicking off a conversation. The utterance set is parameterized with <TripDetails>, indicating that slots in the utterances can contribute to the custom record type named TripDetails, which is defined as follows:

 namespace​ ​com.starport75.types
 
 import com.amazon.ask.types.builtins.AMAZON.DATE
 import slotTypes.PLANETS
 
 type TripDetails {
  PLANETS destination
  DATE departureDate
  DATE returnDate
 }

Notice the properties of TripDetails are of types DATE and PLANETS. The DATE type is one of Alexa’s built-in types and is imported from the com.amazon.ask.types.builtins namespace. The PLANETS type, however, is specific to our skill and defined in the interaction model (the same as we did in Chapter 3, Parameterizing Intents with Slots). Whenever you need to import a custom slot type in ACDL, it should be imported from the slotTypes namespace.

Going back to the expect() action which begins the sample dialog, it is the TripDetails object that is being returned and assigned to a variable named scheduleTripRequest. A little later, we’ll pass scheduleTripRequest to the backend fulfillment code to perform the business logic behind the conversation.

If the user were to initiate the conversation by saying, “Plan a trip to Neptune,” then the destination property of TripDetails would be assigned the value “Neptune”, while the date properties would be empty. On the other hand, if the user simply says, “Plan a trip,” then all the properties of TripDetails will remain empty.

It is Alexa’s job to fill in all empty properties in order to complete the dialog. To do that, she’ll need to ask the user some questions and the user will need to provide some answers. Next, we’ll define this back and forth discussion in the sample dialog.

Gathering Dialog Data

In order to schedule a trip, we need Alexa to request the trip’s destination, departure date, and return date from the user. And, naturally, the user will need to provide those details. This means that the dialog should next have three question and answer pairs, each asking for and collecting the three properties of the TripDetails object.

In ACDL, these question and answer pairs are defined by a response(), in which Alexa prompts the user for some information, and a Inform act, in which the user provides that information. For example, the snippet of ACDL that requests and captures that destination looks like this:

 response​(
  response=request_destination_apla,
  act=Request{ arguments = [
  scheduleTrip.arguments.destination]}
 )
 destination = ​expect​(Inform, InformDestinationEvent)

The response() action, as its name suggests, sends a response to the user. The response is either an APL or APL-A document. In this case, the response parameter is set to request_destination_apla which references an APL-A document that prompts the user for the trip’s destination. We’ll see how the APL-A document is defined in section Defining Response Templates.

The act parameter defines the dialog act that this response() is performing. In this case, we are requesting information from the user, so act is set to Request{...}. Moreover, the Request{...} act is requesting the trip’s destination, so arguments is set to [ scheduleTrip.arguments.destination ].

Next, we see the expect() action again. At the beginning of the sample dialog, we used expect() to initiate the conversation with the Invoke act. But this time, the user is informing the skill of some missing data, which means we need to use the Inform act.

Just as with the Invoke act, we need to specify an utterance-based event that’s associated with the expect() action. In this case, it’s InformDestinationEvent, which is defined as follows:

 namespace​ ​com.starport75.events
 
 import com.amazon.alexa.ask.conversations.*
 import com.starport75.types.TripDestination
 
 InformDestinationEvent = utterances<TripDestination>([
 "{destination}"​,
 "I want to go to {destination}"​,
 "{destination} sounds nice"​,
 "I want to visit {destination}"
 ])

Here, the event is defined as utterances<TripDestination>, indicating that it contributes to the custom TripDestination type. All of the utterances have a {destination} placeholder, so the TripDestination type should have a destination property, as shown here:

 namespace​ ​com.starport75.types
 
 import slotTypes.PLANETS
 
 type TripDestination {
  PLANETS destination
 }

Now we’ve defined how to have Alexa prompt the user for the trip’s destination and accept the answer, we can apply the same constructs to ask for the departure and return dates:

 response​(
  response=request_departureDate_apla,
  act=Request{ arguments=[
  scheduleTrip.arguments.departureDate]}
 )
 departureDate = ​expect​(Inform, InformDepartureDateEvent)
 
 response​(
  response=request_returnDate_apla,
  act=Request{ arguments=[
  scheduleTrip.arguments.returnDate]}
 )
 returnDate = ​expect​(Inform, InformReturnDateEvent)

As you can see, the only significant differences are the APL-A document being used in each response and the event applied to each Inform act. Speaking of the events, the InformDepartureDateEvent and InformReturnDateEvent events are defined as follows:

 namespace​ ​com.starport75.events
 
 import com.amazon.alexa.ask.conversations.*
 import com.starport75.types.TripDepartureDate
 import com.starport75.types.TripReturnDate
 
 InformDepartureDateEvent = utterances<TripDepartureDate>([
 "{departureDate}"​,
 "I want my trip to start {departureDate}"​,
 "I want to depart {departureDate}"
 ])
 InformReturnDateEvent = utterances<TripReturnDate>([
 "{returnDate}"​,
 "I want my trip to end {returnDate}"​,
 "I want to return {returnDate}"
 ])

And their corresponding types, TripDepartureDate and TripReturnDate, are defined here:

 namespace​ ​com.starport75.types
 
 import com.amazon.ask.types.builtins.AMAZON.DATE
 
 type TripDepartureDate {
  DATE departureDate
 }
 
 type TripReturnDate {
  DATE returnDate
 }

Once the user has supplied all of the trip details, we’re ready to send them to the fulfillment backend for processing. Let’s see how to define a custom action in the sample dialog.

Sending Dialog Data to Fulfillment

Up to this point, we’ve used a few of ACDL’s built-in actions to define our sample dialog, including expect(), response(), and utterances(). But ACDL also supports the creation of custom actions, which come in very handy for sending the data gathered in a conversation to the backend fulfillment code for processing.

Custom actions are defined in ACDL using the action type. For example, our dialog will need to send the destination and trip dates to the backend. The following scheduleTrip() definition declares such an action:

 namespace​ ​com.starport75.actions
 
 import com.starport75.types.ScheduleTripResult
 import com.amazon.ask.types.builtins.AMAZON.*
 import slotTypes.PLANETS
 
 action ScheduleTripResult ​scheduleTrip​(PLANETS destination,
  DATE departureDate, DATE returnDate)

As you can see, the definition for scheduleTrip() doesn’t provide any implementation details, but rather just declares the interface through which the dialog will interact with the fulfillment code. We’ll define the fulfillment code that backs this declaration in section Handling the Action Request.

The scheduleTrip() action accepts three parameters: one PLANETS parameter for the destination and two DATE parameters for each of the two trip dates. It returns a ScheduleTripResult, which is a custom type that just echoes back the destination:

 namespace​ ​com.starport75.types
 
 import com.amazon.ask.types.builtins.AMAZON.DATE
 import com.amazon.ask.types.builtins.AMAZON.NUMBER
 import slotTypes.PLANETS
 
 type ScheduleTripResult {
  PLANETS destination
 }

Applying a custom action in ACDL appears very similar to calling a function in JavaScript. For example, here’s the snippet of ACDL to add to the end of our dialog sample to send trip data to the action:

 scheduleTripResult = ​scheduleTrip​(
  scheduleTripRequest.destination,
  scheduleTripRequest.departureDate,
  scheduleTripRequest.returnDate)

Here, the destination and dates that have been collected into properties of scheduleTripRequest in the course of the dialog are being passed as parameters to scheduleTrip(), and the result is assigned to scheduleTripResult. Now we can wrap up the conversation by passing scheduleTripResult to the final response:

 response​(
  scheduleTrip_apla,
  Notify { actionName = scheduleTrip },
  payload = ResponsePayload
  { scheduleTripResult = scheduleTripResult }
 )

This response() is slightly different from previous response() actions we have used. Since it is not asking the user for any information and is simply notifying them of a result, the Notify response act is used. The payload parameter is set to a ResultPayload object used to carry the value of scheduleTripResult on to the APL-A document. We will see in section Defining Response Templates how the APL-A document makes use of this property. Meanwhile, here’s how ResponsePayload is defined:

 namespace​ ​com.starport75.types
 
 type ResponsePayload {
  ScheduleTripResult scheduleTripResult
 }

At this point, our sample dialog is mostly complete. It starts after the user speaks an utterance that triggers ScheduleTripEvent, gathers trip details, and then passes the data on to the scheduleTrip() action before ending the conversation. But there’s one other thing that we should probably add. Let’s take a step back and have Alexa confirm the trip details before invoking the scheduleTrip() action.

Confirming the Dialog Data

Conceptually, dialog confirmation isn’t much different than the information-gathering parts of the dialog. Alexa asks a question (for example, “Is this right?”) and the user responds with a yes or no answer. And, as it turns out, adding confirmation to the sample dialog in ACDL isn’t much different than the ACDL we added in the previous section.

For example, here’s a snippet of ACDL that shows how to add dialog confirmation to a sample dialog:

 response​(
  confirmTrip_apla,
  ConfirmAction { actionName = scheduleTrip },
  payload = TripDetails { destination = scheduleTripRequest.destination,
  departureDate = scheduleTripRequest.departureDate,
  returnDate = scheduleTripRequest.returnDate
  }
 )
 expect​(Affirm, AffirmEvent)

Once again, the response() and expect() actions come into play. This time, however, the act parameter to response() references the ConfirmAction act, specifically establishing that before we can invoke the scheduleTrip() action, this confirmation should take place.

Meanwhile, the expect() here is assuming that the user replies in the affirmative by taking Affirm as the first argument. The affirmative utterances that the user could speak are listed in AffirmEvent as shown here:

 namespace​ ​com.starport75.events
 
 import com.amazon.alexa.ask.conversations.utterances
 
 AffirmEvent = ​utterances​([
 "exactly"​,
 "yeah I'd like that"​,
 "you got it"​,
 "yup"​,
 "correct"​,
 "that's right"​,
 "yeah"​,
 "yep"​,
 "yes"
 ])

Notice that AffirmEvent isn’t parameterized with any specific type as was the case with other event utterance sets. When there are no slots to gather variable input in an utterance set, then it’s unnecessary to specify a type. Optionally, you could still specify the type as <Nothing> like this:

 AffirmEvent = utterances<Nothing>([
  ...
 ])

Nothing is a special built-in type that, as its name suggests, has no value. It’s similar to void in languages like Java and C#.

This particular dialog sample deals with the path where the user affirms the action. But you can also define a sample in which the user denies the action. For that, the expect() action would be defined like this:

 expect​(Deny, DenyEvent)

The Deny request act indicates that the user has denied the action and therefore the action being confirmed should not be called. Just like AffirmEvent, the DenyEvent can be a simple untyped utterance set:

 DenyEvent = utterances<Nothing>([
  "no",
  "nope",
  "no way"
 ])

To be clear, you shouldn’t include both Affirm and Deny request acts in the same dialog sample. We’ve defined a dialog sample that ends with an affirmation and call to the scheduleTrip() action. Another dialog sample would define the path where the user denies the confirmation.

Putting it All Together

We’ve written several components of ScheduleTripDialog, but it helps to see the dialog in its entirety. The following ACDL file pulls all of the individual expect(), response(), and scheduleTrip() actions together into the complete ScheduleTripDialog definition:

 namespace​ ​com.starport75
 
 import com.starport75.actions.*
 import com.starport75.events.*
 import com.starport75.types.*
 
 import com.amazon.alexa.ask.conversations.*
 import com.amazon.alexa.schema.Nothing
 
 import prompts.request_destination_apla
 import prompts.request_departureDate_apla
 import prompts.request_returnDate_apla
 import prompts.scheduleTrip_apla
 import prompts.confirmTrip_apla

Reading it from top-to-bottom, the sample dialog aligns directly with the textual conversation outlined at the beginning of this section. The response() actions map to the “Alexa:”-prefixed lines, while the expect() actions match up with the lines that started with “User:”.

But this sample dialog only shows one possible path through the conversation. What if the user initiates the conversation with some of the trip details by saying, “Plan a trip to Jupiter”? Or what if they provide all of the details up-front by saying, “Schedule a trip to Neptune leaving June 9th and returning June 16th”? Our sample dialog doesn’t cover that path.

To help the AI engine cover as many conversation variations as possible, you should include several sample dialogs. You should also define variant samples where the user provides some or all trip parameters up front and Alexa prompts for the missing information. This could add up to over a half-dozen or more sample dialogs just to handle trip-scheduling.

But ACDL offers a way to make sample dialogs even easier and more powerful with a single sample. Let’s take a look at one of the most magical of ACDL’s built-in actions: ensure().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset