How to make a custom Amazon Alexa Skill on your lunchbreak

The Echo is Amazon’s personal device that has been selling millions of units. Not available yet officially in Ireland, you can buy one from Amazon.co.uk and use it here. You plug it in, set it up, then ask it questions — like “What is the weather in Dublin?”, “Who won Best Actress at the Oscars?” or “Tell me a joke”.

Amazon Echo — source: Amazon.com

Echo also has “Skills” — these are basically apps — software programs that extend Echo’s functionality and features. Some Skills we’ve tried out include the Jeopardy Skill based on the US TV gameshow and the Nasa Mars Skill, where you can ask questions about the Red Planet.

We’ve been experimenting with building Skills for a while and thought we’d write a post on how to make one. If you’re familiar with coding, you should be pleasantly surprised at the speed and ease of development involved.

Amazon have a “Build a Trivia Skill in under an Hour” tutorial, ours is either plus or minus, depending on how long you take for your lunch!

This is what we’re going to build:

What do you need to start?

  • An Amazon Developer account (Amazon’s developer website), free to setup, to create a the Skill.

Note — the Amazon Developer account and website is different to AWS — we spent a good while searching the AWS console for “Alexa” or “Skills” until we realised there’s another completely separate development website! Who knew?! It’s slightly reassuring to know we weren’t alone in this — see Liz Rice’s great Skill tutorial!

  • An Amazon AWS account (Amazon’s cloud services platform), free to setup, to create a Lambda function. (more on Lambda in a minute!)
  • All the tools are web-based, so no extra software needed
  • You don’t need an actual Amazon Echo, though one would be handy for physical testing … and for showing off afterwards!
  • An idea!! The most important bit! Your Skill is basically an app and just like apps, they need a good use-case. Today, I’ve decided to make a Skill to give interesting Irish Mythology facts!

Architecture

The way a Skill is architected is as follows:

  • User asks a question
  • The Skill (defined on the Amazon Developer site) matches that question with an “Intent” (what the user is trying to ask, more on these later) and then sends this Intent to either a custom endpoint (like an API call on your own server) or, as Amazon recommends, AWS Lambda.
  • Lambda (or your own service), is where the “data” is stored. It has code to take the Intent and return the relevant response, taken from the data.
  • The Echo speaks back this response to the user.
  • The user is amazed and delighted*

*The user may not be actually amazed and delighted!

Create the Skill

Ok, once you’re registered and setup on on the Amazon Developer site, hit the Alexa option on the top navbar:

Then hit the box on the left, Alexa Skills Kit

then over the right, hit Add a New Skill:

and you’ll see this:

First, leave the Skill Type as Custom Interaction Model.

Then and this is very important — change the language to English(U.K.) if your device uses English(U.K.).

You can add a language variant later to support English(U.S.) or German, but for now, leave it at English(U.K.) — or your skill won’t work!

Next pick a Name, I pick “Irish Mythology”, this is the actual name of the Skill used on any Skill listings etc.

Now choose an Invocation Name, this is what the user actually says to Alexa to “invoke” the Skill e.g. “Alexa, ask Irish Mythology for an interesting fact” or “Alexa, talk to Irish Mythology”.

Leave Audio Player as No — this is for playing audio files, we won’t be doing that today:

and hit Save, then Next to move to the Interaction Model screen:

This is the meat and potatoes, so to speak, of the Skill. In here we define:

  • an Intent Schema
  • Custom Slot Types
  • Sample Utterances

Intent Schema

These are the “intents” of the Skill, which define the “intent” or purpose of something the user says to the Skill. So we may have a “fact” intent, which signifies that the user wants to know an Irish Mythology fact. Or we may have a “more” intent which means the user wants to here more facts.

The “design” of the Skill should inform what intents to use — what is the Skill for, what type of questions can a user ask etc. I’ve decided to have two intents (as it’s lunch remember and I don’t have much time!):

  • Fact — to hear a short interesting Irish Mythology fact*.
  • Story — to hear a longer Irish Mythology story.

*Dr. Eoin from the team says there are no “facts” in mythology, just stories! Typical data scientist!

The intents are defined in this format:

{
"intents": [
{
"intent": "AMAZON.HelpIntent"
},
{
"intent": "AMAZON.StopIntent"
},
{
"intent": "Fact"
},
{
"intent": "Story"
}
]
}

The AMAZON.HelpIntent is a default Amazon intent that you can use to handle any “Help” type questions. Likewise, AMAZON.StopIntent is a inbuilt intent to handle “stop” intents.

Custom Slot Types

We don’t actually need any Custom Slot Types for this basic Skill. These are used when you have a lists of related values — like colours, or makes of cars or countries. You can read more about them here.

Sample Utterances

Here we give examples of “utterances” — what the user may say for each intent. Each utterance goes on its own line and starts with the intent it is associated with e.g.

Fact Tell me a fact

For each intent, we try to think of as many ways as possible that users may say it:

Fact I want to know a fact
Fact Give me a fact
Fact Tell us a fact
Fact Another fact please

and

Story Tell me a story
Story I want to know a story
Story Give me a story
Story Tell us a story
Story Another story please

We put these in the box and hit Save:

You’ll see a message like:

then:

Note — you can’t put certain characters in these sample utterances, like ? or !

If you do, you’ll get an error like this:

Ok, hit Next and you’ll be taken to Configuration:

This where we specify where the Intent information goes when the user asks a question. The options are:

  • AWS Lambda — a Lambda endpoint
  • HTTPS — your own endpoint

We are going to pick the Lambda option, which then prompts you to select North America or Europe:

As we’re in Europe, we pick that, which pops up:

This is where we give the endpoint address, which we don’t have yet! So …

AWS Lambda

If you haven’t used AWS Lambda before, it’s a service that allows you to host code with its own endpoint, without a server. More info here.

Once you’re setup and logged into AWS, click on the Lambda service:

The first time you may see a blurb and a “Get Started” button … then hit the Create a Lambda function

Now you can pick a “template” for your skill — basically boilerplate code to get you started. When we first built a Skill, we chose the “alexa-skills-kit-color-expert” template and modified as needed:

Note that it’s in node.js. Lambda currently supports node.js, Java or Python.

Today, we’re going to pick Blank Function but we’ll actually be using some of the alexa-skills-kit-color-expert code:

We then see the Configure triggers screen, this is where we define how the Lambda function is triggered / called:

Hit the box with the dotted lines in the middle and a dropdown will open:

Hit Alexa Skills Kit, then hit Next:

which then shows the Configure function screen:

Here we give a Name and a Description:

Scroll down to Lambda function handler and role:

If it’s your first time, select Create a custom role in the Role dropdown:

which will open up a new window:

Hit allow if that’s cool with you, and then back in Lambda, select lambda_execution_role in the Existing Role field:

Hit Next, you’ll see a summary of the function, then hit Create function. Shortly, you’ll see:

Note the “ARN” at the top-right. We’ve masked ours, but here is where the endpoint is — that you then copy into that field back in the Developer Console:

Hit Next in there to save it.

Ok, back to AWS and Lambda. In the Code section, paste this in:

// --------------- Helpers that build all of the responses -----------------------
function buildSpeechletResponse(title, output, repromptText, shouldEndSession) {
return {
outputSpeech: {
type: 'PlainText',
text: output,
},
card: {
type: 'Simple',
title: `SessionSpeechlet - ${title}`,
content: `SessionSpeechlet - ${output}`,
},
reprompt: {
outputSpeech: {
type: 'PlainText',
text: repromptText,
},
},
shouldEndSession,
};
}
function buildResponse(sessionAttributes, speechletResponse) {
return {
version: '1.0',
sessionAttributes,
response: speechletResponse,
};
}
// ---------------

I got these from the alexa-skills-kit-color-expert template; they are used to build the response sent back to Alexa.

Now let’s add some data. I’m just going to add a small bit first, to test everything:

// --------------- Data ---------------------------------
var irishFacts = 
[
{
"name": "Tír na nÓg",
"fact": "Teer na Nogue is a mythical land, roughly translated as Country of the Young where everyone had eternal life. Oisin visited it after falling in love with Niamh.",
},
{
"name": "Fionn Mac Cumhaill",
"fact": "Fionn Mac Cool was a legendary warrior, leader of the Fianna. In some stories he is giant and was who built the Giant's Causeway in Northern Ireland",
},{
"name": "Medb",
"fact": "Maeve was a Queen of Connacht and enemy of Conchubhar mac Nessa of Ulster. She is one of the main protaginists in the epic Táin Bó Cúailnge.",
}
];

Note I’m trying to spell some words phonetically as I’m presuming Alexa can’t handle the Irish language! #GaeilgeShouldBeOnAlexa

We also add a story:

var irishStories = 
[
{
"name": "The Pursuit of Diarmuid and Gráinne",
"story": "The Pursuit of Diarmuid and Gráinne also known as Toraíocht Diarmuid is Gráinne. Long ago a warrior named Diarmuid fell in love with the fiance of his leader, the legendary Fionn MacCool. They eloped and were chased in many amazing adventures. Diarmuid ends up dead of course!",
}
];

Please forgive the glib shortening of this epic story, we’re on the clock remember!

Now we add more functions:

// --------------- Events -----------------------
/**
* Called when the session starts.
*/
function onSessionStarted(sessionStartedRequest, session) {
console.log(`onSessionStarted requestId=${sessionStartedRequest.requestId}, sessionId=${session.sessionId}`);
}
/**
* Called when the user launches the skill without specifying what they want.
*/
function onLaunch(launchRequest, session, callback) {
console.log(`onLaunch requestId=${launchRequest.requestId}, sessionId=${session.sessionId}`);
// Dispatch to your skill's launch.
getWelcomeResponse(callback);
}

The function onLaunch is ran when a user launches the Skill by saying something like “Alexa, open Irish Mythology” as oppose to asking a specific question. It calls the getWelcomeResponse function, which we haven’t written yet, add it now:

function getWelcomeResponse(callback) {
/* If we wanted to initialize the session to have some attributes we could add those here.*/
const sessionAttributes = {};
const cardTitle = 'Welcome';
const speechOutput = 'Welcome to Irish Mythology, you can say: Tell me a fact or Tell me a story' ;
/* If the user either does not reply to the welcome message or says something that is not understood, they will be prompted again with this text.*/
const repromptText = 'You can say: Tell me a fact or Tell me a story ' ;
const shouldEndSession = false;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

The cardTitle is the title of the “card” that gets sent to the Alexa app for that response.

The speechOutput is the actual text spoken back to the user.

The reprompt text is spoken if there’s no reply from the user.

The shouldEndSession specifies whether to exit the Skill or not.

Ok, now add this function:

/**
* Called when the user specifies an intent for this skill.
*/
function onIntent(intentRequest, session, callback) {
console.log(`onIntent requestId=${intentRequest.requestId}, sessionId=${session.sessionId}`);
const intent = intentRequest.intent;
const intentName = intentRequest.intent.name;
// Dispatch to your skill's intent handlers
if (intentName === 'AMAZON.HelpIntent') {
getWelcomeResponse(callback);
} else if (intentName === 'AMAZON.StopIntent') {
handleSessionEndRequest(callback);
} else if (intentName === 'Fact') {
getFactResponse(callback);
} else if (intentName === 'Story') {
getStoryResponse(callback);
}
}

This basically takes in the intent from the Skill and based on that calls the relevant function.

For Help intents we call the getWelcomeResponse function again.

For Stop intents we call the handleSessionEndRequest function, so let’s add that, where we define a parting message:

function handleSessionEndRequest(callback) {
const cardTitle = 'Session Ended';
const speechOutput = 'Thank you for using the Irish Mythology Skill, have a great day!';
// Setting this to true ends the session and exits the skill.
const shouldEndSession = true;
callback({}, buildSpeechletResponse(cardTitle, speechOutput, null, shouldEndSession));
}

For Fact intents, we call the getFactResponse function, so let’s write that:

function getFactResponse(callback) {

//get random index from array of data
var index = getRandomInt(Object.keys(irishFacts).length -1);

// If we wanted to initialize the session to have some attributes we could add those here.
const sessionAttributes = {};

//Get card title from data
const cardTitle = irishFacts[index].name;

//Get output from data
const speechOutput = irishFacts[index].fact;
// If the user either does not reply to the welcome message or says something that is not
// understood, they will be prompted again with this text.
const repromptText = '' ;
const shouldEndSession = false;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

Here we get a random item from the irishFacts array and use them for the Card Title and the Speech Output. It uses this function, so add it:

function getRandomInt(max) {
return Math.floor(Math.random() * (max - 0 + 1)) + 0;
}

We create a similar one for stories:

function getStoryResponse(callback) {

//get random index from array of data
var index = getRandomInt(Object.keys(irishStories).length -1);

// If we wanted to initialize the session to have some attributes we could add those here.
const sessionAttributes = {};

//Get card title from data
const cardTitle = irishStories[index].name;

//Get output from data
const speechOutput = irishStories[index].story;
// If the user either does not reply to the welcome message or says something that is not
// understood, they will be prompted again with this text.
const repromptText = '' ;
const shouldEndSession = false;
callback(sessionAttributes,
buildSpeechletResponse(cardTitle, speechOutput, repromptText, shouldEndSession));
}

and finally, replace the exports.handler function with these two, also taken from the Amazon example:

/**
* Called when the user ends the session.
* Is not called when the skill returns shouldEndSession=true.
*/
function onSessionEnded(sessionEndedRequest, session) {
console.log(`onSessionEnded requestId=${sessionEndedRequest.requestId}, sessionId=${session.sessionId}`);
// Add cleanup logic here
}
// --------------- Main handler -----------------------
// Route the incoming request based on type (LaunchRequest, IntentRequest,
// etc.) The JSON body of the request is provided in the event parameter.
exports.handler = (event, context, callback) => {
try {
console.log(`event.session.application.applicationId=${event.session.application.applicationId}`);
/**
* Uncomment this if statement and populate with your skill's application ID to
* prevent someone else from configuring a skill that sends requests to this function.
*/
/*
if (event.session.application.applicationId !== 'amzn1.echo-sdk-ams.app.[unique-value-here]') {
callback('Invalid Application ID');
}
*/
if (event.session.new) {
onSessionStarted({ requestId: event.request.requestId }, event.session);
}
if (event.request.type === 'LaunchRequest') {
onLaunch(event.request,
event.session,
(sessionAttributes, speechletResponse) => {
callback(null, buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === 'IntentRequest') {
onIntent(event.request,
event.session,
(sessionAttributes, speechletResponse) => {
callback(null, buildResponse(sessionAttributes, speechletResponse));
});
} else if (event.request.type === 'SessionEndedRequest') {
onSessionEnded(event.request, event.session);
callback();
}
} catch (err) {
callback(err);
}
};

Ok, time to test!

Back in the Developer website, go to the Test page:

Type a question into the Enter Utterance field, like “Tell me a story” and hit the Ask Irish Mythology button:

You should see a Service Response which contains the correct response:

Awesome! If you see a red error message instead, go back to AWS Lambda, click on Monitoring and then View logs in CloudWatch. Take a look in there and you’ll probably get a steer.

To try it on an actual Echo, just use an Echo that you’ve logged into using the same Amazon account as you used to develop it. Then just say “Alexa, ask Irish Mythology for story”.

We hoped you found this interesting / useful! Any comments or thoughts please let us know below and feel free to hit the little heart recommend button on go even crazier and share! Andy