fbpx

Blogs from the Ranch

< Back to Our Blog

Developing Alexa Skills Locally with Node.js: Implementing an Intent with Alexa-app and Alexa-app-server

Avatar

Josh Skeen

Editor’s note: This is the second in a series of posts about developing Alexa skills. Read the rest of the posts in this series to learn how to build and deploy an Alexa skill.

In our last post on building Alexa skills, we implemented a model that knows how to talk to the FAA. Now we’ll see how to hook it up to a new Alexa skill. We’ll be using alexa-app as a framework to build our skill, and alexa-app-server will allow us to test interacting with the skill locally.

We will be using these libraries because they grant a path to supporting a local development and testing workflow with an Alexa skill, which allows us to rapidly test and develop. (H/T Matt Kruse!).

Setting up Alexa-app-server

The source code for this project is available on GitHub. To begin, pull down the alexa-app-server GitHub repo with the following command:

git clone https://github.com/matt-kruse/alexa-app-server.git

Once you have cloned the repository, install any required dependencies by running:

npm install

Move the faa-info folder you created in the previous post (or via Github) into the alexa-app-server repository’s ./examples/apps directory and create a new index.js file in the faa-info directory. This is where we’ll implement our skill using the alexa-app Node module we installed just above.

Within the repository, change to the examples directory and run:

node server

You should see:

Loading apps from: apps
Listening on HTTP port 8080

You should now have a running local “skill server,” which gives us a way to test interaction with our skill locally as we build it.

Now we can begin building out our skill.

Handling Launch

The first thing our Airport Info skill should do is respond to a launch request from Alexa. This is what will ultimately happen if a user says, “Alexa, launch Airport Info.”

Because we’re developing this skill using the alexa-app module, we have a nice shorthand syntax for defining both the launch request and the speech response. Add the following to the index.js file you’ve created in the faa-info directory.

'use strict';
module.change_code = 1;
var _ = require('lodash');
var Alexa = require('alexa-app');
var app = new Alexa.app('airportinfo');
var FAADataHelper = require('./faa_data_helper');

app.launch(function(req, res) {
  var prompt = 'For delay information, tell me an Airport code.';
  res.say(prompt).reprompt(prompt).shouldEndSession(false);
});
module.exports = app;

Notice the line var app = new Alexa.app('airportinfo');. This will be what our skill is known as within the context of alexa-app-server.

We recommend having two separate Terminal tabs open at this point: one for directory navigation and one for running your alexa-app-server.

Restart alexa-app-server using “node server” as you did earlier, and open a web browser to http://localhost:8080/alexa/airportinfo. You should see a page with a dropdown for selecting a Request Type. Select “LaunchRequest” and hit “Send Request.” You should see a response similar to the following:

{
  "version": "1.0",
  "sessionAttributes": {},
  "response": {
    "shouldEndSession": false,
    "outputSpeech": {
      "type": "SSML",
      "ssml": "<speak>For delay information, tell me an Airport code.</speak>"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "SSML",
        "ssml": "<speak>For delay information, tell me an Airport code.</speak>"
      }
    }
  },
  "dummy": "text"
}

This looks like exactly what we want: instructions that show a user how to use the Airport Info skill.

Let’s take a moment to understand what output was just generated for us as a launch request through Alexa. If you refer to the index.js file we’ve just created, you’ll see an app.launch method. This method will be automatically triggered any time the skill is invoked, and it requires a response to be sent to the user. In this case, we’re calling a response.say(prompt), which will output the text provided in the var prompt above it.

Afterwards, you will see a reprompt(prompt). This is used any time a user is given the opportunity to say something, and will trigger approximately 8 seconds after the user is prompted the first time. Depending upon the design of your Alexa skill, you may want to shorten this to your liking, e.g. “Tell me an Airport code.”

Lastly, the shouldEndSession(false) method is the key determining factor as to whether your skill will continue listening for user interaction, or close the skill completely. When designing your skill, it’s important to weigh the benefits of keeping your stream open, as it may feel cumbersome to users if you expect them to continually interact with your skill. In this case, however, we want the user to be able to follow up after the welcome message with a desired airport code, so we’re choosing to leave this stream open (shouldEndSession(false)).

Intent Schema and Utterances for Airport Info

We need to map utterances (training data of what the user may say) to intents (requests to perform functions on your skill service). Then in our web service, we take those intents and run any logic we need. If you are new to the concept of utterance and schema, you can learn more by reading the Amazon documentation for a detailed inquiry into those topics.

Here, we’ll get started on the Intent Schema. First, let’s specify the airportinfo intent schema and the utterances that can be used to invoke it using the alexa-app and alexa-utterances module. These will define the voice interface for how our user will interact with the skill.

Add the following to index.js before the module.exports:

app.intent('airportinfo', {
 'slots': {
    'AIRPORTCODE': 'FAACODES'
  },
  'utterances': ['{|flight|airport} {|delay|status} {|info} {|for} {-|AIRPORTCODE}']
},
  function(req, res) {
  }
);

Now visit http://localhost:8080/alexa/airportinfo again. Select “IntentRequest” and then “airportinfo” from the list.

Since we haven’t yet implemented anything, these “Response” fields won’t contain anything interesting—but check out the rather lengthy sample utterances list that was generated for the airportinfo intent:

airportinfo  {AIRPORTCODE}
airportinfo flight {AIRPORTCODE}
airportinfo airport {AIRPORTCODE}
airportinfo  delay {AIRPORTCODE}
airportinfo flight delay {AIRPORTCODE}
airportinfo airport delay {AIRPORTCODE}
airportinfo  status {AIRPORTCODE}
airportinfo flight status {AIRPORTCODE}
airportinfo airport status {AIRPORTCODE}
airportinfo  info {AIRPORTCODE}
airportinfo flight info {AIRPORTCODE}
airportinfo airport info {AIRPORTCODE}
airportinfo  delay info {AIRPORTCODE}
airportinfo flight delay info {AIRPORTCODE}
airportinfo airport delay info {AIRPORTCODE}
airportinfo  status info {AIRPORTCODE}
airportinfo flight status info {AIRPORTCODE}
airportinfo airport status info {AIRPORTCODE}
airportinfo  for {AIRPORTCODE}
airportinfo flight for {AIRPORTCODE}
airportinfo airport for {AIRPORTCODE}
airportinfo  delay for {AIRPORTCODE}
airportinfo flight delay for {AIRPORTCODE}
airportinfo airport delay for {AIRPORTCODE}
airportinfo  status for {AIRPORTCODE}
airportinfo flight status for {AIRPORTCODE}
airportinfo airport status for {AIRPORTCODE}
airportinfo  info for {AIRPORTCODE}
airportinfo flight info for {AIRPORTCODE}
airportinfo airport info for {AIRPORTCODE}
airportinfo  delay info for {AIRPORTCODE}
airportinfo flight delay info for {AIRPORTCODE}
airportinfo airport delay info for {AIRPORTCODE}
airportinfo  status info for {AIRPORTCODE}
airportinfo flight status info for {AIRPORTCODE}
airportinfo airport status info for {AIRPORTCODE}

This was generated for us by this line:

'utterances': ['{|flight|airport} {|delay|status} {|info} {|for} {-|AIRPORTCODE}']

Under the hood, alexa-app uses the alexa-utterances module to generate this list. Check that link to get details on the allowed syntax.

We have our intent schema visible here as well. Notice the line in our skill code:

'slots': {
    'AIRPORTCODE': 'FAACODES'
  }

This defined AIRPORTCODE, which is a slot with a custom type of FAACODES we will share with Amazon when registering our skill, and gave alexa-app enough info to generate the schema for us:

{
  "intents": [
    {
      "intent": "airportinfo",
      "slots": [
        {
          "name": "AIRPORTCODE",
          "type": "FAACODES"
        }
      ]
    }
  ]
}

Now that we have an intent schema definition including an airportcode slot, and sample utterances defined for the airportinfo intent, we can now put to work the FAADataHelper we built in our first post on developing Alexa Skills locally.

To do that, we will need to update the airportinfo intent definition located inside the index.js file we created in the faa-info directory. Update it to the following:

app.intent('airportinfo', {
  'slots': {
    'AIRPORTCODE': 'FAACODES'
  },
  'utterances': ['{|flight|airport} {|delay|status} {|info} {|for} {-|AIRPORTCODE}']
},
  function(req, res) {
    //get the slot
    var airportCode = req.slot('AIRPORTCODE');
    var reprompt = 'Tell me an airport code to get delay information.';
if (_.isEmpty(airportCode)) {
      var prompt = 'I didn't hear an airport code. Tell me an airport code.';
      res.say(prompt).reprompt(reprompt).shouldEndSession(false);
      return true;
    } else {
      var faaHelper = new FAADataHelper();

faaHelper.requestAirportStatus(airportCode).then(function(airportStatus) {
        console.log(airportStatus);
        res.say(faaHelper.formatAirportStatus(airportStatus)).send();
      }).catch(function(err) {
        console.log(err.statusCode);
        var prompt = 'I didn't have data for an airport code of ' + airportCode;
        res.say(prompt).reprompt(reprompt).shouldEndSession(false).send();
      });
      return false;
    }
  }
);

//hack to support custom utterances in utterance expansion string
var utterancesMethod = app.utterances;
app.utterances = function() {
return utterancesMethod().replace(/{-|/g, '{');
};

Now we can test how this behaves from alexa-app-server.

Testing the airportinfo Intent

We have three cases to test:

Given an airportInfo intent request:

  1. When the airportCode slot is empty, I should should get “I didn’t hear an airport code. Tell me an airport code.”
  2. When the FAA Server didn’t recognize our airportCode, I should get “I didn’t have data for an airport code of AIRPORTCODE.”
  3. When the airportCode was a code the FAA Server recognized, then I should get a response matching the FAADataHelper’s response we tested earlier.

Let’s try the first test case. Hit http://localhost:8080/alexa/airportinfo and select “IntentRequest” and “airportinfo” from the list. Don’t type anything into “AIRPORTCODE.” Hit “Send Request,” and you should then see the following in the Response box:

{
  "version": "1.0",
  "sessionAttributes": {},
  "response": {
    "shouldEndSession": false,
    "outputSpeech": {
      "type": "SSML",
      "ssml": "<speak>I didn't hear an airport code. Tell me an airport code.</speak>"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "SSML",
        "ssml": "<speak>Tell me an airport code to get delay information.</speak>"
      }
    }
  },
  "dummy": "text"
}

Nice! That looks right. Now let’s try the second case. Do the same thing but enter “PUNKYBREWSTER” for the AIPRORTCODE field.

{
  "version": "1.0",
  "sessionAttributes": {},
  "response": {
    "shouldEndSession": false,
    "outputSpeech": {
      "type": "SSML",
      "ssml": "<speak>I didn't have data for an airport code of PUNKYBREWSTER</speak>"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "SSML",
        "ssml": "<speak>Tell me an airport code to get delay information.</speak>"
      }
    }
  },
  "dummy": "text"
}

Just what we expected. Now for the last case, let’s try entering “ATL” for the AIRPORTCODE.

{
  "version": "1.0",
  "sessionAttributes": {},
  "response": {
    "shouldEndSession": true,
    "outputSpeech": {
      "type": "SSML",
      "ssml": "<speak>There is currently no delay at Hartsfield-Jackson Atlanta International. The current weather conditions are A Few Clouds, 51.0 F (10.6 C) and wind North at 0.0mph.</speak>"
    }
  },
  "dummy": "text"
}

Success! Thanks to these local results, we can begin the process of deploying to Amazon while feeling fairly confident that our skill works as expected. We’ll still need to test interaction with the the actual device, but with this workflow, we should have greatly improved our chances of deploying without bugs.

Going Live

At this point, you’ve been given some useful tools and techniques for more easily developing an Alexa skill locally. With a local development environment setup, you will gain access to the debugger and the stack trace, and you’ll be able to work more efficiently by quickly testing changes without uploading files to a remote server.

In Part 3 of this series, we move from local to live. We’ll go over how you can test your skill on an Amazon Echo or another Alexa-enabled device by creating a new skill on the Amazon Alexa Developer Console, and then deploying your code to AWS Lambda.

Don’t wait until then. Check out the source code for the project we built above and get going!

Avatar

Josh Skeen

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project