Alexaのためのローカル開発環境を整備する

2018年9月4日(火)
畠中 幸司(はたなか・こうじ)
連載5回目は、より高度なプログラム作成に先駆けて、Alexaのためのローカル開発環境を整備していきます

前回は、スキル・サービスのプログラム構造を解説しました。次は、以下の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のダウンロードサイト

node.jsのダウンロードサイト

ダウンロードのページではいくつかの選択肢がありますが、LTS(Long Term Support)と呼ばれる安定バージョンをお勧めします。原稿執筆時点の最新バージョンは8.11.3です。このインストーラーを使用する他に、各種パッケージマネージャーを使用することができます。WindowsではChocolatey、MacならHomebrewが有名です。インストールを済ませると、コマンドラインでnodeコマンドが利用できるようになります。以下のコマンドを実行してバージョン番号「v8.11.3」が表示されることを確認しましょう。

> node --version
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

{
  "name": "alexa-ex5",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "ask-sdk": "^2.0.7"
  }
}

プロジェクトのディレクトリ内のファイルについて解説しておきます。

ソース管理

名称目的ソース管理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

// (1)Alexa Skills Kit SDK を読み込む
const Alexa = require('ask-sdk-core');

// (2)リクエストハンドラーを定義する
const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak('あいさつスキルです。こんにちは、と言ってみてください')
      .reprompt('あいさつスキルです。こんにちは、と言ってみてください')
      .getResponse();
  },
};

const GreetingIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'GreetingIntent';
  },
  handle(handlerInput) {
    const speechText = 'こんにちは';

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('あいさつスキル', speechText)
      .getResponse();
  },
};

const HelpIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
  },
  handle(handlerInput) {
    const speechText = 'You can say hello to me!';

    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('Hello World', speechText)
      .getResponse();
  },
};

const CancelAndStopIntentHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'
        || handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent');
  },
  handle(handlerInput) {
    const speechText = 'Goodbye!';

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('Hello World', speechText)
      .getResponse();
  },
};

const SessionEndedRequestHandler = {
  canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
  },
  handle(handlerInput) {
    console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);

    return handlerInput.responseBuilder.getResponse();
  },
};

// (3)エラーハンドラーを定義する
const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);

    return handlerInput.responseBuilder
      .speak('うまく聞き取れませんでした。')
      .reprompt('もういちどお願いします。')
      .getResponse();
  },
};

// (4)Lambda 関数ハンドラーを定義する
const skillBuilder = Alexa.SkillBuilders.custom();

exports.handler = skillBuilder
  .addRequestHandlers(
    LaunchRequestHandler,
    GreetingIntentHandler,
    HelpIntentHandler,
    CancelAndStopIntentHandler,
    SessionEndedRequestHandler
  )
  .addErrorHandlers(ErrorHandler)
  .lambda();

これまで開発してきた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:

canHandle(handlerInput) {
   return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
}

また、IntentRequestの場合は、以下のようにリクエストの種類とインテント名の両方で判別しています。

リスト4:

canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'IntentRequest'
      && handlerInput.requestEnvelope.request.intent.name === 'GreetingIntent';
}

handle()関数には、実際にハンドラーが行う処理を記述します。引数に渡されたオブジェクトに、ResponseBuilderオブジェクトが含まれていますので、それを使って返答内容をセットし、getResponce()関数でこのハンドラーの戻り値を生成して返します。

リスト5:

handle(handlerInput) {
    return handlerInput.responseBuilder
      .speak('あいさつスキルです')
      .reprompt('こんにちは、と言ってみてください')
      .getResponse();
}

上のリストで利用されている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関数ハンドラーに必要な処理を内部で行ってくれるのです。

著者
畠中 幸司(はたなか・こうじ)

音楽と自然と猫を愛するソフトウェア&インフラエンジニア。日本ヒューレット・パッカード株式会社でクラウドネイティブなアプリケーションのためのインフラ提案、および構築業務に従事。2000年にウェブスタートアップでエンジニアとしてのキャリアをスタートして以来、メガソフト株式会社の3Dマイホームデザイナーシリーズの開発や、マイクロソフト日本法人にて Windows Phone、Microsoft Officeシリーズの開発など、数多くの国内およびグローバルな開発プロジェクトに携わる。建設業向けモバイルアプリSTUCCO(スタッコ)のスタートアップ起業経験、500 KOBE Pre-Acceleratorへの参加等を経て2017年より現職。

連載バックナンバー

開発ツール技術解説
第6回

Alexa:ステートフルなサービスと聞き取りやすい音声応答

2018/9/20
連載6回目は、ステートフルなサービスの構築と、Alexaの音声応答を聞き取りやすくする手法を紹介します。
開発ツール技術解説
第5回

Alexaのためのローカル開発環境を整備する

2018/9/4
連載5回目は、より高度なプログラム作成に先駆けて、Alexaのためのローカル開発環境を整備していきます
開発ツール技術解説
第4回

Alexa Skills Kit SDK for Node.jsについて知る

2018/8/21
連載4回目は、スキル・サービス側のコードに着目して、フレームワークの理解を深めていきます。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています