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


最近、Webサイトの右下に出てくるチャットボットをよく見ますよね。
本稿では以前にAutoMLで分類した飲食店のFAQをDialogflowに取り込んで精度を見てみます。

実装方針

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

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

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

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

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

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

name_usersays_ja.json
[
  {
    "data": [
      {
        "text": "UserSays1"
      }
    ]
  },
  {
    "data": [
      {
        "text": "UserSays2"
      }
    ]
  }
]

の2つのJSONファイルを作成します。

GASでJSONファイルを作成

CSVをスプレッドシートにインポートして、この記事を参考にJSONをZIPで固めて保存します。

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でダウンロードします。
歯車の設定をクリックします。

タブのExport and Importより、EXPORT AS ZIPをクリックします。
すると、Agentファイル群をZIPで手に入れることができます。

Angetフォルダの中のintentsフォルダに先ほど作成したJSONを全て格納して再びZIPで圧縮します。

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します。

Intentsを見てみると、無事取り込めたことが確認できると思います。

試しに、学習フレーズそのままで話しかけてみると、無事返ってきました!

しかし、学習フレーズには存在せずAutoMLでは精度を出してくれた「ニコチン最高!」を与えてみると、残念ながらFallback Intentに落ちてしまいました。

終わりに

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