会話の流れ
user: 「Alexa, アトリビュートを開いて」
Alexa: 「星座を三回聞きます。適当な星座を言ってください。」
user: 「いて座」
Alexa: 「いて座ですね。二つ目をおっしゃってください。」
user: 「うお座」
Alexa: 「いて座とうお座です。あと一つおっしゃってください。」
user: 「かに座」
Alexa: 「いて座とうお座とかに座ですね。終了します。」
セッション情報の持たせ方
this.attributes['attri1'] = attri1
こんな感じで持たせられる。
次以降の会話では、
var attri1 = this.attributes['attri1']
みたいな感じで参照できる。
同じインテントを区別しなきゃいけない問題
上の会話の例だと、ユーザーの発言はすべて星座1単語で構成されて、同じHoroscopeIntent
というインテントである。
同じインテントなのに、なぜ段階によってAlexaは違う返しができているのかというとステートという概念を取り入れているからである。
ちゃんとした会話が成り立ったら、次のステージへ移る的な概念。
そう。これがないとAlexaは最初の発言の「いて座ですね。二つ目をおっしゃってください。」を繰り返すばかりだ。
Lambda
"use strict";
const Alexa = require('alexa-sdk');
// ステートの定義
const states = {
FIRSTSTATE: '_FIRSTSTATE',
SECONDSTATE: '_SECONDSTATE'
};
exports.handler = function(event, context, callback) {
var alexa = Alexa.handler(event, context);
// alexa.appId = process.env.APP_ID;
alexa.registerHandlers(handlers, firstHandlers, secondHandlers); // 既存のハンドラに加えてステートハンドラ(後半で定義)も登録
alexa.execute();
};
var handlers = {
'LaunchRequest': function () {
this.emit('AMAZON.HelpIntent');
},
'AMAZON.HelpIntent': function () {
this.emit(':ask', '星座を三回聞きます。適当な星座を言ってください。');
},
'AMAZON.StopIntent': function(){
this.emit(':tell', 'ありがとうございました。');
},
'HoroscopeIntent': function () {
var attri1 = this.event.request.intent.slots.StarSign.value; //ユーザが発言発言した内容を取得。
this.handler.state = states.FIRSTSTATE; // ステートをセット
this.attributes['attri1'] = attri1; // 最初の星座をアトリビュートに保存
var message = attri1 + 'ですね。二つ目をおっしゃってください。';
this.emit(':ask', message); //2つ目を聞く
console.log(message);
}
};
// ステートハンドラの定義
var firstHandlers = Alexa.CreateStateHandler(states.FIRSTSTATE, {
'HoroscopeIntent': function() {
this.handler.state = states.SECONDSTATE; //2つ目の星座を扱うためにステートをチェンジ
var attri1 = this.attributes['attri1']; // アトリビュートattri1を参照
var attri2 = this.event.request.intent.slots.StarSign.value; //ユーザーが発言した2個目の星座を取得して格納
this.attributes['attri2'] = attri2;
var message = attri1 + 'と' + attri2 + 'です。あと一つおっしゃってください。'
this.emit(':ask', message); //3つ目を聞く
console.log(message);
},
//ユーザーがちゃんと星座を言ってくれなかった時
'Unhandled': function() {
var reprompt = '二つ目をおっしゃってください。';
this.emit(':ask', reprompt, reprompt);
}
});
var secondHandlers = Alexa.CreateStateHandler(states.SECONDSTATE,{
'HoroscopeIntent': function(){
this.handler.state = ''; //ステートを初期状態に戻す
this.attributes['STATE'] = undefined;
var attri1 = this.attributes['attri1'];
var attri2 = this.attributes['attri2'];
var attri3 = this.event.request.intent.slots.StarSign.value; //三つ目の星座を取得
var message = attri1 + 'と' + attri2 + 'と' + attri3 + 'ですね。終了します。';
this.emit(':tell', message);
},
'Unhandled': function(){
var reprompt = '三つ目をおっしゃってください。';
this.emit(':ask', reprompt, reprompt);
}
})
上から説明
const states = {
FIRSTSTATE: '_FIRSTSTATE',
SECONDSTATE: '_SECONDSTATE'
};
使用するステートを定義している。
alexa.registerHandlers(handlers, firstHandlers, secondHandlers);
使用するハンドラーを引数に渡している。
順番はどうでもいいみたい。
ハンドラー自体はこの下で定義している。
var handlers = {
(...省略...)
'HoroscopeIntent': function () {
var attri1 = this.event.request.intent.slots.StarSign.value; //ユーザが発言発言した内容を取得。
this.handler.state = states.FIRSTSTATE; // ステートをセット
this.attributes['attri1'] = attri1; // 最初の星座をアトリビュートに保存
var message = attri1 + 'ですね。二つ目をおっしゃってください。';
this.emit(':ask', message); //2つ目を聞く
console.log(message);
}
};
5行目のthis.handler.state = states.FIRSTSTATE;
でステートをセットしている。
6行目のthis.attributes['attri1'] = attri1;
でユーザーが最初に発言した星座をこの後の対話でも残せるように保存している。
実際にレスポンスで帰ってくるJSONは
"attributes": {
"attri1": "いて座",
"STATE": "_FIRSTSTATE"
},
こんな感じ。もちろん他のたくさんの情報はあるけど。
8行目のthis.emit(':ask', message)
はtell
じゃなくてask
だとAlexaがもう一度聞き取り体制に入る。
つまり、「いて座ですね。二つ目をおっしゃってください」と発言してから、そこで会話は終わらないで、もう一回会話に入る。
var firstHandlers = Alexa.CreateStateHandler(states.FIRSTSTATE, {
'HoroscopeIntent': function() {
this.handler.state = states.SECONDSTATE; //2つ目の星座を扱うためにステートをチェンジ
var attri1 = this.attributes['attri1']; // アトリビュートattri1を参照
var attri2 = this.event.request.intent.slots.StarSign.value; //ユーザーが発言した2個目の星座を取得して格納
this.attributes['attri2'] = attri2;
var message = attri1 + 'と' + attri2 + 'です。あと一つおっしゃってください。'
this.emit(':ask', message); //3つ目を聞く
console.log(message);
},
//ユーザーがちゃんと星座を言ってくれなかった時
'Unhandled': function() {
var reprompt = '二つ目をおっしゃってください。';
this.emit(':ask', reprompt, reprompt);
}
});
1行目のCreateStateHandler
関数でハンドラーを定義している。
第一引数にstates.FIRSTSTATE
を指定している。先ほどのthis.handler.state = states.FIRSTSTATE
でステートを定義しているので次のハンドラーはこれに結び付くとされる。
ほかはまあ、上と同じ。
var secondHandlers = Alexa.CreateStateHandler(states.SECONDSTATE,{
'HoroscopeIntent': function(){
this.handler.state = ''; //ステートを初期状態に戻す
this.attributes['STATE'] = undefined;
var attri1 = this.attributes['attri1'];
var attri2 = this.attributes['attri2'];
var attri3 = this.event.request.intent.slots.StarSign.value; //三つ目の星座を取得
var message = attri1 + 'と' + attri2 + 'と' + attri3 + 'ですね。終了します。';
this.emit(':tell', message);
},
'Unhandled': function(){
var reprompt = '三つ目をおっしゃってください。';
this.emit(':ask', reprompt, reprompt);
}
})
これもほぼ最初のハンドラー定義と同じだが、3行めでステートを初期化している。出ないと二つ目のステートが永遠と続いてしまう。
おわりに
こんなくだらないスキルでも申請したら通るのか試してみたいと思います。