[日本語Alexa] スロット値の誤認識をバリデートする

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

1 はじめに

スロットが設定されたインテントでは、受け取ったスロット値を処理するわけですは、これが、必ず思い通りに取得できているとは限りません。

今回は、どのような種類の誤認識が存在するかを列挙し、その対策を考えてみたいと思います。

2 テストスキル

最初に簡単なテストスキルを作成して、そのスロットへの入り具合を確認してみます。

作成したテストスキルには、インテントをひとつだけ設定し、下記のようにサンプル発話を設定しました。

{drink} を {number} ください

サンプル発話には、2つのスロットがあり、そのタイプは、AMAZON.NumberLIST_OF_DRINK(カスタムスロット) になっています。

1つ目のスロットのタイプとして指定されているLIST_OF_DRINK には、「ホットコーヒー」、「アイスコーヒー」、「コーラ」及び、「カルピス」の4つが定義されています。

3 認識の状況

認識のパターンを列挙してみます。(Requestは、一部のキーを省略しています)

(1) 期待どおりに取得できた場合

最初に、全に思った通りにスロット値が受け取れた場合です。AMAZON.Numberには数値が入ります。そして、カスタムスロットは、statusER_SUCCESS_MATCHとなり、valueidもセットされています。

ホットコーヒーを一つください
slot value resolutions-status value-id-1 value-id-2
number 1
drink ホットコーヒー ER_SUCCESS_MATCH hot-coffee
"number": {
    "name": "number",
    "value": "1"
},
"drink": {
    "name": "drink",
    "value": "ホットコーヒー",
    "resolutions": {
        "resolutionsPerAuthority": [
            {
                "status": {
                    "code": "ER_SUCCESS_MATCH"
                },
                "values": [
                    {
                        "value": {
                            "name": "ホットコーヒー",
                            "id": "hot-coffee"
                        }
                    }
                ]
            }
        ]
    }
}

(2) カスタムリストがヒットしない場合

続いて、カスタムスロットがヒットしなかった場合です。statusは、ER_SUCCESS_NO_MATCHとなり、valueidを取得しようにも、values自体がありません。よく似た言葉が認識してしまった場合、valuesが取得できますが、やはり、statusは、ER_SUCCESS_NO_MATCHとなっているので、このバターンと同じとしても大丈夫でしょう。

ビールを一つください
slot value resolutions-status value-id-1 value-id-2
number 1
drink ビール ER_SUCCESS_NO_MATCH
"number": {
    "name": "number",
    "value": "1"
},
"drink": {
    "name": "drink",
    "value": "ビール",
    "resolutions": {
        "resolutionsPerAuthority": [
            {
                "status": {
                    "code": "ER_SUCCESS_NO_MATCH"
                }
            }
        ]
    }
}

(3) カスタムリストが複数ヒットした場合

次の例は、カスタムスロットが2種類ヒットしてしまった場合です。この場合は、少し複雑です。statusは、ER_SUCCESS_MATCHとなっているので、一瞬、マッチしているように見えますが、valuseが2つ存在し、複数の値がヒットしている事が分かります。

完全一致しているかどうかの区別をするには、valuesの項目数が複数かどうかを確認するしか無いかもしれません。

コーヒーを一つください
slot value resolutions-status value-id-1 value-id-2
number 1
drink コーヒー ER_SUCCESS_MATCH ice-coffee hot-coffee
"number": {
    "name": "number",
    "value": "1",
},
"drink": {
    "name": "drink",
    "value": "コーヒー",
    "resolutions": {
        "resolutionsPerAuthority": [
            {
                "status": {
                    "code": "ER_SUCCESS_MATCH"
                },
                "values": [
                    {
                        "value": {
                            "name": "アイスコーヒー",
                            "id": "ice-coffee"
                        }
                    },
                    {
                        "value": {
                            "name": "ホットコーヒー",
                            "id": "hot-coffee"
                        }
                    }
                ]
            }
        ]
    }
}

(4) 組み込みタイプが誤認識した場合

最後に、組み込みのタイプのスロットが誤認識してしまった場合です。この場合 value? (無効な値)が入っていることが分かります。

ホットコーヒーをふっつください
slot value resolutions-status value-id-1 value-id-2
number ?
drink ホットコーヒー ER_SUCCESS_MATCH hot-coffee
"number": {
    "name": "number",
    "value": "?"
},
"drink": {
    "name": "drink",
    "value": "ホットコーヒー",
    "resolutions": {
        "resolutionsPerAuthority": [
            {
                "status": {
                    "code": "ER_SUCCESS_MATCH"
                },
                "values": [
                    {
                        "value": {
                            "name": "ホットコーヒー",
                            "id": "hot-coffee"
                        }
                    }
                ]
            }
        ]
    }
}

4 期待値かどうかの判断

先のリクエストの状況から期待している値が入っているかどうかの判断は、次の要領で良いのではないかと考えられます。

(1) 組み込みスロットの場合

組み込みスロットの値が期待値であることの確認は、以下のとおりです。

  • slotname/valueが、適切な値であること

実装例は、次のようになるでしょうか。(AMAZON.Numberの場合)

function getNumber(intent) {
    if (intent.slots.number) {
        // Number()で数値かどうかの判定を行う
        let number = Number(intent.slots.number.value);
        if (!isNaN(number)) {
            return number;
        }
    }
    return undefined;  // 無効な場合は、undefinedを返す
}

'OrderIntent': function () {  
    if (this.event.request.dialogState !== 'COMPLETED') {
        let number = getNumber(this.event.request.intent);
    // ・・・省略・・・

(2) カスタムスロットの場合

カスタムスロットの値が期待値であることの確認は、以下のとおりです。

  • resolutionsPerAuthority/status/code が ER_SUCCESS_MATCHであること
  • resolutionsPerAuthority/valuesが1つであること

実装例です。

function getDrink(intent) {
    if (intent.slots.drink && intent.slots.drink.resolutions && intent.slots.drink.resolutions.resolutionsPerAuthority) {
        if (intent.slots.drink.resolutions.resolutionsPerAuthority[0].status.code == 'ER_SUCCESS_MATCH') {
            if(intent.slots.drink.resolutions.resolutionsPerAuthority[0].values.length == 1) {
                return intent.slots.drink.resolutions.resolutionsPerAuthority[0].values[0].value.id;
            }
        }
    }
    return undefined;  // 無効な場合は、undefinedを返す
}

'OrderIntent': function () {  
    if (this.event.request.dialogState !== 'COMPLETED') {
        let drink = getDrink(this.event.request.intent);
    // ・・・省略・・・

5 最後に

今回は、スロットに期待する値が入らなかった場合の検出を行ってみました。

ダイアログモデルで必須のスロットを設定しても、無効な値が入ってしまってCOMPLETEDとなるケールが多々あります。そして、このような場合、期待する結果を得ることは難しいでしょう。

幸い、ダイアログモデルでは、dialogStateCOMPLETEDになるまでは、スロット値の書き換えが可能です。強固な実装を目指すには、今回例示したようなバリデーションを使用して、無効な値が入っている場合は、その値を消してしまい、再びユーザーに発話を促すような実装が必要なので無いでしょうか。