Dialogflow を使った HangoutsChat Bot 作成

GSuite のチャット機能である Hangouts Chat の Bot と Dialogflow の自然言語処理を組み合わせてちょっとした社内情報を聞いたら教えてくれる QA Bot を作成してみます。

※Dialogflow とは
自然言語処理を行えるGoogle Assistant アプリ作成時にも多く使われているものです。

Hangouts Chat の Bot を作成するには、GCPから Hangouts Chat の API を有効にします。
更に HangoutsChat Bot と Dialogflow とを連携するには、HangoutsChat API 設定の接続設定で"Dialogflow"を選択します。
GCPのプロジェクト作成、DialogflowのAgent作成の効率的な順序は後述したいと思います。

またHangoutsChat Botに対してチャットが送信された際のやり取りは下記のようになっています。

1. HangoutsChat Bot に対してチャットが送信される
2. Dialogflow が自然言語で入力されたチャット内容から Intent・Entity の振り分け
3. Dialogflow から Webhook 先のバックエンドに処理を渡す
4. バックエンドが渡された Intent と Entity に応じて Bot の返答を Dialogflow に返す
5. HangoutsChat Bot が返答をする

上記のやり取りのポイントは、Dialogflow がバックエンドとやり取りをして受け取ったレスポンスを HangoutsChat Bot に渡しているという部分にあります。

これは Dialogflow を使わず GCP の Cloud Functions を利用して HangoutsChat Bot を作成した場合とは異なった流れになります。

HangoutsChat Bot に直接レスポンスを渡しているのか、Dialogflow を通してレスポンスを渡しているのかが違います。そのことを覚えておいてください。

# 作成手順

1. GCP でプロジェクトを作成
2. HangoutsChat API を有効化
3. Dialogflow で Agent 作成
4. Intents / Entities 作成
5. Dialogflow のバックエンドを作成
6. HangoutsChat Bot を 公開

上記の順に作成を行っていきます。
GCP、Dialogflow、バックエンドと作ります。バックエンドは今回 Firebase で作成しています。

詳細な作成方法を下記していきます。

## GCP でプロジェクトを作成

HangoutsChat Bot は、[GCP](https://console.cloud.google.com/home/dashboard) から作成していきます。
プロジェクトのプルダウンをクリックし、プロジェクト一覧を開き、「新しいプロジェクト」からプロジェクトを作成します。

## HangoutsChat API を有効化

プロジェクトを作成後、メニューから「APIとサービス」を選択します。
ダッシュボードの「APIとサービスを有効化」をクリックし、遷移した画面で"HangutsChat API"を検索します。
HangoutsChat APIの画面が開いたら、「有効にする」をクリックします。

<img width="963" alt="Screen Shot 2020-03-11 at 11.09.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/5603177b-79fd-fac2-83ac-ce5a935a4a2c.png">

API 有効後"設定"を行うことができますが、Dialogflowを作成後、設定が上書きされてしまうので、設定は後で行います。

## Dialogflow でエージェント作成

Dialogflow エージェントは、エンドユーザーとの会話を処理する仮想エージェントになります。

[Dialogflowのコンソール](https://dialogflow.cloud.google.com/#/getStarted)で、"Create Agent"からエージェントの新規作成を行います。

エージェント作成時、"GOOGLE PROJECT"で先程GCPで作成したプロジェクトを指定します。

<img width="1013" alt="Screen Shot 2020-03-17 at 10.11.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/6ebcb83b-ae41-b69a-b59e-b47c6e46afec.png">


## Intents / Entities 作成

基本的にGoogleHomeアプリを作る時と同じです。
Entitiesには、チャットボットにヒットさせたい"語群"を登録して、更に入力した内容にヒットしやすいよう、Synonym（同義語や送り仮名違いなど同一分類の言葉）を登録して、"丸め"を行います。

|検索ワード|Synonym|
|:---|:---|
|テレワーク|テレワーク, 在宅勤務|
|仕入|仕入, 仕入れ, 仕入実績|

Intentsには、同じことを意図する文章でも言い方が異なる場合があるので、そのパターンを登録します。

|文章|パターン|
|:---|:---|
|テレワーク|テレワークを検索, テレワークについて, テレワークについて教えて, テレワークのやり方|

## Dialogflow のバックエンドを作成

DialogflowでFullfillmentのWebhookを設定する前に、バックエンドを作成しておきましょう。
今回はメンテナンスサイトも合わせて作りたいので、Firebase Hosting でサイトのホスティングも簡単にできるFirebaseで行います。サイトで更新した内容もFirebase Realtime Databaseを使えば、他にメンテナンスしている人に即時反映されるので、複数人でメンテナンスする場合にも有効に活きてきます。

今回のバックエンドから返すレスポンスは、前述したようにHangoutsChat Botに直接返すのでなく、Dialogflowに返すものになります。

なので、[HangoutsChat Botへ直接返す場合のメッセージフォーマット](https://developers.google.com/hangouts/chat/reference/message-formats)は利用できません。シンプルメッセージやカードといった形のレスポンスは行えるのですが、その中の一部アクションが利用できなくなっていたりします。
直接レスポンスを返す場合とDialogflowを経由してレスポンスを返す場合とではチャットボットで表現できるレスポンスに差があるのを覚えていてください。

Dialogflowを経由してHangoutsChat Botにレスポンスを返す場合に参照するドキュメントは、[Dialogflow API(RPC API) - Package google.cloud.dialogflow.v2でWebhookResponse項目](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2#webhookresponse)を確認します。

上記ドキュメントを元に、リンクを持ったカード型の返信をコーディングしていきます。

```javascript:index.js
app.intent('searchIntent', async (conv, { searchlist }) => {
  let word = searchlist;

  // 返信の内容を取得
  let searchResponse = await searchWordFB(word).catch((error) => console.log(`searchWordFB error:${error}`));

  // 返信の内容を設定
  let responseData = conv.json(createResMessage(word, searchResponse));
  return responseData;
}

function createResMessage(word, resultSearch) {
let title = `"${word}" の検索結果`;
   let subtitle = '以下タイトルから資料に飛びます。'

   let BUTTONS = [];

   resultSearch.forEach(result => {
      let button = {
         "text": result.title,
         "postback": result.url
      }
      BUTTONS.push(button);
   });

   return {
      "fulfillment_text": "",
      "fulfillment_messages": [{
         "card": {
            "title": title,
            "subtitle": subtitle,
            "buttons": BUTTONS
         }
      }]
   }
)
```

レスポンスJSONの"fulfillment_messages"下にカード型返信の情報を記載することになります。

<img width="879" alt="Screen Shot 2020-03-17 at 15.57.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/74d892e0-de3f-6ac4-8016-f40c53a9855d.png">

[ドキュメント](https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2#webhookresponse)によると、"fulfillment_text"には、"Message"オブジェクトということで、何をJSONに記載していくのかたどっていきます。

"Message"オブジェクトには下図のような項目があります。
<img width="895" alt="Screen Shot 2020-03-17 at 15.57.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/5652db08-546d-6719-c904-03370683ec1f.png">

テキストタイプ、イメージタイプ、クイックリプライタイプ、ペイロードタイプと複数のレスポンスメッセージの形式がありますが、今回はカードタイプのレスポンスにします。

<img width="886" alt="Screen Shot 2020-03-17 at 15.56.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/93fd260b-c72d-b5fe-d6b1-ba5f15c2d564.png">

カードタイプのメッセージオブジェクトは上図の形で指定しろとのことなので、項目をJSONに追加していきます。バックエンドからHangoutsChatにレスポンスを直返しする際の項目である"cards"や"header"、"sections"は使えないようです。

また"buttons"ですが、クリックして対象の単語をチャットボットに送信するということができないようです。

<img width="888" alt="Screen Shot 2020-03-18 at 13.55.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/83947333-4f17-8d60-5ad8-a4ca0f0541d4.png">

ドキュメント上では、上図のように"postback"の箇所に「The text to send back to the Dialogflow API」と記載してあったのでできるのかなと思ったのですが、、、

URLとして成立していない文字列を"postback"項目の値に設定すると、下図のような空レスポンスになってしまいました。

<img width="385" alt="Screen Shot 2020-03-18 at 13.52.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/c3a3879b-a7c4-e8d3-9836-1b2dcc9c4af9.png">

他にもレスポンスを作成していて、おかしいな？と思ったのは、メッセージオブジェクトの項目に"platform"というのがあり、定数として"GOOGLE_HANGOUTS"というものが用意してあるようだったので、JSONに記載したのですが、記載すると空レスポンスになってしまい、若干ハマってしまいました。HangoutsChat Bot へのレスポンスの場合、Messageオブジェクトに"platform"項目の記載は必要ないみたいです。

※コードの記述が間違っていた可能性もありますので、できたよ！って方は教えていただけるとうれしいです。

## HangoutsChat Bot を 公開

まずはバックエンドをデプロイして発行されたURLをDialogflowのFullfilmentメニューのWebhookに設定します。
Webhookを有効化したら、バックエンドに渡るIntentのFullfilmentのWebhookを有効化します。

<img width="975" alt="Screen Shot 2020-03-18 at 14.43.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/fe9250a2-527c-4f0c-5c90-8d2a626c0671.png">

最後にIntegrationsメニューのHangoutsChatをクリックし、HangoutsChat Botを公開します。

<img width="1304" alt="Screen Shot 2020-03-18 at 14.49.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/06397d1f-bb64-113d-d392-6f08d395589c.png">

HangoutsChat Botの公開範囲を選択し、"START"をクリックすると公開されます。

最後に、Botに関する説明を設定します。
DialogflowでBot公開後に出る"CONFIGURE BOT DETAILS"をクリックします。

<img width="797" alt="Screen Shot 2020-03-18 at 14.51.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/92506/27ddc559-ff02-5992-e90e-6042fddd8da2.png">

GCPのHangoutsChat APIの設定に遷移するので、ここでBotのアイコン、説明、公開範囲の詳細などを設定します。接続設定が"Dialogflow"になっているのも確認しておきましょう。

# まとめ

HangoutChat Botへのレスポンス時にDialogflowを挟むことで、元々できたHangoutsChat Botの返答表現ができなくなっているものが存在しています。特にボタン選択でのチャットボットへのテキスト送信ができなかったのが、対話型のチャットボットを作成する上で致命的なのではないかと感じています。
ただDialogflowを通すことで自然言語で問いかけられた時、同音異義語がある場合、コーディング量が減り、Botの認識率も上げることができます。

HangoutsChatはGsuiteのみの機能で、Botはドメイン内のアカウントにしか共有できない狭い世界のものですが、業務効率化のためなどうまく使っていけたらと思います。