【Dialogflow】一問一答でFAQに答えるチャットボット

最近、Webサイトの右下に出てくるチャットボットをよく見ますよね。
本稿では以前に[AutoMLで分類した飲食店のFAQ](https://qiita.com/mako0715/items/7614e27e98fe950365ac)をDialogflowに取り込んで精度を見てみます。

# 実装方針

- FAQの1つを1つのIntentに割り当てる
- Intent名は連番とする
- レスポンスはLabel名とする

# データのインポートに関する注意

FAQのIntentを手動で投入してくのは骨が折れるので、インポート機能を使います。
しかし、このインポート機能がまた不便で、**JSONファイルしかインポートできません。**
そしてさらに不便なことに、**JSONファイルを一括でアップロードする機能がありません。**
仕方がないので、**Agent単位でインポートする方法をとります。**
AgentファイルをZIPでDLするとIntentフォルダに1Intentに対して、**メタ情報と学習フレーズが別々のJSONファイルで保存されています。**
なので、CSVからこの２つのJSONに変換するスクリプトを書きます。
今回は学習フレーズと回答しか考慮しないため、以下の必要最低限のフィールドのみでJSONを作成します。

| No | Response | UserSays1 | UserSays2 | ... | UserSays10 |
|---|---|---|---|---|---|
| 1 | toilet | お手洗いはどこですか | お手洗いはありますか | ... | トイレはどこ |

のような、1行1IntentのCSVデータから

```json:name.json
{
  "name": "No",
  "responses": [
    {
      "messages": [
        {
          "type": "message",
          "speech": [
            "Response"
          ]
        }
      ]
    }
  ]
}
```

と

```json:name_usersays_ja.json
[
  {
    "data": [
      {
        "text": "UserSays1"
      }
    ]
  },
  {
    "data": [
      {
        "text": "UserSays2"
      }
    ]
  }
]
```
の２つのJSONファイルを作成します。

# GASでJSONファイルを作成

CSVをスプレッドシートにインポートして、[この記事を参考](https://qiita.com/mako0715/items/825001ee2ac5930280f9)にJSONをZIPで固めて保存します。

```js
function toIntents() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const blobs = new Array();
  for (let i = 2; i <= sheet.getLastRow(); i++) {
    const name = ('000' + (i-1)).slice(-3);
    // name.jsonの作成
    const intent = {
      "name": name,
      "responses": createResponses(sheet.getRange(i, 2).getValue()),
    }
    const intentBlob = Utilities.newBlob(JSON.stringify(intent, null, 4),'application/json',name + '.json');
    blobs.push(intentBlob);
    // name_usersays_ja.jsonの作成
    const userSays = createUserSays(sheet, i);
    const userSaysBlob = Utilities.newBlob(JSON.stringify(userSays, null, 4),'application/json',name + '_usersays_ja.json');
    blobs.push(userSaysBlob);
  }
  const folder = DriveApp.getFolderById('hogehoge');
  const zip = Utilities.zip(blobs, 'jsons.zip');
  folder.createFile(zip);
}

function createResponses(speech) {
  return [
    {
      "messages": [
        {
          "type": "message",
          "speech": [
            speech
          ]
        }
      ]
    }
  ]
}

function createUserSays(sheet, rowNo) {
  const userSays = new Array();
  for (let i = 3; i <= 12; i++) {
    userSays.push({
      "data": [
        {
          "text":sheet.getRange(rowNo, i).getValue()
        }
      ]
    })
  }
  return userSays;
}
```

# データのインポート

データができたら実際にインポートします。
前述しましたが、**Agent単位**でインポートします。
まずはAgentファイル群をZIPでダウンロードします。
**歯車の設定**をクリックします。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/9fb9937d-31d3-c2db-bdaf-60efb6cdf7db.png)
タブの**Export and Import**より、**EXPORT AS ZIP**をクリックします。
すると、Agentファイル群をZIPで手に入れることができます。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/489111ac-2c5c-7ea2-6ae2-be4d7bd83527.png)
Angetフォルダの中のintentsフォルダに先ほど作成したJSONを全て格納して再びZIPで圧縮します。

```bash:tree
├── agent.json
├── intents
│   ├── 001.json
│   ├── 001_usersays_ja.json
│   ├── 002.json
│   ├── 002_usersays_ja.json
│   ├── 003.json
│   ├── 003_usersays_ja.json
│   ├── 004.json
│   ├── 004_usersays_ja.json
│   ├── 005.json
│   ├── 005_usersays_ja.json
│   ├── 006.json
│   ├── 006_usersays_ja.json
│   ├── 007.json
│   ├── 007_usersays_ja.json
│   ├── 008.json
│   ├── 008_usersays_ja.json
│   ├── 009.json
│   ├── 009_usersays_ja.json
│   ├── 010.json
│   ├── 010_usersays_ja.json
│   ├── Default\ Fallback\ Intent.json
│   ├── Default\ Welcome\ Intent.json
│   └── Default\ Welcome\ Intent_usersays_ja.json
└── package.json
```

再び圧縮したZIPをRESTOREします。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/64c2688b-1ce5-a044-e467-f0abcdee1e06.png)
Intentsを見てみると、無事取り込めたことが確認できると思います。
![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/f7ba2768-cb0a-a363-3f9d-f3d939297e81.png)
試しに、学習フレーズそのままで話しかけてみると、無事返ってきました！
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/abf9715d-9ca7-ae1f-81ce-5926b3c86687.png)
しかし、学習フレーズには存在せずAutoMLでは精度を出してくれた「ニコチン最高！」を与えてみると、残念ながらFallback Intentに落ちてしまいました。
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/115823/6cd80a5e-4a37-a1c4-0bb5-c43faa9deabb.png)

# 終わりに
エクセルで管理できるような単純な一問一答FAQであれば、簡単に取り込むことができました。
ただし、一括で取り込むのが超メンドクサイ...Googleさん、もう少しどうにかなりませんか？
もっとスマートな取り込み方法があれば教えてください。
また、FAQは色々な聞き方があるので、学習フレーズを全てを網羅するのは難しいですよね。少しでもフレーズから外れてしまうと、Fallback Intentに落ちてしまいます。未知のワードに関しては、AutoMLよりも弱いかも？
そこで、[以前記事](https://qiita.com/mako0715/items/c24e804641adb33d095b)にした**構文を分割することで、1Intentで全てのFAQを対応してしまおう！**という構成に落ち着きました。