Explicitly Handling Dialog Delegation

Earlier in section Validating Slot Values, we added validation for the destination slot. But at that time, we deferred validation for the travel date slots. That’s because when it comes to travel dates, we will need to ensure that the return date falls after the departure date. And although it is possible to automatically validate dates, it’s not so easy to validate two or more slots with how they relate to each other. For that, we’ll need to write some explicit validation code that steps in to compare the two dates.

But first, let’s add some simple validation for the dates. The “isInDuration” validation rules ensures that a date slot’s value falls within a range. The following excerpt from the interaction model’s dialog rules shows how to do some basic validation on the two date slots:

 { "name": "departureDate", "type": "AMAZON.DATE", "elicitationRequired": true, "prompts": { "elicitation": "Slot.Elicitation.ScheduleTrip.DepartureDate" },
» "validations": [
» {
» "type": "isInDuration",
» "prompt": "Slot.Validation.DepartureDateInTheFuture",
» "start": "P1D",
» "end": "P30D"
» }
» ]
  }, { "name": "returnDate", "type": "AMAZON.DATE", "elicitationRequired": true, "prompts": { "elicitation": "Slot.Elicitation.ScheduleTrip.ReturnDate" },
» "validations": [
» {
» "type": "isInDuration",
» "prompt": "Slot.Validation.ReturnDateInTheFuture",
» "start": "P1D",
» "end": "P60D"
» }
» ]
 }

Both date slots apply the “isInDuration” validation to require that the dates are in the near-ish future. These date ranges are specified in the start and end properties, which are expressed in ISO-8601 duration format.[17] Per ISO-8601 duration format, the “P” stands for “period” and “D” stands for “day”. Therefore, “P1D” means 1 day out from the current day, “P30D” means 30 days out, and “P60D” means 60 days out. Therefore, the departure date must be within the next 30 days, after today. And the return date must be within the next 60 days after today.

That’s a good start and better than no validation at all. But it still doesn’t prevent the user from specifying a return date that is before the departure date. Star Port 75 Travel specializes in space travel, not time travel, so it’s important to be sure that the traveler returns after they depart.

In order to do that, we’ll need to disable fully-automatic delegation for the ScheduleTripIntent with the following tweak to the intent’s entry in the dialog section of the interaction model:

 "dialog": {
  "intents": [
  {
  "name": "ScheduleTripIntent",
  "delegationStrategy": "SKILL_RESPONSE",
 ...
  }
  ]
 },

By setting delegationStrategy to “SKILL_RESPONSE”, we can write intent handlers that have a say in the dialog flow. It doesn’t make the dialog delegation completely manual—the other dialog rules we defined for automatic delegation will still work. Instead, think of it as semi-automatic, where an intent handler can be part of the dialog flow.

If the delegation strategy is “SKILL_RESPONSE”, a state machine is applied where a dialog has one of three states: “STARTED”, “IN_PROGRESS”, or “COMPLETED”. The following state machine diagram illustrates the states that a dialog goes through on its way to completion:

images/dialog/dialog-states.png

When handling an intent request, we can write code that inspects the dialogState property of the request to know the current state of the dialog. But if we do this in the intent handler’s handle() function, we’ll end up with a mess of if/else if/else blocks. Instead, it’s neater to write two or more distinct intent handlers and check for the dialog state in each intent handler’s canHandle() function.

For the purposes of validating departure and return dates, we’ll write a new intent handler that handles the ScheduleTripIntent when its dialog is not in “COMPLETED” state (in other words, in “STARTED” or “IN_PROGRESS” state):

 const​ Alexa = require(​'ask-sdk-core'​);
 
 const​ ScheduleTripIntentHandler_InProgress = {
  canHandle(handlerInput) {
 return​ Alexa.getRequestType(
  handlerInput.requestEnvelope) === ​'IntentRequest'
  && Alexa.getIntentName(
  handlerInput.requestEnvelope) === ​'ScheduleTripIntent'
» && Alexa.getDialogState(handlerInput.requestEnvelope) !== ​'COMPLETED'​;
  },
  handle(handlerInput) {
 const​ currentIntent = handlerInput.requestEnvelope.request.intent;
 const​ departureString =
  Alexa.getSlotValue(handlerInput.requestEnvelope, ​'departureDate'​);
 const​ returnString =
  Alexa.getSlotValue(handlerInput.requestEnvelope, ​'returnDate'​);
 
 if​ (departureString && returnString) {
 const​ departureDate = ​new​ Date(departureString);
 const​ returnDate = ​new​ Date(returnString);
»if​ (departureDate >= returnDate) {
» currentIntent.slots[​'returnDate'​].value = ​null​;
»return​ handlerInput.responseBuilder
» .speak(​"Star Port Seventy Five specializes in space travel, "​ +
»"not time travel. Please specify a return date that is "​ +
»"after the departure date."​)
» .addDelegateDirective(currentIntent)
» .getResponse();
» }
  }
 
 return​ handlerInput.responseBuilder
  .addDelegateDirective(currentIntent)
  .getResponse();
  },
 };
 
 module.exports=ScheduleTripIntentHandler_InProgress;

The canHandle() function looks similar to the canHandle() function we wrote for ScheduleTripIntentHandler, but in addition to inspecting the request type and intent name, it also checks that the dialogState property is not “COMPLETED”.

If so, then the handle() function will have a chance to inspect the request further and validate slots or even change slot values. In this case, it first checks to see if both the departure date and return date are specified—if not, then there’s no point in applying a comparison validation against them. It then compares the return date with the departure date. If the return date is the same as or earlier than the departure date, then it clears out the “returnDate” slot (so that the user can provide a new value) and adds an appropriate validation message to be spoken by Alexa.

Whether or not the dates are valid, it’s important to allow Alexa to continue handling the dialog delegation once the handle() function completes. The call to addDelegateDirective() sends a directive to Alexa, telling her to carry on with automatic dialog delegation—the same as if we had set the delegationStrategy to “ALWAYS”.

Although it’s optional, we pass the current intent to addDelegateDirective() so that the change we made to the “returnDate” slot carries forward in the dialog. If you don’t pass the current intent to addDelegateDirective(), then any changes to the slot values will be lost when the handle() function completes. In the case of ScheduleTripIntentHandler_InProgress, this means that the return date won’t be cleared out and the user won’t be prompted to provide the return date again.

As with any new intent handler, don’t forget to register it with the skill builder:

 const​ ScheduleTripIntentHandler_InProgress =
  require(​'./ScheduleTripIntentHandler_InProgress'​);
 
 ...
 
 exports.handler = Alexa.SkillBuilders.custom()
  .addRequestHandlers(
  HelloWorldIntentHandler,
  ScheduleTripIntentHandler,
» ScheduleTripIntentHandler_InProgress,
  StandardHandlers.LaunchRequestHandler,
  StandardHandlers.HelpIntentHandler,
  StandardHandlers.CancelAndStopIntentHandler,
  StandardHandlers.FallbackIntentHandler,
  StandardHandlers.SessionEndedRequestHandler,
  StandardHandlers.IntentReflectorHandler)
  .addErrorHandlers(
  StandardHandlers.ErrorHandler)
  .addRequestInterceptors(
  LocalisationRequestInterceptor)
  .lambda();

ScheduleTripIntentHandler_InProgress is now ready to handle the partially complete ScheduleTripIntent while dialog delegation gathers slot values. But our original intent handler, ScheduleTripIntentHandler, needs its canHandle() function tweaked ever so slightly.

The purpose of ScheduleTripIntentHandler is to handle the complete ScheduleTripIntent once all slot values are gathered and validated—when the dialog state is “COMPLETED”. Therefore, to prevent ScheduleTripIntentHandler from handling requests whose dialog state is “STARTED” or “IN_PROGRESS”, we must change its canHandle() function to check that the dialog state is “COMPLETED”:

 canHandle(handlerInput) {
 return​ Alexa.getRequestType(
  handlerInput.requestEnvelope) === ​'IntentRequest'
  && Alexa.getIntentName(
  handlerInput.requestEnvelope) === ​'ScheduleTripIntent'
» && Alexa.getDialogState(handlerInput.requestEnvelope) === ​'COMPLETED'​;
 },

Now we’re ready to try out our new date validation logic. After deploying the skill, we can test it in the developer console’s simulator. Planning a trip to Jupiter with invalid travel dates might look like the screenshot.

images/dialog/simulator-explicit.png

Clearly, June 24th is after June 17th. Thus it’s not possible to leave on June 24th for an excursion to Jupiter and return a week earlier. Our new intent handler catches that case and prevents such a backwards trip from being scheduled.

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

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