Skip to content

Latest commit

 

History

History
1028 lines (868 loc) · 66.6 KB

Readme.jp.md

File metadata and controls

1028 lines (868 loc) · 66.6 KB

This version of the Alexa Skills Kit SDK is no longer supported. Please use the v2 release found here

Alexa Skills Kit SDK for Node.js

概要

Alexa SDKチームは、新しい Alexa Node.js SDK をお届けできることを誇りに思います。これは、開発者のために開発者が作成したオープンソースのAlexaスキル開発キットです。

現在のAlexaスキル開発で最も一般的な方法の1つが、Alexa Skills Kit、Node.js、AWS Lambdaを使用してスキルを作成することです。Node.jsのイベント駆動型ノンブロッキングI/OモデルはAlexaスキルに適していますし、Node.jsは世界のオープンソースライブラリの最も大きなエコシステムの1つです。さらに、AWS Lambdaは毎月最初の100万回の呼び出しが無料で、ほとんどのAlexaスキル開発者にとって満足できます。また、AWS Lambdaを使用する場合、Alexa Skills Kitは信頼できるトリガーなので、SSL証明書をあなたが管理する必要はありません。

AWS Lambda、Node.js、Alexa Skills Kitを使用したAlexaスキルの構築は、簡単な作業ではありましたが、たくさんのコードを書かなければなりませんでした。そこで、Alexa SDKチームは、Node.js用のAlexa Skills Kit SDKを作成しました。これは、一般的なつまづきを避けて、定型コードではなくスキルのロジックに集中することをお手伝いします。

新しいalexa-sdkでの私たちの目標は、不要な複雑さを避けて、あなたがスキルを素早く構築するのを助けることです。現在、このSDKは、次の機能を備えています。

  • あらゆるNode.js環境に簡単にデプロイできるNPMパッケージとしてホストされています
  • ビルトインのイベントを使用してAlexaのレスポンスを構築する機能
  • 「catch-all」なイベントとして使えるNew SessionイベントとUnhandledイベント
  • インテント処理をステートマシンベースで構築するためのヘルパー機能
  • この機能で、スキルの現在の状態に基づいて異なるイベントハンドラを定義できます
  • DynamoDBで属性の永続性を有効にする簡単な設定
  • すべての音声出力がSSMLとして自動的にラップされます
  • Lambdaイベントとコンテキストオブジェクトは、this.eventthis.contextを通して完全に利用できます
  • ビルトイン関数を上書きすることで、状態の管理やレスポンスの構築をより柔軟な方法でおこなえます。たとえば、AWS S3に状態の属性を保存できます。

セットアップガイド

alexa-sdkは、Githubですぐに入手できます。あなたのNode.js環境で次のコマンドを実行して、nodeパッケージとしてデプロイできます。

npm install --save alexa-sdk

Getting Started: Hello Worldスキルの記述

基本的なプロジェクトの構造

HelloWorldスキルには次のものが必要です。

  • スキルに必要なすべてのパッケージをインポート、イベントの受け取り、appIdの設定、dynamoDBテーブルの設定、ハンドラ登録などをする、スキルのエントリポイント
  • 各リクエストを処理するハンドラ関数

エントリポイントを設定する

あなたのプロジェクトにエントリポイントを設定するには、単にindex.jsという名前のファイルを作成して、次の内容を追加します。

const Alexa = require('alexa-sdk');

exports.handler = function(event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.appId = APP_ID // APP_ID とは、スキルを作成するAmazon開発者コンソールに表示されるあなたのスキルIDです。
    alexa.execute();
};

上のコードは、alexa-sdkをインポートして、これから作業するAlexaオブジェクトをセットアップします。

ハンドラ関数を実装する

次に、スキルのためにイベントとインテントを処理する必要があります。alexa-sdkを使うと、関数にインテントを発火させることが簡単になります。ハンドラ関数は、先ほど作成したindex.jsファイルに実装することも、別のファイルに記述して後からインポートすることもできます。 たとえば、 'HelloWorldIntent'のハンドラを作成するには、次の2つの方法があります。

const handlers = {
    'HelloWorldIntent' : function() {
        // レスポンスを直接返します
        this.emit(':tell', 'Hello World!');
    }
};

または

const handlers = {
    'HelloWorldIntent' : function() {
        // まずresponseBuilderを使ってレスポンスを構築してから返します
        this.response.speak('Hello World!');
        this.emit(':responseReady');
    }
};

alexa-sdkは、responseBuilderでspeak/listenに対応するoutputSpeechレスポンスオブジェクトを生成するにあたり、tell/askレスポンス方式にしたがいます。

this.emit(':tell', 'Hello World!'); 
this.emit(':ask', 'What would you like to do?', 'Please say that again?');

上のコードは、次のコードと同等です。

this.response.speak('Hello World!');
this.emit(':responseReady');

this.response.speak('What would you like to do?')
            .listen('Please say that again?');
this.emit(':responseReady');

:ask/listen と :tell/speak の違いは、:tell/speakアクションの後、ユーザーがさらに入力するのを待つことなくセッションが終了することです。レスポンスを使用する方法と、responseBuilderを使用してレスポンスオブジェクトを作成する方法を、次のセクションで比較します。

ハンドラはリクエストを互いに転送できるので、ハンドラを連鎖することでより良いユーザーフローを実現できます。ここでは、LaunchRequestと (HelloWorldIntentの) IntentRequestが、両方同じ「Hello World」メッセージを返す例を示します。

const handlers = {
    'LaunchRequest': function () {
    	this.emit('HelloWorldIntent');
	},

	'HelloWorldIntent': function () {
    	this.emit(':tell', 'Hello World!');
	}
};

イベントハンドラをセットアップしたら、先ほど作成したalexaオブジェクトのregisterHandlers関数を使って、イベントハンドラを登録する必要があります。そのため、index.jsファイルに次の行を追加します。

const Alexa = require('alexa-sdk');

exports.handler = function(event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.registerHandlers(handlers);
    alexa.execute();
};

一度に複数のハンドラオブジェクトの登録もできます。

alexa.registerHandlers(handlers, handlers2, handlers3, ...);

上の手順を完了すると、スキルがデバイスで正しく動作するはずです。

レスポンス vs ResponseBuilder

現在、Node.js SDKでレスポンスオブジェクトを生成するには、2つの方法があります。最初の方法は、this.emit(:${action}, 'responseContent') の形式にしたがう構文を使用します。一般的なスキルのレスポンスについて、例の完全なリストを次に示します。

レスポンス構文 説明
this.emit(':tell',speechOutput); speechOutputを使ったTell
this.emit(':ask', speechOutput, repromptSpeech); speechOutputrepromptSpeechを使ったAsk
this.emit(':tellWithCard', speechOutput, cardTitle, cardContent, imageObj); speechOutputstandard cardを使ったTell
this.emit(':askWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, imageObj); speechOutput, repromptSpeechstandard cardを使ったAsk
this.emit(':tellWithLinkAccountCard', speechOutput); linkAccount cardを使ったTell, 詳しくはこちらをクリック
this.emit(':askWithLinkAccountCard', speechOutput); linkAccount cardを使ったAsk, 詳しくはこちらをクリック
this.emit(':tellWithPermissionCard', speechOutput, permissionArray); permission cardを使ったTell, 詳しくはこちらをクリック
this.emit(':askWithPermissionCard', speechOutput, repromptSpeech, permissionArray) permission cardを使ったAsk, 詳しくはこちらをクリック
this.emit(':delegate', updatedIntent); dialog modeldelegate directiveを使ったレスポンス
this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech, updatedIntent); dialog modelelicitSlot directiveを使ったレスポンス
this.emit(':elicitSlotWithCard', slotToElicit, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj); carddialog modelelicitSlot directiveを使ったレスポンス
this.emit(':confirmSlot', slotToConfirm, speechOutput, repromptSpeech, updatedIntent); dialog modelconfirmSlot directiveを使ったレスポンス
this.emit(':confirmSlotWithCard', slotToConfirm, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj); carddialog modelconfirmSlot directiveを使ったレスポンス
this.emit(':confirmIntent', speechOutput, repromptSpeech, updatedIntent); dialog modelconfirmIntent directiveを使ったレスポンス
this.emit(':confirmIntentWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj); carddialog modelconfirmIntent directiveを使ったレスポンス
this.emit(':responseReady'); レスポンスが構築された後、Alexaサービスに返される前に呼び出される。saveStateを呼び出す。上書きできる。
this.emit(':saveState', false); this.attributesの内容と現在のハンドラの状態をDynamoDBに保存し、それより前に構築したレスポンスをAlexaサービスに送信する。異なる永続性プロバイダを使用したい場合は、上書きする。2番目の属性はオプションで、保存を強制するために'true'を設定できる。
this.emit(':saveStateError'); 状態の保存中にエラーが発生すると呼び出される。上書きしてエラーを処理する。

独自のレスポンスを手動で作成するには、this.responseを使います。this.responseには一連の関数が含まれており、レスポンスのさまざまなプロパティを設定できます。これを使うことで、Alexa Skills Kitに組み込みのオーディオとビデオプレイヤーの機能を使えます。レスポンスを設定したら、this.emit(':responseReady')を呼び出すだけでAlexaにレスポンスを送れます。this.response内の関数も連鎖可能なので、1行の中で好きなだけ繋げて使えます。responseBuilderを使用してレスポンスを作成する例の完全なリストを次に示します。

レスポンス構文 説明
this.response.speak(speechOutput); 最初の音声出力をspeechOutputに設定する
this.response.listen(repromptSpeech); 再読み上げの音声出力をrepromptSpeechに設定し、shouldEndSessionをfalseにする。この関数が呼び出されないかぎり、this.responseは、shouldEndSessionをtrueに設定する。
this.response.cardRenderer(cardTitle, cardContent, cardImage); レスポンスとしてstandard cardにcardTitle, cardContent, cardImageを追加する
this.response.linkAccountCard(); レスポンスとしてlinkAccount cardを追加する。詳しくはこちらをクリック
this.response.askForPermissionsConsentCard(permissions); レスポンスにperimissionを要求するカードを追加する。詳しくはこちらをクリック
this.response.audioPlayer(directiveType, behavior, url, token, expectedPreviousToken, offsetInMilliseconds);(Deprecated) 提供されたパラメータでレスポンスにAudioPlayer directiveを追加する。
this.response.audioPlayerPlay(behavior, url, token, expectedPreviousToken, offsetInMilliseconds); 提供されたパラメータを使用してAudioPlayer directiveを追加し、ディレクティブタイプとしてAudioPlayer.Playを設定する。
this.response.audioPlayerStop(); AudioPlayer.Stop directiveを追加する
this.response.audioPlayerClearQueue(clearBehavior); AudioPlayer.ClearQueue directiveを追加してディレクティブをクリアする振る舞いを設定する。
this.response.renderTemplate(template); レスポンスにDisplay.RenderTemplate directiveを追加する
this.response.hint(hintText, hintType); レスポンスにHint directiveを追加する
this.response.playVideo(videoSource, metadata); レスポンスにVideoApp.Play directiveを追加する
this.response.shouldEndSession(bool); shouldEndSessionを手動で設定する

レスポンスの設定が完了したら、単にthis.emit(':responseReady')を呼び出してレスポンスを送信してください。いくつかのレスポンスオブジェクトでレスポンスを作成する2つの例を次に示します。

//Example 1
this.response.speak(speechOutput)
            .listen(repromptSpeech);
this.emit(':responseReady');
//Example 2
this.response.speak(speechOutput)
            .cardRenderer(cardTitle, cardContent, cardImage)
            .renderTemplate(template)
            .hint(hintText, hintType);
this.emit(':responseReady');

responseBuilderにはリッチなレスポンスオブジェクトを作成できる柔軟性があるので、このメソッドを使用してレスポンスを構築することをお勧めします。

Tips

  • :ask, :tell, :askWithCardなどのレスポンスイベントのいずれかが出力されたとき、開発者がcallback関数を渡さなければ、Lambdaのcontext.succeed()メソッドが呼び出され、それ以上のバックグラウンドタスクの処理を直ちに停止します。未完了のあらゆる非同期ジョブは完了されず、レスポンスemit構文よりも下の行のコードは実行されません。これは、:saveStateのようなレスポンスを返さないイベントの場合には当てはまりません。
  • インテント「転送」と呼ばれる、ある状態ハンドラから別の状態ハンドラへリクエストを転送するには、this.handler.stateを対象の状態の名前に設定する必要があります。対象の状態が "" の場合は、this.emit("TargetHandlerName")を呼び出します。他の状態のときは、代わりにthis.emitWithState("TargetHandlerName")を呼び出さなければなりません。
  • promptとrepromptの値の内容は、SSMLタグでラップされます。つまり、値に含まれるすべての特殊なXML文字をエスケープする必要があります。たとえば、this.emit(":ask", "I like M&M's")は、そのままだと失敗します。&文字を&amp;にエンコードする必要があるためです。エンコードする必要がある他の文字には、< -> &lt;> -> &gt;があります。

標準のリクエストとレスポンス

Alexaは、HTTPSを使用するリクエスト/レスポンスの仕組みを介して、スキルサービスと通信します。ユーザーがAlexaスキルと対話すると、あなたのサービスはJSONのbodyを含むPOSTリクエストを受け取ります。リクエストbodyには、サービスがロジックを実行してJSON形式のレスポンスを生成するために必要なパラメータが含まれています。Node.jsはJSONをネイティブに処理できるので、Alexa Node.js SDKはJSONのシリアライズとデシリアライズをする必要はありません。開発者は、Alexaがユーザーのリクエストに応答するために、適切なレスポンスオブジェクトを提供する責任だけを持ちます。リクエストbodyのJSON構造に関するドキュメントは、こちらにあります。

SpeechletResponseには、次の属性が含まれます。

  • OutputSpeech
  • Reprompt
  • Card
  • Directiveのリスト
  • shouldEndSession

一例として、音声とカードの両方を含む簡単なレスポンスを次のように構築できます。

const speechOutput = 'Hello world!';
const repromptSpeech = 'Hello again!';
const cardTitle = 'Hello World Card';
const cardContent = 'This text will be displayed in the companion app card.';
const imageObj = {
	smallImageUrl: 'https://imgs.xkcd.com/comics/standards.png',
	largeImageUrl: 'https://imgs.xkcd.com/comics/standards.png'
};
this.response.speak(speechOutput)
            .listen(repromptSpeech)
            .cardRenderer(cardTitle, cardContent, imageObj);
this.emit(':responseReady');

インターフェース

AudioPlayerインターフェース

開発者は、(それぞれの) スキルのレスポンスに次のディレクティブを含めることができます。

  • PlayDirective
  • StopDirective
  • ClearQueueDirective

オーディオのストリームにPlayDirectiveを使用する例を次に示します。

const handlers = {
    'LaunchRequest' : function() {
        const speechOutput = 'Hello world!';
        const behavior = 'REPLACE_ALL';
        const url = 'https://url/to/audiosource';
        const token = 'myMusic';
        const expectedPreviousToken = 'expectedPreviousStream';
        const offsetInMilliseconds = 10000;
        this.response.speak(speechOutput)
                    .audioPlayerPlay(behavior, url, token, expectedPreviousToken, offsetInMilliseconds);
        this.emit(':responseReady');
    }
};

上の例では、AlexaはまずspeechOutputを発声してからオーディオを再生しようとします。

AudioPlayerインターフェースを利用するスキルを構築する場合、playback状態への変更を通知するために、スキルにplaybackリクエストが送信されます。それぞれのイベントに対してハンドラ関数を実装できます。

const handlers = {
    'AudioPlayer.PlaybackStarted' : function() {
    	console.log('Alexa begins playing the audio stream');
    },
    'AudioPlayer.PlaybackFinished' : function() {
    	console.log('The stream comes to an end');
    },
    'AudioPlayer.PlaybackStopped' : function() {
    	console.log('Alexa stops playing the audio stream');
    },
    'AudioPlayer.PlaybackNearlyFinished' : function() {
    	console.log('The currently playing stream is nearly complate and the device is ready to receive a new stream');
    },
    'AudioPlayer.PlaybackFailed' : function() {
    	console.log('Alexa encounters an error when attempting to play a stream');
    }
};

AudioPlayerインターフェースについての追加のドキュメントは、こちらにあります。

注意: imgObjに関する仕様については、こちらを参照してください。

Dialogインターフェース

Dialogインターフェースは、スキルとユーザーの間で複数のターンを持つ会話を管理するディレクティブを提供します。ユーザーの要求を満たすために必要な情報をユーザーに質問するために、このディレクティブを使用できます。Dialog Interfaceスキルビルダーのドキュメントを参照してください。

this.event.request.dialogStateを使って、現在のdialogStateにアクセスできます。

Delegateディレクティブ

ユーザーとの対話で次のターンを処理するコマンドをAlexaに送信します。このディレクティブは、スキルがダイアログモデルを使っていて、ダイアログの現在のステータス (dialogState) がSTARTEDまたはIN_PROGRESSの場合に使用できます。dialogStateCOMPLETEDの場合は、このディレクティブを出力できません。

this.emit(':delegate')を使って、delegate directiveのレスポンスを送ることができます。

const handlers = {
    'BookFlightIntent': function () {
        if (this.event.request.dialogState === 'STARTED') {
            let updatedIntent = this.event.request.intent;
            // Pre-fill slots: デフォルト値を持つスロットの値でインテントオブジェクトを
            // 更新してから、この更新されたインテントで :delegate を送信する。
            updatedIntent.slots.SlotName.value = 'DefaultValue';
            this.emit(':delegate', updatedIntent);
        } else if (this.event.request.dialogState !== 'COMPLETED'){
            this.emit(':delegate');
        } else {
            // すべてのスロットが入力済み (slot/intentの確認を選択した場合、確認がおこなわれる)
            handlePlanMyTripIntent();
        }
    }
};

Elicit Slotディレクティブ

ユーザーに特定のスロットの値を問い合わせるコマンドをAlexaに送信します。slotToElicitに取得するスロット名を指定してください。speechOutputにはユーザーにスロット値を問い合わせる際のpromptを指定します。

Elicit Slot ディレクティブのレスポンスを送信するために、this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech, updatedIntent)、またはthis.emit(':elicitSlotWithCard', slotToElicit, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)を使えます。

this.emit(':elicitSlotWithCard', slotToElicit, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)を使用する場合、updatedIntentimageObjはオプションのパラメータです。これらをnullに設定したり、渡さないこともできます。

const handlers = {
    'BookFlightIntent': function () {
        const intentObj = this.event.request.intent;
        if (!intentObj.slots.Source.value) {
            const slotToElicit = 'Source';
            const speechOutput = 'Where would you like to fly from?';
            const repromptSpeech = speechOutput;
            this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
        } else if (!intentObj.slots.Destination.value) {
            const slotToElicit = 'Destination';
            const speechOutput = 'Where would you like to fly to?';
            const repromptSpeech = speechOutput;
            const cardContent = 'What is the destination?';
            const cardTitle = 'Destination';
            const updatedIntent = intentObj;
            // スキルに送信されたインテントを表わすインテントオプジェクト。
            // このプロパティのセットを使用するか、必要に応じてスロット値と確認状態を変更できる。
            const imageObj = {
                smallImageUrl: 'https://imgs.xkcd.com/comics/standards.png',
                largeImageUrl: 'https://imgs.xkcd.com/comics/standards.png'
            };
            this.emit(':elicitSlotWithCard', slotToElicit, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj);
        } else {
            handlePlanMyTripIntentAllSlotsAreFilled();
        }
    }
};

Confirm Slotディレクティブ

Alexaに特定のスロットの値を確認するコマンドを送信してから、ダイアログを続行します。slotToConfirmで確認するスロット名を指定します。speechOutputにはユーザーに確認を求める際のpromptを指定します。

this.emit(':confirmSlot', slotToConfirm, speechOutput, repromptSpeech, updatedIntent)、またはthis.emit(':confirmSlotWithCard', slotToConfirm, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)を使って、Confirm Slotディレクティブのレスポンスを送信できます。

this.emit(':confirmSlotWithCard', slotToConfirm, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)を使用する場合、updatedIntentimageObjはオプションのパラメータです。これらをnullに設定したり、渡さないこともできます。

const handlers = {
    'BookFlightIntent': function () {
        const intentObj = this.event.request.intent;
        if (intentObj.slots.Source.confirmationStatus !== 'CONFIRMED') {
            if (intentObj.slots.Source.confirmationStatus !== 'DENIED') {
                // スロット値は確認されない
                const slotToConfirm = 'Source';
                const speechOutput = 'You want to fly from ' + intentObj.slots.Source.value + ', is that correct?';
                const repromptSpeech = speechOutput;
                this.emit(':confirmSlot', slotToConfirm, speechOutput, repromptSpeech);
            } else {
                // ユーザーがスロット値の確認を拒否する
                const slotToElicit = 'Source';
                const speechOutput = 'Okay, Where would you like to fly from?';
                this.emit(':elicitSlot', slotToElicit, speechOutput, speechOutput);
            }
        } else if (intentObj.slots.Destination.confirmationStatus !== 'CONFIRMED') {
            if (intentObj.slots.Destination.confirmationStatus !== 'DENIED') {
                const slotToConfirm = 'Destination';
                const speechOutput = 'You would like to fly to ' + intentObj.slots.Destination.value + ', is that correct?';
                const repromptSpeech = speechOutput;
                const cardContent = speechOutput;
                const cardTitle = 'Confirm Destination';
                this.emit(':confirmSlotWithCard', slotToConfirm, speechOutput, repromptSpeech, cardTitle, cardContent);
            } else {
                const slotToElicit = 'Destination';
                const speechOutput = 'Okay, Where would you like to fly to?';
                const repromptSpeech = speechOutput;
                this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech);
            }
        } else {
            handlePlanMyTripIntentAllSlotsAreConfirmed();
        }
    }
};

Confirm Intentディレクティブ

インテントのためにユーザーが提供したすべての情報を確認するコマンドをAlexaに送信してから、スキルがアクションを実行します。speechOutputにユーザーに確認を求める際のpromptを指定します。promptにはユーザーが確認する必要があるすべての値を含めてください。

this.emit(':confirmIntent', speechOutput, repromptSpeech, updatedIntent)、またはthis.emit(':confirmIntentWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)を使って、Confirm Intentディレクティブのレスポンスを送信できます。

this.emit(':confirmIntentWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)を使う場合、updatedIntentimageObjはオプションのパラメータです。これらをnullに設定したり、渡さないこともできます。

const handlers = {
    'BookFlightIntent': function () {
        const intentObj = this.event.request.intent;
        if (intentObj.confirmationStatus !== 'CONFIRMED') {
            if (intentObj.confirmationStatus !== 'DENIED') {
                // インテントは確認されない
                const speechOutput = 'You would like to book flight from ' + intentObj.slots.Source.value + ' to ' +
                intentObj.slots.Destination.value + ', is that correct?';
                const cardTitle = 'Booking Summary';
                const repromptSpeech = speechOutput;
                const cardContent = speechOutput;
                this.emit(':confirmIntentWithCard', speechOutput, repromptSpeech, cardTitle, cardContent);
            } else {
                // ユーザーがインテントの確認を拒否する。スロットの値が正しくない可能性がある。
                handleIntentConfimationDenial();
            }
        } else {
            handlePlanMyTripIntentAllSlotsAndIntentAreConfirmed();
        }
    }
};

Dialogインターフェースについての追加のドキュメントは、こちらにあります。

Displayインターフェース

幅広い表現をサポートするために、AlexaはいくつかのDisplay templatesを提供しています。現在、Display templatesには2つのカテゴリがあります。

  • BodyTemplateは、テキストと選択できないイメージを表示します。現在5つのオプションがあります。
  • BodyTemplate1
  • BodyTemplate2
  • BodyTemplate3
  • BodyTemplate6
  • BodyTemplate7
  • ListTemplateは、項目のスクロールが可能なリストと、それぞれに関連づけられたテキストとオプションの画像を表示します。これらの画像を選択可能にできます。現在2つのオプションがあります。
  • ListTemplate1
  • ListTemplate2

開発者は、スキルのレスポンスにDisplay.RenderTemplateディレクティブを含めなければいけません。 Template Builderは、alexa-sdkのtemplateBuilders名前空間に含まれています。これらは、Display.RenderTemplateディレクティブ用のJSONテンプレートを構築するヘルパーメソッドのセットを提供します。次の例では、BodyTemplate1Builderを使ってBody templateを構築します。

const Alexa = require('alexa-sdk');
// ImageとTextFieldオブジェクトを作成するユーティリティメソッド
const makePlainText = Alexa.utils.TextUtils.makePlainText;
const makeImage = Alexa.utils.ImageUtils.makeImage;

// ...
'LaunchRequest' : function() {
	const builder = new Alexa.templateBuilders.BodyTemplate1Builder();

	const template = builder.setTitle('My BodyTemplate1')
							.setBackgroundImage(makeImage('http://url/to/my/img.png'))
							.setTextContent(makePlainText('Text content'))
							.build();

	this.response.speak('Rendering a body template!')
				.renderTemplate(template);
	this.emit(':responseReady');
}

ImageおよびTextFieldオブジェクトを構築するために、ヘルパーユーティリティメソッドを追加しました。これらは、Alexa.utils名前空間にあります。

const ImageUtils = require('alexa-sdk').utils.ImageUtils;

// 単一のソースで画像をアウトプットする
ImageUtils.makeImage(url, widthPixels, heightPixels, size, description);
/**
Outputs {
    contentDescription : '<description>'
    sources : [
        {
            url : '<url>',
            widthPixels : '<widthPixels>',
            heightPixels : '<heightPixels>',
            size : '<size>'
        }
    ]
}
*/

ImageUtils.makeImages(imgArr, description);
/**
Outputs {
    contentDescription : '<description>'
    sources : <imgArr> // array of {url, size, widthPixels, heightPixels}
}
*/


const TextUtils = require('alexa-sdk').utils.TextUtils;

TextUtils.makePlainText('my plain text field');
/**
Outputs {
    text : 'my plain text field',
    type : 'PlainText'
}
*/

TextUtils.makeRichText('my rich text field');
/**
Outputs {
    text : 'my rich text field',
    type : 'RichText'
}
*/

次の例では、ListTemplate1BuilderとListItemBuilderを使用してListTemplate1を構築します。

const Alexa = require('alexa-sdk');
const makePlainText = Alexa.utils.TextUtils.makePlainText;
const makeImage = Alexa.utils.ImageUtils.makeImage;
// ...
'LaunchRequest' : function() {
    const itemImage = makeImage('https://url/to/imageResource', imageWidth, imageHeight);
    const listItemBuilder = new Alexa.templateBuilders.ListItemBuilder();
    const listTemplateBuilder = new Alexa.templateBuilders.ListTemplate1Builder();
    listItemBuilder.addItem(itemImage, 'listItemToken1', makePlainText('List Item 1'));
    listItemBuilder.addItem(itemImage, 'listItemToken2', makePlainText('List Item 2'));
    listItemBuilder.addItem(itemImage, 'listItemToken3', makePlainText('List Item 3'));
    listItemBuilder.addItem(itemImage, 'listItemToken4', makePlainText('List Item 4'));
    const listItems = listItemBuilder.build();
    const listTemplate = listTemplateBuilder.setToken('listToken')
    										.setTitle('listTemplate1')
    										.setListItems(listItems)
    										.build();
    this.response.speak('Rendering a list template!')
    			.renderTemplate(listTemplate);
    this.emit(':responseReady');
}

Display.RenderTemplateディレクティブを (echoのような) 画面表示機能の無いデバイスに送ると、無効なディレクティブのエラーが投げられます。デバイスが特定のディレクティブをサポートしているかを確認するには、デバイスのsupportedInterfacesプロパティを確認します。

const handler = {
    'LaunchRequest' : function() {

    this.response.speak('Hello there');

    // Display.RenderTemplateディレクティブをレスポンスに追加できる
    if (this.event.context.System.device.supportedInterfaces.Display) {
        //... TemplateBuilderを使用してmytemplateを構築する
        this.response.renderTemplate(myTemplate);
    }

    this.emit(':responseReady');
    }
};

ビデオの場合も同様に、VideoAppインターフェイスがデバイスでサポートされているかどうかをチェックします。

const handler = {
    'PlayVideoIntent' : function() {

    // VideoApp.Playディレクティブをレスポンスに追加できる
    if (this.event.context.System.device.supportedInterfaces.VideoApp) {
        this.response.playVideo('http://path/to/my/video.mp4');
    } else {
        this.response.speak("The video cannot be played on your device. " +
        "To watch this video, try launching the skill from your echo show device.");
    }

        this.emit(':responseReady');
    }
};

Displayインターフェースに関する追加のドキュメントは、こちらにあります。

Playback Controllerインターフェース

PlaybackControllerインターフェースは、ユーザーがデバイスのボタンやリモコンのようなプレイヤーコントロールとやり取りするときに送信されるリクエストを、スキルで処理できるようにします。これらのリクエストは、意図を表わすリクエストとして標準的である「アレクサ、次の曲」のような通常の音声による要求と異なります。スキルにPlaybackControllerリクエストを処理させるには、開発者がAlexa Node.js SDKでPlaybackControllerインターフェースを実装しなければいけません。

const handlers = {
    'PlaybackController.NextCommandIssued' : function() {
        //スキルは、あらゆるAudioPlayerディレクティブでNextCommandIssuedに応答できる。
    },
    'PlaybackController.PauseCommandIssued' : function() {
        //スキルは、あらゆるAudioPlayerディレクティブでPauseCommandIssuedに応答できる。
    },
    'PlaybackController.PlayCommandIssued' : function() {
        //スキルは、あらゆるAudioPlayerディレクティブでPlayCommandIssuedに応答できる。
    },
    'PlaybackController.PreviousCommandIssued' : function() {
        //スキルは、あらゆるAudioPlayerディレクティブでPreviousCommandIssuedに応答できる。
    },
    'System.ExceptionEncountered' : function() {
        //スキルは、System.ExceptionEncounteredにレスポンスを返せない。
    }
};

PlaybackControllerインターフェースについての追加のドキュメントは、こちらにあります。

VideoAppインターフェース

Echo Showでネイティブビデオファイルをストリーミングするには、開発者はVideoApp.Launchディレクティブを送信する必要があります。Alexa Node.js SDKは、responseBuilderにJSONレスポンスオブジェクトを構築するための関数を提供します。 ビデオをストリーミングする例を次に示します。

//...
'LaunchRequest' : function() {
    const videoSource = 'https://url/to/videosource';
    const metadata = {
    	'title': 'Title for Sample Video',
    	'subtitle': 'Secondary Title for Sample Video'
    };
    this.response.playVideo(videoSource metadata);
    this.emit(':responseReady');
}

VideoAppインターフェースに関する追加のドキュメントは、こちらにあります。

SkillとList Events

Alexaスキルイベントにスキルがサブスクライブすると、イベントが発生した時にスキルは通知を受けます。

イベントをスキルがサブスクライブするには、SMAPIでスキルにイベントを追加するに書かれている説明にしたがって、Alexaスキル管理API (SMAPI) へのアクセスを設定する必要があります。

あなたのスキルがこれらのイベントを受け取るように設定されたら、イベントが発生した時にスキルがイベントを受信するようになります。デフォルトのイベントハンドラにイベント名を追加することで、動作を指定できます。

const handlers = {
    'AlexaSkillEvent.SkillEnabled' : function() {
        const userId = this.event.context.System.user.userId;
        console.log(`skill was enabled for user: ${userId}`);
    },
    'AlexaHouseholdListEvent.ItemsCreated' : function() {
        const listId = this.event.request.body.listId;
        const listItemIds = this.event.request.body.listItemIds;
        console.log(`The items: ${JSON.stringify(listItemIds)} were added to list ${listId}`);
    },
    'AlexaHouseholdListEvent.ListCreated' : function() {
        const listId = this.event.request.body.listId;
        console.log(`The new list: ${JSON.stringify(listId)} was created`);
    }
    //...
};

exports.handler = function(event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.registerHandlers(handlers);
    alexa.execute();
};

スキルイベントをサブスクライブするプロセスを説明するためにサンプルスキルとウォークスルーを作成しました。

サービス

Device Addressサービス

Alexa NodeJS SDKは、Device Address APIを利用してユーザーのデバイスアドレス情報を取得するDeviceAddressServiceヘルパークラスを提供します。現在、次の方法が提供されています。

getFullAddress(deviceId, apiEndpoint, token)
getCountryAndPostalCode(deviceId, apiEndpoint, token)

リクエストのthis.event.context.System.apiEndpointthis.event.context.System.user.permissions.consentTokenから、パラメータapiEndpointtokenをそれぞれ取得できます。

また、deviceIdはリクエストのthis.event.context.System.device.deviceIdから取得できます。

const Alexa = require('alexa-sdk');

'DeviceAddressIntent': function () {
    if (this.event.context.System.user.permissions) {
        const token = this.event.context.System.user.permissions.consentToken;
        const apiEndpoint = this.event.context.System.apiEndpoint;
        const deviceId = this.event.context.System.device.deviceId;

        const das = new Alexa.services.DeviceAddressService();
        das.getFullAddress(deviceId, apiEndpoint, token)
        .then((data) => {
            this.response.speak('<address information>');
            console.log('Address get: ' + JSON.stringify(data));
            this.emit(':responseReady');
        })
        .catch((error) => {
            this.response.speak('I\'m sorry. Something went wrong.');
            this.emit(':responseReady');
            console.log(error.message);
        });
    } else {
        this.response.speak('Please grant skill permissions to access your device address.');
        this.emit(':responseReady');
    }
}

List Managementサービス

Alexaのユーザーは、Alexa to-doとAlexaショッピングの2つのデフォルトリストにアクセスできます。さらに、Alexaのユーザーは、リストをサポートするスキルでカスタムリストを作成し、管理できます。

Alexa NodeJS SDKは、開発者がAlexaのデフォルトリストとカスタムリストをより簡単に管理するスキルを作成できるように、ListManagementServiceヘルパークラスを提供します。現在、次のメソッドが提供されています。

getListsMetadata(token)
createList(listObject, token)
getList(listId, itemStatus, token)
updateList(listId, listObject, token)
deleteList(listId, token)
createListItem(listId, listItemObject, token)
getListItem(listId, itemId, token)
updateListItem(listId, itemId, listItemObject, token)
deleteListItem(listId, itemId, token)

tokenは、リクエストのthis.event.context.System.user.permissions.consentTokenから取得できます。

listIdは、GetListsMetadataを呼び出すことで取得できます。 itemIdは、GetListを呼び出すことで取得できます。

const Alexa = require('alexa-sdk');

function getListsMetadata(token) {
    const lms = new Alexa.services.ListManagementService();
    lms.getListsMetadata(token)
    .then((data) => {
        console.log('List retrieved: ' + JSON.stringify(data));
        this.context.succeed();
    })
    .catch((error) => {
        console.log(error.message);
    });
};

ディレクティブサービス

enqueue(directive, endpoint, token)

スキルの実行中に、Alexaデバイスへディレクティブを非同期に返します。現在のところ、SSML (MP3オーディオを含む) とプレーンテキスト出力形式の両方がサポートされているspeakディレクティブだけを受け付けます。ディレクティブは、スキルがアクティブな場合にだけ、元のデバイスに返すことができます。リクエストのthis.event.context.System.apiEndpointthis.event.context.System.apiAccessTokenから、パラメータapiEndpointtokenをそれぞれ取得できます。

  • レスポンスの音声は、600文字以内に制限する必要があります。
  • SSMLで参照されるすべてのオーディオスニペットは、30秒以内に制限する必要があります。
  • スキルがディレクティブサービスを通して送信できるディレクティブの数に制限はありません。必要に応じて、スキルは実行ごとに複数のリクエストを送信できます。
  • ディレクティブサービスには重複排除の処理が含まれておらず、ユーザが同じディレクティブを複数回受け取る可能性があるため、リトライ処理はおすすめしません。
const Alexa = require('alexa-sdk');

const handlers = {
    'SearchIntent' : function() {
        const requestId = this.event.request.requestId;
        const token = this.event.context.System.apiAccessToken;
        const endpoint = this.event.context.System.apiEndpoint;
        const ds = new Alexa.services.DirectiveService();

        const directive = new Alexa.directives.VoicePlayerSpeakDirective(requestId, "Please wait...");
        const progressiveResponse = ds.enqueue(directive, endpoint, token)
        .catch((err) => {
            // APIエラーを捕捉してスキルの処理を続ける
        });
        const serviceCall = callMyService();

        Promise.all([progressiveResponse, serviceCall])
        .then(() => {
            this.response.speak('I found the following results');
            this.emit(':responseReady');
        });
    }
};

機能を拡張する

スキルの状態管理

alexa-sdkは、入ってきたインテントを状態マネージャーを使用して正しい関数ハンドラにルーティングします。状態は、スキルの現在の状態を示す文字列としてセッション属性に格納されます。インテントハンドラを定義するときに状態文字列をインテント名に追加することで、ビルトインのインテントルーティングをエミュレートできますが、alexa-sdkはそれをするのに役立ちます。

SDKでの状態管理の仕組みを説明する例として、サンプルスキルhighlowgameを見てみましょう。このスキルで、ユーザーは数字を推測して、Alexaはその数字が大きいか低いかを伝えます。また、ユーザーが何回プレイしたかも伝えます。このスキルは、'start'と'guess'の2つの状態を持っています。

const states = {
	GUESSMODE: '_GUESSMODE', // ユーザーは数字を推測する。
	STARTMODE: '_STARTMODE' // ユーザーにゲームの開始または再開をうながす。
};

newSessionHandlersのNewSessionハンドラは、スキルが受け取ったインテントまたは起動要求のハンドラに先立って実行されます。

const newSessionHandlers = {
    'NewSession': function() {
        if(Object.keys(this.attributes).length === 0) { // スキルが初めて呼び出されたのかを確認する
            this.attributes['endedSessionCount'] = 0;
            this.attributes['gamesPlayed'] = 0;
        }
        this.handler.state = states.STARTMODE;
        this.response.speak('Welcome to High Low guessing game. You have played '
                        + this.attributes['gamesPlayed'].toString() + ' times. Would you like to play?')
                    .listen('Say yes to start the game or no to quit.');
        this.emit(':responseReady');
    }
};

新しいセッションが開始された時、this.handler.stateSTARTMODEに設定するだけでスキルの状態がスキルのセッション属性に自動的に保持されます。オプションで、DynamoDBテーブルを設定すれば、セッションをまたいで保持されます。

NewSessionは素晴らしいcatch-allの動作をする良いエントリーポイントですが、必須ではないことに留意してください。NewSessionは、その名前を持つハンドラが定義されている場合にだけ呼び出されます。ビルトインの永続化を使用していれば、定義した各状態毎に独自のNewSessionハンドラを持つことができます。上の例では、柔軟性を持たせるために、states.STARTMODEstates.GUESSMODEの両方に異なるNewSessionの動作を定義しました。

スキルのさまざまな状態に対応するインテントを定義するには、Alexa.CreateStateHandler関数を使用する必要があります。ここに定義されたインテントハンドラは、スキルが特定の状態にある時だけ機能することで、さらに大きな柔軟性をもたらします。

たとえば、上で定義したGUESSMODE状態の時に、質問に応答しているユーザーに対処したいとします。これは、次のようにStateHandlerを使って実現できます。

const guessModeHandlers = Alexa.CreateStateHandler(states.GUESSMODE, {

'NewSession': function () {
    this.handler.state = '';
    this.emitWithState('NewSession'); // STARTMODEモードのNewSessionハンドラと同等
},

'NumberGuessIntent': function() {
    const guessNum = parseInt(this.event.request.intent.slots.number.value);
    const targetNum = this.attributes['guessNumber'];

    console.log('user guessed: ' + guessNum);

    if(guessNum > targetNum){
        this.emit('TooHigh', guessNum);
    } else if( guessNum < targetNum){
        this.emit('TooLow', guessNum);
    } else if (guessNum === targetNum){
        // コールバックでアロー関数を使うことで、正しい'this'コンテキストを保持する
        this.emit('JustRight', () => {
            this.response.speak(guessNum.toString() + 'is correct! Would you like to play a new game?')
                        .listen('Say yes to start a new game, or no to end the game.');
            this.emit(':responseReady');
        });
    } else {
        this.emit('NotANum');
    }
},

'AMAZON.HelpIntent': function() {
    this.response.speak('I am thinking of a number between zero and one hundred, try to guess and I will tell you' +
    ' if it is higher or lower.')
                .listen('Try saying a number.');
    this.emit(':responseReady');
},

'SessionEndedRequest': function () {
    console.log('session ended!');
    this.attributes['endedSessionCount'] += 1;
    this.emit(':saveState', true); // :saveStateを呼んで、セッション属性をDynamoDBに保持する
},

'Unhandled': function() {
    this.response.speak('Sorry, I didn\'t get that. Try saying a number.')
                .listen('Try saying a number.');
    this.emit(':responseReady');
}
});

一方で、スキルの状態がSTARTMODEの場合のStateHandlerは以下のように定義することができます:

const startGameHandlers = Alexa.CreateStateHandler(states.STARTMODE, {

    'NewSession': function () {
        this.emit('NewSession'); // newSessionHandlers内のハンドラを使う
    },

    'AMAZON.HelpIntent': function() {
        const message = 'I will think of a number between zero and one hundred, try to guess and I will tell you if it' +
        ' is higher or lower. Do you want to start the game?';
        this.response.speak(message)
                    .listen(message);
        this.emit(':responseReady');
    },

    'AMAZON.YesIntent': function() {
        this.attributes['guessNumber'] = Math.floor(Math.random() * 100);
        this.handler.state = states.GUESSMODE;
        this.response.speak('Great! ' + 'Try saying a number to start the game.')
                    .listen('Try saying a number.');
        this.emit(':responseReady');
    },

    'AMAZON.NoIntent': function() {
        this.response.speak('Ok, see you next time!');
        this.emit(':responseReady');
    },

    'SessionEndedRequest': function () {
        console.log('session ended!');
        this.attributes['endedSessionCount'] += 1;
        this.emit(':saveState', true);
    },

    'Unhandled': function() {
        const message = 'Say yes to continue, or no to end the game.';
        this.response.speak(message)
                    .listen(message);
        this.emit(':responseReady');
    }
});

この状態で 'yes'または'no'という応答には意味がないため、AMAZON.YesIntentAMAZON.NoIntentguessModeHandlersオブジェクトの中に定義されていないことを確認してください。それらのインテントは、Unhandledハンドラによって捕捉されます。

また、両方の状態でNewSessionUnhandledの動作が違うことに気づいたでしょうか。このゲームでは、newSessionHandlersオブジェクトに定義されたNewSessionハンドラを呼び出すことで、状態を'reset'します。この定義を省略することもできます。そうした場合、alexa-sdkは、現在の状態のインテントハンドラを呼び出します。alexa.execute()を呼び出す前に、状態ハンドラを登録することを忘れないでください。登録を忘れると、状態ハンドラが見つけられなくなります。

セッションを終了すると属性は自動的に保存されますが、ユーザーがセッションを終了した場合は強制的に保存するために:saveStateイベント (this.emit(':saveState', true)) を発行しなければいけません。ユーザーが「中止」と言ったりタイムアウトしたりしてセッションを終了した時に呼び出されるSessionEndedRequestハンドラで、これを行う必要があります。上の例を見てください。

明示的に状態をリセットしたい場合は、次のコードが有効です。

this.handler.state = '' // this.handler.stateの削除は、参照エラーを引き起こす可能性がある
delete this.attributes['STATE'];

DynamoDBによるスキル属性の保持

セッション属性値を後で使うためにストレージに保存したいと思う場合が多いでしょう。alexa-sdkはAmazon DynamoDB (NoSQLデータベースサービス) と直接統合されているため、たった1行のコードでこれを実現できます。

alexa.executeを呼び出す前に、DynamoDBテーブルの名前をalexaオブジェクトに設定するだけです。

exports.handler = function (event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.appId = appId;
    alexa.dynamoDBTableName = 'YourTableName'; // これだけでOK!
    alexa.registerHandlers(State1Handlers, State2Handlers);
    alexa.execute();
};

その後、値を設定するには、alexaオブジェクトのattributesプロパティを単に呼び出します。もうput関数やget関数は不要です!

this.attributes['yourAttribute'] = 'value';

事前にテーブルを手動で作成するか、Lambda関数にDynamoDBのテーブル作成を許可することで、自動的に実行されます。最初の呼び出しで、テーブルが作成されるまでに1〜2分かかることだけを覚えておいてください。手動でテーブルを作成する場合、主キーは"userId"という文字列値でなければいけません。

注意: Lambdaでスキルをホストし、DynamoDBでスキル属性を保持することを選んだ場合は、Lambda関数の実行ロールにDynamoDBへのアクセス権限を含めてください。

スキルへの多言語対応の追加

ここでHello Worldの例を見てみましょう。ユーザー対応するすべての言語の文字列を次の形式で定義します。

const languageStrings = {
    'en-GB': {
        'translation': {
            'SAY_HELLO_MESSAGE' : 'Hello World!'
        }
    },
    'en-US': {
        'translation': {
            'SAY_HELLO_MESSAGE' : 'Hello World!'
        }
    },
    'de-DE': {
        'translation': {
            'SAY_HELLO_MESSAGE' : 'Hallo Welt!'
        }
    }
};

alexa-sdkで文字列の国際化機能を有効にするには、上で作成したオブジェクトにリソースを設定します。

exports.handler = function(event, context, callback) {
    const alexa = Alexa.handler(event, context);
    alexa.appId = appId;
    // 文字列の国際化 (i18n) 機能を有効にするため、リソースオブジェクトを設定する。
    alexa.resources = languageStrings;
    alexa.registerHandlers(handlers);
    alexa.execute();
};

言語文字列の定義と有効化を完了したら、this.t()関数を使ってこれらの文字列にアクセスできます。文字列は、入ってきたリクエストのlocaleと一致する言語で表示されます。

const handlers = {
    'LaunchRequest': function () {
        this.emit('SayHello');
    },
    'HelloWorldIntent': function () {
        this.emit('SayHello');
    },
    'SayHello': function () {
        this.response.speak(this.t('SAY_HELLO_MESSAGE'));
        this.emit(':responseReady');
    }
};

多言語でのスキルの開発とデプロイについて、さらに詳しい情報はこちらを参照してください。

Device IDのサポート

ユーザーがAlexaスキルを有効化すると、スキルは、ユーザーのAlexaデバイスに関連づけられたアドレスデータを使用するためのユーザー許可を取得できます。このアドレスデータを使用して、スキルに重要な機能を提供したり、ユーザー体験を強化したりできます。

deviceIdは、各リクエストのコンテキストオブジェクトを通して公開され、任意のインテントハンドラからthis.event.context.System.device.deviceIdを通してアクセスできます。スキルでユーザーのデバイスアドレスを使うためにdeviceIdとAddress APIを利用する方法については、Address API サンプルスキルを参照してください。

Speechcons (感嘆詞)

Speechconsは、Alexaがより表現力豊かに発音する特殊な単語とフレーズです。出力するテキストにSSMLマークアップを入れるだけで使えます。

  • this.emit(':tell', 'あなたが教えてくれたAlexaスキルを見ると、わたしはときどき <say-as interpret-as="interjection">やれやれ</say-as> と言わざるをえません。');
  • this.emit(':tell', '<say-as interpret-as="interjection">いらっしゃいませ</say-as><break time="1s"/> これはただの一例です。');

開発環境のセットアップ

  • 要件
  • Gulp と mocha npm install -g gulp mocha
  • npm installを実行して必要なものを用意する
  • テストやlintを実行するためにgulpを実行する

Alexa Skills Kitを開始する方法の詳細は、次の追加のドキュメントを参照してください。

Alexa Dev Chat Podcast

Alexa Training with Big Nerd Ranch

Alexa Skills Kit (ASK)

Alexa Developer Forums

Training for the Alexa Skills Kit

-Dave ( @TheDaveDev)