notionのナレッジを元に自動で回答してくれるLLMチャットbotを半日でつくる(Difyあり GASあり)


概要

同僚に、(notionを)参照参照って言いたくないですよね。
でも、我が社は大丈夫👌
notionとリンクした、自動回答システム。
LLMを使った、分かりやすくて楽しいチャットbot。
やり方一つで、こんなに変わるものなんです。

1. やりたかったこと

背景

私はPdMです。

社内では、普段からslack上でプロダクトの仕様や運用に関する様々な質問と回答が飛び交っています。プロダクトに関する質問は基本的にはプロダクトに詳しいPdMが回答することが多いです。
ともなると、勤務中にあらゆる質問に回答することによい、メインの業務に集中する時間が削られる&スイッチングコストにより脳のメモリを消耗してしまいます。

このようなQ&Aの差し込みを減らすために、最近社内ではnotion上にFAQデータベースを作成しました。
プロダクトに関するQ&Aは基本的には一問一答形式でDBに保存していくことでナレッジを1箇所に集約し、質問する時にはあらかじめそのDBを参照してもらうことで、なるべく質問を削減し、PdMの工数を減らそうという狙いです。

しかーし、FAQの回答が薄い、notionの検索精度があまり高くなかったりする、など、質問する側としてなるべく手間をかけずに必要な情報をゲットしたいというニーズをそこまで満たせていないのが現状の課題としてあります。

そこで、今回はLLMと検索エンジンの力を借りて、与えられた質問に対して社内ナレッジを検索、適切な回答を自動で生成するbotを作成して、FAQのポテンシャルをフルに活かそうという計画です!

要求

  • slack上でなにか質問をしたら、botが社内ナレッジに関する情報を参照して自動で回答してくれる

要件

  • LLM側はDifyをつかってアプリケーションを構築

  • slack上でメンションされると、difyアプリが起動し回答を生成。その回答がslackに表示される

  • 回答はスレッドにぶら下げる形で回答する

  • ナレッジは社内notionの情報を参照する

2. 実装実装実装実装

Difyでアプリ作成

まずDifyでアプリを作りましょう。Dify(ディファイと読む)はノーコードでLLMを利用したアプリを作成できるプラットフォームです。爆速でLLMアプリを作ることができるのでプロトタイプ作りにはもってこいのサービスになります。

アカウントを作成したら、

notionを連携しましょう。
設定→データソース→ノーションを選択し、アカウント認証を行うとページ単位で連携することができます。

データソースを連携したら、ナレッジを新規作成します。
データソースで「notionから同期」を選択すると、先ほど選択したページのうちどれを連携するかを選択することができます。

※現在の仕様だと、一番上位のページの内容のみ読み込む感じになっていて、階層構造のページの中身は読み取れていないので、notionから直エクスポートして、difyに手動でアップロードする方がよいかもしれません。

次にLLMチャットbotを作成します。
最初は初心者向けのフォーマットで良いでしょう、「チャットボット」を選択して下さい。

次にLLMに与えるマニュアルとコンテクストを指定します。
手順のところには下記のように回答してもらう上での条件などを事前に指示します。

特に、FAQに参照情報が存在しない場合は嘘をつかずに素直にわかりませんということを言っておくと、ハルシネーション(AIの幻覚)のリスクを軽減できる気がします。

あなたは最高のカスタマーサポート担当者として振る舞ってください。

# 条件
- 最初は季節の挨拶から始めてください。どんな時でも礼節をかかさずに。

- ユーザーから受け付けた質問に対して、適切な回答をコンテキストから検索して適切な回答をします。

- 回答の内容は下記の書式を守ってください。思考はフレームワークによって規定されます。
* 課題の特定
* 回答内容
* 根拠

- 回答が100%正しいとは限らない場合に、懸念、論理的な過不足について言及してください。ハルシネーションは絶対起こさないように!

- 回答はなるべく具体例をふくむようにしてください。

- コンテキストに存在しない質問が来たときは 「回答内容」以下で
「申し訳ありませんがその質問には答えられません。プロダクトチームにエスカレーションいたします。」と回答して。

コンテクストには、先ほど作ったナレッジが表示されるので、そのナレッジを選択してください。AIが回答を検索するときの参照先になります。

その後、右側のプレビューである程度問題なく動くことがわかればアプリを公開してください。特に最初はそこまで精度の高い回答が得られることはないので、ナレッジに突っ込んだまったくそのままの内容を質問して回答が返って来ればひとまずよしとしましょう。

Difyアプリをslackと連携

つぎに、サイドバーの「APIアクセス」から右上のAPIキーを発行すると、外部からDifyアプリにアクセスできるAPIトークンを発行できるので、それを控えておきます。

slack appを作成します。
slack appに関しては下記の記事が詳しいので別途参考にしてください。紙面の都合上省略します。

最後にslack appとDifyを連携します。
繋ぎのサーバーとして、AWSなどのクラウドサービスを利用することもできますが、今回は簡易的にGASを利用します。

gasのプロジェクトのデフォルトのファイル(コード.gs)に以下のサンプルコードを貼ります。

const sheet=SpreadsheetApp.getActiveSheet()


function doPost(e) {
  // スクリプトプロパティからトークンを取得する場合(あらかじめプロパティストアで設定)
  var difyApiKey = PropertiesService.getScriptProperties().getProperty('DIFY_API_KEY');
  var slackToken = PropertiesService.getScriptProperties().getProperty('SLACK_TOKEN');

  try {
    // リクエストボディのJSONをパース
    var eventData = JSON.parse(e.postData.contents);

    // Slackイベントのclient_msg_idをキーとして、冪等性を担保
    let cache = CacheService.getScriptCache();
    if (cache.get(eventData.event.client_msg_id) === 'done') {
      return ContentService.createTextOutput();
    } else {
      cache.put(eventData.event.client_msg_id, 'done', 600);
    }

    var text = eventData.event.text;
    var channel = eventData.event.channel;
    var thread_ts = eventData.event.ts;
    var user = eventData.event.user;

    lastrow = sheet.getLastRow()
    sheet.getRange(lastrow+1,1).setValue('スレッドID'+thread_ts);
    
    // Difyへのリクエスト
    var difyResponse = sendRequest(
      'https://api.dify.ai/v1/chat-messages',
      {
        query: text,
        response_mode: 'blocking',
        user: user,
        inputs: {}
      },
      difyApiKey
    );

    // Slackへメッセージ送信
    sendRequest(
      'https://slack.com/api/chat.postMessage',
      {
        channel: channel,
        text: difyResponse.answer,
        thread_ts: thread_ts
      },
      slackToken
    );

    return ContentService.createTextOutput('Message sent successfully').setMimeType(ContentService.MimeType.TEXT);
  } catch (error) {
    return ContentService.createTextOutput('Internal Server Error').setMimeType(ContentService.MimeType.TEXT);
  }
}

function sendRequest(url, postData, token) {
  var options = {
    'method': 'post',
    'headers': {
      'Authorization': 'Bearer ' + token,
      'Content-Type': 'application/json'
    },
    'payload': JSON.stringify(postData),
    'muteHttpExceptions': true
  };

  var response = UrlFetchApp.fetch(url, options);
  var responseText = response.getContentText();
  return JSON.parse(responseText);
}

事前に控えたDifyのキーとSlack Appのトークンを、スクリプトプロパティから登録しておきます。

これで、slackアプリが特定のチャンネルでメンションされると、slackからイベントがgasで立てたサーバーに送信され、そこからDifyのLLMチャットbotが呼び出されるようになりました!うまくいけば、生成したレスポンスがslackに表示されようになります。

3.いざ検証!

それでは、検証してみましょう👩‍⚕️☝️

ナレッジとして貯まっている障害対応時の方法についてきいてみる
ほどなくして、スレッドに返信が・・・!
おお〜!なんかそれっぽい答えが返ってきている!

無事回答が返ってきましたが、まだ安心はできません。この内容では一般論を回答しているようにも見えます。そこで、さらに具体的にきいてみることにします。

どうせ、「該当の責任者」とかなんとかいって、ごまかすんだろう?

4.検証結果と今後の課題・可能性

というわけで、無事にslack上でnotionのFAQを参照して回答するbotを動作させることができました。
ただ、とはいってもこれ以外の質問もした結果、まだまだ回答の精度が低く、エンタープライズレベルで実戦投入するのは難しそうです。ありました。現状では下記のような課題があります。

  1. データソース連携だとSlackに全てのnotionの内容を持たせることができない

    1. notionからエクスポートする場合でも、Difyの容量制限もあり、肥大化するFAQの内容を全て登録するのに工夫が必要

  2. データソースにnotionのURLが載ってないので、該当記事のURLを貼ることができない

  3. 検索性能のチューニングが必要 (Dify内で限界がある場合は自分で検索システムを構築する必要あり)

  4. プロンプトチューニングをして、より適切な回答内容・フォーマットを引き出す必要がある

それでも、Dify、GASという便利グッズを使って爆速でアプリを構築することができたおかげで、Day 1でLLMの精度にフォーカスすることができています。やり方一つで、こんなに変わるものなんです。
自前で地道に改善していっても良いし、あるいはプロトタイプとして見せて、本格的な開発計画を勝ち取るというシナリオでも十分耐えうるものが用意できると思います。
とにかく、これでみなさんの業務時間は無事確保されそうですね!よかったよかった!

次回、『「botの回答のことで聞きたいことがあるんだけどちょっと時間いいかな」、で死す』
デュエル、スタンバイ!


ぜひこの記事が誰かの役に立てば幸いです。Happy New Year!

参考文献

いいなと思ったら応援しよう!