[日本語Alexa] Alexa-SDK Ver2(その4) スキル属性

Alexa SDKでは、セッション中や、セッションを跨いだデータの保存が可能です。 これは、Skill Attributesという概念で、その生存期間に応じて次の3種類に分類されています。 1.Request attributes (1回のリクエスト中のみ有効) 2.Session attributes (セッション継続間に有効) 3.Persistent attributes (セッションを跨いで永続化 DynamoDB等が必要) 本記事では、それぞれについて詳しく見ていきます。
2018.04.26

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

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) ディスプレイ表示