SJCNet

SJCNet is the home of architect/developer/techie, Simon Coope.

Amazon Alexa Skills: Post 3 - Create a Custom Skill

This post is a continuation of a series of posts describing how to create a custom skill for the Alexa Voice System (AVS). The posts in this series are:

1. Overview and prerequisites.
2. How to set-up your development environment.
3. How to develop a custom skill (this post).
4. How to test your skill on an Alexa-enabled device.
5. How to successfully certify your skill.

All the source code for this project can be found on Github.

Overview

In this post we're going to create the code for our custom skill. We're going to start by creating intents and then generating our utterances.

I used Test-Driven Development (TDD) while developing this code, but in the interests of brevity I won't go through each test/function that was created. I'll give an example of the process and then highlight any important concepts. All the code can be found on Github for reference.

Additionally, the Lambda function expects an index.js file as the entry point for the code. So this will be the file that directly references the alexa-app package.

While developing this code the approach I took highlighted good candidates for decoupling from the index.js file. To that end I opted to separate the intent functionality out into a specific module called intentHandler.js and also split the speech prompts out into another module called prompts.js.

SSML

When creating the intents it's important to note that when your skill returns a response to a user the prompt that you provide to the AVS converts this to speech. This uses Speech Synthesis Markup Language (SSML). SSML can be used to give you additional control over how the AVS generates the speech (pauses, etc.) to make the speech more natural sounding.

As such we need to pull in a third-party module that will allow us to convert to and from SSML to properly test the output of our tests. To do this we need to download and reference the to-ssml.js file that's part of the alexa-app library. This file can be found here.

I've downloaded this and dropped it in a test/refs folder.

Create the Launch Intent

As an example, I'm going to create the launch intent. This intent will be fired when the application launches, and will output prompts to welcome the user and instruct them on how to use the skill.

Test Set-up

To create the launch intent I first created the basic layout of the test file, as follows:

// Third party modules
var alexa = require('alexa-app');
var ssml = require('./refs/ssml');
var chai = require('chai');
var should = chai.should();

// App modules
var intentHandler = require('../src/intentHandler');
var prompts = require('../src/prompts');

describe('intentHandler', function() {
    it('is a sample test', function() {
        false.should.equal(true);
    });
});

In order to get the above test to run, first we need to stub out our source files. To that end I also created the following files in the src folder.

intentHandler.js

'use strict';
var intentHandler = {};
module.exports = intentHandler;

prompts.js

'use strict';
var prompts = {};
module.exports = prompts;

Now when I run npm test at the command prompt I get the following (expected) output:

FailingTestOutput

Create the Test

Next I create the failing test for the launch intent, as follows:

describe('intentHandler', function() {
    describe('on launch', function() {
        // Trigger the launch function
        var response = new alexa.response();
        var request = {};
        intentHandler.launch(request, response);

        // Get the speech response
        var speechResponse = response.response.response;
        var prompt = speechResponse.outputSpeech;
        var reprompt = speechResponse.reprompt.outputSpeech;

        it('should have correct prompt', function () {
            should.exist(prompt);
            prompt.type.should.equal('SSML');

            var expected = ssml.fromStr(prompts.launch.prompt)
            prompt.ssml.should.equal(expected);
        });

        it('should have correct reprompt', function () {
            should.exist(reprompt);
            reprompt.type.should.equal('SSML');

            var expected = ssml.fromStr(prompts.launch.reprompt);
            reprompt.ssml.should.equal(expected);
        });
    });
});

In the above code, we first create an alexa.response object and a default request object and call the intentHandler.launch function passing in the objects. This is the function that the index.js file will call when interacting with the alexa-app library on launch of the skill.

Next we get the speech response and extract the prompt and the reprompt. The prompt is used by alexa when the skill is first activated. The reprompt is used if no user interaction occurs for a set period of time.

Finally, we have the tests to check that we have the expected prompt and reprompt.

At the moment, when we run this test we should get an error and the tests should fail. This is because we haven't yet created the intentHandler.launch function.

Create the Launch Function

The next step is to create the function that receives the launch request and generates the correct response. To do this we update our intentHandler.js file to the following:

intentHandler.js

'use strict';
var intentHandler = {};
var prompts = require('./prompts');

intentHandler.launch = function (request, response) {
    var prompt = prompts.launch.prompt;
    var reprompt = prompts.launch.reprompt;
    
    response.say(prompt).reprompt(reprompt).shouldEndSession(false);
};

module.exports = intentHandler;

The above code simply configures the correct prompt for the response and reprompt and then stops the Alexa session from ending (which closes the skill).

You'll notice we're also using the prompt module, which we've not yet populated. Now we need to set the prompts we want to use on welcome and reprompt. To do this we update the prompt.js file as follows:

prompts.js

'use strict';
var prompts = {};

prompts.launch = {
    prompt: "Welcome to Who Is Right?  You can ask me a question like, who is right, is it Simon or Claire? ... Now, how can I help?",
    reprompt: "For instructions on what you can say, please say help me."
},

module.exports = prompts;

Now if you run the tests again, you should see them passing!

PassingTests

As mentioned previously, I'm not going to go through creating all the intents. These can be seen in the source code on Github.

After you've created all the intents and related tests, you can create the index.js file and wire the intents up to the alexa-app library.

Create the Index File

The index.js file is where we wire up our intentHandler module to the alexa-app module. The layout of the file (with all intents created) is as follows:

'use strict';

var alexa = require('alexa-app');
var app = new alexa.app('whoisright');
var intentHandler = require('./intentHandler');

app.intent('WhoIsRight', {
    'slots': {
        'FirstPerson': 'LIST_OF_NAMES',
        'SecondPerson': 'LIST_OF_NAMES'
    },
    'utterances': [
        '{FirstPerson} or {SecondPerson}',
        '{FirstPerson} all {SecondPerson}',
        'is it {FirstPerson} or {SecondPerson}',
        'is it {FirstPerson} all {SecondPerson}',
        '#who is right {FirstPerson} or {SecondPerson}',
        'who has the best idea {FirstPerson} or {SecondPerson}'
    ],
}, intentHandler.whoIsRight);

app.launch(intentHandler.launch);
app.error = intentHandler.error;
app.intent('AMAZON.HelpIntent', intentHandler.help);
app.intent('AMAZON.StopIntent', intentHandler.stop);
app.intent('AMAZON.CancelIntent', intentHandler.cancel);

module.exports = app;

Here we can see that we've created the WhoIsRight intent and specified the slots and expected utterances. The slots are just a list of custom types that we expect (i.e. names). The utterances are the phrases we expect the user to use when executing the skill. In the utterances we pass the values in the slots as parameters.

After the first intent, we have some build-in intents to handle launch, errors, help, stop and cancel events.

Now we're ready to test the skill in the local alexa-app-server instance.

Test the Skill

To test the skill we need to copy the src files, package.json and the node_modules to the alexa-app-server/examples/apps/ directory and put them in a new colder called whoisright.

Note: You must put the source JS files in the root of the whoisright folder because the index.js entry point must be available in the root.

When the source files are there, restart your alexa-app-server by hitting CTRL-C in the command window and running the node server command again. This time it should list the whoisright app in the output. Now the app has been registered you shouldn't need to restart the alexa-app-server again.

Now if you open a browser and navigate to the app server url (in my case it's http://localhost:8080/alexa/whoisright), you should be able to interact with your skill.

Build File

To automate the above process I've created a build file to copy the files to the app server location. This is included in the source code, but please note that this isn't a great example at a build file, it was just something I created to quickly copy the required files over.

Once you've got the skill working correctly on your local machine, it's time to test it on an alexa-enabled device! See the next section for how to set this up.

Author image
About Simon Coope
Sydney, Australia Website
Experienced developer/consultant. Loves all things development, technology, gadgets, football and running.