このポストは alexa-skills-kit-sdk-for-nodejsについてのものだが、V2がリリースされ、すでに過去のバージョンについてのものになった。バージョンが上がり、SDKの機能も実装スタイルも変わったので、リポジトリのV2のドキュメントの日本語訳を参考にされるのがよいと思う。
v1についてのドキュメントを参照するならば、
2018年3月、リポジトリに日本語版が追加されたのでそちらを参照いただきたい。
以下は私がalexa-skills-kit-sdk-for-nodejs/Readme.mdをざっくり訳したもの。v1のものである。
Overview
Alexa SDKチームは新しいAlexa Node.js SDKをリリースする。これは開発者による開発者のためのオープンソースのSDKだ。
Alexa Skill Kit、Node.jsそしてLambdaの組み合わせは最も人気あるAlexaスキル開発のソフトウェアスタックだ。
イベントドリブンでノンブロッキングなI/Oを持つNode.jsはAlexaスキルによく合っており、Node.jsはオープンソースライブラリの巨大なエコシステムでもある。加えて、AWS Lambdaは100万リクエスト/月が無料でほとんどの開発者にとって十分な無料枠だ。また、Alexaトリガーは信頼できるので、LambdaのSSL証明書の管理もいらない。
AWS Lambdaと Node.js、Alexa Skills Kitを使ったAlexaスキルのセットアップはシンプルだ。しかもコードも少なくて済む。Alexa SDKチームはAlexa Skills Kit SDK specifically for Node.js を共通の問題を簡単に実装し、スキルのロジックに注力できるように作った。
新しいalexa-sdkを使えば、早くそして、不要な複雑さを避けた開発ができる。
SDKは次の特徴がある。
- npmパッケージなのでどんなNode.js環境にも容易にデプロイできる
- 組み込みイベントを使ってAlexaのレスポンスを生成できる
- 新規セッションと未処理イベントのためのキャッチオールイベント
- インテント処理ベースのインベントマシーンを作るヘルパ関数
- 属性をDynamoDBに永続化できるシンプルな設定
- すべての音声出力は自動的にSSMLでラップされる
-
this.event
とthis.context
を通じて、Lambdaのイベントとコンテキストも利用できる - 組み込み関数をオーバーライドして状態管理やレスポンスの生成を柔軟に変更できる。たとえば、S3に状態を保存することも可能だ。
Setup Guide
alexa-sdkはGithubからすぐに利用できる。次のコマンドでインストールできる。
npm install --save alexa-sdk
Getting Started: Writing a Hello World Skill
Basic Project Structure
HelloWorldスキルでは次が必要。
- スキルのエントリーポイント。
- すべての必要なパッケージ、イベントの受信、appIdの設定、dynamoDBテーブルの設定、ハンドラの登録などが含まれる。
- それぞれのリクエストを扱うハンドラ関数
Set Entry Point
上記のため、次の内容で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 developer consoleでスキルを作ると発行されるskill id
alexa.execute();
};
これで、alexa-sdkをインポートして、Alexaオブジェクトを生成できる。
Implement Handler Functions
次に必要なのは、スキルのイベントとインテントを処理すること。Alexa-sdkを使えばインテントを簡単に扱える。
ハンドラ関数をindex.jsに実装すればよい。あるいは別のファイルに実装してインポートしても良い。
例えば、HelloWorldIntent
ハンドラを作るには2つの実装方法がある。
const handlers = {
'HelloWorldIntent' : function() {
//emit response directly
this.emit(':tell', 'Hello World!');
}
};
あるいは次の様にも書ける。
const handlers = {
'HelloWorldIntent' : function() {
//build response first using responseBuilder and then emit
this.response.speak('Hello World!');
this.emit(':responseReady');
}
};
Alexa-sdk では responseBuilder
の speak
/listen
で生成する発話レスポンスオブジェクトを 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
がユーザのインプットの待受をやめてセッションを終了するということだ。
次節でレスポンスオブジェクトを作る場合の2つの方法の違いについて比較する。
ハンドラは互いにリクエストを送り合うことができる。ハンドラのチェーンも可能だ。
次の例では LaunchRequest
と HelloWorldIntent
が同じ'Hello World'を返す。
const handlers = {
'LaunchRequest': function () {
this.emit('HelloWorldIntent');
},
'HelloWorldIntent': function () {
this.emit(':tell', 'Hello World!');
}
};
イベントハンドラーを作ったら、それを registerHandlers
でAlexaオブジェクトに登録する必要がある。
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, ...);
ここまでのステップを終えれば、スキルはデバイスで動作する。
Response vs ResponseBuilder
Node.js SDK ではresponse objectsを生成する2つの方法がある。
一つ目の方法は this.emit(:${action}, 'responseContent')
だ。
以下に一般的なスキルのレスポンスの完全なリストの例を示す。
Response Syntax | Description |
---|---|
this.emit(':tell',speechOutput); | Tell with speechOutput |
this.emit(':ask', speechOutput, repromptSpeech); | Ask with speechOutput and repromptSpeech |
this.emit(':tellWithCard', speechOutput, cardTitle, cardContent, imageObj); | Tell with speechOutput and standard card |
this.emit(':askWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, imageObj); | Ask with speechOutput, repromptSpeech and standard card |
this.emit(':tellWithLinkAccountCard', speechOutput); | Tell with linkAccount card, for more information, click here |
this.emit(':askWithLinkAccountCard', speechOutput); | Ask with linkAccount card, for more information, click here |
this.emit(':tellWithPermissionCard', speechOutput, permissionArray); | Tell with permission card, for more information, click here |
this.emit(':askWithPermissionCard', speechOutput, repromptSpeech, permissionArray) | Ask with permission card, for more information, click here |
this.emit(':delegate', updatedIntent); | Response with delegate directive in dialog model |
this.emit(':elicitSlot', slotToElicit, speechOutput, repromptSpeech, updatedIntent); | Response with elicitSlot directive in dialog model |
this.emit(':elicitSlotWithCard', slotToElicit, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj); | Response with card and elicitSlot directive in dialog model |
this.emit(':confirmSlot', slotToConfirm, speechOutput, repromptSpeech, updatedIntent); | Response with confirmSlot directive in dialog model |
this.emit(':confirmSlotWithCard', slotToConfirm, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj); | Response with card and confirmSlot directive in dialog model |
this.emit(':confirmIntent', speechOutput, repromptSpeech, updatedIntent); | Response with confirmIntent directive in dialog model |
this.emit(':confirmIntentWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj); | Reponse with card and confirmIntent directive in dialog model |
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 を使う場合のレスポンス作成例のリストだ。
Response Syntax | Description |
---|---|
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 and 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を追加し、 directiveのclearBehaviorをセットする |
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
などを発行するとき、Lambda関数はcontext.succeed()
を実行する。callback
関数を渡していなければ、バックグラウンドのタスクの処理がすぐに停止する。終了していない非同期のジョブは完了せず、その後のコードも実行されない。:saveState
のような応答のないイベントは除く。 -
リクエストをある状態から他の状態に「送る」ことをインテントのフォワーディングといい、
this.handler.state
に送り先の状態をセットする。そして、もしターゲットの状態が””なら、this.emit("TargetHandlerName")
を実行する。他の状態ならthis.emitWithState("TargetHandlerName")
を実行する。 -
prompt と reprompt の値はSSMLのタグで囲われる。XMLとして意味を持つ文字はエスケープしておく必要がある。例えば、
this.emit(":ask", "I like M&M's")
を実行するなら、&
は&
にエスケープしないとエラーになる。<
を<
,>
を>
とエスケープするのも同様。
Standard Request and Response
Alexa は HTTPS でスキルサービスと通信する。ユーザがAlexaスキルを実行すると スキルサービスは HTTP POSTでJSONを受け取る。リクエストにはロジックを動かし、JSONのレスポンスを返すのに必要なパラメータが含まれる。Node.jsがネイティブにJSONを処理できるのでAlexa Node.js SDKではJSONのシリアライゼーション処理が不要となっている。Alexaがユーザの要求に応えられるよう適切なレスポンスを返さねばならない。リクエストのJSONの仕様はこちらを参照.
レスポンスには次の属性を含む必要がある。
- OutputSpeech
- Reprompt
- Card
- List of Directives
- 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');
Interfaces
AudioPlayer Interface
スキルのレスポンスに次のディレクティブをそれぞれ含めることができる。
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');
}
};
上の例では最初にspeechOutput
を発話してから音声再生を試みる。
AudioPlayerインターフェースを使ったスキルを開発するとき、再生状態を変更するために playback
リクエストを送信する。それぞれのイベントに対応したハンドラ関数を実装することができる。
const handlers = {
'PlaybackStarted' : function() {
console.log('Alexa begins playing the audio stream');
},
'PlaybackFinished' : function() {
console.log('The stream comes to an end');
},
'PlaybackStopped' : function() {
console.log('Alexa stops playing the audio stream');
},
'PlaybackNearlyFinished' : function() {
console.log('The currently playing stream is nearly complate and the device is ready to receive a new stream');
},
'PlaybackFailed' : function() {
console.log('Alexa encounters an error when attempting to play a stream');
}
};
AudioPlayer
インターフェースのドキュメントはこちら
注意: imgObj
に関する仕様はこちら
Dialog Interface
Dialog
インターフェースはAlexaとユーザのマルチターン会話を管理するディレクティブを提供する。ユーザのリクエストに応えるための情報を尋ねるのに使える。ダイアログディレクティブについて詳しくはDialog InterfaceとSkill Editorを参照のこと。
現在のdialogState
を見るにはthis.event.request.dialogState
を参照する。
Delegate Directive
ユーザーとのダイアログで次のターンを処理するコマンドをAlexaに送信する。スキルがダイアログモデルを実装しており、ダイアログの状態(dialogState
)がSTARTED
か IN_PROGRESS
なら、このディレクティブが使える。もし dialogState
が COMPLETED
ならこのディレクティブを発行できない。
this.emit(':delegate')
を使うとディレクティブの応答をAlexaに任せることができる
const handlers = {
'BookFlightIntent': function () {
if (this.event.request.dialogState === 'STARTED') {
let updatedIntent = this.event.request.intent;
// Pre-fill slots: update the intent object with slot values for which
// you have defaults, then emit :delegate with this updated intent.
updatedIntent.slots.SlotName.value = 'DefaultValue';
this.emit(':delegate', updatedIntent);
} else if (this.event.request.dialogState !== 'COMPLETED'){
this.emit(':delegate');
} else {
// All the slots are filled (And confirmed if you choose to confirm slot/intent)
handlePlanMyTripIntent();
}
}
};
Elicit Slot Directive
あるスロットの値をユーザから聞き出すようをAlexaに指示する。slotToElicit
で聞き出すスロットの名前を指定する。スロットの値を聞き出すためのフレーズをspeechOutput
に指定する。
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)
を使う場合、updatedIntent
と imageObj
は任意で指定する(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;
// An intent object representing the intent sent to your skill.
// You can use this property set or change slot values and confirmation status if necessary.
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 Directive
これはAlexaにあるスロットの値の値を確認してからダイアログを続行するように指示する。slotToConfirm
で確認するスロットの名前を指定する。ユーザに確認する際の台詞はspeechOutput
で指定する。
this.emit(':confirmSlot', slotToConfirm, speechOutput, repromptSpeech, updatedIntent)
または this.emit(':confirmSlotWithCard', slotToConfirm, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)
を使って、スロットを確認するように指示する。
this.emit(':confirmSlotWithCard', slotToConfirm, speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)
を使う場合、updatedIntent
と imageObj
は任意だ。null
を渡すか、何も渡さなければ指定しないことになる。
const handlers = {
'BookFlightIntent': function () {
const intentObj = this.event.request.intent;
if (intentObj.slots.Source.confirmationStatus !== 'CONFIRMED') {
if (intentObj.slots.Source.confirmationStatus !== 'DENIED') {
// Slot value is not confirmed
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 {
// Users denies the confirmation of slot value
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 Directive
これはAlexaにスキルの処理実行前にすべての情報を確認するためのものだ。speechOutput
でユーザへの問いかけを指定する。ユーザは問いかけのすべての値を確認する必要がある。
this.emit(':confirmIntent', speechOutput, repromptSpeech, updatedIntent)
または
this.emit(':confirmIntentWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)
を使う。
this.emit(':confirmIntentWithCard', speechOutput, repromptSpeech, cardTitle, cardContent, updatedIntent, imageObj)
では、updatedIntent
and imageObj
は任意で指定する。
const handlers = {
'BookFlightIntent': function () {
const intentObj = this.event.request.intent;
if (intentObj.confirmationStatus !== 'CONFIRMED') {
if (intentObj.confirmationStatus !== 'DENIED') {
// Intent is not confirmed
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 {
// Users denies the confirmation of intent. May be value of the slots are not correct.
handleIntentConfimationDenial();
}
} else {
handlePlanMyTripIntentAllSlotsAndIntentAreConfirmed();
}
}
};
Dialog
インターフェースについてのドキュメントはこちら
Display Interface
Alexa provides several Display templates
to support a wide range of presentations. Currently, there are two categories of Display templates
:
Alexaは、さまざまなプレゼンテーションをサポートするため、いくつかのDisplay templates
を提供する。現状、Display templates
には2つのカテゴリーがある。
-
BodyTemplate
はテキストを画像を表示する。それらは選択的には表示できない。現状、4つのオプションがある。BodyTemplate1
BodyTemplate2
BodyTemplate3
BodyTemplate6
BodyTemplate7
-
ListTemplate
はスクロールできるリストを表示する。それぞれ、テキストと任意で画像を表示できる。現状、2つのオプションがある。ListTemplate1
ListTemplate2
Developers must include Display.RenderTemplate
directive in their skill responses.
Template Builders are now included in alexa-sdk in the templateBuilders namespace. These provide a set of helper methods to build the JSON template for the Display.RenderTemplate
directive. In the example below we use the BodyTemplate1Builder
to build the Body template
.
Display.RenderTemplate
ディレクティブをレスポンスに含める必要がある。
alexa-sdk ではtemplateBuildersにテンプレートビルダーが含まれている。Display.RenderTemplate
ディレクトのためにJSONのテンプレートを作るヘルパーメソッドがある。
次の例はBodyTemplate1Builder
でBody template
を作るものだ。
const Alexa = require('alexa-sdk');
// utility methods for creating Image and TextField objects
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');
}
画像とテキストフィールドオブジェクトを作るためのユーティリティメソッドもあり、Alexa.utils
に含まれている。
const ImageUtils = require('alexa-sdk').utils.ImageUtils;
// Outputs an image with a single source
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のようなディスプレイのないデバイスに送るとinvalid directive error が発生する。どのディレクティブをサポートするかはデバイスのsupportedInterfaces属性を確認すればよい。
const handler = {
'LaunchRequest' : function() {
this.response.speak('Hello there');
// Display.RenderTemplate directives can be added to the response
if (this.event.context.System.device.supportedInterfaces.Display) {
//... build mytemplate using TemplateBuilder
this.response.renderTemplate(myTemplate);
}
this.emit(':responseReady');
}
};
同様に動画は VideoAppがデバイスでサポートされているかどうかチェックすれば良い。
const handler = {
'PlayVideoIntent' : function() {
// VideoApp.Play directives can be added to the response
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 Interface
PlaybackController
インターフェースはユーザがデバイス上のボタンやリモコンを操作した際にリクエストを送信できるようにする。これらは「Alexa, 次の曲」のような音声コマンドとは異なる。PlaybackController
リクエストを扱えるようにするためにPlaybackController
インターフェースをAlexa Node.js SDKを使って実装する必要がある。
const handlers = {
'NextCommandIssued' : function() {
//Your skill can respond to NextCommandIssued with any AudioPlayer directive.
},
'PauseCommandIssued' : function() {
//Your skill can respond to PauseCommandIssued with any AudioPlayer directive.
},
'PlayCommandIssued' : function() {
//Your skill can respond to PlayCommandIssued with any AudioPlayer directive.
},
'PreviousCommandIssued' : function() {
//Your skill can respond to PreviousCommandIssued with any AudioPlayer directive.
},
'System.ExceptionEncountered' : function() {
//Your skill cannot return a response to System.ExceptionEncountered.
}
};
PlaybackController
インターフェースに関するドキュメントはこちらを参照のこと。
VideoApp Interface
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 and List Events
スキル開発者はAlexaスキルイベントを直接組み込むことができる。もし、スキルがイベントを購読するとイベント発生時に通知される。
スキルサービスでイベントを利用するためには、Add Events to Your Skill With SMAPIにかかれているとおり、Alexa Skill Management API (SMAPI)へのアクセスを設定する必要がある。
Skill and List Events come out of session. Once your skill has been set up to receive these events. You can specify behaviour by adding the event names to your default event handler.
Skill and List Eventsはセッションを終了させる。一旦、スキルがイベントを受けるよう設定されると、デフォルトのイベントハンドラにイベント名を追加するとその振る舞いを定義できる。
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();
};
sample skill and walk-throughをスキルイベントの参考にしてほしい
Services
Device Address Service
Alexa NodeJS SDKはDeviceAddressService
ヘルパークラスを提供している。これはユーザのデバイスの住所情報を取得するためのDevice Address APIユーティリティだ。現在、次のメソッドが用意されている1。
getFullAddress(deviceId, apiEndpoint, token)
getCountryAndPostalCode(deviceId, apiEndpoint, token)
{
"addressLine1": null,
"addressLine2": null,
"addressLine3": null,
"districtOrCounty": null,
"stateOrRegion": null,
"city": null,
"countryCode": "JP",
"postalCode": "XXX-XXXX"
}
apiEndpoint
と token
はリクエストのthis.event.context.System.apiEndpoint
とthis.event.context.System.user.permissions.consentToken
から取得できる。
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 Service
Alexa ユーザは 2つのデフォルトリストが利用できる。Alexa ToDoリストと Alexa ショッピングリストだ。
加えてスキルからカスタムリストを作って管理できる。
Alexa NodeJS SDKは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);
});
};
Directive Service
enqueue(directive, endpoint, token)
これはスキル実行中に非同期にAlexaデバイスにディレクティブを返す。現在、発話ディレクティブのみサポートしている。
SSML (MP3 audioを含む)とテキスト形式をサポートしている。スキルが有効な時にディレクティブは元のデバイスに返される。apiEndpoint
と token
パラメータはそれぞれリクエストのthis.event.context.System.apiEndpoint
と this.event.context.System.apiAccessToken
から取得できる。
- レスポンスの発話は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) => {
// catch API errors so skill processing an continue
});
const serviceCall = callMyService();
Promise.all([progressiveResponse, serviceCall])
.then(() => {
this.response.speak('I found the following results');
this.emit(':responseReady');
});
}
};
Extend Features
Skill State Management
Alexa-sdk は受信したインテントを適切なハンドラに渡すためにステートマネージャを使う。セッション属性に文字列として保存された状態が、スキルの現在の状態を示す。組み込みのインテントルーティングでも、インテント名に状態文字列を付加することで同様の実装が可能だが、alexa-sdkのステートマネージャを使って実装することができる。
highlowgameをは状態管理の例だ。このスキルではユーザが数を予想し、Alexaが大きいか小さいかや、ユーザが何回プレイしたかを答える。
'start' と 'guess'の2つの状態を持っている。
const states = {
GUESSMODE: '_GUESSMODE', // User is trying to guess the number.
STARTMODE: '_STARTMODE' // Prompt the user to start or restart the game.
};
newSessionHandlersのNewSessionハンドラは入力インテントや起動リクエストのショートカットで、ルーティングを行っている。
const newSessionHandlers = {
'NewSession': function() {
if(Object.keys(this.attributes).length === 0) { // Check if it's the first time the skill has been invoked
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.state
によって、簡単にSTARTMODE
に設定されていることに注意。スキルの状態は自動的にセッション属性として保存される。もし、DynamoDBを使えばセッションを超えて保存することもできる。
NewSession
はキャッチオールな振る舞いで、よいエントリーポイントだが、必須ではないことに注意しよう。NewSession
はその名前を持つハンドラが定義されている場合にのみ呼び出される。それぞれの状態はもし、組み込みの永続化を利用するなら呼び出されるそれぞれのNewSession
ハンドラを定義することができる。上の例では異なるNewSession
の振る舞いをstates.STARTMODE
とstates.GUESSMODE
に実装でき、柔軟性を得ることができる。
スキルのさまざまな状態に対応するインテントを定義するには、 Alexa.CreateStateHandler
関数を使う。
スキルが特定の状態にあるときだけ動くインテントハンドラが定義でき、これは大きな柔軟性を与える。
例えば、上記で定義したGUESSMODE
状態では、質問に回答するユーザを扱う。次の様にStateHandlersを使う。
const guessModeHandlers = Alexa.CreateStateHandler(states.GUESSMODE, {
'NewSession': function () {
this.handler.state = '';
this.emitWithState('NewSession'); // Equivalent to the Start Mode NewSession handler
},
'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){
// With a callback, use the arrow function to preserve the correct 'this' context
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); // Be sure to call :saveState to persist your session attributes in 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
のStateHandlers
を次の様に定義する。
const startGameHandlers = Alexa.CreateStateHandler(states.STARTMODE, {
'NewSession': function () {
this.emit('NewSession'); // Uses the handler in 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');
}
});
AMAZON.YesIntent
とAMAZON.NoIntent
はguessModeHandlers
では定義されていない。それは、この状態では'yes' や 'no'の返答には反応しなくていいからだ。それらのインテントはUnhandled
ハンドラで受けられる。
また、NewSession
と Unhandled
ハンドラの振る舞いがこのゲームの両方の状態で異なっているのにも気付いたか?
このゲームでは、 newSessionHandlers
オブジェクトで定義された NewSession
ハンドラを呼び出して状態をリセットする。 それを定義することなく、alexa-sdkは現在の状態のインテントハンドラを呼び出す。
alexa.execute()
を呼ぶ前に各状態のハンドラを登録するを忘れないように。見つからないよ。
セッションを終了するとき、属性は自動的に保存される。しかし、ユーザがセッションを終了するなら、強制的に保存するように:saveState
イベントを発行する(this.emit(':saveState', true)
)必要がある。ユーザによるセッション「終了」やタイムアウト時に呼ばれるSessionEndedRequest
ハンドラでそれを実装する。上の例を確認してほしい。
もし明確に状態をリセットするなら、次のコードでできる。
this.handler.state = '' // delete this.handler.state は参照エラーの原因になる。
delete this.attributes['STATE'];
Persisting Skill Attributes through 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'; // That's it!
alexa.registerHandlers(State1Handlers, State2Handlers);
alexa.execute();
};
そして、後必要なのは、alexaオブジェクトの属性プロパティに値をセットすることだけだ。put
とget
にも分かれていない。
this.attributes['yourAttribute'] = 'value';
あらかじめマニュアルでテーブルを作る ことができる。あるいは Lambda 関数に DynamoDBの テーブル作成権限を与えておいても良い。その場合、自動的に作成される。最初の呼び出し時にテーブルの作成に1分かそこらかかることを忘れないように。もし、テーブルをマニュアルで作るなら、プライマリキーは文字列で"userId"をしておくこと。
注意:lambdaでスキルをホストして、スキルの属性をDynamoDBに保存するなら、lambdaの実行ロールがDynamoDBへのアクセス権を持っていることを確認すること。
Adding Multi-Language Support for Skill
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;
// To enable string internationalization (i18n) features, set a resources object.
alexa.resources = languageStrings;
alexa.registerHandlers(handlers);
alexa.execute();
};
一旦、各言語の文字列を定義して有効にすると、this.t()
関数でそれらの文字列にアクセスできる。受け付けたリクエストのロケールにあった言語で文字列が取得できる。
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 Support
ユーザがAlexaスキルを有効にすると、スキルはユーザに許可を求めることができる。許可されれば、Alexaデバイスに紐付いたアドレス情報が得られる。
このアドレス情報をスキルの機能やユーザ体験の向上に使うことができる。
deviceId
は各リクエストのcontextオブジェクトに含まれ、this.event.context.System.device.deviceId
でインテントハンドラからアクセス可能。スキル内でどうやってdeviceIdとAddress APIを使うかはAddress API sample skillを参照。
Speechcons (Interjections)
SpeechconsはAlexaの発生をより自然にするための単語やフレーズだ。それを使うためにSSMLマークアップをテキストに含めて発行すれば良い。
this.emit(':tell', 'Sometimes when I look at the Alexa skills you have all taught me, I just have to say, <say-as interpret-as="interjection">Bazinga.</say-as> ');
this.emit(':tell', '<say-as interpret-as="interjection">Oh boy</say-as><break time="1s"/> this is just an example.');
Speechcons(感嘆符)は英語(US/UK/India)とドイツ語でサポートされている
Setting up your development environment
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
-
getFullAddress(deviceId, apiEndpoint, token)
のレスポンスは次の通り。見ての通り,端末に登録されていない項目はnull
が返ってくるのでそのハンドリングも必要になる。サービスシミュレータではちゃんと動かないので注意。 ↩