この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
1 はじめに
ダイアログモデルを使用すると、一切コーディングなしで必要なスロットを収集することができます。しかし、開発者コンソールからの設定だけでは、一定の型に収まらない、ちょっと複雑なパターンに対応するのは難しいでしょう。
下記は、コード側でスロットやステータスを操作して、ちょっと複雑なパターンに対応させてみた例です。
今回は、今一度、このダイアログモデルの動作について整理し、いくつかのパターンでスロットを書き換える例を紹介したいと思います。
2 ステータス
(1) dialogState
最初にダイアログモデルのステータスです。ステータスには、下記の3つがあります。
- STARTED
- IN_PROGRESS
- COMPLETED
そして、このステータスは、Alexaから送られてくるリクエスト(JSON)の中にdialogStateとして入っています。
// ・・・略・・・
"request": {
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.xxxxxxxx",
"timestamp": "2018-03-17T00:00:00Z",
"locale": "ja-JP",
"intent": {
// ・・・略・・・
},
"dialogState": "STARTED"
}
// ・・・略・・・
(2) STARTED
STARTEDは、ダイアログモデルで初めてコード側にリクエストが渡る時のステータスです。最初に必ず一回送られますので、各種の状態に応じた初期値の設定などが、ここで可能です。
STARTEDでは、delegate、confirmSlot若しくは、confirmIntentを返します。
ちょっと、気をつけなければならないのは、初めてリクエストが来た時に、既に必須スロットが全部埋まっていた場合も、COMPLETEDの前にSTARTEDが送られてくることです。
例えば、下記のようなサンプル発話があって、このインテントには、{number}という必須スロットが一つあったとしましょう。
コーヒーを{number}つください
コーヒーをください
「コーヒーを一つください」のようにユーザーが話すと、一発で必須スロットが埋まることになります。しかし、この場合も、初回は、ステータスがSTARTEDになったリクエストが、ちゃんと来ます。そして、その時、スロット値を消したりしない限り、直ちにCOMPLETEDのリクエストが来ることになります。ユーザーからの発話は一回なのに、プログラム側には、2回のリクエストが来ることになります。
逆に、ユーザーが「コーヒーを下さい」のように発話すると、{number}スロットが空の状態でSTARTEDを受け取ることになりますが、この時、強制的にスロット値を埋めたりすると、直ちにCOMPLETEDとなったリクエストが来ることになります。
(3) IN_PROGRESS
初回以外で、必須スロットが全部埋まっていない場合は、このステータスになります。ここでも、各種の状態(スロットの値など)に応じた値の設定が可能です。
IN_PROGRESSは、delegate、confirmSlot若しくは、confirmIntentを返します。
STARTEDの後、すぐにCOMPLETEDになる場合がありますので、IN_PROGRESSは、受け取れない可能性があることに注意が必要です。
(4) COMPLETED
必須スロットが全部埋まった時、このステータスになります。ここでは、スロット値の変更はできません。COMPLETEDでは、通常のレスポンス(tell、askなど)を返します。
COMPLETEDは、あくまで、必須スロットが全部埋まった時であり、ユーザーの発話がトリガーとなって呼ばれているとは限らないことに注意が必要です。
3 スロットの書き換え例
(1) デフォルト値
STARTEDでデフォルト値を設定している例です。
'OrderIntent': function () {
let intent= this.event.request.intent;
if (this.event.request.dialogState == 'STARTED') {
if (intent.slots.number.value) {
intent.slots.number = '1';
}
this.emit(':delegate', intent);
} else {
let number = intent.slots.number.value;
this.emit(':tell','承知しました。コーヒーを' + number + 'つ、用意させて頂きます。' );
}
}
初回、ダイアログモデルの起動時に、スロット{number}に値が入っていなかった場合、スロット{number}に1を設定しています。
このため、このインテントの動作は次のようになります。
- ユーザーが数を指定した場合
U: コーヒーを3つ下さい。
A: 承知しました。コーヒーを3つ用意させて頂きます。
- ユーザーが数を指定しなかった場合
U: コーヒーを下さい。
A: 承知しました。コーヒーを1つ用意させて頂きます。
(2) 強制書き換え
今度は、スロットの値を強制的に変更している例です。スロット{number}に2が入ると、しれっと3に書き換えています。
'OrderIntent': function () {
let intent= this.event.request.intent;
if (this.event.request.dialogState != 'COMPLETED') {
if (intent.slots.number.value && intent.slots.number.value == '2') {
intent.slots.number.value = '3';
}
this.emit(':delegate', intent);
} else {
let number = intent.slots.number.value;
this.emit(':tell','承知しました。コーヒーを' + number + 'つ、用意させて頂きます。' );
}
}
このため、このインテントの動作は次のような感じです。
- 1つ注文した場合
U: コーヒーを1つ下さい。
A: 承知しました。コーヒーを1つ用意させて頂きます。
- 2つ注文した場合
U: コーヒー2つ下さい。
A: 承知しました。コーヒーを3つ用意させて頂きます。
(3) 提案
'OrderIntent': function () {
let intent = this.event.request.intent;
if (this.event.request.dialogState !== 'COMPLETED') {
if (!intent.slots.number.value) {
intent.slots.number.value = '1';
let speechOutput = '1つで宜しいですか?';
let reprompt = '1つでいいですか?'
this.emit(':confirmSlot','number', speechOutput, reprompt, intent);
} else {
this.emit(':delegate');
}
} else {
let number = this.event.request.intent.slots.number.value;
this.emit(':tell','承知しました。コーヒーを' + number + 'つ、用意させて頂きます。' );
}
}
- ユーザーが数を指定した場合
U: コーヒーを2つください
A: 承知しました。コーヒーを2つ用意させて頂きます。
- ユーザーが数を指定しなかった場合
U: コーヒーをください
A: 1つで宜しいですか
U: はい
A: 承知しました。コーヒーを1つ用意させて頂きます。
- ユーザーが数を指定しなかった場合 (※ 確認時に「いいえ」を返した場合のプロンプトは、開発者コンソールで設定したものが動作しています。)
U: コーヒーをください
A: 1つで宜しいですか
U: いいえ
A: いくつにしますか
U: 3つください
A: 承知しました。コーヒーを1つ用意させて頂きます。
4 最後に
今回は、ダイアログモデルのステートについて整理し、いくつかのスロットの書き換え例を紹介しました。 定型的な動作や、デフォルトの動きを開発者コンソールで設定し、設定できないパターンや、条件分岐などが必要な場合にコーディングするというスタイルが、効率的だと思います。
まずは、dialogStateの違いをしっかり理解することが大事かもしれませんね。