Skip to main content

Developing an Alexa Skill

An Echo Plus (Alexa speaker) on a table.
7th January 2018AppsAlexa, JavaScript

Project Details

This is a short blog covering how I went about developing my first skill for Alexa. I had the idea that I wanted to create a simple skill for a bit of fun at work (we recently bought an Echo Plus for the office).

I started by running through the “Custom Skill” tutorial on the Amazon developer site.

https://developer.amazon.com/docs/custom-skills/steps-to-build-a-custom-skill.html

I have not made my new skill publicly available, it was more for me to get a feel for what was needed, and I just wanted it to run on specific devices.

When I followed the instructions at the above link, some of the pages/fields had changed from what was explained. I imagine that they will again change from what I describe below, but hopefully it will still guide you through what you need to do.

Who’s the Best?

I wanted my new skill to store a list of employees and their job titles and for Alexa to randomly pick one as a response to the above question. It should also be possible to ask

Who’s the best developer?

or

Who’s the best account manager?

and for Alexa to respond with someone with that occupation. As we have a range of job titles (e.g. “Technical Developer”, “Front End Developer”), it also needed to group and associate them together.

Getting Started

In order to create a new Alexa skill, you need to do two things.

  1. Create the new skill on the Amazon developer website and fill in the details about how the skill should work.
  2. Create the code that implements the skill and host it on a publicly available host. As recommended by Amazon, I used Amazon Web Services (AWS) to do this as, for low usage, it is free.

Create the Skill

To create the skill, head over to the Amazon developer website and sign in using your normal Amazon login (the one you use to buy stuff), or create one if you don’t already have it. It’s a good idea to use the same account as your Alexa device is signed up to, as you can then use that device to test without needing to publish the skill.

https://developer.amazon.com/home.html

Once in the developer console select the “Alexa” tab at the top of the screen and click on “Get Started” for the Alexa Skills Kit. Here you’ll see a list of skills that you have already created (if any) and you can click on “Add a New Skill” to get started.

Under “Skill Information” set the values as follows.

  • Skill Type: Custom Interaction Model.
  • Language: whichever language you want to use.
  • Name: Choose a name that describes the skill, for when it is published.
  • Invocation Name: These are the two (or more) words that tell Alexa that you want to use this skill. I chose “Lewis Judge”, just make sure that you follow the Invocation Name Guidelines. Unless your skill proves very popular, you’ll need to name the skill for it to work, e.g. “Alexa, ask Lewis Judge who’s the best”.
  • You can leave the other fields set to their default values.

When I developed my first skill the Interaction Model Builder was still undergoing Beta testing, but hopefully it won’t have changed much since then. It shows a dedicated page on which you define what phrases Alexa should accept for this skill. Some Intents should already have been created and you will need to add a new one, I called mine “GetBestIntent”.

Enter all of the variations of the phrases that you wish to handle, where part of the phrase can change you should use a “slot” (or a variable) which can be picked up in the code later on. I used {Role} to substitute the occupation that was being asked for. My phrases were:

  • who the best {Role} is
  • who’s the best {Role}
  • who is the best {Role}

I didn’t need a separate “Who’s the best” for the everybody case, as the last two cover that – the {Role} will just be blank.

You will notice that as you add slots to your phrases, each unique Intent Slot will be shown on the right-hand side of the page. Each will require a “slot type”, which indicates what kind of information is expected for this part of the phrase. You will see that there are some standard types in the drop-down list (e.g. city, number, date), but we can also define our own.

Under the “Intents” on the left-hand side of the page you can add a new slot type and set the possible values allowed. In my case they were things like “Coder”, “Developer”, “Front End Developer”, “Senior Account Manager”. You can then return to the intent and set the Role to use the new slot type, to indicate that these are the possible phrases to expect in that position.

Save and build the model (the latter will take a short time) and then return to the main skill definition.

Eventually we’ll set up an endpoint, which links to the actual implementation of the skill, but first we need to create the code and host it somewhere.

The Code

The actual logic of a skill is some code that you have hosted somewhere. I decided to stick with the Amazon recommendation and hosted my code on the AWS. To do this I logged into my AWS management console and created a “Lambda” function. Lots more information can be found in the following location, which will likely to be more up-to-date than this blog.

https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-an-aws-lambda-function.html

Here’s my code that implemented “Who’s the Best?”. The app isn’t available publicly, but I was able to set it up so that it worked on our work Echo Plus.

/* eslint-disable  func-names */
/* eslint quote-props: ["error", "consistent"]*/
/**
 * This sample demonstrates a simple skill built with the Amazon Alexa Skills
 * nodejs skill development kit.
 * This sample supports multiple lauguages. (en-US, en-GB, de-DE).
 * The Intent Schema, Custom Slots and Sample Utterances for this skill, as well
 * as testing instructions are located at https://github.com/alexa/skill-sample-nodejs-fact
 **/

'use strict';
const Alexa = require('alexa-sdk');

//=========================================================================================================================================
//TODO: The items below this comment need your attention.
//=========================================================================================================================================

//Replace with your app ID (OPTIONAL).  You can find this value at the top of your skill's page on http://developer.amazon.com.
//Make sure to enclose your value in quotes, like this: const APP_ID = 'amzn1.ask.skill.bb4045e6-b3e8-4133-b650-72923c5980f1';
const APP_ID = undefined;

const SKILL_NAME = 'The Best';
const IS_THE_BEST = " is the best ";
const HELP_MESSAGE = 'You can ask me who is the best. Go on, ask me.';
const HELP_REPROMPT = 'What can I help you with?';
const STOP_MESSAGE = 'Goodbye!';

//=========================================================================================================================================
//TODO: Replace this data with your own.  You can find translations of this data at http://github.com/alexa/skill-sample-node-js-fact/lambda/data
//=========================================================================================================================================
const data = [
    ['<sub alias="laowra">laura</sub>','senior account manager','f'],
    ['david','managing director','m'],
    ['kirsty','client services director','f'],
    ['mark','senior designer','m'],
    ['scott walker','design director','m'],
    ['douglas','production manager','m'],
    ['jonathan','front end developer','m'],
    ['ali','front end developer','f'],
    ['alysha','account manager','f'],
    ['charles','technical director','m'],
    ['stuart','senior technical developer','m'],
    ['scott millar','art director','m'],
    ['gillian','account director','f'],
    ['nikki','account director','f'],
    ['garry','front end developer','m'],
    ['dale','junior designer','m'],
    ['kari','senior designer','f'],
    ['barry','finance director','m'],
    ['colette','group services manager','f'],
    ['<sub alias="owen">owain</sub>','technical developer','m'],
    ['lauren','digital marketing assistant','f'],
    ['zara','account manager','f'],
    ['nicola','management accountant','f'],
    ['martin','technical developer','m'],
    ['laura','senior account manager','f'],
    ['lily','project manager','f']
];

const alternateRoles = [
    ['designer','senior designer','design director','art director','junior designer'],
    ['creative','senior designer','design director','art director','junior designer'],
    ['developer','front end developer','technical director','senior technical developer','technical developer'],
	['frontend developer','front end developer'],
    ['back end developer','senior technical developer','technical developer'],
	['backend developer','senior technical developer','technical developer'],
    ['coder','senior technical developer','technical developer','front end developer'],
    ['account manager','senior account manager','client services director','account director']
];

//=========================================================================================================================================
//Editing anything below this line might break your skill.
//=========================================================================================================================================

exports.handler = function(event, context, callback) {
    var alexa = Alexa.handler(event, context);
    alexa.appId = APP_ID;
    alexa.registerHandlers(handlers);
    alexa.execute();
};

const handlers = {
    'LaunchRequest': function () {
        this.emit('GetBestIntent');
    },
    'GetBestIntent': function () {
		var role = "";
		if (this.event.request.intent.slots.Role)
		{
			role = this.event.request.intent.slots.Role.value;
    	    if (!role || role == null)
		    {
		        role = "";
            }
		}
        var factArr = [];
		var lastPersonSex = "f";
		
		for (var person of data)
		{
			if (role.length > 0 && person[1] == role)
			{
				factArr.push(person[0]);
				lastPersonSex = person[2];
			}
			else if (role.length > 0)
			{
				for (var alternateRole of alternateRoles) 
				{
					if (alternateRole[0] == role && alternateRole.includes(person[1],1))
					{
						factArr.push(person[0]);
						lastPersonSex = person[2];
					}
				}
			}
			else
			{
				// No role
				factArr.push(person[0]);
				lastPersonSex = person[2];
			}
		}
		
		var speechOutput = "I don't understand: " + role;
		var personName = "No one";
		if (factArr.length > 0)
		{
			const factIndex = Math.floor(Math.random() * factArr.length);
			personName = factArr[factIndex];
			speechOutput = personName + IS_THE_BEST;
			if (role.length > 0)
			{
				speechOutput += role;
			}
			
			if (factArr.length == 1)
			{
				if (lastPersonSex == "f")
				{
					speechOutput += ", but then she's the <emphasis level='strong'>only</emphasis> one.";
				}
				else
				{
					speechOutput += ", but then he's the <emphasis level='strong'>only</emphasis> one.";
				}
			}
		}
		else
		{
			speechOutput = "I can't find anyone with the role: " + role;
		}
		
        this.response.cardRenderer(SKILL_NAME, personName);
        this.response.speak(speechOutput);
        this.emit(':responseReady');
    },
    'AMAZON.HelpIntent': function () {
        const speechOutput = HELP_MESSAGE;
        const reprompt = HELP_REPROMPT;

        this.response.speak(speechOutput).listen(reprompt);
        this.emit(':responseReady');
    },
    'AMAZON.CancelIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
    'AMAZON.StopIntent': function () {
        this.response.speak(STOP_MESSAGE);
        this.emit(':responseReady');
    },
};