[日本語Alexa] ダイアログモデルでスロットを自由自在に操作するためのドキュメント
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の違いをしっかり理解することが大事かもしれませんね。