[日本語Alexa] Alexa-SDK Ver2(その4) スキル属性
1 はじめに
本記事は、Alexa SDK for Node.js Ver2入門と題して、入門用という位置付けで、Alexa SDKの使い方を、順に紹介しているものです。(対象は、Node.js用のみです。Java用には触れておりません)
その4では、スキル属性(セッション情報等)について少し詳しく見ていきたいと思います。
Alexa SDKでは、セッション中や、セッションを跨いだデータの保存が可能です。 これは、Skill Attributesという概念で、その生存期間に応じて次の3種類に分類されています。
- Request attributes (1回のリクエスト中のみ有効)
- Session attributes (セッション継続間に有効)
- Persistent attributes (セッションを跨いで永続化 DynamoDB等が必要)
以下、それぞれについて見ていきます。
2 Request attributes(1回のリクエストでのみ有効)
Request attributesは、一回のリクエストの中でのみ有効です。Alexaからのリクエストが到着した時点では空であり、レスポンスを返した時に破棄されます。 利用場面の一例としては、追加の情報やヘルパークラスを使用したい場合に、インターセプターでこれを注入し、ハンドラ内で既存の機能のように利用することです。
下記の例は、インターセプタで、ローカライズ用のヘルパークラスを作成し、それをハンドラ内で利用しているものです。ちょうど、Ver1にあったローカライズ用のリソースをVer2でそのまま使うようになっています(必須ハンドラー、エラー制御などは省略されています)
「alexa/skill-sample-nodejs-howto」を参考にさせて頂きました。
const Alexa = require('ask-sdk'); const i18n = require('i18next'); const sprintf = require('i18next-sprintf-postprocessor'); let skill; exports.handler = async function (event, context) { if (!skill) { skill = Alexa.SkillBuilders.custom() .addRequestInterceptors(LocalizationInterceptor) // インターセプターの追加 .addRequestHandlers( LaunchRequestHandler, StopIntentHandler) .create(); } return skill.invoke(event); } // リソース const languageStrings = { 'ja-JP': { translation: { GOODBYE_MESSAGE: 'さようなら', WELCOME_MESSAGE: 'ようこそ', }, }, 'en-US': { translation: { GOODBYE_MESSAGE: 'Good bye', WELCOME_MESSAGE: 'Welcome', }, } }; // インターセプター const LocalizationInterceptor = { process(handlerInput) { // ヘルパー関数の定義 const localizationClient = i18n.use(sprintf).init({ lng: handlerInput.requestEnvelope.request.locale, overloadTranslationOptionHandler: sprintf.overloadTranslationOptionHandler, resources: languageStrings, returnObjects: true }); // リクエスト情報の取得 const attributes = handlerInput.attributesManager.getRequestAttributes(); // リクエスト情報への注入 attributes.t = function (...args) { return localizationClient.t(...args); }; }, }; // 起動時の処理 const LaunchRequestHandler = { canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; }, handle(handlerInput) { // リクエスト情報の取得 const requestAttributes = handlerInput.attributesManager.getRequestAttributes(); const speechText = requestAttributes.t('WELCOME_MESSAGE'); return handlerInput.responseBuilder .speak(speechText) .reprompt(speechText) .getResponse(); } }; // その他の処理 const StopIntentHandler = { canHandle(handlerInput) { return true; }, handle(handlerInput) { // リクエスト情報の取得 const requestAttributes = handlerInput.attributesManager.getRequestAttributes(); const speechText = requestAttributes.t('GOODBYE_MESSAGE'); return handlerInput.responseBuilder .speak(speechText) .getResponse(); } };
リソースが変化していることを確認できます。
3 Session attributes(セッション継続間に有効)
Session attributesは、セッション継続中に有効です。 保存された情報は、レスポンスでAlexaサービスに渡され、次のリクエストで、再びリクエストに含まれるようになります。外部ストレージなどは一切必要ありません。
例として、セッション中に数値を記憶する、下記のようなスキルを作成してみました。
U:アレクサ、テストスキルをスタートして A:現在記憶している数字は4です。2乗しますか? U:はい A:現在記憶している数字は16です。2乗しますか? U:はい A:現在記憶している数字は256です。2乗しますか? U:いいえ A:終わります。
下記が、その実装です。(必須ハンドラー、エラー制御などは省略されています)
const Alexa = require('ask-sdk'); let skill; exports.handler = async function (event, context) { if (!skill) { skill = Alexa.SkillBuilders.custom() .addRequestHandlers( LaunchRequestHandler, YesIntentHandler, StopHandler) .create(); } return skill.invoke(event); } function response(handlerInput, counter) { const speechText = `現在記憶している数字は${counter}です。2乗しますか?`; return handlerInput.responseBuilder .speak(speechText) .reprompt(speechText) .getResponse(); } function readCounter(handlerInput, pow) { // セッション情報の取得 let attributes = handlerInput.attributesManager.getSessionAttributes(); // 初期化 if(!attributes.counter){ attributes.counter = 2; } // 2乗する if (pow) { attributes.counter = Math.pow(attributes.counter, 2); } // セッション情報の保存 handlerInput.attributesManager.setSessionAttributes(attributes); return attributes.counter; } // スキル起動時の処理 const LaunchRequestHandler = { canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; }, handle(handlerInput) { // 2乗しないでカウンターを取得する const counter = readCounter(handlerInput, false); return response(handlerInput, counter); } }; // 「はい」と答えた場合の処理 const YesIntentHandler = { canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'IntentRequest' && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.YesIntent'; }, handle(handlerInput) { // 2乗してカウンターを取得する const counter = readCounter(handlerInput, true); return response(handlerInput, counter); } }; // その他の処理 const StopHandler = { canHandle(handlerInput) { return true; }, handle(handlerInput) { return handlerInput.responseBuilder .speak('終わります') .getResponse(); } };
セッション期間中だけ、情報が保存されていることを確認できます。
4 Persistent attributes(セッションを跨いで永続化する DynamoDB等が必要)
Persistent attributesは、セッションのライフサイクルを超えてデータを永続化します。
Persistent attributesを使用するためには、インスタンスの生成時にPersistenceAdapterの設定が必要であり、具体的には、withTableName()の呼び出しになります。また、withTableName()を使用するために、スキルの生成もAlexa.SkillBuilders.standard()を使用することになります。
const Alexa = require('ask-sdk'); let skill; exports.handler = async function (event, context) { if (!skill) { skill = Alexa.SkillBuilders.standard() // <= standard() .addRequestHandlers( LaunchRequestHandler, YesIntentHandler, StopHandler) .withTableName("sampleTableName") // これを追加(テーブル名) .withAutoCreateTable(true) //テーブル作成もスキルから行う場合は、これも追加 .create(); } return skill.invoke(event); } function response(handlerInput, counter) { const speechText = `現在記憶している数字は${counter}です。2乗しますか?`; return handlerInput.responseBuilder .speak(speechText) .reprompt(speechText) .getResponse(); } async function readCounter(handlerInput, pow) { // 永続化情報の取得 let attributes = await handlerInput.attributesManager.getPersistentAttributes() // 初期化 if(!attributes.counter){ attributes.counter = 2; } // 2乗する if (pow) { attributes.counter = Math.pow(attributes.counter, 2); } // 永続化情報の保存 handlerInput.attributesManager.setPersistentAttributes(attributes); await handlerInput.attributesManager.savePersistentAttributes(); return attributes.counter; } // スキル起動時の処理 const LaunchRequestHandler = { canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; }, async handle(handlerInput) { // 2乗しないでカウンターを取得する const counter = await readCounter(handlerInput, false); return response(handlerInput, counter); } }; // 「はい」と答えた場合の処理 const YesIntentHandler = { canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'IntentRequest' && handlerInput.requestEnvelope.request.intent.name === 'AMAZON.YesIntent'; }, async handle(handlerInput) { // 2乗してでカウンターを取得する const counter = await readCounter(handlerInput, true); return response(handlerInput, counter); } }; // その他の処理 const StopHandler = { canHandle(handlerInput) { return true; }, handle(handlerInput) { return handlerInput.responseBuilder .speak('終わります') .getResponse(); } };
永続化データは、Id(UserId)をキーにしてDynamoDBに保存されています。
実行している様子です。スキルが終了してもデータが保持されている様子が分かります。
5 最後に
今回は、スキル属性(セッション情報等)について確認してみました。情報の生存期間が明確となっていますので、それに応じた記述が必要になります。
Ver1では、ステートという概念が有り、インテント名とステート名でルーティングを行っていましたが、Ver2には、その機能はありません。同様の仕組みを使用する場合は、セッション情報に保存して、独自にステートの仕組みを実装する必要があります。
6 参考リンク
Alexa Skills Kit SDK for Node.js
Now Available: Version 2 of the ASK Software Development Kit for Node.js
GitHub repository for the SDK v2 for Node.js
[日本語Alexa] Alexa SDK for Node.js Ver2入門(その1)はじめの一歩
[日本語Alexa] Alexa SDK for Node.js Ver2入門(その2)ハンドラの登録
[日本語Alexa] Alexa SDK for Node.js Ver2入門(その3)レスポンスの作成
[日本語Alexa] Alexa-SDK Ver2(その4)スキル属性
[日本語Alexa] Alexa-SDK Ver2(その5)ダイアログモード
[日本語Alexa] Alexa-SDK Ver2(その6) 所在地情報
[日本語Alexa] Alexa-SDK Ver2(その7) ディスプレイ表示