Create an Alexa Skill Part 3: Writing a Lambda Function
What is an Alexa Skill?
Created in 2014, the Amazon Echo is a hands-free speaker you control with your voice. Alexa is the voice service that powers Echo and allows customers to interact with the device. As third-party developers, we can create apps, or, in this case, "skills" that users can download and interact with. An Alexa skill might help you find a good book while another might give you information about your favorite TV Show. The possibilities are endless!
Last time...
In the last two tutorials, we made a skill that gives facts about a given Zodiac sign. For example, if you said to echo, "Tell me about Leos," echo would respond with "Leos are known to be proud, loyal, charismatic, and stylish" or something along the line. You could also say, "Tell me about Virgos," and echo would respond with a fact about people with the Virgo sign. Before we jumped into the functionality side of the application, we created the voice interface and interaction model and set up our AWS account and Lambda function for the skill. If you haven't started the voice interface yet, check out this tutorial and if you haven't set up your AWS account and Lambda function on the cloud, check out this tutorial.
Writing the Code
Before we write the code for this skill, let's take a look at this JavaScript template for writing a Lambda function for an Alexa Skill. The first part of the template will route the incoming request based on its type. The type will either be LaunchRequest, IntentRequest, or SessionEndedRequest and this function will be triggered whenever we interact with Alexa. The event parameter will have all the information we need about the given request.
exports.handler = function (event, context) {
try {
if (event.session.new) {
onSessionStarted({requestId: event.request.requestId}, event.session);
}
if (event.request.type === "LaunchRequest") {
onLaunch(event.request,
event.session,
function callback(sessionAttributes, speechletResponse) {
context.succeed(buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === "IntentRequest") {
onIntent(event.request,
event.session,
function callback(sessionAttributes, speechletResponse) {
context.succeed(buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === "SessionEndedRequest") {
onSessionEnded(event.request, event.session);
context.succeed();
}
} catch (e) {
context.fail("Exception: " + e);
}
};
For now, we will put this code in a file named index.js
. Feel free to use Sublime, Atom, or any other plain-text editor you wish to write this code with. Next, we'll write the onSessionStarted, onLaunch, onIntent, and onSessionEnded functions that our exports.handler
calls.
/** Called when the session starts */
function onSessionStarted(sessionStartedRequest, session) {
// add any session init logic here
}
/** Called when the user invokes the skill without specifying what they want. */
function onLaunch(launchRequest, session, callback) {
}
/** Called when the user specifies an intent for this skill. */
function onIntent(intentRequest, session, callback) {
var intent = intentRequest.intent
var intentName = intentRequest.intent.name;
// dispatch custom intents to handlers here
}
/** Called when the user ends the session - is not called when the skill returns shouldEndSession=true. */
function onSessionEnded(sessionEndedRequest, session) {
}
The comments are pretty self-explanatory — onSessionStarted is called when we start a session with Alexa, onLaunch is called when we open a skill, onIntent is called when a specific intent is specified, and onSessionEnded is called when the user ends the session. In our template, we will also have helper functions that will help us build Alexa's response to the user.
// ------- Helper functions to build responses for Alexa -------
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: output
},
card: {
type: "Simple",
title: title,
content: output
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
};
}
function buildSpeechletResponseWithoutCard(output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: "PlainText",
text: output
},
reprompt: {
outputSpeech: {
type: "PlainText",
text: repromptText
}
},
shouldEndSession: shouldEndSession
};
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: "1.0",
sessionAttributes: sessionAttributes,
response: speechletResponse
};
}
Each of these functions builds a different type of response for Alexa to return. The buildSpeechletResponse method takes a title, output, repromptText
, and shouldEndSession
value and returns a formatted JSON object for Alexa to read and say. In this case, Alexa will say the output, reprompt the user with repromptText
if the user does not respond, and end the session if shouldEndSession
is true. This function also builds a card that will appear in the Alexa app — the title of the card has the value of the title parameter and the paragraph text will be whatever Alexa says (the output).
The buildSpeechletResponseWithoutCard function takes an output, repromptText
, and shouldEndSession
value and simply generates a spoken response with a reprompt if the user does not respond, but does not create a card. The buildResponse method only generates a spoken response. You can add more helper functions depending on what you want Alexa to do, but these are good ones to use when starting out.
With our template finished, we can now start adding information that are pertinent to our skill. We won't be messing with exports.handler, but we will be implementing most of the functions it calls - i.e. onLaunch
, onIntent
, etc. To start off, let's look at the onLaunch function, which will be called when we start the skill. Here, we'll write what Alexa should say in the speechOutput variable, what Alexa should reprompt the user with in reprompt, the header for the card, and whether we should end the session or not. Then, we'll create a sessionAttributes variable that will hold the speechOutput
and repromptText
. With all of that done, we can call the callback function and input the sessionAttributes
as well as the correctly formatted response that Alexa should say to the user. This response will be built with the helper functions we wrote before.
function onLaunch(launchRequest, session, callback) {
var speechOutput = "Welcome to Zodiac Facts! I can tell you facts about all the Zodiac signs. Which sign are you interested in?"
var reprompt = "Which sign are you interested in? You can find out about Aquaries, Aries, Taurus, Pisces, Gemini, Geminis, Cancer, Leo, Virgo, Libra, Scorpio, Sagittarius, and Capricorn."
var header = "Zodiac Facts!"
var shouldEndSession = false
var sessionAttributes = {
"speechOutput" : speechOutput,
"repromptText" : reprompt
}
callback(sessionAttributes, buildSpeechletResponse(header, speechOutput, reprompt, shouldEndSession))
}
Now, when we start the skill, Alexa will say "Welcome to Zodiac Facts!" ... etc. Most of our functions will follow this same format.
Moving on to the onIntent function, we'll write a switch statement, which will separate the handlers for our different intents.
function onIntent(intentRequest, session, callback) {
var intent = intentRequest.intent
var intentName = intentRequest.intent.name;
// dispatch custom intents to handlers here
if (intentName == "GetZodiacFactIntent") {
handleGetZodiacFactRequest(intent, session, callback)
} else if (intentName == "AMAZON.HelpIntent") {
handleHelpRequest(intent, session, callback)
} else if (intentName == "AMAZON.StopIntent" || intentName == "AMAZON.CancelIntent") {
handleFinishSessionRequest(intent, session, callback)
} else {
throw "Invalid intent"
}
}
In this code, we grab the intent and the intentName and match the intentName to the specific intent in order to run the appropriate handler function. Now, we must write each of the handler functions, but before we do that, let's create an object, zodiacSigns, that will hold all of our facts. We'll access this in our first handler function, handleGetZodiacFactRequest.
var zodiacSigns = {
"aries" : {
"fact" : "Aries is the first sign of the zodiac. Those who are Aries are independent and courageous. They enjoy leading others and bringing excitement into the lives of others. An Aries is enthusiastic and very goal-oriented"
},
"taurus" : {
"fact" : "The second sign of the zodiac, those who are a Taurus are solid and fight for what they want. A Taurus is very easy going but can also be stubborn. A Taurus can be procrastinators but also have a good-work ethic."
},
"gemini" : {
"fact" : "Gemini is the third sign of the zodiac. Geminis have many sides and are known for their energy. They are very talkative and are considered social butterflies. A Gemini will always take their lives in the direction they want to go."
},
"cancer" : {
"fact" : "Cancer is the fourth sign of the zodiac. This sign is marked by inconsistency. They enjoy security but also seek adventure. A Cancer is not very predictable and always keep others guessing.",
},
"leo" : {
"fact" : "Leo is the fifth sign in the zodiac. Leos have high self esteem and are very devoted. They are also very kind and generous. A Leo is known for being hot tempered yet forgiving."
},
"virgo" : {
"fact" : "The sixth sign of the zodiac, Virgo is very mind oriented. They are constantly analyzing and thinking. They enjoy bettering themselves and those around them."
},
"libra" : {
"fact" : "The seventh sign of the zodiac, Libras are known for their diplomatic nature. They get along well with everyone and are ambitious. They have very expensive taste and work hard to make money."
},
"scorpio" : {
"fact" : "The eight sign of the zodiac, Scorpios are very intense. They like to question everything and work hard at making sense of things. Scorpios treat others with kindness and loyalty."
},
"sagittarius" : {
"fact" : "The ninth sign of the zodiac, a Sagittarius has a very positive outlook on life. They have vibrant personalities and enjoy meeting new people. They can also be reckless."
},
"capricorn" : {
"fact" : "The 10th sign of the zodiac, those who are Capricorns are marked by their ambitious nature. They have very active minds and always have to be in control of their lives."
},
"aquarius" : {
"fact" : "Aquarius is the 11th sign of the zodiac. Aquarians don't always care what others think about them. They take each opportunity they have and work towards formulating new ideas."
},
"pisces" : {
"fact" : "Pisces is the 12th and last sign of the zodiac. Those who are Pisces are extremely sensitive and reserved. They like to escape from reality. A Pisces is a very good listener and friend."
}
}
With our object created, we can move on to our handler functions. We'll start with handleGetZodiacFactRequest. Here, we'll grab the sign the user asked for out of the zodiac slot and match it to the appropriate sign. Then, we'll check to see if this sign is one of the signs in our zodiacSigns object. If not, then the user asked for facts about a zodiac slot that does not exist, which we will respond to by saying that the sign is invalid. If the sign is one of the signs in our zodiacSigns object, then we go ahead and access the appropriate fact attribute and form the appropriate response. We also used a function called capitalizeFirst to capitalize the first letter of each zodiac sign for the card's header, which is implemented below as well.
function handleGetZodiacFactRequest(intent, session, callback) {
var sign = intent.slots.Zodiac.value.toLowerCase()
sign = matchSign(sign)
if (!zodiacSigns[sign]) {
var speechOutput = "That's not a Zodiac sign. Try asking about another sign."
var repromptText = "Try asking about another Zodiac sign."
var header = "Does Not Exist"
} else {
var fact = zodiacSigns[sign].fact
var speechOutput = fact
var repromptText = "Do you want to hear about more Zodiac signs?"
var header = capitalizeFirst(sign)
}
var shouldEndSession = false
callback(session.attributes, buildSpeechletResponse(header, speechOutput, repromptText, shouldEndSession))
}
function matchSign(sign) {
switch(sign) {
case "geminis":
return "gemini"
case "cancers":
return "cancer"
case "leos":
return "leo"
case "virgos":
return "virgo"
case "libras":
return "libra"
case "scorpios":
return "scorpio"
case "sagittariuses":
return "sagittarius"
case "capricorns":
return "capricorn"
default:
return sign
}
}
function capitalizeFirst(s) {
return s.charAt(0).toUpperCase() + s.slice(1)
}
At the end of the implementation, we call the callback function with the variables we created above. Now, onto the next handler, which is called handleHelpRequest. Here, we'll simply generate a response using the buildSpeechletResposneWithoutCard method, which will explain how the application works to the user.
function handleGetHelpRequest(intent, session, callback) {
// Ensure that session.attributes has been initialized
if (!session.attributes) {
session.attributes = {};
}
var speechOutput = "I can tell you facts about all the different Zodiac signs, including Aquaries, Aries, Taurus, Pisces, Gemini, Geminis, Cancer, Leo, Virgo, Libra, Scorpio, Sagittarius, and Capricorn. Which sign are you interested in?"
var repromptText = speechOutput
var shouldEndSession = false
callback(session.attributes, buildSpeechletResponseWithoutCard(speechOutput, repromptText, shouldEndSession))
}
Now we just need to write handleFinishSessionRequest, which will exit the user from the skill. Here, we simply make a response without a card and give it to the callback function.
function handleFinishSessionRequest(intent, session, callback) {
// End the session with a "Good bye!"
callback(session.attributes,
buildSpeechletResponseWithoutCard("Good bye! Thank you for using Zodiac Facts!", "", true));
}
If this function is called, Alexa will say "Good bye! Thank you for using Zodiac Facts!" and the skill will close.
THAT'S IT! You have completed the code for your index.js file and your Lambda function. To put it on the cloud, just zip up the one file and upload it where we left off in the last tutorial on the AWS website
Once your zipped index.js file is uploaded and you've clicked save, your screen should look like this.
In the upper-right hand corner, you should see an ARN number that looks something like this, but with more numbers and symbols. This is called an Amazon Resource Name and it will connect the functionality of our skill to the voice interface. We will use the ARN in the next step.
Now, we will be able to connect the functionality to our skill's interface and test it on the Amazon Developer site. Navigating back to the voice interface for our skill, you'll see the Configuration tab. In this panel, select your region, paste in your ARN endpoint that we grabbed earlier, and hit Next to start testing.
To test our skill, we scroll down to the Service Simulator. In the Enter Utterance text box, enter something that you might say to your echo with this skill. For this example, let's say Tell me about a Leo. After pressing Ask Zodiac Facts, you'll see the Lambda request on the right and the Lambda response on the left.
In the Lambda response, under response, outputSpeech, and ultimately text, you'll see exactly what Alexa will say to the user. You can put in all types of requests and see what the output is to test your skill. You may run into a few errors and that's okay! No one gets a skill perfect the first time and you can always edit your Lambda function and re-upload.
Really helped me, how do you fix bugs after I tested I need to update utterance’s but the changes do not update? I guess I need to compile another endpoint?