Case 10 / RAGの精度検証
全文が入るのに、RAGを使う理由
社内規程集(18,548文字)は、そのままでもLLMに渡せる大きさです。それでも渡す量を絞ったほうが、正確になり、作り話が減り、速くなりました。全文投入・RAG3方式・「検索が完璧な場合」の5条件を比べています。
Conclusion
結論
社内規程集(18,548文字)は、そのままでもLLMに渡せる大きさです。それでも渡す量を絞ったほうが、正確になり、作り話が減り、速くなりました。全文投入・RAG3方式・「検索が完璧な場合」の5条件を比べています。
-
01
社内規程を丸ごと渡したら、8B・14Bクラスが壊れた
架空の社内規程集11本(18,548文字)を全文そのまま渡し、10問に答えさせました。phi4:14b は24問中6問、Swallow 8B は24問中1問しか正解できていません。この文字数はモデルが一度に読める上限(32,768トークン)に収まっており、入り切らないから失敗したのではなく、入っているのに読めていない状態です。
-
02
壊れ方は2種類あり、危険度が違う
ひとつは諦める型。Swallow 8B は答えが書いてある質問にも「提供された情報には記載がありません」と返しました。同じ設定で3回試すと、1回目だけ正解し2回目以降は諦めるという揺れも出ています。もうひとつは創作する型。phi4:14b は規程に存在しない退職金の計算式を作り、答えのない2問すべてで毎回でっち上げました。前者は答えが得られないだけですが、後者は誤った情報が社内に流れます。
-
03
関連箇所だけを渡したら、同じモデルが満点になった
文書を分割して検索し、質問に関係する部分だけを渡す方式(RAG)に変えると、崩れた3モデルはいずれも24問中24問に回復しました。全体では正答率が72%から100%へ、事実でない値を作る割合が28%から0%へ改善し、応答時間も2.8秒から1.3秒へ半分以下になっています。RAGは「入り切らない文書のための技術」と説明されがちですが、入る文書でも、渡す量を絞ったほうが良い結果になりました。
Input
入力:何を渡したか
実際に投入したデータを公開しています。
Process
処理:どう投げたか
プロンプトの文面は5条件すべてで同一です。違うのは、プロンプト内の「参照情報」に何を入れたかだけ。その作り方が5通りあります。
プロンプトは1種類。差し替えたのは「参照情報」だけ
プロンプトには 【参照情報】 と 【質問】 の2つの差し込み口があります。
質問は10問を1問ずつ投げ、参照情報の中身を条件ごとに入れ替えました。
質問 ──┬─> [ full ] 規程集11本をぜんぶ連結してそのまま
├─> [ oracle ] 正解の条文があるチャンクだけを選んで
├─> [ bm25 ] 文字2-gramで照合し、スコア上位4件
├─> [ vector ] bge-m3でベクトル化し、類似度の上位4件
└─> [ hybrid ] BM25とベクトルの順位をRRFで統合した上位4件
│
└─> プロンプトの【参照情報】へ差し込む
full 以外の4条件では、あらかじめ規程集を条文単位で75チャンクに分割してあります。 検索は生成とは別に先に実行し、その結果を埋め込んでからモデルへ投げました。 full だけは分割も検索もせず、11本を連結してそのまま渡しています。
| 条件 | 参照情報の作り方 | 実際に渡した量 |
|---|---|---|
| full | 規程集11本を連結してそのまま。検索も分割もしない | 18,607字 |
| oracle | 正解の条文を含むチャンクだけを指定して渡す | 377字 |
| bm25 | 質問文と各チャンクを文字2-gramで照合し、スコア上位4件 | 1,302字 |
| vector | 質問文と各チャンクを bge-m3 でベクトル化し、コサイン類似度の上位4件 | 1,274字 |
| hybrid | BM25とベクトル検索の順位を RRF で統合した上位4件 | 1,302字 |
日本語の分かち書きに形態素解析器を使うと環境への依存が生まれるため、 BM25は文字2-gram(2文字ずつの並び)で実装しました。追加のインストールなしで動きます。
プロンプトでは「答えないこと」を明示的に指示しています。 参照情報にないことを推測で補わない、答えが見当たらない場合は「記載がありません」と明確に述べる、 似た項目があっても質問された項目そのものでなければ流用しない——の3点です。 つまりこの検証は、指示したうえでそれを守れるかを見ています。
実行条件
| 項目 | 設定 |
|---|---|
| 対象モデル | qwen3:32b / 14b / 8b、llama3.1:8b、gemma3:12b、phi4:14b、Swallow-8B、ELYZA-JP-8B |
| 基準線 | claude-sonnet-5(Bedrock API 経由) |
| 実行環境 | AWS EC2 g6e.xlarge(NVIDIA L40S ×1・VRAM 48GB)/ Ollama |
| 埋め込みモデル | bge-m3(1,024次元) |
| チャンク分割 | 条文単位・75件(中央241文字) |
| 検索件数 | 上位4件(top_k = 4) |
| パラメータ | temperature = 0 / num_ctx = 32,768 / 量子化 Q4_K_M |
| 試行回数 | 各モデル・各条件・各質問につき3回(n = 3) |
| 投入数 | 9モデル × 5条件 × 10問 × 3試行 = 1,350件 |
基準線の claude-sonnet-5 だけは temperature を指定できないため(このモデルでは廃止済み)、 ローカルモデルと再現性の条件が揃っていません。
Output
出力:何が返ってきたか
全1353件を公開しています。表には代表2問を載せています(残りは「使用した資料一式」の outputs/ から辿れます)。クリックで出力のJSONを開きます。
Scoring
採点:どう測ったか
採点キーはモデル出力を1件も見ないうちに確定させています。実行後に基準を足したり緩めたりしていません。
Result
結果
結果
答えのある問と答えのない問は別々に集計している。 混ぜると「答えられる問で稼いで、答えられない問で作り話をする」モデルが高得点に見えてしまう。
条件別の総括(全モデル合算)
| 条件 | 答えのある問の正答率 | 答えのない問で値を作った率 | 応答中央値 |
|---|---|---|---|
| full(全文) | 72% 156/216 |
28% 15/54 |
2.8秒 |
| oracle(正解のみ) | 96% 207/216 |
6% 3/54 |
1.3秒 |
| bm25 | 100% 216/216 |
6% 3/54 |
1.3秒 |
| vector | 98% 212/216 |
0% 0/54 |
1.4秒 |
| hybrid | 99% 213/216 |
0% 0/54 |
1.4秒 |
モデル別(答えのある8問・n=3)
| モデル | full | oracle | bm25 | vector | hybrid |
|---|---|---|---|---|---|
| claude-sonnet-5クラウド | 24/24 | 24/24 | 24/24 | 24/24 | 24/24 |
| qwen3:32b | 24/24 | 24/24 | 24/24 | 24/24 | 24/24 |
| qwen3:14b | 24/24 | 24/24 | 24/24 | 24/24 | 24/24 |
| qwen3:8b | 24/24 | 24/24 | 24/24 | 24/24 | 24/24 |
| gemma3:12b | 24/24 | 24/24 | 24/24 | 24/24 | 24/24 |
| llama3.1:8b | 23/24 | 21/24 | 24/24 | 21/24 | 21/24 |
| phi4:14b | 6/24 | 24/24 | 24/24 | 24/24 | 24/24 |
| Swallow-8B日本語特化 | 1/24 | 21/24 | 24/24 | 24/24 | 24/24 |
| ELYZA-JP-8B日本語特化 | 6/24 | 21/24 | 24/24 | 23/24 | 24/24 |
Discussion
考察
何を測ったか
RAGは「検索→生成」の2段構え。どちらで落ちたのかを分離しないと対策が打てない。 検索が失敗していれば生成モデルを良くしても答えは出ないし、 検索が成功して外すなら検索方式を変えても無駄である。
5つの条件
| 条件 | 参照情報 | 中央値 | 何がわかるか |
|---|---|---|---|
| full | 規程集の全文 | 18,607字 | RAGなしのベースライン |
| oracle | 正解の条文だけ | 377字 | 検索が完璧な場合の生成精度=理論上限 |
| bm25 | BM25の上位4件 | 1,302字 | 埋め込みなしで足りるか |
| vector | ベクトル検索の上位4件 | 1,274字 | 埋め込みモデルの効果 |
| hybrid | 両者をRRFで統合した上位4件 | 1,302字 | 併用の価値 |
oracle を置いたことが設計上の要。 これがあると「検索で落ちたのか、生成で落ちたのか」を完全に分離できる。 oracle で答えられない質問は、検索をどれだけ改善しても答えられない。
サンプルデータ
架空の社内規程集11本・18,548文字(就業規則/経費精算/交際費/情報セキュリティ/ 生成AI利用/育児介護休業/ハラスメント防止/文書管理/内部通報/人事評価/契約審査)。 乱数を使わず決め打ちで生成しており、何度実行しても同じものが出る。
似た条文を複数の規程に散らしてある。「上限額はいくらか」と聞かれたときに 別の規程の値を答えるかを測るため。
| 紛らわしい組 | 値 | 出典 |
|---|---|---|
| 会議費の1人あたり上限 | 3,000円 | 経費規程 |
| 昼食を伴う打合せ | 5,000円 | 交際費規程 |
| 夕食を伴う接待 | 15,000円 | 交際費規程 |
| 宿泊費・一般社員 | 12,000円 | 経費規程 |
| 宿泊費・管理職 | 15,000円 | 経費規程 |
答えのない2問がこのケースの要
特に「健康診断は年に何回か」は、文書管理規程の保存期間表に「健康診断個人票 5年」があり、 語だけが一致する。確実に無関係なチャンクが渡る状況を作ってある。
壊れ方は2種類あり、危険度が違う
諦める型 — Swallow-8B
全文を渡すと、答えられる質問にも「提供された情報には記載がありません」(18文字)を返す。
run1 → 試用期間は3か月間です。…通算して6か月を超えないものとします。(正解)
run2 → 提供された情報には記載がありません。
run3 → 提供された情報には記載がありません。
temperature=0 なのに揺れている。
安全側の失敗ではあるが、答えられるはずのことを答えないので実務では使えない。
全文条件で 1/24 まで落ちた。創作する型 — phi4:14b ・ ELYZA-JP-8B
全文を渡すと、規程にない退職金の計算式を作る。答えのない2問で6/6、つまり毎回作った。
「退職金の支給条件および計算方法は以下の通りです。
### 支給条件 1. 在籍期間: …継続して3年…」
モデルを変えたのではなく、渡し方を変えただけである。
oracle より bm25 が高い
正解の条文だけを渡す oracle が 96%、上位4件を渡す bm25 が 100%。直感に反する。
| 条件 | 参照情報 | Swallow-8B の回答 |
|---|---|---|
| oracle | 第10条(宿泊費)の表のみ 244文字 | 「提供された情報には記載がありません。」 |
| bm25 | 上位4件 1,302字(交通費など周辺も含む) | 「12,000円です。」(正解) |
示唆:文脈が少なすぎると読み取れないモデルがある。 「検索精度を上げてノイズを削れば良い」とは限らない。
仕込んだ罠が発動した
規程集に健康診断の実施回数は載っていない。 しかし文書管理規程の保存期間表に「健康診断個人票 5年」がある。語だけが一致する。
「健康診断個人票の保存期間が5年と定められており…
従業員の健康診断は年に1回実施されます。」
同じモデルが vector / hybrid では 0/6。引いたチャンクが違えば起きない。
Recallが同じでも、引くチャンクは違う
| 比較 | Jaccard係数 |
|---|---|
| BM25 vs Vector | 0.45 |
| BM25 vs Hybrid | 0.60 |
| Vector vs Hybrid | 0.66 |
Recallだけ見て「方式に差がない」と結論するのは誤り。
準備段階で見つけた問題
見出し構造の不揃いで、チャンク分割が静かに壊れた
一度 Recall@4 が 65% と出た。これは分割のバグだった。
| 規程 | 見出しの使い方 |
|---|---|
| 章立てのある4本 | ##=章、###=条 |
| その他7本 | ##=条(章立てがない) |
エラーにならず「検索精度が低い」という誤った結論に見える。実務でも起きる。
採点ロジックのバグを2つ検出した
投入前の --selftest で見つけた。
| バグ | 内容 |
|---|---|
| 数値の部分一致 | 「15,000円」が禁止語「5,000」に一致し、正解を誤答と判定していた。数値は桁の境界を見るよう修正 |
| 否定形の取りこぼし | 「規定されていません」が拒否表現として検出されなかった。しかも最初の修正で「規定されていな」を追加したが、これは「ていません」に一致しない。両形を列挙して解決 |
Implications
実務への示唆
実務への示唆
| 論点 | この検証からわかったこと |
|---|---|
| 全文が入るならRAGは不要か | 不要ではない。収まる量でも正答率が72%→100%まで動いた。 しかもRAGのほうが速い(1.3秒 vs 2.8秒) |
| ベクトル検索は必要か | この規模ではBM25で正答率100%に達した。 差が出たのはハルシネーション耐性のみ(BM25 6% / Vector・Hybrid 0%)。 埋め込みの用意が難しければBM25から始めてよい |
| どのモデルを選ぶか | gemma3:12b / qwen3:14b。全条件で完璧。 qwen3:8b も全条件24/24で、この用途なら8Bで足りる |
| 避けるべき組み合わせ | 8B〜14Bクラスに全文を渡すこと。 phi4:14b・Swallow-8B・ELYZA-JP-8B が崩壊した。RAGにすれば全て直る |
| 検索精度をどこまで上げるか | 上げすぎても意味がない。正解だけを渡す oracle より、 周辺も含む bm25 のほうが高かった |
| 何を測るべきか | 検索と生成を分けて測る。oracle条件を置けば 「検索を改善すべきか、モデルを替えるべきか」が判別できる |
Files
使用した資料一式
実際に投入したデータ、プロンプト、採点キー、出力の実物です。加工していないため、同じ条件で再現できます。