Introduction
In this article of custom Alexa skills, I am going to demonstrate how to create a custom Alexa skill on Alexa Hosted node.js server. For that, I will be creating a custom Alexa skill named City Facts. With the help of this skill, I will show how to implement dialog delegation in an Alexa skill. With the help of this skill, Users can ask Alexa to fetch information about different cities.
Each time Alexa asks if the user wants to know a fact about different cities. The users can give response by saying for example “give me a fact about London”. Alexa will fetch facts about the City London and prompt a message giving information about that particular city. The backend code required for the Alexa skill to work will be stored in a lambda function inside the Amazon developer console.
Creating an Alexa Skill
To create a new skill, first we need to login into the Alexa developer console, we need to mention the unique skill name and select the default language according to our location.
After that, we can choose a model to add to our skill. To create a custom skill, we can select custom model.
We can also choose a method or a template to host the skill’s backend code inside a lambda function.
We can choose 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.
Next step is to choose a template to add to our skill, which we customize later according to our need and click on create skill button. Now as skill has been created, we need to make adjustments to skill’s frontend. Now I will be creating intents, slots and custom slot types to create 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.
Now we have to create intents
Here, I have added a new intent named factIntent along with some sample utterances such as give me a fact, tell me a fact.
A custom slot type named CityName is defined for this Alexa skill.
Different slot values are defined such as Paris, Toronto, Sydney, etc.
After creating a model for a particular skill, we can save and build the model by clicking on save model and build model button on the top.
Json code for above frontend is as follows:
- {
- "interactionModel": {
- "languageModel": {
- "invocationName": "city detail",
- "intents": [
- {
- "name": "AMAZON.NavigateHomeIntent",
- "samples": []
- },
- {
- "name": "AMAZON.CancelIntent",
- "samples": []
- },
- {
- "name": "AMAZON.HelpIntent",
- "samples": []
- },
- {
- "name": "AMAZON.StopIntent",
- "samples": []
- },
- {
- "name": "factIntent",
- "slots": [
- {
- "name": "city",
- "type": "cityName",
- "samples": [
- "give me a fact about {city}",
- "tell me some facts about {city}",
- "{city}"
- ]
- }
- ],
- "samples": [
- "give me a fact",
- "get a fact",
- "tell me about {city}",
- "get me a fact for {city}",
- "tell me a fact",
- "tell me a fact about {city}"
- ]
- }
- ],
- "types": [
- {
- "name": "cityName",
- "values": [
- {
- "name": {
- "value": "paris"
- }
- },
- {
- "name": {
- "value": "toronto"
- }
- },
- {
- "name": {
- "value": "sydney"
- }
- },
- {
- "name": {
- "value": "london"
- }
- },
- {
- "name": {
- "value": "new delhi",
- "synonyms": [
- "delhi"
- ]
- }
- }
- ]
- }
- ]
- },
- "dialog": {
- "intents": [
- {
- "name": "factIntent",
- "confirmationRequired": false,
- "prompts": {},
- "slots": [
- {
- "name": "city",
- "type": "cityName",
- "confirmationRequired": false,
- "elicitationRequired": true,
- "prompts": {
- "elicitation": "Elicit.Slot.251925459829.983270759031"
- }
- }
- ]
- }
- ],
- "delegationStrategy": "SKILL_RESPONSE"
- },
- "prompts": [
- {
- "id": "Elicit.Slot.1162780729786.1432530920282",
- "variations": [
- {
- "type": "PlainText",
- "value": "which city are you going to?"
- },
- {
- "type": "PlainText",
- "value": "Where are you going?"
- }
- ]
- },
- {
- "id": "Elicit.Slot.1162780729786.1143025724798",
- "variations": [
- {
- "type": "PlainText",
- "value": "where are you starting your trip?"
- },
- {
- "type": "PlainText",
- "value": "What city are you leaving from?"
- }
- ]
- },
- {
- "id": "Elicit.Slot.1162780729786.801508773141",
- "variations": [
- {
- "type": "PlainText",
- "value": "what date are you flying out?"
- },
- {
- "type": "PlainText",
- "value": "When will you start this trip?"
- }
- ]
- },
- {
- "id": "Elicit.Slot.251925459829.983270759031",
- "variations": [
- {
- "type": "PlainText",
- "value": "which city do you want to know the fact about,
- you can say london or paris or new delhi?"
- }
- ]
- }
- ]
- }
- }
Creating the backend resource for the Alexa skill
To create backend code inside a lambda function, we can write code inside the index.js node.js file. The code for the custom Alexa skill is as follows:
- const Alexa = require('ask-sdk-core');
-
- const facts = {
- 'london': "Big Ben is arguably London's most famous landmark. Surprisingly, it is actually meant to go by the name 'The Clock Tower', while Big Ben is the name of the bell.",
- 'paris': "The Eiffel Tower was supposed to be a temporary installation, intended to stand for 20 years after being built for the 1889 World Fair.",
- 'toronto': "Almost 25% of Canada's population lives within a 160 km radius of Toronto.",
- 'sydney': "The Sydney Harbour Bridge is the widest long-span bridge and tallest steel arch bridge in the world, and the 5th longest spanning-arch bridge according to Guinness World Records.",
- 'new delhi': "Delhi is the second most populous city in the world with 25 million inhabitants! Census, in 2015, recorded 18.2 million people living in the city."
- }
-
- const LaunchRequestHandler = {
- canHandle(handlerInput) {
- return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
- },
- async handle(handlerInput) {
-
- const responseBuilder = handlerInput.responseBuilder;
-
- let speechText = 'Say tell me a fact to see the dialog delegate in action.';
- let repromptText = 'Say tell me a fact to see the dialog delegate in action.';
-
- return responseBuilder
- .speak(speechText)
- .reprompt(repromptText)
- .getResponse();
- },
- };
-
- const InProgressfactIntentHandler = {
- canHandle(handlerInput) {
- const request = handlerInput.requestEnvelope.request;
- return request.type === 'IntentRequest' &&
- request.intent.name === 'factIntent' &&
- request.dialogState !== 'COMPLETED';
- },
- handle(handlerInput) {
- const currentIntent = handlerInput.requestEnvelope.request.intent;
- return handlerInput.responseBuilder
- .addDelegateDirective(currentIntent)
- .getResponse();
- },
- };
-
- const factIntentHandler = {
- canHandle(handlerInput) {
- return handlerInput.requestEnvelope.request.type === 'IntentRequest'
- && handlerInput.requestEnvelope.request.intent.name === 'factIntent';
- },
- async handle(handlerInput) {
- const responseBuilder = handlerInput.responseBuilder;
- const city = slotValue(handlerInput.requestEnvelope.request.intent.slots.city);
-
- let speechText = `Here's a fact about ${city} - ${facts[city]}`;
-
- return responseBuilder
- .speak(speechText)
- .getResponse();
- },
- };
-
- const HelpIntentHandler = {
- canHandle(handlerInput) {
- return handlerInput.requestEnvelope.request.type === 'IntentRequest'
- && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
- },
- handle(handlerInput) {
- const speechText = 'You can say tell me a fact about london';
-
- return handlerInput.responseBuilder
- .speak(speechText)
- .reprompt(speechText)
- .getResponse();
- },
- };
-
- const CancelAndStopIntentHandler = {
- canHandle(handlerInput) {
- return handlerInput.requestEnvelope.request.type === 'IntentRequest'
- && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'
- || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent');
- },
- handle(handlerInput) {
- const speechText = 'Goodbye!';
-
- return handlerInput.responseBuilder
- .speak(speechText)
- .getResponse();
- },
- };
-
- const SessionEndedRequestHandler = {
- canHandle(handlerInput) {
- return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
- },
- handle(handlerInput) {
- console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);
-
- return handlerInput.responseBuilder.getResponse();
- },
- };
-
- const ErrorHandler = {
- canHandle() {
- return true;
- },
- handle(handlerInput, error) {
- console.log(`Error handled: ${error.message}`);
-
- return handlerInput.responseBuilder
- .speak('Sorry, I can\'t understand the command. Please say again.')
- .reprompt('Sorry, I can\'t understand the command. Please say again.')
- .getResponse();
- },
- };
-
- function slotValue(slot, useId){
- if(slot.value === undefined){
- return "undefined";
- }
- let value = slot.value;
- let resolution = (slot.resolutions && slot.resolutions.resolutionsPerAuthority && slot.resolutions.resolutionsPerAuthority.length > 0) ? slot.resolutions.resolutionsPerAuthority[0] : null;
- if(resolution && resolution.status.code === 'ER_SUCCESS_MATCH'){
- let resolutionValue = resolution.values[0].value;
- value = resolutionValue.id && useId ? resolutionValue.id : resolutionValue.name;
- }
- return value;
- }
-
- const skillBuilder = Alexa.SkillBuilders.standard();
-
- exports.handler = skillBuilder
- .addRequestHandlers(
- LaunchRequestHandler,
- InProgressfactIntentHandler,
- factIntentHandler,
- HelpIntentHandler,
- CancelAndStopIntentHandler,
- SessionEndedRequestHandler
- )
- .addErrorHandlers(ErrorHandler)
- .lambda();
-
-
To receive request from the user, request handlers are created for each intent to handle.
Inside each handlers, canHandle and handle functions are defined.
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.
With the help of LaunchRequestHandler, Alexa greets the user and says “Say tell me a fact to see the dialog delegate in action”. It asks the user to say “tell me a fact”. The user can ask for details of any city by making utterances such as “tell me a fact about New Delhi”.
After that, FactIntentHandler is defined to handle each and every request of user to know a random city fact. This handler will enable Alexa to tell users a fact about any city the user as asked for and also asks user if he/she wants to know about anymore city.
Inside InProgressFactIntentHandler, if dialog state shows not completed that is the request is not completed, then the intent will delegate the current intent passin the control back to Alexa service to decide what the next step is.
When the dialog state is completed, then factIntentHndler handles the result.
Output
As we can see from the output above, to invoke a skill, user can just say the invocation name. Here city details is an invocation name to invoke this skill. Alexa then greets and asks user to say tell me a fact and then Alexa prompts facts about different cities user has asked for.
Summary
In this article, I created a custom Alexa skill. I also defined invocation name, intents and sample utterances. I demonstrated the method to select city slot from the interaction model and tell a fact about that particular city. Proper coding snippets along with output for backend of skill is also provided.