はじめに
Alexaスキルを試行錯誤で作成したので、調べたことなどをクイズサンプルを解説する形でまとめておきます。
##Alexaのイメージ
まずはAlexaって何よ?ってところですが、簡単な流れとしてはAmazonEcho(EchoDotsやEchoShowなど)に語りかけると、Alexaスキルが起動し、
何らかの応答をするという流れになっています。
裏側の処理はlambdaとしていますが、別のサーバでももちろん可能です。(Amazonとしてはlambdaの利用を推奨しているようです)
Trivia Skill
では本題のクイズサンプルを見ていきましょう。
環境の作成
まぁ、英語でビルド手順が書いてあるので、迷わないと思いますが手順としては以下になります。詳しい手順は省略します。
- Amazon開発者コンソールからスキルを作成する
-
ここから
git clone
してnpm install --alexa-sdk
してzipに固める - AWS上でAlexaスキルをトリガーとしたlambda関数を作る(リージョンはバージニア北部)
- 2.で作成したzipを3.で作成した関数へアップロード
- 1.で作成したAlexaスキルのConfigurationから3.で作成した関数を繋げる
- AlexaスキルのInteractionModelを設定して終了
ユーザの発声を理解する
Alexaがユーザの言葉を理解するには適切なInteraction Modelの設計が必要になります。
Triviaスキルでは以下の設定をしています。
インテント
ユーザが何をしたいのかという_意思_をintentとして登録します。
{
"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へつなげています。
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をどう処理するのかを記述しています。
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ステート内の処理が実行されます。
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にセッション情報を保管しておくことができます。
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へのアクセス権限を付与しておく必要があります。