プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

Dialogflow のチュートリアルを試してみた : events を使って書き換え (2/2)

Dialogflow のチュートリアルを試した続きです。

blog.mori-soft.com

上の記事で Dialogflow のチュートリアルを試しまして。ここでは、 context と fulfillment を使って、会話の流れを制御しています。

個人的には、このスタイルだと、 context を操作しているのが、 Intent の output context の箇所と fulfillment のコードに分かれていて、ちょっと気持ち悪いなと感じています。

Dialogflow のドキュメントを読むと Events という機能があり、ユーザー定義の Custom Event も扱えるようです。

そこで、この Events 機能を使い、context を直接操作するのではなく、 Event により Intent を切り替えて、 Intent の output context でコンテキストを制御するようなスタイルにしてはどうかと考えました。そうすると、

  • contexts : Intent で制御
  • events : fulfillment で制御

と分けることができるので、すっきりするかな?という目論見です。 ということで、やったことをメモっときます。

Events 機能について

Events 機能そのものについては、公式ドキュメントなどを参照してください。

あと、下記の記事なども参考にさせていただきました。

Dialogflowのeventを使ってみる。 - Qiita

Events を使った場合の処理

元々のチュートリアルでは、下記のところで context を切り替えていました。

  • fulfillment の checkAppintment 関数:予約が埋まっていた場合に context を reservation-followup から reservation-suggestion に切り替え
  • fullfillment の suggestAppintment 関数:『提案』機能をユーザーが了解したのち、 context を reservation-suggestion から reservation-followup に切り替え(元の予約のコンテキストに戻す)

これを Events を使う形にしたいと思います。

checkAppointment 関数でのイベント

Webhook からイベントを投げるには、 followupEventInput オブジェクトをレスポンスに追加してあげればOKです。
ここでは、 イベント名 suggest をパラメータ付きで投げます。

  if (flg) {
    response.fulfillmentText = `了解しました。 ${date} 日の ${time} 時の予約でよろしいでしょうか?`;
  } else {
    // event を発行
    response.followupEventInput = {
      name: "suggest",
      languageCode: "ja-JP",
      parameters: {
          date: date,
          time: time,
          suggested_time: "2019-07-22T13:00:00+9:00"
      }
    }

suggest イベントを受け取る Intent の追加

投げられた suggest イベントを受け取る Intent を新規に追加します。この Intent の名前を suggestion とします。

f:id:junichim:20190627143659p:plain

Intent を作成したら、 Events を開いて、『suggest』と入力して、保存すればOKです。これで、 suggest イベントが投げられれば、この Intent が呼ばれます。
また、今回はユーザー入力ではなくイベントにより呼び出すため、 Training phrases は空欄のままとします。

次に、 suggest イベントと共に渡されるパラメータを受け取るために、下記のようにこの Intent に対してパラメータを設定します。

f:id:junichim:20190627114054p:plain

パラメータの設定において、 Value 列に対して #event_name.parmeter_name という形式を指定することで、イベントパラメータで指定されたパラメータを Intent のパラメータとして扱うことが可能になります。このようにすると、この Intent の followup intent を定義した際に、パラメータを参照することが可能になります。

なお、Parameter Value については公式ドキュメントのこちらの記事などに詳しく記載されています。

Response の欄には、 suggest イベント発行により、この Intent が呼ばれた際に表示したい Response テキストを設定しておきます。

f:id:junichim:20190627114137p:plain

『最初に指定した日時が埋まっていたので、別の日程を提案しますよ』、という旨の文章を表示するようにします。

なお、この Intent に対する fullfilment は不要なので、 fulfillment は無効のままとしておきます。

suggestion インテントの followup インテント

上記の suggestion インテントへの応答を受け取るために、yes および no の followup インテントを作成しておきます。

f:id:junichim:20190627114638p:plain

reservation の時と同じく yes の followup インテントについては fulfillment を有効にしておきます。

suggestAppointment 関数でのイベント

この関数は、 suggest 処理で別の予約日時を示して、ユーザーが提案を受け入れる、とした際に呼ばれる処理になります。 イベントを使った場合は、 suggest インテントに対して、ユーザーが肯定の応答をた歳の、 suggestion - yes の Intent に対して呼ばれることになります。

なので、最初に Intent に応じて振り分ける処理を修正します。

    // intent と対応するハンドラ関数のマップを生成
    let intentMap = new Map();
    intentMap.set('reservation', checkAppointment);
    intentMap.set('reservation - yes', makeAppointment);
    intentMap.set('suggestion - yes', suggestAppointment);

chckAppointment 関数と同様に、イベントを投げるには、 followupEventInput オブジェクトをレスポンスに追加します。
今回は、イベント名 suggest-agree をパラメータ付きで投げます。

  // event を発行
  response.followupEventInput = {
    name: "suggest-agree",
    languageCode: "ja-JP",
    parameters: {
        suggested_time: suggested_time
    }
  };

なお、この関数の先頭で context を取得する際は、 suggestion-followup になるので、そこも修正しておきます。

async function suggestAppointment() {

  console.log("suggestAppointment called");

  const context = getContext(response, "suggestion-followup");

suggest-agree イベントを受け取るインテント

最後に、 suggest-agree イベントを受け取るインテント suggestion-agree を定義します。

f:id:junichim:20190627143925p:plain

suggest インテントと同様に、パラメータを定義して、イベントで投げられたパラメータをこの Intent のパラメータに割り当てます。

f:id:junichim:20190627141513p:plain

なお、fulfillment で context を切り替える方式だと、ユーザーに返す応答文も fulfillmentText で一緒に定義していましたが、イベントの場合は呼び出された Intent 側で設定する必要があります。

また、この Intent の output context を reservation-followup とすることで、提案がない場合のコンテキストへ戻すことを行っています。

動作

全体の動きは、チュートリアルと同じなので割愛します。イベントを使った処理の部分だけ、見ておきます。

ユーザーが『予約をしたい』と入力して、日付と時間も指定後、fulfillment 処理の中で、checkAppointment 関数がよばれます。 checkAppointment 関数では、(本来なら予約の有無を確認しますが、ここでは乱数で決めている)予約が取れない場合に、suggest イベントを発行します。

f:id:junichim:20190627142518p:plain

すると、上記のように(suggest イベントが発行されることで) suggestion インテントが呼ばれます(画面からはイベントが発行されたことはわかりません)。画面上では、想定していた Intent および Parameters の欄が想定通りになっていることが確認できます。 また、 context は suggestion-followup になり、返答を受け取る準備ができていることがわかります。

ここで、提案を受け入れるため、『はい』などとユーザーが入力すると、

f:id:junichim:20190627145600p:plain

のように、応答メッセージが表示されます。また、通常の応答処理になるように context が reservation-followup に切り替わっていることがわかります。

これで、fulfillment 内の context 切り替えで行っていた処理を events を用いて代替することができました。

ソース等はリポジトリをご覧ください。

比較

さて、公式チュートリアルを、 context の切り替えと events を用いた方法の2つで同じ処理を実現してみました。

当初のイメージとしては、後者のほうが見通しが良くなるのではないか?というのがあったのですが、実際に両方試した印象としては、どっちもたいして変わらないかな、という感じです (全然参考になってなくて、すいません・・・)。

元々、context と event の用途が違うためかもしれません。

ドキュメント等を読むと、context は文脈を定義し、その文脈を利用することで同じユーザー入力を区別するために用いるとあります。 一方、 event はユーザー入力を介せずに、Intent を呼び出すためにあります。

となると、これは比較するために用いた例が、違いを際立たせないという意味で、あまり良くなかったのかもしれません。 難しいなー。

ちなみに、前者はチュートリアルのタイトルが、『 Build an agent from scratch using best practices 』となっているので、このような例の場合は context の切り替えがベストなんだろうか?という気もしてます。

正直、もうちょっといろいろと組まないと使い分けの勘所は見えなさそうです。

まとめ

まあ、そうはいっても、これで一通り Dialogflow を使ってみることができました。このチュートリアルでは Custom Entities などは試していませんが、そんなにややこしい印象はないので簡単に試せるかと思います。

さて、Dialoflow を使う準備ができてきたので、おいおい LINE WORKS でのボットの改良を進めようかと思います。