How to Effectively Manage Your Client-Partner Relationships
Clients Project StrategyClients leave when they feel that their needs aren’t being met. The strength of your client-partner relationship relies on a deep understanding of your client’s...
Editor’s note: This is the fourth post in our series on developing Alexa skills.
By now, we’ve made a lot of progress in building our Airport Info skill. We tested the model and verified that the skill service behaves as expected. Then we tested the skill in the simulator and on an Alexa-enabled device. In this post, we’ll implement persistence in a new skill so that users will be able to access information saved from their previous interactions.
We’ll go over how to write Alexa skill data to storage, which is useful in cases where the skill would time out or when the interaction cycle is complete. You can see this at work in skills like the 7-Minute Workout skill, which allows users to keep track of and resume existing workouts, or when users want to resume a previous game in The Wayne Investigation.
For this experiment, we’ll build upon an existing codebase and improve it. The skill, a cooking assistant called CakeBaker, guides users in cooking a cake, step by step. A user interacts with CakeBaker by asking Alexa to start a cake, then advances through the steps of the recipe by saying “next” after each response, like so:
This continues until the user reaches the last step. But what if the skill closes before the user is able to finish a step? By default, Alexa skills close if a user doesn’t respond within 16 seconds. Right now, that means that a user would be forced to start over at the first step, losing the progress made.
Let’s fix that by implementing two new methods in our skill, called saveCakeIntent
and loadCakeIntent
, which will allow users to save and load their current progress to and from a database. We’ll also test the database functionality in our local environment using the alexa-app-server and alexa-app libraries we discussed in our post on implementing an intent in an Alexa Skill.
This experiment will use Node.js and alexa-app-server to develop and test the skill locally, so we will need to set up those dependencies first. If you haven’t yet done so, read our posts on setting up a local environment and implementing an intent—they will guide you in setting up a local development environment for this skill, which will involve more advanced requirements.
Let’s get started by downloading the source code for CakeBaker. We’ll be improving this source code so that it supports saving and loading cakes to the database.
To complete the experiment, we’ll need a working installation of alexa-app-server and Node.js. If you haven’t done so, install Node.js and then install alexa-app-server, using the instructions outlined in the linked posts.
Clone CakeBaker into the alexa-app-server/examples/apps
directory by opening a new terminal window and entering the following within the alexa-app-server/examples/apps
directory:
git clone https://github.com/bignerdranch/alexa-cakebaker
Change directories into alexa-app-server/examples/apps/alexa-cakebaker
and run the following command:
npm install
This will fetch the dependencies the project requires in order to work correctly.
The database we will use to store the state of the cake is Amazon’s DynamoDB, a NoSQL-style database that will ultimately live in the cloud on Amazon’s servers. To facilitate testing, we’ll install a local instance of DynamoDB. We will use the brew package manager to add DynamoDB to our local development environment.
Install Homebrew if you haven’t already done so:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Once this command completes, install a local version of DynamoDB via Homebrew:
brew install dynamodb-local
On Windows? Follow these steps.
When the brew
command completes, open a new tab in your terminal and run the following command:
dynamodb-local -sharedDb -port 4000
You should see something similar to the following:
Initializing DynamoDB Local with the following configuration:
Port: 4000
InMemory: false
DbPath: null
SharedDb: true
shouldDelayTransientStatuses: false
CorsParams: *
Now we can begin developing our database functionality and test db behaviour in our local environment. Leave the tab open while you work.
At this point, the CakeBaker skill is cloned locally and our test database instance is set up, so we’re ready to begin adding the save and load features. In order to implement them, we need two new intents for these actions: saveCakeIntent
and loadCakeIntent
. Let’s begin by adding the intent definitions to the bottom of the index.js
file.
One for saving the cake:
skillService.intent('saveCakeIntent', {
'utterances': ['{save} {|a|the|my} cake']
},
function(request, response) {
//code goes here!
}
);
And one for loading the cake:
skillService.intent('loadCakeIntent', {
'utterances': ['{load|resume} {|a|the} {|last} cake']
},
function(request, response) {
//code goes here!
}
);
Implementing the Save Command
Here’s a diagram of how the save command will work:
In the diagram, the user’s utterance is resolved to the saveCakeIntent
and then processed by the skill service. The skill service saves the cake data to the database, and once this operation completes the service, it responds to the skill interface, indicating that the write to the database succeeded.
The CakeBaker source code we checked out contains a helper called database_helper.js
. Open this file, and you should see the following:
'use strict';
module.change_code = 1;
var _ = require('lodash');
var CAKEBAKER_DATA_TABLE_NAME = 'cakeBakerData';
var dynasty = require('dynasty')({});
function CakeBakerHelper() {}
var cakeBakerTable = function() {
return dynasty.table(CAKEBAKER_DATA_TABLE_NAME);
};
CakeBakerHelper.prototype.createCakeBakerTable = function() {
return dynasty.describe(CAKEBAKER_DATA_TABLE_NAME)
.catch(function(error) {
return dynasty.create(CAKEBAKER_DATA_TABLE_NAME, {
key_schema: {
hash: ['userId',
'string'
]
}
});
});
};
CakeBakerHelper.prototype.storeCakeBakerData = function(userId, cakeBakerData) {
return cakeBakerTable().insert({
userId: userId,
data: cakeBakerData
}).catch(function(error) {
console.log(error);
});
};
CakeBakerHelper.prototype.readCakeBakerData = function(userId) {
return cakeBakerTable().find(userId)
.then(function(result) {
return result;
})
.catch(function(error) {
console.log(error);
});
};
module.exports = CakeBakerHelper;
This file contains the basic logic for creating a new table in DynamoDB we will name cakeBakerData
to write cake data to. It also contains methods for reading and writing the cake data to the DynamoDB instance.
Our first task, saving a cake, will be aided by the storeCakeBakerData
method the helper contains. Notice storeCakeBaker
’s signature: it expects a userId
and cakeBakerData
. The userId
is a unique identifier provided by the Alexa service upon a user enabling a skill. We will pull the userId
from the request received by our service from the skill interface, and it will uniquely identify the Alexa account that the Skill is attached to so that the skill can keep track of data for different users. It is also the key we will use to look up a user’s cakeBakerData
on the database.
The helper also makes use of Dynasty, an open-source library for interacting with the DynamoDB instance. Because we are developing locally, the first code change we will make is to the connection settings for the Dynasty object.
For testing locally, we will use our local machine’s DynamoDB instance. In order to do that we will need to edit the database_helper.js
file and comment the line:
//var dynasty = require('dynasty')({});
and add:
//var dynasty = require('dynasty')({});
var localUrl = 'http://localhost:4000';
var localCredentials = {
region: 'us-east-1',
accessKeyId: 'fake',
secretAccessKey: 'fake'
};
var localDynasty = require('dynasty')(localCredentials, localUrl);
var dynasty = localDynasty;
This will enable us to test against the local DynamoDB instance we started in the terminal using port 4000.
Before we can save or read cake data from DynamoDB, we’ll first need to ask DynamoDB to create a table to store it in. We can use a helpful feature of alexa-app called a “pre” hook, which will execute before the intent handlers in the skill are executed.
Open the index.js
file in the alexa-cakebaker folder and add the following at line 9, right below var databaseHelper = new DatabaseHelper();
:
skillService.pre = function(request, response, type) {
databaseHelper.createCakeBakerTable();
};
This will execute before any intent is handled. If the table doesn’t exist, and if it’s already created, Dynasty will simply return an error, which we handle in the DatabaseHelper
class.
Let’s implement a saveCake function, at the bottom of the index.js
file before the module.exports = CakeBakerHelper; :
var saveCake = function(cakeBakerHelper, request) {
var userId = request.userId;
databaseHelper.storeCakeBakerData(userId, JSON.stringify(cakeBakerHelper))
.then(function(result) {
return result;
}).catch(function(error) {
console.log(error);
});
};
The method pulls the userId
from the request, passing it and a stringified version of the cake data to be written to the database.
Now we’ll put the saveCake method to use. Update the saveCakeIntent
intent handler we defined earlier in the index.js
file:
skillService.intent('saveCakeIntent', {
'utterances': ['{save} {|a|the|my} cake']
},
function(request, response) {
var cakeBakerHelper = getCakeBakerHelperFromRequest(request);
saveCake(cakeBakerHelper, request);
response.say('Your cake progress has been saved!');
response.shouldEndSession(true).send();
return false;
}
);
Perfect! This should write the cake’s progress to the database when a user explicitly requests it from the skill.
We will also need to update the advanceStepIntent
to make use of the saveCake
method as well. When a user requests “next,” the cake should be saved implicitly to avoid any lost progress due to a timeout or the skill’s request cycle ending.
Update the advanceStepIntent
to call saveCake
, just after the cakeBakerHelper
is incremented:
skillService.intent('advanceStepIntent', {
'utterances': ['{next|advance|continue}']
},
function(request, response) {
var cakeBakerHelper = getCakeBakerHelperFromRequest(request);
cakeBakerHelper.currentStep++;
saveCake(cakeBakerHelper, request);
cakeBakerIntentFunction(cakeBakerHelper, request, response);
}
);
A user should be able to load the cake after the skill has exited. Once the cake is loaded, the skill should pick back up at the step that the user left from, eliminating the pain of starting over from the beginning.
In order to enable this, we will have the skill read the cake from the database after looking it up with our userId
and set up the CakeBakerHelper
object from the persisted state. Then we’ll call cakeBakerIntentFunction
to generate the response that should be sent to Alexa. Edit the index.js
file and replace the loadCakeIntent
Intent with the following:
skillService.intent('loadCakeIntent', {
'utterances': ['{load|resume} {|a|the} {|last} cake']
},
function(request, response) {
var userId = request.userId;
databaseHelper.readCakeBakerData(userId).then(function(result) {
return (result === undefined ? {} : JSON.parse(result['data']));
}).then(function(loadedCakeBakerData) {
var cakeBakerHelper = new CakeBakerHelper(loadedCakeBakerData);
return cakeBakerIntentFunction(cakeBakerHelper, request, response);
});
return false;
}
);
Now we can test that the new functionality works against the local database. First, let’s start the alexa-app-server. Change to the alexa-app-server/examples
directory and run the local development server:
node server
Now, visit the test page at http://localhost:8080/alexa/cakebaker. We want to mimic a cake that has advanced several steps, so we’ll send several requests on the server. Configure the type to IntentRequest
, and the Intent to cakeBakeIntent
and hit “Send Request”. This should start a new cake.
Next, change the Intent to advanceStepIntent
and hit “Send Request”—this mimics a user saying “next” in order to move the recipe along to the next step. Hit “Send Request” three more times. In the response area of the test page, you should see:
"response": {
"shouldEndSession": false,
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>Beat 2 sticks butter and 1 and 1/2 cups sugar in a large bowl with a mixer on medium-high speed until light and fluffy, about 3 minutes. to hear the next step, say next</speak>"
},
"reprompt": {
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>I didn’t hear you. to hear the next step, say next</speak>"
}
}
},
Great! Now we can test that saving to the database works. Switch the Intent to saveCakeIntent
and click “Send Request”. You should see the following in the response area:
{
"version": "1.0",
"sessionAttributes": {
"cake_baker": {
"started": false,
"currentStep": 4,
"steps": [
//removed for brevity
]
}
},
"response": {
"shouldEndSession": true,
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>Your cake progress has been saved!</speak>"
}
}
Our cake has now been saved to the database! To verify whether the skill service is working, reload the test page, then set the Intent to loadCakeIntent
. Click “Send Request”. This mimics a user saying “Alexa, ask Cake Baker to load the cake.”
The response should pick up where the user left off with the fourth step in the cake recipe.
{
"version": "1.0",
"sessionAttributes": {
"cake_baker": {
"started": false,
"currentStep": 4,
"steps": [
//removed for brevity
]
}
},
"response": {
"shouldEndSession": false,
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>Beat 2 sticks butter and 1 and 1/2 cups sugar in a large bowl with a mixer on medium-high speed until light and fluffy, about 3 minutes. to hear the next step, say next</speak>"
},
"reprompt": {
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>I didn’t hear you. to hear the next step, say next</speak>"
}
}
},
"dummy": "text"
}
Now that we’ve tested the skill locally, let’s deploy it live! Fortunately, because DynamoDB is already wired to work easily with an AWS Lambda skill, we will have to do very little to deploy.
First, let’s change database_helper.js
to production mode. Open database_helper.js
. Uncomment:
var dynasty = require('dynasty')({});
Then comment the local development configuration we added. The top of our database_helper.js
file should look like this:
'use strict';
module.change_code = 1;
var _ = require('lodash');
var CAKEBAKER_DATA_TABLE_NAME = 'cakeBakerData';
var dynasty = require('dynasty')({});
// var localUrl = 'http://localhost:4000';
// var localCredentials = {
// region: 'us-east-1',
// accessKeyId: 'fake',
// secretAccessKey: 'fake'
// };
...
Now, before we run through the usual Alexa Skill deployment process, we need to configure DynamoDB on AWS. Visit https://console.aws.amazon.com/dynamodb/home and create a new table. For the table name, enter “cakeBakerData”, and for primary key enter “userId”. Finally, click “Create”.
Next, we will follow the usual Alexa Skill deployment process—but with two big differences. First, we will run through setting up the skill service on AWS Lambda. Visit the Lambda dashboard and click “Create Lambda Function”. Click “skip” on the resulting page.
Zip the files within the cakebaker
directory and click the “Upload a ZIP file” option in the Lambda configuration, keeping in mind that your index.js
file should be at the parent level of your archive. Click “Upload” and select the archive you created.
It’s important to note that the Lambda function handler and role selection are different here than they are in an AWS Lambda skill without a database. Rather than “Basic Execution Role”, select “Basic with DynamoDB”. This will redirect to a new screen, where you should click “Allow”. This step allows our AWS Lambda-backed skill service to use a DynamoDB datastore on our AWS Account.
Here is what your configuration should now look like:
Click “Next” and then “Create Function”.
Note the long “ARN” at the top right of the page. This is the Amazon Resource Name, and it will look something like arn:aws:lambda:us-east-1:333333289684:function:myFunction
. You will need it when setting up the skill interface, so be sure to copy it from your AWS Lambda function.
Finally, click on the “Event sources” tab and click “Add event source”. Select “Alexa Skills Kit” in the Event Source Type dropdown and hit “Submit”.
Next, we’ll set up the skill interface. Visit the Amazon Developer Console skills panel and click “Add a New Skill”. In the Skill Information tab, enter “Cake Baker” for the “Name” and “Invocation Name” fields. Leave “Custom Interaction Model” selected for the Skill Type.
Click “Next”.
Now we need to set up the interaction model. Copy the intent schema and utterances from the alexa-app-server test page into the respective fields.
For the “Intent Schema” field, use:
{
"intents": [
{
"intent": "advanceStepIntent",
"slots": []
},
{
"intent": "repeatStepIntent",
"slots": []
},
{
"intent": "cakeBakeIntent",
"slots": []
},
{
"intent": "loadCakeIntent",
"slots": []
},
{
"intent": "saveCakeIntent",
"slots": []
}
]
}
and for the “Sample Utterances” field, use:
advanceStepIntent next
advanceStepIntent advance
advanceStepIntent continue
cakeBakeIntent new cake
cakeBakeIntent start cake
cakeBakeIntent create cake
cakeBakeIntent begin cake
cakeBakeIntent build cake
cakeBakeIntent new a cake
cakeBakeIntent start a cake
cakeBakeIntent create a cake
cakeBakeIntent begin a cake
cakeBakeIntent build a cake
cakeBakeIntent new the cake
cakeBakeIntent start the cake
cakeBakeIntent create the cake
cakeBakeIntent begin the cake
cakeBakeIntent build the cake
loadCakeIntent load cake
loadCakeIntent resume cake
loadCakeIntent load a cake
loadCakeIntent resume a cake
loadCakeIntent load the cake
loadCakeIntent resume the cake
loadCakeIntent load last cake
loadCakeIntent resume last cake
loadCakeIntent load a last cake
loadCakeIntent resume a last cake
loadCakeIntent load the last cake
loadCakeIntent resume the last cake
saveCakeIntent save cake
saveCakeIntent save a cake
saveCakeIntent save the cake
saveCakeIntent save my cake
Click “Next”.
On the Configuration page, select “Lambda ARN (Amazon Resource Name)” and enter the ARN you copied when you set up the Lambda endpoint. Click “Next”. You can now test that the skill behaves as it did in local development. If you have an Alexa-enabled device registered to your developer account, you can now test the save and load functionality with the device. Amazon has more information on registering an Alexa-enabled device for testing, if you’re not familiar with the process.
Try the following commands, either in the test page or against a real device: “Alexa, ask Cake Baker to bake a cake”, “next”, “next”, and “Save Cake”.
Wait for a moment while the skill times out, and then say, “Alexa, ask Cake Baker to load the cake”. The skill should pick up where we left off, on the third step of Cake Baker.
Congratulations; you’ve implemented basic persistence in an Alexa Skill! In the next post, we’ll cover submitting your custom Alexa skills for certification so that they can be used by anybody with an Alexa-enabled device.
Clients leave when they feel that their needs aren’t being met. The strength of your client-partner relationship relies on a deep understanding of your client’s...
Responding to change is a key tenant of Agile Software Development. Predicting change is difficult, but software can be developed in a manner that...
A lot of this confusion surfaces around the scope of machine learning. While there is a lot of hype around deep learning, it is...