Creating Custom Restaurant Reservation Alexa Skill Using Alexa Hosted NodeJS

Introduction

Here is another article on custom Alexa skills. In this article, I am going to demonstrate how to create a custom Alexa skill on a hosted node.js server. For that, I will be creating a custom Alexa skill named Eating House. With the help of this skill, users can ask Alexa to book restaurants in advance.

The users can tell the timing, day, number of people to book for reservations, and the name of the person under whom the booking has to be done. The backend code required for the Alexa skill to work will be stored in a lambda function inside the Amazon developer console. To know the basics of what components are needed to create an Alexa skill you can visit my previous article.

Creating Alexa Skill

To create a new skill, first, we need to log in to the Alexa developer console, we need to mention the unique skill name, and select the default language according to our location.

Alexa Skill

After that, we can choose a model to add to our skills. To create a custom skill, we can select a custom model.

Model

We can also choose a method or a template to host the skill’s backend code inside a lambda function.

Lambda function

We can choose Alexa Alexa-hosted node.js or Python template. We can also mention our own endpoint or server to store backend resources for the required Alexa skills.

The next step is to choose a template to add to our skill, which we customize later according to our needs,s and click on the Create Skill button.

Now that the skill has been created, we need to make adjustments to the skill’s front end. Now I will be creating intents, slots, and custom slot types to create the skill’s frontend.

First, we need to mention the invocation name. Users say a skill's invocation name to begin an interaction with a particular custom skill.

Users

Now we have to create intents.

Create intents

Here I have added a new intent named CreateReservation Intent along with a sample utterance. Inside the utterance “I want to make {ReservationDate} at {ReservationTime} for {NumberOfPeople}”, ReservationDate, ReservationTime, and NumberOfPeople are slots defined for the skill. Slots are defined within curly brackets.

Slots and slot types of each slot are defined as follows.

CreateReservation

Here NumberOfPeople, ReservationDate, ReservationTime, and NumberOfPeople are slots created for CreateReservation intent.

Custom slot types can also be created by clicking on the Add Slot Type button under the slot types menu. To create a custom slot type, type the name of the custom slot also fill in the required slot values along with ID and synonyms.

We can also create dialogs such as Alexa speech prompts and user utterances for each and every slot being created. When the slot required to fulfill the intent is not mentioned in the sample utterance of a user then dialogs are created to ask users to mention the slot values to fulfill the intent.

Here Alexa speech prompt is whatever Alexa will say to prompt the user to fill a particular slot and user utterances are whatever the user might say in response to Alexa prompts.

To create a dialog under each slot

  • Enable the option “Is this slot required to fulfill the intent”.
  • Type Alexa speech prompts and user utterances accordingly.

Alexa speech prompts

The above dialogs were created for the NumberOfPeople slot.

The JSON code for the frontend part is as follows.

{
    "interactionModel": {
        "languageModel": {
            "invocationName": "eating house",
            "intents": [
                {
                    "name": "AMAZON.CancelIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.HelpIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.StopIntent",
                    "samples": []
                },
                {
                    "name": "AMAZON.NavigateHomeIntent",
                    "samples": []
                },
                {
                    "name": "CreateReservation",
                    "slots": [
                        {
                            "name": "NumberOfPeople",
                            "type": "AMAZON.NUMBER",
                            "samples": [
                                "there will be {NumberOfPeople} people"
                            ]
                        },
                        {
                            "name": "ReservationDate",
                            "type": "AMAZON.DATE",
                            "samples": [
                                "make reservation on {ReservationDate}"
                            ]
                        },
                        {
                            "name": "ReservationTime",
                            "type": "AMAZON.TIME",
                            "samples": [
                                "i want to book at {ReservationTime}"
                            ]
                        },
                        {
                            "name": "PersonName",
                            "type": "AMAZON.FirstName",
                            "samples": [
                                "{PersonName}"
                            ]
                        }
                    ],
                    "samples": [
                        "please make reservation for {NumberOfPeople} people on {ReservationDate} at {ReservationTime} for name {PersonName}",
                        "i want to make reservation on {ReservationDate} at {ReservationTime} for {NumberOfPeople} people",
                        "i want to make a reservation for {NumberOfPeople} people",
                        "i want to make a reservation"
                    ]
                }
            ],
            "types": []
        },
        "dialog": {
            "intents": [
                {
                    "name": "CreateReservation",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "NumberOfPeople",
                            "type": "AMAZON.NUMBER",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.735905142355.536259982955"
                            }
                        },
                        {
                            "name": "ReservationDate",
                            "type": "AMAZON.DATE",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.735905142355.431728293953"
                            }
                        },
                        {
                            "name": "ReservationTime",
                            "type": "AMAZON.TIME",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.735905142355.1527575461050"
                            }
                        },
                        {
                            "name": "PersonName",
                            "type": "AMAZON.FirstName",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.735905142355.1189785990153"
                            }
                        }
                    ]
                }
            ],
            "delegationStrategy": "ALWAYS"
        },
        "prompts": [
            {
                "id": "Elicit.Slot.735905142355.536259982955",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "How many people will be there?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.735905142355.431728293953",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "what is the reservation date?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.735905142355.1527575461050",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "at what time?"
                    }
                ]
            },
            {
                "id": "Elicit.Slot.735905142355.1189785990153",
                "variations": [
                    {
                        "type": "PlainText",
                        "value": "under whose name do you want to make the reservation?"
                    }
                ]
            }
        ]
    }
}

After creating a model for a particular skill, we can save and build the model by clicking on the save model and build model button on the top.

Creating the backend resource for the Alexa skill

To create backend code inside the lambda function, we can write code inside index.js node.js file. The code for the custom Alexa skill is as follows.

// This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).  
// Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,  
// session persistence, api calls, and more.  

const Alexa = require('ask-sdk-core');

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speakOutput = 'Hello! Welcome to Create Reservation. How can I help you today?';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

const CreateReservationHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
            Alexa.getIntentName(handlerInput.requestEnvelope) === 'CreateReservation';
    },
    handle(handlerInput) {
        const NumberOfPeople = handlerInput.requestEnvelope.request.intent.slots.NumberOfPeople.value;
        const ReservationDate = handlerInput.requestEnvelope.request.intent.slots.ReservationDate.value;
        const ReservationTime = handlerInput.requestEnvelope.request.intent.slots.ReservationTime.value;
        const PersonName = handlerInput.requestEnvelope.request.intent.slots.PersonName.value;

        const speakOutput = `Thanks, the reservation has been done under ${PersonName} for ${NumberOfPeople} people at ${ReservationTime} on ${ReservationDate}.`;
        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
            Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'You can say hello to me! How can I help?';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

const CancelAndStopIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest' &&
            (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent' ||
                Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
    },
    handle(handlerInput) {
        const speakOutput = 'Goodbye!';
        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};

const SessionEndedRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
    },
    handle(handlerInput) {
        // Any cleanup logic goes here.
        return handlerInput.responseBuilder.getResponse();
    }
};

// The intent reflector is used for interaction model testing and debugging.  
// It will simply repeat the intent the user said. You can create custom handlers  
// for your intents by defining them above, then also adding them to the request  
// handler chain below.  
const IntentReflectorHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
    },
    handle(handlerInput) {
        const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
        const speakOutput = `You just triggered ${intentName}`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

// Generic error handling to capture any syntax or routing errors. If you receive an error  
// stating the request handler chain is not found, you have not implemented a handler for  
// the intent being invoked or included it in the skill builder below.  
const ErrorHandler = {
    canHandle() {
        return true;
    },
    handle(handlerInput, error) {
        console.log(`~~~~ Error handled: ${error.stack}`);
        const speakOutput = `Sorry, I had trouble doing what you asked. Please try again.`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

// The SkillBuilder acts as the entry point for your skill, routing all request and response  
// payloads to the handlers above. Make sure any new handlers or interceptors you've  
// defined are included below. The order matters - they're processed top to bottom.  
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        CreateReservationHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler // make sure IntentReflectorHandler is last so it doesn't override your custom intent handlers
    )
    .addErrorHandlers(
        ErrorHandler
    )
    .lambda();

To receive requests from the user, request handlers are created for each intent to handle.

Inside each handler, canHandle and handle functions are written.

The canHandle() function is where you define what requests the handler responds to. The handle() function returns a response to the user. If your skill receives a request, the canHandle() function within each handler determines whether or not that handler can service the request.

In this case, the user wants to launch the skill, which is a LaunchRequest. Therefore, the canHandle() function within the LaunchRequestHandler will let the SDK know it can fulfill the request. In computer terms, the canHandle returns true to confirm it can do the work.

After that CreateReservationHandler is defined to handle each and every user’s reservation request. This handler will receive NumberOfPeople, ReservationDate, ReservationTime and PersonName as requested slot parameters and give a response accordingly.

Output

Output

If a user fails to mention any of the slot values while making a request to Alexa for a reservation then Alexa will prompt messages and asks the user to provide specific slot values as well.

Summary

In this article, I created a custom Alexa skill. I also defined intents, slots, and custom slot types for each slot. I demonstrated the method to create custom slot types and slot type values. Proper coding snippets along with output for the backend of the skill are also provided.


Similar Articles