Amazon Lexでスロットのバリデーションを行う

2018.01.25

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

渡辺です。

Amazon Lexのスロットを理解するでは、Lexボットでのスロットについて紹介しました。 スロットとは、ユーザから入力されるパラメータのセットです。 例えば、予約インテントであれば、予約日と予約時間が必須パラメータとしてスロットに定義します。

Lexでは、必要なパラメータが揃うまでユーザにプロンプトを表示し、パラメータが揃った時点でLambdaを呼び出すことができます。 これは自動化されており非常に便利です。 一方、細かい制御、例えば値のバリデーション(妥当性チェック)を行いたい場合にはカスタマイズが必要です。 今回は、Lexボットでスロットのバリデーションを紹介します。

バリデーション関数の設定

はじめに、以下のコードでLambda関数(Node.js 6.10)を作成し、バリデーション関数として設定しましょう。

exports.handler = function (event, context, callback) {
    console.log(JSON.stringify(event));
    var response = {
        sessionAttributes: event.sessionAttributes,
        dialogAction: {
            type: 'Delegate',
            slots: event.currentIntent.slots
        }
    };
    callback(null, response);
  };

続けて、Lexボットのインテントでバリデーションを有効にし、Lambda関数を選択します。

これで、ユーザがスロットの値を入力後、Lexが処理する前にLambda関数が呼ばれるようになります。 現在の実装は、Lambdaで何もせずにLexに処理を委譲(Delegate)している状態です。

入力イベントのスロット情報

Lambdaでは、Lexボットからの入力をJSON形式でイベントを受けます。 この入力イベントには、次のように、スロット情報やセッション情報が含まれます。

{
  "currentIntent": {
    "name": "intent-name",
    "slots": {
      "slot name": "value",
      "slot name": "value"
    },
    "slotDetails": {
      "slot name": {
        "resolutions" : [
          { "value": "resolved value" },
          { "value": "resolved value" }
        ],
        "originalValue": "original text"
      },
      "slot name": {
        "resolutions" : [
          { "value": "resolved value" },
          { "value": "resolved value" }
        ],
        "originalValue": "original text"
      }
    },
    "confirmationStatus": "None, Confirmed, or Denied (intent confirmation, if configured)",
  },
  "sessionAttributes": { 
     "key": "value",
     "key": "value"
  }
}

入力イベントから必要なスロット値を取得し、バリデーション(妥当性チェック)を行いましょう。

slots

currentIntent.slots には、スロットの情報が「スロット名: 値」の形式で含まれます。 例えば、DateとTimeがスロット値として定義されているならば、次のようになるでしょう。

    "slots": {
      "Date": "2017-1-25",
      "Time": null
    }

ここではDateはユーザが入力済みなため日付けが設定されていますが、Timeは未入力なのでnullが設定されています。 単純なバリデーションであれば、slotsの値だけで判定できます。

slotDetails

currentIntent.slotDetails には、スロットの詳細情報が含まれます。

originalValueにはユーザが入力したテキストが、そのまま含まれます。 Lexでは、例えば、Dateタイプで「明日」というユーザの入力があった場合、自動的に日付け計算を行い、例えば「2018-1-26」という日付けに変換する機能があります。 ユーザが入力した元の値が、originalValueです。

resolutions配は、スロットで認識された値のリストです。 originalValueからどのように値を解決したのかに関する情報が5個まで含まれます。

出力イベントのdialogAction

レスポンスとなる出力イベントのdialogActionでは、次にユーザが入力する情報についてのヒントをLexに返します。 今回はバリデーションを失敗時に、dialogAction.typeElicitSlotを指定し、ユーザに再入力を求めます。 バリデーションが不要な場合や妥当な値だった場合は、Delegateを指定し、Lexに処理を委譲します(おまかせモード)。

{
    "sessionAttributes": {
    "key1": "value1",
    "key2": "value2"
    ...
  },
  "dialogAction": {
    "type": "ElicitIntent, ElicitSlot, ConfirmIntent, Delegate, or Close",
    Full structure based on the type field. See below for details.
  }
}

妥当性チェックを実装する

実装してみましょう。 ここでは予約可能時間が10時から17時までとしてみます(微妙にバグってますが気にしないでください)。

exports.handler = function(event, context, callback) {
    console.log(JSON.stringify(event));
    var time = event.currentIntent.slots.Time;
    if (time != null && !isValidateTime(time)) {
        event.currentIntent.slots.Time = null;
        var response = {
            sessionAttributes: event.sessionAttributes,
            dialogAction: {
                type: 'ElicitSlot',
                message: {
                    contentType: "PlainText",
                    content: "Sorry, it is out of business hours.\nYou can reserve from 10:00 to 17:00."
                },
                intentName: event.currentIntent.name,
                slotToElicit: 'Time',
                slots: event.currentIntent.slots
            }
        };
        callback(null, response);
    }
    else {
        var response = {
            sessionAttributes: event.sessionAttributes,
            dialogAction: {
                type: 'Delegate',
                slots: event.currentIntent.slots
            }
        };
        callback(null, response);
    }
};

const isValidateTime = function(time) {
    var hour = Number(time.split(':')[0]);
    return 10 <= hour && hour <= 17;
}

dialogActionElicitSlotを指定する場合、intentName, slotToElicit, slotsが必須です。

intentNameは、今回、インテントが変わるわけではないため、現在のインテント名で問題ありません。。 slotToElicitは、入力を促すスロット名(Time)を指定します。 slotsには不正な値をクリアするため、未入力(null)にしておきます。

メッセージは必須ではありませんが、営業時間外であることを伝え、営業時間を入力するように促します。 未指定の場合、Lexボットのスロット定義にあるプロンプトが表示されます。

動作確認

それでは動作確認してみます。

しっかりと、バリデーションが効いていることが確認出来ました。

まとめ

インターフェイスを作成するならば、値のバリデーションは必要不可欠です。 Lexでは必要最小限のコードで、バリデーションを行い、良い感じにユーザに入力を促せることができます。 曜日判定なども行えば、より洗練された予約ボットとなるでしょう。 24/365で受付を行うブラックボットは作らないでくださいね。