ask
Alexa
AlexaSkillsKit

ASK-SDK(v2)の変更点メモ

はじめに

スマートスピーカーのスキル作成方法などまとめていて、そろそろLambda部分かなーと思っていたらそういえばv2出てたんですよね・・・。私の知識はv1止まりなので先にv2の勉強も兼ねてプログラムの書き方をメモっておきます。

どんな人向け?

  • alexa-sdkを使用してスキル開発をしたことがある人

本家リンク

使用するパッケージ

パッケージ名が変更されるとともに、幾つかに分解されたようですね。

  • v1
    • alexa-sdk
  • v2
    • ask-sdk
    • ask-sdk-core
    • ask-sdk-model
    • ask-sdk-dynamodb-persistence-adapter
    • ask-sdk-v1adapter

実際に使うところまで検証できていないのですが、Dynamo使わないならcoreで大丈夫みたい。
また、v1時代のプログラムからv2に移行する際には下位バージョン互換をサポートするv1adapterが使えるようですね。大きなプログラムを移行するのはシンドイですもんね・・・。

私が基本的に使ってたSDKたちと変更後の書き方

今回の調査対象とする部分です。スキルを作成する上でこれくらいを把握できていれば簡単なスキルは作成できるかな〜と思ってます。ダイアログ関係は次のステップにします。

  • ハンドラーの作り方
  • this.emit()を使用したask/tell
  • state管理
  • セッション管理(と言うか値の持ち回し)
  • スロットとシノニムからの値の取得方法

ハンドラーの作り方とstate管理

入り口から大きく変わりましたね。
旧バージョンはこのような感じでした。

exports.handler = function (event, context, callback) {

    var alexa = Alexa.handler(event, context);
    alexa.appId = APP_ID;
    // ここで、ステート毎に作成しておいたハンドラーを列挙していく
    alexa.registerHandlers(handlers, handlers1, handlers2, ...);
    alexa.execute();

};

新バージョンはこんな感じ

exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(  // ここから登録された順にハンドラーが評価される
        handlers,
        handlers1,
        handlers2,
        SessionEndedRequestHandler)
    .addErrorHandlers(ErrorHandler)
    .withSkillId(APP_ID)
    .lambda();

書き方が変わっただけで、やってることは変わらないかな〜と見えるのですが実はハンドラーの書き方が激変しています。

v1のハンドラーの書き方

v1ではステート毎にハンドラーを作成して、ハンドラーの中で各インテントの処理を記載していました。

const handlers = {
    'LaunchRequest': function () {
        this.handler.state = 'STATE1';
        this.emit(':ask', 'こんにちは。って言ってね!');
    },
    'HelloIntent': function () {
        this.handler.state = 'STATE1';
        this.emit(':ask', 'こんにちは。って言ってね!');
    },
    'Unhandled': function () {
        this.emit(':tell', '終了しますね!');
    }
};

const handlers1 = Alexa.CreateStateHandler('STATE1', {
    'HelloIntent': function () {
        this.handler.state = 'STATE2';
        this.emit(':ask', '次は、こんばんわ。って言ってね!');
    },
    'Unhandled': function () {
        this.emit(':tell', '終了しますね!');
    }
});

const handlers2 = Alexa.CreateStateHandler('STATE2', {
    'GoodEveningIntent': function () {
        this.emit(':tell', 'ありがとう!');
    },
    'Unhandled': function () {
        this.emit(':tell', '終了しますね!');
    }
});

処理内容はともかくとして・・・STATEが設定されていない時と設定されている時のハンドラーをそれぞれ準備して、各インテント用のファンクションを用意するイメージでした。

v2のハンドラーの書き方

コードを見てみましょう。

const handlers = {
    // 今の状況下で、このハンドラーを処理するかどうかを判断する
    canHandle (handlerInput) {
        const request = handlerInput.requestEnvelope.request;
        const state = handlerInput.attributesManager.getSessionAttributes().state;
        return request.type === 'LaunchRequest' ||
              (request.type === 'IntentRequest' &&
                request.intent.name === 'HelloIntent' &&
                !state);
    },
    // 上記の判断でtrueとなる場合に実行される
    handle (handlerInput) {
        const speechText = 'こんにちわ。って言ってね!';
        handlerInput.attributesManager.setSessionAttributes({'state': 'STATE1'});
        return handlerInput.responseBuilder
            .speak(speechText)
            .reprompt(speechText)
            .getResponse();
    }
};

const handlers1 = {
    canHandle (handlerInput) {
        const request = handlerInput.requestEnvelope.request;
        const state = handlerInput.attributesManager.getSessionAttributes().state;
        return request.type === 'IntentRequest' &&
                request.intent.name === 'HelloIntent' &&
                state === 'STATE1';
    },
    handle (handlerInput) {
        const speechText = '次は、こんばんわ。って言ってね!';
        handlerInput.attributesManager.setSessionAttributes({'state': 'STATE2'});
        return handlerInput.responseBuilder
            .speak(speechText)
            .reprompt(speechText)
            .getResponse();
    }
};

const handlers2 = {
    canHandle (handlerInput) {
        const request = handlerInput.requestEnvelope.request;
        const state = handlerInput.attributesManager.getSessionAttributes().state;
        return request.type === 'IntentRequest' &&
                request.intent.name === 'GoodEveningIntent' &&
                state === 'STATE2';
    },
    handle (handlerInput) {
        const speechText = 'ありがとう!';
        return handlerInput.responseBuilder
            .speak(speechText)
            .getResponse();
    }
};

const ErrorHandler = {
    canHandle (handlerInput) {
        return true;
    },
    handle (handlerInput) {
        return handlerInput.responseBuilder
            .speak('終了しますね!')
            .getResponse();
    }
};

canHandleという部分で、そのハンドラーの実処理部分を実行するかを判断します。
そしてhandleという部分で実処理を記載します。
このハンドラーが、Alexa.SkillBuilders.custom().addRequestHandlers()に列挙した順に実行されるので、順にcanHandleで判断されて一番最初に実処理を行うと判断されたところでストップします。

なるほどーと思いますが、これ難しくないですか・・・?

ask / tell

v1でもあったと思うのですが、Responseを使う形式で統一された?ようです。
WikiのInterfaceがとても見やすいです。

tellの書き方

上記のハンドラーの例にも既に書いちゃっていますがこんな感じです。

return handlerInput.responseBuilder
    .speak('通常応答メッセージ!')
    .getResponse();

askの書き方

repromptを付けるとaskになります。

return handlerInput.responseBuilder
    .speak('通常応答メッセージ!')
    .reprompt('応答が無かった時の再確認メッセージ!')
    .getResponse();

Wikiを見る限りだとwithShouldEndSessionメソッドが存在するので、reprompt無しでも制御できそうですが、askする限りはrepromptがあった方がいいと思うので、この形で良さそうです。

他にもカードを使ったりなど色々とメソッドが用意されていますね。

セッション管理

AttributesManagerというものを使用します。
こちらもWikiがとても見やすいです。

生存期間が、1リクエスト、1セッション、永続の3種があるようなので用途に応じて使い分けるのが楽しそうです。
上記のハンドラーの例では従来通りのセッション管理をしています。

保持

handlerInput.attributesManager.setSessionAttributes({'Key': 'Value'});

取得

const val = handlerInput.attributesManager.getSessionAttributes().key;

スロットとシノニムからの値取得

v1の時と同じようですね。

// スロット値(発話した内容。同義語を言った場合は同義語値)
handlerInput.requestEnvelope.request.intent.(スロット名).value
// スロット値(値を言っても同義語を言っても、こちらは「値」として設定したもの)
handlerInput.requestEnvelope.request.intent.(スロット名).resolutions.resolutionsPerAuthority[0].values[0].value

この深い方の配列が複数になるのってどういう条件だろう・・・?

終わりに

Alexaスキルはドキュメントもサンプルも充実しているので、とりあえず何か作ってみるのが簡単でいいですよね!
v2についてはまだ調べ始めたばかりなので間違っている点などあるかもしれません。何かあればご指摘いただけるとありがたいです。

ここにメモした事以外にも色々ありそうなので、有用な情報などあれば書いていきたいと思います。
読んでいただいてありがとうございました!