チャットボットを作ろう・・Dialogflow


今は大変便利な世の中で、サーバレス、ノンコーディング、ブラウザ上の操作のみで、オリジナルのチャットボットが作れます。
それも既に普及しているUIを活用して実装できるのが凄いところです。
例えばAmazonのチャットボット構築基盤であるLexはAlexaと統合できます。
GoogleのDialogflowなら作成したボットとGoogle AssistantやGoogle Homeを使って会話ができます。
ほかにもWeb、LINE、FacebookMessenger、Twitterなど様々な既存のサービスと連動できるので敢えてUIを作る必要がありません。

もはや鮮度の低い記事かもしれませんが、今回Dialogflowを触る機会があったので、作り方を簡単に解説してゆきましょう。
とても簡単にオリジナルチャットボットが作れるのにメニューが解りにくくて損をしているので、ここではなるべく簡潔に説明するために最低限必要な機能だけを解説します。
こんな機能もあるよとか、こんな風に設定したら便利だよといったことがあれば是非フォローアップをお願いします。

事前準備(設計はしっかりやりましょう)

さてシステムにログインする前にやっておかなければいけない重要なことがあります。
どんな目的でチャットボットを作成するのか、その目的を達成するためにはどんなバリエーションの会話が必要か、ボットの人格はどうするのか、他のシステムとの連動はあるのか等々です。
システム設計をちゃんとやっておかないとグダグダになりますからね。
例えば江ノ島の観光案内をしてくれるボットを作るとしましょう。
まず当たり障りのない挨拶から始まって、相手の名前や趣味嗜好を聞き出し、それによってレコメンデーションするといった味付けが必要になります。
或いはレストランとかカフェとかビューポイントとかアクティビティといったカテゴリを選んでもらうとかいった対話形式をうまく設計しておくことも重要になります。
この対話の流れを目的ごとに分割しておきましょう。
今回は単純に以下のような分割を行います。

-挨拶
-相手の情報ヒアリング。
-相手の情報に応じて質問を分岐。
-どんなことに興味があるのか聞き出す。
-興味ごとのレコメンデーションと対話
-予約処理

等々です。
考えられる会話の目的や意図を具体的に列挙して、問いと回答を一対にして分解してゆきましょう。
更に列挙した会話を流れに応じてツリー階層化しておきましょう。
それから会話からキーワードを抽出して同義語や類義語を列挙しておくとその後の設定をスムーズに進めることができます。

メニューの概要

さあDialogflowにログインしてみましょう。
Googleアカウントがあればサービスにログインできます。

メニューはわかりにくいです。
しかしここで覚えておくべきはたったの4つです。

-Intents
-Entities
-Fulfillment
-Integration

まずIntentsはユーザーの問いかけとそれに対するボットのレスポンスを登録する機能になります。
1つの問いかけとレスポンスを1つのIntentとして登録し、これを階層化したり繋いだりすることで全体の対話を作り上げてゆきます。
1つの問いかけとレスポンスとはいっても、さまざまなパターンや言葉の揺らぎがあるので、各Intentでは想定される会話ごとの言い回しのバリエーションとそのレスポンスを複数登録します。
先ほど対話の流れを目的別に分割しておくと書きましたが、このIntentsがその分割単位だと思っていただければ良いかと思います。
同じ名前のIntentを分割して階層化してゆく方法と別の名前で作って後で繋げる方法があります。
エンジニアの皆さんには同じ名前のIntentsの塊がClassでその中の個別のIntentがメソッドだと思えば解りやすいかと思います。

次にEntitiesですが、会話の中に出てくる単語に対して、同義語や類義語を登録する機能です。
またこのEntitiesが変数のような役割を持っていて、会話の中で出てきたキーワードをもとにEntitie名に応じたレスポンスを返すことになります。
またこのEntitiesはチャットボットにとってのPARAMETERになり、このPARAMETERに応じてその後の会話のスレッドを分岐したり情報を収集します。
Entitie名とシノニムは1:1またはnのPARAMETERとVALUEの関係となります。
冒頭で会話からキーワードを抽出して同義語や類義語を列挙しておくと書いたのはEntitiesをスムーズに登録するためです。

このIntents とEntitiesの組み合わせで会話のバリエーションを何通りにでも膨らませることができるので、少ない工数で多くの会話のバリエーションを登録することができるのです。
Dialogflowにおける主な作業はこのIntents とEntitiesの登録になりますので、この二つの機能をしっかり押さえておけばあとはオマケみたいなものです。
より多くの言い回しのパターンを登録しておくと、あとはDialogflowのAIが保管して多少の揺らぎは吸収してくれるようになります。

FulfillmentはWebhookとInline Editorの二種類の機能が提供されています。
それぞれIntentsで有効にすると機能します。
Webhookの方は外部のWebサービスに対しPOSTリクエストを送信する機能です。
対話の内容をJSONで吐き出して他のWebサービスのAPIと連携してより詳細な情報を提供したり、予約情報等を外部のDBに登録することができます。
Inline Editorの方はFirebaseを活用するための機能です。Inline Editor上における僅かな修正でFirebaseと連動して完全なサーバレスを実現できます。

IntegrationsはUIを選択するための機能になります。
例えばWebのチャット、FacebookMessenger、Twitter、Slack、LINE、Skypeといった既存のUIを利用してボットと会話できるようになります。

登録してみよう

それでは実際に登録してみましょう。
先ずはagentを作成しましょう。
Create new agentで適当な名前を登録します。
ここではEnoshimaという名前を付けてみました。
このagentは用途に応じて複数登録できます。各々のagentは作成したEntitiesを共有できますので、複数作られる場合は共有を念頭に置いたEntitiesの登録を心掛けると良いでしょう。
これで最低限の準備が整いましたので、会話を登録してゆきましょう。
実は登録の順番は決まっていません。
Intentsから入ってもいいし、事前にシナリオがしっかり固まっているのであればキーワードも抽出できていると思いますのでEntitiesを先に一括登録しても良いと思います。
最初は試行錯誤しながらになると思いますので、今回はIntentsから登録してゆきましょう。

Intents

メニューからIntentsを選びます。
するとDefault Fallback IntentとDefault Welcome Intentという二つのIntentがデフォルトで用意されています。

Default Fallback Intentの方はシステムが言葉を理解できなかった場合に返すレスポンスになります。
実際に開いて見るとText Responseにいろんな文言が記載されているのを確認できます。
『もっとハッキリ喋ってくれよ!わかんねーよ』みたいなオリジナルの文言を登録することもできます。

Default Welcome Intentはその名の通り挨拶というか導入部分になります。
DialogflowはデフォルトでこのDefault Welcome Intentからスタートする設定になっていますので、Default Welcome Intentをカスタマイズするところから始めましょう。
勿論スタートするIntentをオリジナルのものに変更することも可能です。
Default Welcome Intentをクリックしてみてください。
上から順にContexts、Events、Training phrases、Action and parameter、Responses、Fulfillmentという項目がありますが、全部説明すると混乱するのでTraining phrasesとResponsesだけに着目してください。
Training phrasesがユーザーからの問いかけでResponsesはボットからの回答になります。

Training phrasesにはなるべく考えられる問いかけのパターンを数多く登録した方が良いです。
これをもとにAIが多少の揺らぎは吸収してくれる仕組みになっています。
Training phrasesに挨拶が登録されていますが、『押忍』とか『こにゃにゃちわ』とか追加登録すれば反応するようになります。
Responsesには複数登録できます。
複数登録されているとどれかをランダムで返してくるようです。
たくさん登録しても返ってくるレスポンスはひとつだけなので、言い回しが違うだけで基本的には内容が同じものを登録します。
ここでは会話を続けたいので名前を訊いてみましょう。
Responsesに『こんにちは!はじめまして。お名前を教えてください。』と入力します。
一旦Intentsの一覧に戻り、リストの右端をマウスオーバーするとAdd follow intentというワードが表示されるのでクリックするとコンテキストメニューがポップアップして、次の階層の会話を登録できます。

デフォルトで色々入っていますが、見分けがつけば良いので何を選んでも問題ないと思います。
こんな感じで新たな階層が登録されるので、クリックして編集を行います。

System Entities

名前を訊かれたので教えてあげましょう。
『佐藤です』『佐藤だよ』『佐藤でごわす』みたいな感じでバリエーションを登録します。
登録後佐藤の部分を範囲指定するとポップアップメニューが出ます。

これはEntitiesの一覧で、佐藤さんに対して選んだEntitiesがマッピングされます。
システム側でデフォルトのEntitieが登録されているので、今回はsys.last-nameを選んでみましょう。
これによって佐藤さんの代わりに@sys.last-nameがparameterとしてマッピングされるので、『鈴木でごわす』でも『西郷でごわす』でもシステム側が名前として理解できるようになります。
Action and parametersにはマッピングしたEntitiesが一覧表示されます。
続けて出身地を訊いてみましょう。
折角名前を訊きだしたので、名前でレスポンスします。
Action and parameters のVALUEの部分をText Responseの名前の部分にマッピングします。
『ようこそ$last-nameさん。素敵な名前ですね。どちらのご出身ですか?』
こんな感じです。

続けて次の階層のIntentを作成してTraining phrases で『東京です。』『東京だよ』といった感じで登録して、東京の部分を範囲指定してSys.Geo-StateというEntitieをマッピングしてみましょう。
これで沖縄でも北京でもプノンペンでも受け付けるようになります。
『$geo-stateのご出身ですか。江の島に興味はありますか?』といったようなレスポンスにして、次の階層ではyes,noに応じて会話の流れを分けるといったことが可能になります。
冒頭で書いた通り、会話をツリー階層で分類する必要性はこうしたスレッドの登録をスムーズに行わせるためです。
ここで各IntentのContextsをご覧ください。

input元とOutput元のIntentが登録されていると思います。
ContextsはこのようにIntentとIntentを関連付けるために使用します。
一見同じ名前でスレッドを分割しているように見えて実は異なるIntentsをContextsで繋いでいるわけです。

ユーザー定義のIntentsとCUSTOM Entities

次にユーザー定義のIntentsとEntitiesを作成してみましょう。
江の島のどんなところに興味がありますか?という問いに対してユーザーに答えさせて、そのカテゴリから色々レコメンデーションするようなものを想定し、Categoryという名のIntentを登録してみます。
先に書いた通りContextsでinputContexts outputContextsを登録することで他のIntentsと連携できるのでDefault Welcome Intentで作成した繋げたい階層のIntentと繋いでおきましょう。
ユーザー定義のIntentsは話の流れに応じて異なるスレッドから連結できるので、目的別に分けて設計しておけば後から繋ぎ変えることができて便利です。
今回はあまり深く考えずに自然、アクティビティ、歴史、観光、グルメといったカテゴリごとにカスタムEntitieを登録してみましょう。
『自然に興味あり』とか『アクティビティ』に興味ありなどTraining phrasesに登録して自然やアクティビティを範囲指定します。
この場合、カテゴリによってはその後異なるスレッドになる可能性があるので、それぞれ異なるEntitieで登録します。
会話のスレッドが分岐しない場合は一つのEntitieとして登録してもOKです。
冒頭で書いた通りEntitie名とシノニムはPARAMETERとVALUEの関係なので、Dialogflow内で分岐したり何かを行いたい場合はPARAMETERを分けて、そうでない場合は分けないといった設計の段階での判断が重要になります。
範囲指定したらポップアップメニューの一番下から+Create newをクリックしましょう。

するとEntitiesの登録画面に遷移します。
ここでアクティビティならactivityとEntitie名を付けて、あとはシノニムつまり同義語や類義語を登録してゆきます。
例えばサーフィン、ウィンドサーフィン、ヨット、ディンギー、フィッシング、船釣り、磯釣り、海水浴・・・といった感じです。

ほかのカテゴリについても登録してゆきましょう。
この後は各カテゴリごとに異なるスレッドで情報提供をしたり予約処理をしたりといった流れになります。
その際に必要になってくるのがFulfillmentです。

Fulfillment

Intentsの一番下にFulfillmentを有効にするトグルがありますので、データを送信したいIntentはこれをOnにします。
またサイドメニューのFulfillmentにおいてもWebhook,InlineEditorのいずれかをENABLEDにします。

はい。ここまでできたら右端上部のTry it nowに『こんにちは』と入れてみましょう。
するとシミュレータが反応するはずです。

ユーザー:こんにちは

ボット:こんにちは!はじめまして。お名前を教えてください。

ユーザー:田中です。

ボット:田中さんようこそ。ご出身はどちらですか?

ユーザー:神奈川です。

ボット:へー神奈川県産まれなんですね。江の島に興味はありますか?

ユーザー:はい

ボット:それは素晴らしいです。江の島のどんなところに興味がありますか?

ユーザー:サーフィンに興味があります

こんな会話が成立するはずです。
それぞれのIntentでEntitieが設定されているところはPARAMETERとVALUEが採れていることを目視確認できるはずです。
この状態で一番下のDIAGNOSTIC INFOをクリックしてみてください。
以下のようなJSONが出力されると思います。

{
  "responseId": "8349636a-xxxx-xxxx-xxxx-xxxxxx",
  "queryResult": {
    "queryText": "サーフィンに興味があります",
    "parameters": {
      "Interest": "に興味",
      "activity": "アクティビティ",
      "Sightseeing": "",
      "Nature": "",
      "Gourmet": "",
      "history": "",
      "shoping": ""
    },
    "allRequiredParamsPresent": true,
    "fulfillmentMessages": [
      {
        "text": {
          "text": [
            ""
          ]
        }
      }
    ],
    "outputContexts": [
      {
        "name": "projects/manual-twiehd/agent/sessions/xxxxx/contexts/category",
        "lifespanCount": 4,
        "parameters": {
          "Sightseeing.original": "",
          "Interest.original": "に興味があります",
          "last-name": "田中",
          "Sightseeing": "",
          "Nature.original": "",
          "Nature": "",
          "last-name.original": "田中",
          "geo-state": "神奈川県",
          "history": "",
          "shoping": "",
          "history.original": "",
          "shoping.original": "",
          "Gourmet": "",
          "Gourmet.original": "",
          "geo-state.original": "神奈川",
          "activity.original": "サーフィン",
          "Interest": "に興味",
          "activity": "アクティビティ"
        }
      }
    ],
    "intent": {
      "name": "projects/manual-twiehd/agent/intents/xxxxx",
      "displayName": "Category"
    },
    "intentDetectionConfidence": 1,
    "diagnosticInfo": {
      "webhook_latency_ms": 4988
    },
    "languageCode": "ja"
  },
  "webhookStatus": {
    "code": 4,
    "message": "Webhook call failed. Error: DEADLINE_EXCEEDED."
  }
}

神奈川の田中さんは江の島でサーフィンがやりたいことがわかります。
更にここから初心者か上級者か、スクールにはいらないか、いつ予約する?といった会話に繋げることができます。
FulfillmentでWebhookが有効になっていればこのようなJSONデータを外部サーバのAPIにPOSTして更にレスポンスを受けることができます。
ここでより多くの情報を提供したり、予約をとったり、アンケートを実施したりといった様々な仕掛けを投入することが可能になります。
因みにContextsが繋がっていないとJSONはIntents ごとにぶつ切りになるみたいです。
このようにFulfillmentを使うと色々できますが、勿論Fulfillment無しで単純に会話を楽しむのでも構いません。

Integrations

最後にIntegrationsです。

Webデモの設定

先ずはWebデモを有効にしてみましょう。トグルをオンにするだけです。
Webデモをクリックするとiframeのソースが表示されます。

<iframe
    allow = "microphone;"
    width = "350"
    height = "430"
    src = "https://console.dialogflow.com/api-client/demo/embedded/xxxxx">
</ iframe>

これをhtmlに張り付ければ自分のサイト上にチャットボットを配置できます。
簡単ですね。

Google Assistantからの呼び出し

Google Assistantとはデフォルトの状態で接続されています。
ただ呼び出し方法を設定しないと作成したものをGoogle AssistantやGoogle Homeから呼び出すことができません。
https://console.actions.google.com/
Google AssistantのActionConsoleに接続してEnoshimaの名前でNewProjectを登録します。
言語の指定もしておきましょう。
TOP画面にProjectの一覧としてEnoshimaも表示されるので、クリックしてDevelopを選択します。
ここで呼び出しに使う名前を登録します。
例えばEnoshimanと登録し、念のため日本語の読みも追加登録します。
最後にOverviewのリリースの設定を行えば、共有することができます。
仲間内で楽しむだけならAlphaとかBataで十分でしょう。
これでGoogle Assistantにおいて『Enoshimanと繋いで』と呼びかければ許可された範囲のユーザーが呼び出せるようになります。

はいそんなわけで簡単ではありますがチャットボットの作り方でした。
いろんな機能や設定を端折ってはいますが、これで最低限のものは作れると思います。
もっと細かい機能はenoshimanも把握していないのでご自分で調べてみてください(笑)
でわでわ・・