LoginSignup
22

More than 5 years have passed since last update.

はじめてのAlexa

Last updated at Posted at 2017-07-27

はじめに

Alexaスキルを試行錯誤で作成したので、調べたことなどをクイズサンプルを解説する形でまとめておきます。

Alexaのイメージ

まずはAlexaって何よ?ってところですが、簡単な流れとしてはAmazonEcho(EchoDotsやEchoShowなど)に語りかけると、Alexaスキルが起動し、
何らかの応答をするという流れになっています。
裏側の処理はlambdaとしていますが、別のサーバでももちろん可能です。(Amazonとしてはlambdaの利用を推奨しているようです)

フロー

Trivia Skill

では本題のクイズサンプルを見ていきましょう。

環境の作成

まぁ、英語でビルド手順が書いてあるので、迷わないと思いますが手順としては以下になります。詳しい手順は省略します。

1. Amazon開発者コンソールからスキルを作成する
2. ここからgit cloneしてnpm install --alexa-sdkしてzipに固める
3. AWS上でAlexaスキルをトリガーとしたlambda関数を作る(リージョンはバージニア北部)
4. 2.で作成したzipを3.で作成した関数へアップロード
5. 1.で作成したAlexaスキルのConfigurationから3.で作成した関数を繋げる
6. AlexaスキルのInteractionModelを設定して終了

ユーザの発声を理解する

Alexaがユーザの言葉を理解するには適切なInteraction Modelの設計が必要になります。
Triviaスキルでは以下の設定をしています。

インテント

ユーザが何をしたいのかという意思をintentとして登録します。

IntentSchema.json
{
  "intents": [
    {
      "intent": "AnswerIntent",
      "slots": [
        {
          "name": "Answer",
          "type": "AMAZON.NUMBER"
        }
      ]
    },
    {
      "intent": "DontKnowIntent"
    },
    {
      "intent": "AMAZON.StartOverIntent"
    },
    {
      "intent": "AMAZON.RepeatIntent"
    },
    {
      "intent": "AMAZON.HelpIntent"
    },
    {
      "intent": "AMAZON.YesIntent"
    },
    {
      "intent": "AMAZON.NoIntent"
    },
    {
      "intent": "AMAZON.StopIntent"
    },
    {
      "intent": "AMAZON.CancelIntent"
    }
  ]
}

intent内でAMAZON.が付いているintentはビルトインのもので、既にAMAZONによって定義されています。

アテランス

ユーザが何を言っているのかという内容をutteranceとして設定しています。utteranceで設定したユーザ言わんとしている内容をintentへつなげています。

SampleUtterances_en_US.txt
AnswerIntent the answer is {Answer}
AnswerIntent my answer is {Answer}
AnswerIntent is it {Answer}
AnswerIntent {Answer} is my answer
AnswerIntent {Answer}

AMAZON.StartOverIntent start game
AMAZON.StartOverIntent new game
AMAZON.StartOverIntent start
AMAZON.StartOverIntent start new game

DontKnowIntent i don't know
DontKnowIntent don't know
DontKnowIntent skip
DontKnowIntent i don't know that
DontKnowIntent who knows
DontKnowIntent i don't know this question
DontKnowIntent i don't know that one
DontKnowIntent dunno

上記のutteranceではユーザが「i don't know」と言えばDontKnowIntentとして解釈され、lambdaへはDontKnowIntentが来たよと通知されます。

lambdaコード

では、lambdaへintentが渡ってきた場合の処理についてですが、このサンプルでは、Alexaの会話状態をステートとして管理しており、そのステート内でintentをどう処理するのかを記述しています。

(一部)index.js
var newSessionHandlers = {
    "LaunchRequest": function () {
        this.handler.state = GAME_STATES.START;
        this.emitWithState("StartGame", true);
    },
    "AMAZON.StartOverIntent": function() {
        this.handler.state = GAME_STATES.START;
        this.emitWithState("StartGame", true);
    },
    "AMAZON.HelpIntent": function() {
        this.handler.state = GAME_STATES.HELP;
        this.emitWithState("helpTheUser", true);
    },
    "Unhandled": function () {
        var speechOutput = this.t("START_UNHANDLED");
        this.emit(":ask", speechOutput, speechOutput);
    }
};

上記はステートがない、つまり初期状態のintentの振り分けを定義しています。
LaunchRequestはAlexaがInvocationName(起動ワード)で呼ばれたときに呼ばれるintentになります。
Trivia SkillでInvocation Nameを"trivia master"としていた場合、「Alexa, trivia master」と呼びかけるとLaunchRequestが呼ばれ、ステートがSTARTに設定され、this.emitWithState("StartGame", true);により、STARTステート内の処理が実行されます。

(一部)index.js
var startStateHandlers = Alexa.CreateStateHandler(GAME_STATES.START, {
    "StartGame": function (newGame) {
        var speechOutput = newGame ? this.t("NEW_GAME_MESSAGE", this.t("GAME_NAME")) + this.t("WELCOME_MESSAGE", GAME_LENGTH.toString()) : "";
        // Select GAME_LENGTH questions for the game
        var translatedQuestions = this.t("QUESTIONS");
        var gameQuestions = populateGameQuestions(translatedQuestions);
        // Generate a random index for the correct answer, from 0 to 3
        var correctAnswerIndex = Math.floor(Math.random() * (ANSWER_COUNT));
        // Select and shuffle the answers for each question
        var roundAnswers = populateRoundAnswers(gameQuestions, 0, correctAnswerIndex, translatedQuestions);
        var currentQuestionIndex = 0;
        var spokenQuestion = Object.keys(translatedQuestions[gameQuestions[currentQuestionIndex]])[0];
        var repromptText = this.t("TELL_QUESTION_MESSAGE", "1", spokenQuestion);

        for (var i = 0; i < ANSWER_COUNT; i++) {
            repromptText += (i+1).toString() + ". " + roundAnswers[i] + ". ";
        }

        speechOutput += repromptText;

        Object.assign(this.attributes, {
            "speechOutput": repromptText,
            "repromptText": repromptText,
            "currentQuestionIndex": currentQuestionIndex,
            "correctAnswerIndex": correctAnswerIndex + 1,
            "questions": gameQuestions,
            "score": 0,
            "correctAnswerText": translatedQuestions[gameQuestions[currentQuestionIndex]][Object.keys(translatedQuestions[gameQuestions[currentQuestionIndex]])[0]][0]
        });

        // Set the current state to trivia mode. The skill will now use handlers defined in triviaStateHandlers
        this.handler.state = GAME_STATES.TRIVIA;
        this.emit(":askWithCard", speechOutput, repromptText, this.t("GAME_NAME"), repromptText);
    }
});

STARTステート内では音声出力用の文字列speechOutputなどの出力内容の作成とセッション情報の保存を行っています。
alexa-sdkではthis.attributesのプロパティに情報を保管しています。
this.attributesに保管した情報はAlexaがセッションを保持している、つまり、会話中(スキルが動作している最中)はセッション情報として、リクエストに保管した情報を投げてくれます。
ステート以外に保持したい情報はthis.attributesに持たせることでセッション管理ができます。

セッション保持のタイミング

では、どんなタイミングでセッションが保持されているのでしょうか。
:saveState:saveStateError以外でemitするタイミングで保持されます。まあ想像通りですね。

DynamoDBへの保管

セッション情報はalexaスキルが終了した時点で破棄されます。
次にスキルを呼び出したときに前回の情報を保持しておきたい場合はDynamoDBにセッション情報を保管しておくことができます。

(一部)index.js
exports.handler = function(event, context, callback) {
    var alexa = Alexa.handler(event, context);
    // この1行を追加するだけでemitするたびにDynamoDBに
    // ステートを含めたセッション情報が保管されます
    alexa.dynamoDBTableName = 'セッション保管用のテーブル名';

    alexa.appId = APP_ID;
    // To enable string internationalization (i18n) features, set a resources object.
    alexa.resources = languageString;
    alexa.registerHandlers(newSessionHandlers, startStateHandlers, triviaStateHandlers, helpStateHandlers);
    alexa.execute();
};

DynamoDBにセッションを保管する際にはlambda関数にDynamoDBへのアクセス権限を付与しておく必要があります。

参考文献

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22