前回は、スキル・サービスのプログラム構造を解説しました。次は、以下の2つのテーマに取り組んでいきます。
- Dynamo DBでステートフルなサービスを実現する
- SSMLを使って音声応答を聞き取りやすくする
かなり高度な処理を行うことになるので、今回はこのメインのテーマに先駆けて、ローカル開発環境を整備しておきたいと思います。
ローカル開発環境を準備する
スキル・サービスのプログラムが複雑になるにつれ、デバッグにかかる時間も増えていきます。AWS Lambda関数の更新とシミュレーターを使ったスキルのテストを繰り返すだけでも、多くの時間を取られてしまいます。そこで今回は次回以降の準備として、手元のPCでスキルをデバッグできる環境を整えることにします。この連載ではスキル・サービスの開発にNode.jsを使用していますので、Node.jsのランタイムが動作するPCが必要になります。多くのプラットフォームがNode.jsをサポートしており、Windows、Macに加え代表的なLinuxのディストリビューションも利用できます。
Node.jsの実行環境をインストールする
Node.jsの公式サイトからインストーラーをダウンロードして実行します。
Node.js公式サイト:https://nodejs.org/en/download/
node.jsのダウンロードサイト
ダウンロードのページではいくつかの選択肢がありますが、LTS(Long Term Support)と呼ばれる安定バージョンをお勧めします。原稿執筆時点の最新バージョンは8.11.3です。このインストーラーを使用する他に、各種パッケージマネージャーを使用することができます。WindowsではChocolatey、MacならHomebrewが有名です。インストールを済ませると、コマンドラインでnodeコマンドが利用できるようになります。以下のコマンドを実行してバージョン番号「v8.11.3」が表示されることを確認しましょう。
プロジェクトを作成する
次にNode.jsのプロジェクトを作成します。ここではプロジェクト=AWS Lambda関数となります。プロジェクト専用のディレクトリを作成し、その中でnpm initコマンドを実行します。パッケージ名など必要な情報を尋ねるプロンプトが表示されますが、すべて既定値のまま[Enter]を入力するだけで構いません。公の場で使用するプログラムを作成する場合にはlicense等の情報を正しく入力する必要がありますので、その点ご留意ください。
> mkdir alexa-ex5
> cd alexa-ex5
> npm init
package name: (alexa-ex5)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to C:\users\koji\src\alexa-book\5\alexa-ex5\package.json:
{
"name": "alexa-ex5",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
この操作で、プロジェクトのディレクトリalexa-ex5内にpackage.jsonファイルが作成されました。このファイルは、Node.jsのプロジェクトに必要な情報を管理するためのものです。Alexa Skills SDKのような他のパッケージへの依存関係もこのファイルに記載されるので、必ず作成しておくようにします。
Alexa Skills Kit SDKを追加する
作成したプロジェクトにAlexa Skills Kit SDKパッケージを追加します。
> npm install ask-sdk
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN alexa-ex5@1.0.0 No description
npm WARN alexa-ex5@1.0.0 No repository field.
+ ask-sdk@2.0.7
added 18 packages in 5.078s
この操作で、package-lock.jsonというファイルとnpm_modulesというディレクトリが生成されているはずです。また、package.jsonファイルが更新され、dependenciesの項目にask-sdkへの依存関係が記録されています。
リスト1:Alexa Skills Kit SDKの項目が追加されたpackage.json
07 | "test": "echo \"Error: no test specified\" && exit 1" |
プロジェクトのディレクトリ内のファイルについて解説しておきます。
ソース管理
名称 | 目的 | ソース管理 | AWS Lambda への配置 |
node_modules | 必要なパッケージの実体を格納する | しない | 必要 |
package-lock.json | 必要なパッケージ間のバージョンごとの依存関係を管理する | する | 不要 |
package.json | プロジェクト(正確にはNPMのパッケージ)の名称や依存関係等を定義する | する | 不要 |
package.jsonファイルがあれば残りのファイルはnpm installコマンドで生成できます。したがってGit等のソースコード管理ツールでプロジェクトを共有する場合は、このファイルを対象とすれば良いでしょう。しかしそれだけでは、複数のメンバーで協力して開発を行う際に問題が起きることがあります。npmが取得するパッケージのバージョンが、取得のタイミングによって異なってしまう場合があるからです。それを防ぐための仕組みがpackage-lock.jsonです。このファイルもソースコード管理の対象に加えておくようにしましょう。
Hello worldスキルを実装する
新しく開発環境を整えたところなので、まずは簡単なプログラムで動作を確認しましょう。スキルを開くと一言返してくれるプログラム、いわゆるHello Wordスキルです。プロジェクトのフォルダー直下に、以下のリストに示したファイルを作成します。
リスト2:コード例:alexa-ex5/index.js
001 | // (1)Alexa Skills Kit SDK を読み込む |
002 | const Alexa = require('ask-sdk-core'); |
005 | const LaunchRequestHandler = { |
006 | canHandle(handlerInput) { |
007 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; |
009 | handle(handlerInput) { |
010 | return handlerInput.responseBuilder |
011 | .speak('あいさつスキルです。こんにちは、と言ってみてください') |
012 | .reprompt('あいさつスキルです。こんにちは、と言ってみてください') |
017 | const GreetingIntentHandler = { |
018 | canHandle(handlerInput) { |
019 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' |
020 | && handlerInput.requestEnvelope.request.intent.name === 'GreetingIntent'; |
022 | handle(handlerInput) { |
023 | const speechText = 'こんにちは'; |
025 | return handlerInput.responseBuilder |
027 | .withSimpleCard('あいさつスキル', speechText) |
032 | const HelpIntentHandler = { |
033 | canHandle(handlerInput) { |
034 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' |
035 | && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent'; |
037 | handle(handlerInput) { |
038 | const speechText = 'You can say hello to me!'; |
040 | return handlerInput.responseBuilder |
042 | .reprompt(speechText) |
043 | .withSimpleCard('Hello World', speechText) |
048 | const CancelAndStopIntentHandler = { |
049 | canHandle(handlerInput) { |
050 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' |
051 | && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent' |
052 | || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent'); |
054 | handle(handlerInput) { |
055 | const speechText = 'Goodbye!'; |
057 | return handlerInput.responseBuilder |
059 | .withSimpleCard('Hello World', speechText) |
064 | const SessionEndedRequestHandler = { |
065 | canHandle(handlerInput) { |
066 | return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest'; |
068 | handle(handlerInput) { |
069 | console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`); |
071 | return handlerInput.responseBuilder.getResponse(); |
076 | const ErrorHandler = { |
080 | handle(handlerInput, error) { |
081 | console.log(`Error handled: ${error.message}`); |
083 | return handlerInput.responseBuilder |
084 | .speak('うまく聞き取れませんでした。') |
085 | .reprompt('もういちどお願いします。') |
090 | // (4)Lambda 関数ハンドラーを定義する |
091 | const skillBuilder = Alexa.SkillBuilders.custom(); |
093 | exports.handler = skillBuilder |
095 | LaunchRequestHandler, |
096 | GreetingIntentHandler, |
098 | CancelAndStopIntentHandler, |
099 | SessionEndedRequestHandler |
101 | .addErrorHandlers(ErrorHandler) |
これまで開発してきたAlexa Skills Kit SDKバージョン1(alexa-sdkモジュール)を用いたコードとは、いくつか表記の方法が変わっています。いくつかの要点を解説していきましょう。
(1)Alexa Skills Kit SDKを読み込む
これまでのalexa-sdkではなく、ask-sdkを読み込みます。また、このコード例のように「ask-sdk-core」とするとDynamoDBのサポートなどを省いた基本機能だけを含むパッケージが読み込まれます。npmでパッケージを取得する際にask-sdk-coreを指定しておくことで、Lambdaに配置するファイル一式のサイズを小さく抑えることができます。
(2)リクエストハンドラーを定義する
リクエストハンドラーはcanHandle()関数とhandle()関数を含むオブジェクトとして定義します。
canHandle()関数はリクエストの詳細情報を持ったオブジェクトを引数に取る関数で、このハンドラーが処理すべきリクエストであればtrueを返します。例えばLaunchRequestのハンドラーではcanHandle()関数は以下のようになります。
リスト3:
1 | canHandle(handlerInput) { |
2 | return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; |
また、IntentRequestの場合は、以下のようにリクエストの種類とインテント名の両方で判別しています。
リスト4:
1 | canHandle(handlerInput) { |
2 | return handlerInput.requestEnvelope.request.type === 'IntentRequest' |
3 | && handlerInput.requestEnvelope.request.intent.name === 'GreetingIntent'; |
handle()関数には、実際にハンドラーが行う処理を記述します。引数に渡されたオブジェクトに、ResponseBuilderオブジェクトが含まれていますので、それを使って返答内容をセットし、getResponce()関数でこのハンドラーの戻り値を生成して返します。
リスト5:
2 | return handlerInput.responseBuilder |
4 | .reprompt('こんにちは、と言ってみてください') |
上のリストで利用されているResponseBuilderオブジェクトの関数は、それぞれ以下のような機能を持ちます。
ResponseBuilderオブジェクトの関数
関数 | 説明 |
speak(speechOutput: string): this; | 応答内容の文字列をセットする |
reprompt(repromptSpeechOutput: string): this; | Alexaからの質問にユーザーが応答しない場合の再プロンプトの文言をセットする |
withSimpleCard(cardTitle: string, cardContent: string): this; | Alexaアプリに送るカードのタイトルと内容をセットする |
getResponse(): Response; | スキル・インターフェースへの応答を生成して返す |
ResponseBuilderのさらに詳しい情報は、以下のページを参照してください。
ResponseBuilderの詳細情報:https://ask-sdk-for-nodejs.readthedocs.io/ja/latest/Building-Response.html#available-methods
(3)エラーハンドラーを定義する
エラーハンドラーも他のハンドラーと同じようにして定義しますが、以下の点が異なります。
- エラーハンドラーを1つだけ定義する場合は、canHandle()の戻り値が常にtrueになるようにしておく。
- handle()関数は第二引数にerrorオブジェクトを受け取るので、その情報を使ってエラーを処理、記録する。
(4)Lambda関数ハンドラーを定義する
SDKのバージョンが変わったといってもAWS Lambda関数であることには変わりはないので、Lambda関数ハンドラーを定義するところまでは同じです。初めにスキルビルダーと呼ばれるオブジェクトをAlexa.SkillBuilders.custom()関数で取得します。次にスキルビルダーのaddRequestHandlers()関数に、(2)で定義したハンドラーオブジェクトを渡します。戻り値は同じスキルビルダーのオブジェクトですので、続けてaddErrorHandlers()に(3)で定義したエラーハンドラーオブジェクトを渡します。最後にlambda()関数を呼び出すことで、Lambda関数ハンドラーを取得できますので、それをそのままexports.handlerにセットします。つまり、新しいSDKはスキルビルダーに必要な情報をセットすることで、Lambda関数ハンドラーに必要な処理を内部で行ってくれるのです。