DialogFlow Essentials コードを使った複雑なレスポンスの作成方法

こんにちはAIチームの友松です

この記事は以前公開した【AI Shift Advent Calendar 2021】DialogFlow Essentialsの基本動作の解説の続編となっています。まずはそちらを読んでいただいてからこの記事をご覧いただくと内容の理解がしやすいと思います。

本日はDialogFlow Essentialsでのコードを使った複雑なレスポンスの作成方法について解説します。

1. オウム返し

まずは、チュートリアルとしてよく用いられるユーザの発話をそのまま引用して応答文に含めるやり方について解説します。

インテントやコンテキスト、エンティティの概念は前回の記事をご参照ください。ここでは、上図のoumugaeshiインテントに遷移した際に、遷移情報を用いてコードによる応答の記述方法について説明します。

1.1 動的レスポンスの構成

コードによる応答記述は、Webhookサーバーを用意することで実現します。

今回はDialogFlowと同じGCP環境で扱えるGoogle Cloud Functionsを用いてWebhookサーバーを構築していきます。なお、Google Cloud Functionsの詳細な説明はこの記事では省略します。

Webhookサーバーにリクエストを送るために、Google Cloud Functionsにて用意したエンドポイントをDialogflow EssentialsのFulfillmentという設定項目にURLを設定します。

1.2 DialogFlowの設定

Welcome Intent

Welcome Intentは前回の記事のものを踏襲しているので説明は省略します。

oumugaeshi Intent

oumugaeshiインテントでWebhookとの接続について説明していきます。

まず、TrainingPhraseはどんな文章でも受け取るようにしたいので、@sys.anyというシステムエンティティを指定します。

Responseは、Webhook Callの場合は使われないのですが、万が一エラーが起きた場合にこちらのテキストが使用されます。

最後にFulfillmentのEnable webhook call for this intentにチェックを入れます。このチェックを入れることでこのインテントが発火した際にWebhookサーバーに応答文を要求します。

1.3 オウム返しコードの実装

DialogFlowのWebhookのドキュメントはこちらを参照ください。下記のコードをもとに解説します。

import json

def main(request) -> str:
  body = request.get_json() # Request Body
  intent_name =  body["queryResult"]["intent"]["displayName"]

  if intent_name == "oumugaeshi": # Enable webhook call for this intent.を指定したintent名
    return fulfillment_oumu(body)
  else:
    raise Exception

def fulfillment_oumu(body):
  session = body["session"] # sessionId
  query_text = body["queryResult"]["queryText"] # ユーザーが発話した内容
  fulfillment_text = f"お問い合わせ内容は「{query_text}」ですね。"

  return json.dumps(
    {
      "fulfillmentText": fulfillment_text,
      "outputContexts": [
        {
          "name": f"{session}/contexts/oumugaeshi",
          "lifespan_count": 1,
        },
      ]
    }, 
    indent=2,
    ensure_ascii=False
  )

main関数

Webhookから受けたrequestはmain関数の中で処理が行われます。この関数の中では、どのインテントが発火して送られてきたリクエストかによって、処理先を振り分けています。(今回は一つのインテントしかないが、複数箇所設定した場合に役に立ちます。)

fulfillment_oumu関数

こちらの関数のなかでoumugaeshiインテントに対するレスポンスを記述していきます。

Request Bodyからユーザーの発話内容であるquery_textを参照し、応答文として発話させたい内容をfulfillment_textとして設定します。

OutputContextsはコンテキストのリストを返すようになっており、コンテキストはname(必須), lifespanCount(必須), parameter(任意)で構成されます。

コンテキストの概念についても前回記事をご参照ください。また、今回の例では用いていませんが、コンテキストにparameterを設定することでDialog Flowの後続の処理で値を参照することができます。

1.4 挙動の確認

DialogFlowのコンソールで設定した項目のレスポンスが返ってくるか試します。

Welcome Intentの挙動については省略します。

今回設定したoumugaeshiについて見ていくと、responseの文章としてお問い合わせの内容は「お名前を教えて下さい」ですね。のように、ユーザーからのテキストを含めたレスポンスを作成できています。

また、CONTEXTSの部分を見てみると、コードで設定したoumugaeshiというコンテキストが設定されているのがわかります。

2. 営業時間判定

次の例として、予約時間を受け取って営業時間内か営業時間外かによってレスポンスの文章を変えたり、付与するコンテキストを変更して、結果として遷移先を変える動きについて実践します。

2.1 DialogFlowの設定

エンティティ

ユーザーが発話した時間を捉えるエンティティとしてhourエンティティを正規表現のカスタムエンティティとして作成します。

※今回は簡単化のために、0-23時以外の時間が発話されるケースは考慮しないものとします。

※また、システムエンティティで日時を扱えるものもありますがこちらも簡単化のために使用しません。

インテント

Welcome Intent

Welcome Intentは先程の例のResponse文のみ変更しています。

Request Reservation Hour Intent

Request Reservation Hour IntentはWebhook Callの設定を入れたIntentになります。parameterとして先程作成したカスタムエンティティのhourを受け取り、hourが営業時間内(10時〜19時)か営業時間外(それ以外)かによってWebhookサーバー側でレスポンスを生成します。

2.2 営業時間判定コードの実装

import json
import re

def main(request) -> str:
  body = request.get_json() # Request Body
  intent_name =  body["queryResult"]["intent"]["displayName"]

  if intent_name == "Request Reservation Hour Intent": # Enable webhook call for this intent.を指定したintent名
    return fulfillment_request_reservation_hour(body)
  else:
    raise Exception

def fulfillment_request_reservation_hour(body):
  session = body["session"] # sessionId
  hour = body["queryResult"]["parameters"]["hour"] # ユーザー発話から抽出したhourパラメータ
  hour_int = int(re.search(r'\d+', hour).group()) # ◯時の表現から数字部分を抜き出す

  if hour_int >= 24:
    raise Exception
  elif 10 <= hour_int <= 19:
    fulfillment_text = f"{hour_int}時ですね。こちらは営業時間内です。予約の空き状況を確認致します。"

    return json.dumps(
      {
        "fulfillmentText": fulfillment_text,
        "outputContexts": [
          {
            "name": f"{session}/contexts/during_business_hours", # 営業時間内コンテキスト
            "lifespan_count": 1,
          },
        ]
      }, 
      indent=2,
      ensure_ascii=False
    )
  else:
    fulfillment_text = f"{hour_int}時ですね。こちらは営業時間外なので受付できません。"

    return json.dumps(
      {
        "fulfillmentText": fulfillment_text,
        "outputContexts": [
          {
            "name": f"{session}/contexts/after_business_hours", # 営業時間外コンテキスト
            "lifespan_count": 1,
          },
        ]
      }, 
      indent=2,
      ensure_ascii=False
    )

main関数

main関数の役割は先程のオウム返しの例と同じく、intent名によって関数に振り分けています。

fulfillment_request_reservation_hour関数

この関数では、まずparameterの◯時という表現から数字部分を抽出しhour_intに代入します。hour_intの値によってfulfillmentText(レスポンス文)とoutputContextsを変更します。outputContextsを変更することで後続の状態遷移先を変えることができます。

2.3 挙動の確認

Webhook Callを設定したRequest Reservation Hour Intentについて見ていきます。

ユーザー発話として10時を受け取った場合は営業時間内の判定となっており、レスポンス文やContextにduring_business_hoursが入っていることが確認できます。

ユーザー発話として19時を受け取った場合は営業時間外の判定となっており、レスポンス文やContextにafter_business_hoursのものが入っていることが確認できます。

3. おわりに

今回は、Webhookサーバーと接続して複雑なレスポンスを作成する方法について解説しました。コードを使用することでレスポンス文や遷移先を変えることができます。例えば予約システムとAPI連携をし、予約の空き状況を参照しながら対話状態を遷移させ、予約の実行を行うというったかなり複雑な処理も行うことが可能となり、対話の幅を一気に広げることができます。

次回はeventの取り扱いについて解説できたらと思います。

ご覧いただきありがとうございました。