0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Difyで社内向けAIチャットボットを作ってみた ― RAGは「データの作り方」が大事だった

0
Posted at

はじめに

株式会社LYDIAでは、社内業務への生成AI活用を少しずつ進めています。

今回は、就業規則や各種社内ルールについて、社員がチャット形式で質問できる社内向けAIチャットボットをDifyで作成しました。

例えば、

  • 有給休暇は入社時に何日もらえる?
  • 資格取得一時金の申請方法は?
  • Web勤怠の使い方を教えて
  • リファラル採用制度について教えて

といった質問に対して、登録した社内ナレッジを検索し、その内容をもとにAIが回答します。

最終的には社内イントラにチャットウィンドウを埋め込み、社員が普段使っているイントラからそのまま質問できるようにしました。

今回作ってみて特に感じたのは、

RAGは「とりあえず資料を登録すればいい」というものではない

ということでした。

Difyでチャットボットを作ること自体よりも、 「RAGにどういう形でデータを渡すか」 の方にかなり時間を使いました。

この記事ではDifyの操作手順を細かく説明するというより、実際に社内RAGを作る中で工夫したポイントや、実際にハマったところを中心に紹介します。


今回作ったもの

今回作成した構成は以下です。

社員
 ↓
社内イントラ
 ↓
Dify Chatflow
 ↓
質問補完
 ↓
社内ナレッジ検索
 ↓
LLMによる回答

社内ナレッジには、就業規則、賃金・給与関連資料、各種社内規程、社内制度、各種申請・手続き、Git運用ルール、開発環境に関する資料などを登録しました。

Difyはセルフホスト版(v1.14.2)を使用しています。

image.png


まず社内資料をMarkdown化する

最初に行ったのが、RAGへ登録する社内資料のMarkdown化です。

元データにはPDF、Word、Excelなど様々な形式のファイルがあります。

これらをそのままDifyへ登録する方法もありますが、今回は一度Markdownへ変換してからRAG用データを作る方針にしました。

Markdownへの変換には、以前社内で作成したRAG Markdown Converterを使用しています。

関連記事:
RAG Markdown Converterを作ってみた

Markdown変換したら終わり、ではない

RAG Markdown ConverterでMarkdown化したあと、そのままDifyへ登録したわけではありません。

まず人間の目で変換結果を確認しました。

  • 見出しが正しいか
  • 条文番号が崩れていないか
  • 表が正しくMarkdown化されているか
  • 箇条書きや番号が崩れていないか
  • PDF由来の不要な改行や文字が入っていないか

さらに、問題がある箇所は人力でMarkdownを修正しました。

PDF / Word / Excel
 ↓
RAG Markdown Converter
 ↓
Markdown
 ↓
人間による目視確認
 ↓
必要箇所を手作業で修正
 ↓
RAG用データへ再整形

元データの内容が間違った状態でRAGに入ってしまうと、AIはその間違った情報を正しい情報として使ってしまいます。

そのため、Markdown化の自動化と、人間による最終確認・修正は分けて考えることにしました。


一番苦労したのは「RAG用データの作り方」

今回、一番時間を使ったのがここです。

当初は、

Markdownとして綺麗に整っていれば、そのままRAGへ登録すればいい

くらいに考えていました。

実際、Markdownには、

# 文書タイトル
## 第1章
### 第1条

というように見出し構造があります。

人間が読む資料としては非常に分かりやすいです。

しかしDifyへ登録してチャンクのプレビューを確認してみると、想定とは違う分割が行われるケースがありました。

「人間にとって読みやすいMarkdown」と「RAGが検索しやすいデータ」は違う

例えば就業規則では、最初は次のような普通のMarkdownにしていました。

## 第1章 総則

### 第1条(目的)

本文...

### 第2条(適用範囲)

本文...

人間が読む分には、これが一番自然です。

ところがRAGで本当にやりたいのは、

社員:
「試用期間は何ヶ月?」

↓

第8条(試用期間)を検索

↓

その条文を根拠に回答

という処理です。

つまりRAGから見ると、Markdownの見た目よりも、

「どの範囲を1回の検索で取得したいか」

の方が重要です。

そこで、規程類は1条文 = 1検索単位という方針にしました。


Markdown見出しで想定外のチャンク分割が発生

就業規則は47条あるので、理想的には47チャンク程度になる想定でした。

しかしDifyのプレビューを見ると、107チャンクになりました。

文書タイトル、章タイトル、条文見出し、RAG用ヘッダー、条文本文などが細かく分割されていました。

つまり、人間にとって分かりやすいMarkdown構造が、今回のDifyのチャンク処理では必ずしも都合がよくありませんでした。

そこでRAG投入用Markdownでは、見出しの文字自体は残しながら、

## 第1章 総則

を、

第1章 総則

のように変更しました。

元のMarkdownは人間が読む資料として保持し、RAG用データは別フォルダへ生成する形にしました。

人間向けMarkdownと、AI検索向けMarkdownは別物として管理する

これは今回かなり大きな学びでした。


チャンク識別子でもハマった

検索単位を明示するため、当初はMarkdownでもよく使う --- を区切り文字として使用しました。

ところがMarkdownの表には、

| 項目 | 値 |
| --- | --- |
| A | B |

という記述があります。

Difyのチャンク識別子に --- を指定すると、表の中にある --- まで区切り文字として認識されてしまいました。

その結果、第26条の年次有給休暇の表が大量に分割され、1条しかないテストデータが18チャンクになることもありました。

そこで最終的に、Markdown構文と衝突しない専用文字列を使用しました。

RAG_CHUNK_SEPARATOR

例えば、

文書:就業規則LYDIA_20260401
章:第1章 総則
条:第1条(目的)

第1条(目的)

本文...

RAG_CHUNK_SEPARATOR

文書:就業規則LYDIA_20260401
章:第1章 総則
条:第2条(適用範囲)

第2条(適用範囲)

本文...

という構造です。

Dify側でも、

チャンク識別子:RAG_CHUNK_SEPARATOR

を指定しました。

その結果、就業規則47条 → 47チャンクと、狙った通りの検索単位になりました。

この時点で、

チャンクは文字数で切るのではなく、検索したい意味の単位で切る

という考え方に変わりました。


RAG用ヘッダーも付けた

各チャンクには、検索対象が何なのか分かるように簡単なヘッダーも追加しました。

文書:就業規則LYDIA_20260401
章:第5章 休暇等
条:第26条(年次有給休暇)

これによって検索されたチャンクだけを見ても、何の文書・何章・何条なのかが分かるようにしています。


全資料を「同じルール」で分割しない

すべての資料を同じチャンク設計にはしていません。

  • 規程 → 条文単位
  • 手順書 → 手順・章単位
  • FAQ → Q&A単位
  • 表データ → 検索される項目単位

というように、資料ごとに検索単位を変えました。


Difyのナレッジベースを作成

ナレッジベース名はLYDIA社内ナレッジです。

検索方式は最終的に、

ハイブリッド検索
セマンティック:0.7
キーワード:0.3
Top K:4
スコア閾値:0.4

としました。

関係のないチャンクが引用として表示されるケースがあったためスコア閾値を設定しましたが、高くしすぎると必要な情報まで落ちるため、実際の質問を繰り返しながら調整しました。


「有給休暇」と「有給」はRAGでは同じではなかった

「有給休暇は入社時に何日もらえますか?」では正しく検索できるのに、「有給は入社時に何日?」では検索結果が0件になるケースがありました。

そこでChatflowへ質問補完用LLMを追加しました。


質問補完を入れる

質問補完では、会話履歴を補完するだけではなく、RAGが検索しやすい表現へ質問を書き換える役割も持たせました。

有給は入社時に何日?

を、

年次有給休暇は入社時に何日付与されますか?

へ変換します。


「じゃあ2年目は?」にも対応

例えば、

ユーザー:
有給は入社時に何日?

AI:
原則10日です。

ユーザー:
じゃあ2年目は?

という会話では、「じゃあ2年目は?」だけを検索しても何についての2年目か分かりません。

質問補完LLMで、

入社2年目には年次有給休暇が何日付与されますか?

へ変換してから検索することで、正しく11日と回答できるようになりました。


回答は社内ナレッジだけを根拠にする

回答生成LLMには、

  • 社内ナレッジに記載された内容のみを根拠にする
  • 一般論で社内ルールを補完しない
  • 金額・日数・期限等は正確に回答する
  • 条番号が確認できる場合は根拠を表示する
  • ナレッジに存在しない情報は推測しない

というルールを設定しました。

入社時の年次有給休暇は、原則10日です。

根拠:就業規則 第26条

Difyの「引用と帰属」も有効にし、回答の下から元資料を確認できるようにしました。

image.png


「知らない」と言えることも重要

「今年の社員旅行はどこですか?」のように、社内ナレッジに存在しない質問も試しました。

社内ナレッジから、社員旅行については確認できませんでした。
必要に応じて管理部へお問い合わせください。

と回答するようにしています。

存在しない情報を、それっぽく作らないこと

も社内向けRAGでは重要だと考えています。


公開前に10問の受入テスト

公開前には、就業規則、退職手続き、資格取得一時金、給与テーブル、会話継続、口語的な質問、有給休暇、Git運用ルール、ナレッジに存在しない質問など、10問の受入テストを行いました。

特に、

15等級の給与はいくら?
↓
じゃあ16等級は?

のような会話継続も確認し、最終的には10問すべて期待した回答になりました。


作ってみて分かったこと

今回一番感じたのは、

RAGはLLMよりも「検索させるデータをどう作るか」が重要

ということです。

実際、LLMモデルを変更するよりも、

  • 何を1チャンクにするか
  • 見出しをどう扱うか
  • 専用のチャンク識別子をどうするか
  • 表データをどう持たせるか
  • RAG用ヘッダーをどう付けるか
  • ユーザーの質問をどう検索用クエリへ変換するか

を調整した時の方が、検索結果は大きく改善しました。

特に、

人間が読みやすい文書構造を、そのままAIに渡すことが最適とは限らない

ということを実感しました。

現在は、

人間が読むMarkdown
      ↓
RAG用Markdown
      ↓
Dify

という形で分けています。

原本を無理にRAG向けへ変更するのではなく、AI検索用のデータを別途生成するという考え方です。


今後やりたいこと

まずは社内で実際に使ってもらいます。

Difyでは利用者の質問・回答ログを確認できるため、

社員が質問
 ↓
ログを確認
 ↓
回答できなかった質問を発見
 ↓
元資料・FAQを追加/修正
 ↓
RAG用データを更新
 ↓
再テスト

というサイクルを回していく予定です。

最初から完璧なチャットボットを作るのではなく、実際の社員の質問をもとに少しずつ育てていきます。


最後に

今回、DifyとRAGを使って、社内向けAIチャットボットを構築しました。

作る前は、

社内資料をMarkdown化してDifyへ登録すれば、あとはLLMがいい感じに回答してくれるだろう

くらいに考えていました。

実際に作ってみると、一番時間を使ったのは RAGへ投入するデータの整形とチャンク設計 でした。

ただ、その部分をしっかり設計したことで、

  • 普段の言葉で質問できる
  • 会話の続きを理解できる
  • 条文や給与表を正しく検索できる
  • ナレッジにない質問には答えない

という、社内で実際に使えそうなところまで持っていくことができました。

今後も実際に運用しながら改善を続け、新しい工夫や気づきがあれば、また記事として紹介したいと思います。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?