Building Alexa Skills
NodeJS + Lambda
twitter.com/@johncmckim
johncmckim.me
medium.com/@johncmckim
Software Engineer at A Cloud Guru
John McKim
@johncmckim
Contribute to Serverless Framework
https://acloud.guru
What is A Cloud Guru
Training Courses for Engineers
Serverless Framework
https://serverless.com
Agenda
- What is Alexa
- Alexa re:Invent Competition
- Developer Registration
- Configuring an Alexa Skill
- Developing a Lambda for Alexa
- Bonus Level - GraphQL & React
- What I learnt
What is Alexa
Amazon Echo and Echo Dot
What is Alexa
What can it do
- Order from amazon.com
- Play music - Spotify / Amazon music
- Read books
- Alarm Clock
- Control a Smart Home
- Run custom Skills (like apps)
What is Alexa
What can I make it do?
-
Voice Activated Pitching Machine
-
Baby monitor
-
Control Servers - (cause chaos)
-
Bris JS reminder
-
Anything ?
Alexa re:Invent Contest
Alexa re:Invent Contest
Close but no Cigar
No Echo. No Problems.
https://echosim.io
Before the fun stuff
Registration & Configuration
Developer Registration
https://developer.amazon.com
Create Account
Developer Registration
https://developer.amazon.com
Hand over your details
Developer Registration
https://developer.amazon.com
Sign your life away
Developer Registration
https://developer.amazon.com
Choose not to make money :)
Configuring your Skill
Demo
- Invocation word
- Intents
- Sample Utterances
- Lambda
Interaction Model
Intents
{
"intents": [
{
"intent": "NextEvent",
"slots": []
}
]
}
NextEvent when is the next event
NextEvent when is the next meetup
NextEvent when is BrisJS on next
Sample Utterances
Invocation Word
Bris J S
Lambda Configuration
Developing your Skill
Serverless Framework Config
service: alexa-meetup-skill
provider:
name: aws
runtime: nodejs4.3
functions:
alexa:
handler: src/alexa.handler
environment:
ALEXA_APP_ID: ${env:ALEXA_APP_ID}
MEETUP_API_URL: https://api.meetup.com
MEETUP_URL_NAME: ${env:MEETUP_URL_NAME}
events:
- alexaSkill
Developing your Skill
Lambda Handler
'use strict';
const Alexa = require('alexa-sdk');
const handlers = require('./handlers');
const APP_ID = process.env.ALEXA_APP_ID;
module.exports.handler = (event, context) => {
var alexa = Alexa.handler(event, context);
alexa.appId = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
};
Developing your Skill
const moment = require('moment');
const meetup = require('../lib/meetup');
module.exports = {
'NextEvent': function() {
const emit = this.emit.bind(this);
meetup.listEvents('upcoming', false).then((events) => {
const any = events.length > 0;
if (!any) {
emit(':tell', 'There are no upcoming meetups');
} else {
const time = moment(events[0].time);
emit(':tell',
`The next meetup is on
<say-as interpret-as="date">${time.format('YYYYMMDD')}</say-as>.
${time.diff(moment(), 'days')} days from now.`
);
}
})
.catch(() => {
emit(':tell', 'Sorry. I am having trouble finding the next meetup.')
});
}
};
Intent Handler
Demo Time
Code + Ask BrisJS
Developing your Skill
Taking it up a Level
Developing your Skill
Intents with Slots
{
"intents": [
{
"intent": "RandomKill",
"slots": [{
"name": "Count",
"type": "AMAZON.NUMBER"
}]
},
{
"intent": "CountInstances"
},
{
"intent": "StartInstances",
"slots": [{
"name": "Count",
"type": "AMAZON.NUMBER"
}]
},
{
"intent": "AMAZON.HelpIntent"
},
{
"intent": "AMAZON.StopIntent"
}
]
}
Developing your Skill
Utterances with Slots
RandomKill run chaos monkey
RandomKill chaos monkey
RandomKill kill {Count} server
RandomKill kill {Count} servers
RandomKill kill {Count} instance
RandomKill kill {Count} instances
RandomKill terminate {Count} server
RandomKill terminate {Count} servers
CountInstances how many servers are running
CountInstances how many instances are running
CountInstances how many e c two instances are running
StartInstances start a server
StartInstances start an instance
StartInstances start an e c two instance
StartInstances run a server
StartInstances run an instance
StartInstances run an e c two instance
StartInstances start {Count} server
StartInstances start {Count} servers
StartInstances start {Count} instance
StartInstances start {Count} instances
StartInstances start {Count} e c two instance
StartInstances start {Count} e c two instances
StartInstances run {Count} server
StartInstances run {Count} servers
StartInstances run {Count} instance
StartInstances run {Count} instances
StartInstances run {Count} e c two instance
StartInstances run {Count} e c two instances
Developing your Skill
Lambda Events
{
"session": {
"sessionId": "SessionId.0887154f-ab0c-4db4-9293-9c6e0f3c3247",
"application": {
"applicationId": "amzn1.ask.skill.69483f2c-0154-4cf1-a9d9-f7b697c65a54"
},
"attributes": {},
"user": {
"userId": "amzn1.ask.account.xxxxx"
},
"new": true
},
"request": {
"type": "IntentRequest",
"requestId": "EdwRequestId.2ccc70da-4418-4e22-9244-43fa77e4d7a1",
"locale": "en-US",
"timestamp": "2017-03-05T06:35:07Z",
"intent": {
"name": "StartInstances",
"slots": {
"Count": {
"name": "Count",
"value": "3"
}
}
}
},
"version": "1.0"
}
Developing your Skill
Handling requests
'use strict';
const chaosService = require('../../chaos-service');
const countHelper = require('./count-text-helper');
module.exports = (intent) => {
const countSlot = intent.slots ? intent.slots.Count : null;
const count = countSlot ? countSlot.value : 1;
return chaosService
.terminate({ count })
.then((result) => {
const terminatingCount = result.terminate &&
result.terminate.TerminatingInstances ?
result.terminate.TerminatingInstances.length : 0;
const countText = result.count ? countHelper.getCountText(result.count) : '';
const text = terminatingCount > 0 ?
`Booooom. You just killed ${terminatingCount} ${terminatingCount === 1 ? 'server' : 'servers'}. ${countText}` :
`I didn't kill any servers. ${countText}`;
return {
sessionAttributes: {},
cardTitle: "Kill",
speechOutput: text,
repromptText: "",
shouldEndSession: true
}
});
};
Developing your Skill
Lambda Responses
{
"version": "1.0",
"response": {
"outputSpeech": {
"type": "PlainText",
"text": "I started 3 servers for you."
},
"card": {
"content": "I started 3 servers for you.",
"title": "Start",
"type": "Simple"
},
"reprompt": {
"outputSpeech": {
"type": "PlainText",
"text": ""
}
},
"shouldEndSession": true
},
"sessionAttributes": {}
}
Developing your Skill
Handling requests
const BbPromise = require('bluebird');
const AWS = require('aws-sdk');
const lambda = new AWS.Lambda({
region: 'us-east-1' //change to your region
});
const invokeLambda = (lambdaName, event) => {
const params = {
FunctionName: lambdaName,
Payload: JSON.stringify(event),
};
return new BbPromise((resolve, reject) => {
lambda.invoke(params, (err, data) => {
if (err) {
reject(err);
return;
}
console.log('Received result: ', data);
resolve(data);
});
});
}
const lambdaResolver = (lambdaName) =>
(event) => invokeLambda(lambdaName, event).then(
(data) => JSON.parse(data.Payload)
);
const countBy = lambdaResolver(process.env.COUNT_LAMBDA);
const start = lambdaResolver(process.env.START_LAMBDA);
const terminate = lambdaResolver(process.env.TERMINATE_LAMBDA);
const stop = lambdaResolver(process.env.STOP_LAMBDA);
module.exports = {
countBy,
start,
terminate,
stop,
}
Developing your Skill
Causing Chaos
'use strict';
const AWS = require('aws-sdk');
const chaosService = require('./chaos-service');
const ec2 = new AWS.EC2();
// {
// "count": 3
// }
module.exports.handler = (event, context, callback) => {
return chaosService
.terminate(ec2, event.count)
.then((result) => callback(null, result))
.catch(err => callback(err));
};
Demo Time
Chaos Alexa
Bonus Level
GraphQL with Serverless & React
type Group {
key: String
value: String
}
type Count {
total: Int!
groups: [Group!]
}
type Tag {
Key: String
Value: String
}
type InstanceState {
Code: Int!
Name: String!
}
type InstancePlacement {
AvailabilityZone: String!
}
type Instance {
InstanceId: String!
ImageId: String!
State: InstanceState!
Placement: InstancePlacement!
Tags: [Tag!]
}
enum CountSelector {
az
name
size
state
}
type Query {
countBy(selector: CountSelector): Count!
list(state: String): [Instance!]
}
Bonus Level
GraphQL Resolvers
const BbPromise = require('bluebird');
const AWS = require('aws-sdk');
const lambda = new AWS.Lambda();
const invokeLambda = (lambdaName, event) => {
const params = {
FunctionName: lambdaName,
Payload: JSON.stringify(event),
};
return new BbPromise((resolve, reject) => {
lambda.invoke(params, (err, data) => {
if (err) {
reject(err);
return;
}
console.log('Received result: ', data);
resolve(data);
});
});
}
const lambdaResolver = (lambdaName, getEvent) =>
(args) => invokeLambda(lambdaName, getEvent(args))
.then((data) => JSON.parse(data.Payload));
const countBy = lambdaResolver(process.env.COUNT_LAMBDA, (args) => ({ selector: args.selector }));
const list = lambdaResolver(process.env.LIST_LAMBDA, (args) => ({ state: args.state }));
module.exports = {
countBy,
list,
};
Bonus Level
Dashboard in React
import React from 'react';
import { Doughnut } from 'react-chartjs';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import colorHelper from './color-helper'
const InstancesQuery = gql`query InstanceCount($selector: CountSelector) {
countBy(selector: $selector) {
total,
groups { key, value }
}
}`;
...
const InstancesChart = ({ data }) =>{
...
return (
<div>
<Doughnut data={chartData} options={chartOptions} height="200" />
</div>
)
}
export default graphql(InstancesQuery, {
options: ({ selector }) => ({
pollInterval: 30 * 1000,
variables: { selector }
}),
})(InstancesChart);
Demo Time
Chaos Dashboard
What I Learnt
Alexa skills are different but fun
- Conversational UI is not simple
- Writing responses for speech is different to writing for reading
- Rolling your own SDK for Alexa is easy
- Backends for Frontends is great for applications with multiple clients
Resources
Reading
- https://developer.amazon.com/blogs/tag/Alexa
- https://read.acloud.guru/alexa-champ/home
- https://github.com/johncmckim/alexa-meetup-skill
- www.hackster.io/a-cloud-guru/alexa-chaos-monkey-280d79
- https://github.com/alexa-ops
Alexa Projects
We're Hiring
Front-end and Full-Stack JS Devs
Thanks for Listening!
Questions?
twitter.com/@johncmckim
johncmckim.me
medium.com/@johncmckim
Building Alexa Skills
By John McKim
Building Alexa Skills
- 139