ちょっと話題の記事

[日本語Alexa] 強制的にステータスをCOMPLETEDに遷移させることで、複雑なダイアログモデルを作成してみる

2018.03.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

ダイアログモデルでは、必須のスロットを設定することで、必要なパラメータを非常に簡単に集めることができます。しかし、開発者コンソールからの設定だけでは、自由な設計にはちょっと限界があります。

例えば、「Aスロットは必須だけど、その内容によっては、Bスロットが必須になったり、Cスロットが必須になったり、はたまた、BやCは不要になったり」みたいな・・・

今回は、スロットの状態(内容)に応じて、コード側でこれを処理し、スロットを書き換えたり、不要なスロットにダミーを入れたりして、強制的にステータスをCOMPLETEDに遷移させることで、ちょっと複雑なダイアログモデルに挑戦してみたいと思います。

今回の題材は、レストランでの注文です。複雑と言っては大袈裟ですが、次のようなメニューがあったとします。

  • 定食(Aセット・Bセット・Cセット)
  • 蕎麦(暖かい・冷たい)
  • うどん

メニューは、「定食」「蕎麦」及び、「うどん」の3種類です。そして定食はAセットからCセットの3種類から選べます。また、蕎麦は、温かいものと、冷たいものが選べます。

2 モデルの設計

(1) スロット

考えられるスロットは、次の3つでしょう。

  • menu (メニューの種類)
  • set (定食の種類)
  • hotCold (温かい若しくは、冷たい)

(2) カスタムスロット

そして、それぞれのスロットのタイプに指定するためのカスタムスロットを作成します。

  • LIST_OF_MENU 定食・蕎麦・うどん
  • LIST_OF_SET Aセット・Bセット・Cセット
  • LIST_OF_HOT_COLD 温かい・冷たい

(3) サンプル発話

これらを使用したサンプル発話を、とりあえず下記のようにしました。

  • {menu}を下さい
  • {hotCold}{menu}を下さい
  • {set}を下さい

これで、「定食を下さい」「温かいおそばを下さい」「Aセットを下さい」みたいな発話を受け取ることが出来ます。

3 条件によるスロットの要否

「格納されたスロット」によって、更に必要なスロットと、もう必要ないスロットをマトリックスにしてみました。

発話例 格納されるスロット 必要 不要
定食を下さい menu(定食) set hotCold
A定食を下さい set menu,hotCold
温かいそばを下さい menu(そば),hotCold set
うどんを下さい menu(うどん) hotCold,set
そばを下さい menu(そば) hotCold set

上の表から、「格納されたスロット」を見て、必要なスロットがある場合は、それを要求し、不要なスロットには、ダミーを埋めてしまいます。全部のスロットが入れば、ダイアログモデルはCOMPLETEDになります。

  • setが入っている場合 => menuに「定食」をセットして COMPLETED
  • menuに「うどん」が入っている場合 => COMPLETED
  • menuに「蕎麦」が入っている場合 hotColdが入ってなければ要求する
  • menuに「蕎麦」が入っている場合 hotColdが入っていれば => COMPLETED
  • menuに「定食」が入っている場合 setが入ってなければ要求する
  • menuに「定食」が入っている場合 setが入っていれば => COMPLETED

これをコードにすると次のようになります。

'OrderIntent': function () {  
    let intent = this.event.request.intent;

    if (this.event.request.dialogState !== 'COMPLETED') {
        if (intent.slots.set.value) { // セットが指定されてい場合
            intent.slots.menu.value = '定食'; // メニューに「定食」を設定
            intent.slots.hotCold.value = '?';// 不要スロットの無効化
        } else if (intent.slots.menu.value) { // メニューが設定されている場合
            let menu = intent.slots.menu.resolutions.resolutionsPerAuthority[0].values[0].value.id;
            if(menu == 'udon') { // メニューに「うどん」が設定されている場合
                intent.slots.hotCold.value = '?';// 不要スロットの無効化
                intent.slots.set.value = '?';// 不要スロットの無効化
            } else if(menu == 'soba') {// メニューに「蕎麦」が設定されている場合
                intent.slots.set.value = '?';// 不要スロットの無効化
                if (!intent.slots.hotCold.value) {
                    let speechOutput = '温かいお蕎麦と、冷たいお蕎麦のどちらになさいますか?';
                    this.emit(':confirmSlot','hotCold', speechOutput, speechOutput);
                    return;
                }
            } else { // メニューに「定食」が設定されている場合
                intent.slots.hotCold.value = '?'; // 不要スロットの無効化
                if (!intent.slots.set.value) {
                    let speechOutput = 'Aセット、Bセット、Cセットの3種類から選べます。どれになさいますか?';
                    this.emit(':confirmSlot','set', speechOutput, speechOutput);
                    return;
                }
            }
        }
        this.emit(':delegate', intent);
    } else {
        let answer = '';
        let menu = '';
        if(intent.slots.menu.resolutions){
            menu = intent.slots.menu.resolutions.resolutionsPerAuthority[0].values[0].value.id;
        } else { // 強制的に「定食」を入れた場合、resolutionsが無い
            menu = "setMenu";
        }
        if(menu == 'udon') {
            answer = 'おうどん';
        } else if(menu == 'soba') {
            let hotCold = intent.slots.hotCold.resolutions.resolutionsPerAuthority[0].values[0].value.id;
            if(hotCold=='hot') {
                answer = '温かいお蕎麦';
            } else {
                answer = '冷たいお蕎麦';
            }
        } else { //setMenu
            let set = intent.slots.set.resolutions.resolutionsPerAuthority[0].values[0].value.id;
            if(set=='a_set') {
                answer = 'Aセット';
            }else if(set=='b_set') {
                answer = 'Bセット';
            } else {
                answer = 'Cセット';
            }
        }
        this.emit(':tell',answer + 'を用意させて頂きます。' );
    }
}

テストしている様子です。

4 最後に

今回は、強制的にスロットを埋めて、ステータスをCOMPLETEDに遷移させることで、必須スロットが、A and B or C のような複雑なダイアログ管理が可能になることを確認してみました。

うまく組み合わせると、かなり自由にオーダーが取得できるのでは無いでしょうか。