🤖

RAGでなぜハルシネーションが「増える」のか?検索から運用まで分解して考える

に公開
2025/12/19

はじめに

生成AIのハルシネーション対策として、Retrieval-Augmented Generation(RAG)を導入するケースが増えています。

ところが、いざ社内ドキュメントやFAQとつないでみると、こんな声が出てきませんか?

  • 「検索してるはずなのに、情報がそれっぽく“創作”されている」
  • 「RAGにしたら安全になると思っていたのに、むしろ嘘に自信満々で答えるようになった」
  • 「どこからがハルシネーションなのか、デバッグしづらい」

この記事では、RAGに特有のハルシネーションの発生源を分解しながら、どこをどう設計・評価・運用していくと現実的にマシになるのかを整理します。

この記事で分かること

  • なぜ「RAGを入れたのにハルシネーションが残る(むしろ増える)」のか
  • RAGで新たに生まれるハルシネーションの発生源(検索ノイズ・埋め込み誤差・モデルの帰納的バイアスなど)
  • リトリーバの評価方法と、再ランキングを含めた実務的な改善パターン
  • プロダクション環境で「ハルシネーション低減のためにどこをモニタリングすべきか」の考え方

1. 全体像:RAGは「万能な事実性ブースター」ではない

まず前提として、RAGとは何かを手短に整理します。

  • Retrieval(検索): ユーザーのクエリから関連しそうなドキュメントを検索する
  • Augmented: 検索したドキュメントをプロンプトに埋め込み、モデルに渡す
  • Generation: LLMがそれを読んで回答を生成する

ざっくり図にすると、次のような流れです。

[User Query]
   ↓
[Retriever] ──> 上位 k 件のドキュメント
   ↓(コンテキストとして結合)
[LLM]  ← System / Instruction
   ↓
[Answer]

ここで直感的には、

モデルが知らないことはドキュメントから読ませればいい → だからハルシネーションは減るはず

と考えたくなります。ただ実際には、RAGを入れることで

  • 「検索されたけど実は関係ないドキュメント」
  • 「微妙に古い情報」
  • 「埋め込みの誤差で引っかかってしまった似て非なるケース」

などがコンテキストとして混入し、モデルから見ると

“それっぽいけど微妙に違う” 前提を信じて推論してしまう

という新しいハルシネーションの源泉が増えます。

つまり、

  • 通常のLLM: パラメータ内の知識のあいまいさでハルシネーション
  • RAG付きLLM: それに加えて検索・埋め込み・前処理のあいまいさでもハルシネーション

という二重構造になります。

この記事では特に後者、RAG特有の部分にフォーカスします。


2. RAGのどこでハルシネーションが生まれるのか

RAGパイプラインをもう少し細かく分解してみます。

Query
  ↓
Query 前処理(正規化、拡張など)
  ↓
埋め込み生成
  ↓
ベクトル検索 / キーワード検索
  ↓
再ランキング(任意)
  ↓
ドキュメント前処理(要約、フィルタリングなど)
  ↓
LLM へのコンテキストとして投入
  ↓
Generation

ざっと見ただけでも、ハルシネーションの原因になりそうなポイントがいくつかあります。

2-1. 検索結果のノイズ

一番わかりやすいのは、単純に 不適切なドキュメントが混ざる ケースです。

  • ユーザーの質問があいまい
  • ドキュメントの質にばらつきがある
  • 埋め込みの表現力が質問とミスマッチ

この結果、

  • 上位 k 件のうち「本当に必要なドキュメント」は 1〜2件だけ
  • 残りは似ているけど少し文脈が違う、古い、別のサービスの情報

といった状態になりがちです。

モデルからすると、

コンテキストに出ているテキストは「正しい前提」だろう

というバイアスがかかるため、少数の正しい文書より、多数の「それっぽいけど違う文書」に引きずられやすくなります。

2-2. 埋め込みの類似度誤差

RAGの多くはベクトル検索を使います。ここでのキーは、

  • クエリ埋め込み
  • ドキュメント埋め込み
  • 類似度計算(cosine / L2 等)

ですが、埋め込みモデルは万能ではないので、

  • 本当は重要度が高いが、語彙が違うため類似度が低くなる
  • 逆に、本質的には違うが表現が似ているため類似度が高くなる

といった「類似度誤差」が出ます。

この誤差は、検索結果が全部外れるほどひどくはないけれど、

  • 1位〜3位: そこそこ関連
  • 4位〜10位: かなり怪しい

みたいな「グラデーション」で現れることが多いです。

ここに固定で top_k = 5 のように機械的な閾値を当てると、

妙に説得力のある誤情報を 2〜3枚コンテキストに混ぜる

という状態になるわけです。

2-3. モデル側の帰納的バイアス

少し抽象度を上げると、LLMは「入力を見て一貫したストーリーを作ろうとする」帰納的バイアスを持っています。

  • たとえ文書同士が矛盾していても、矛盾をうまく丸めようとする
  • ドキュメントに明示されていない部分も、常識や過去の学習から補完する

RAGで複数の文書を読ませると、

  • 文書 A: 古い仕様
  • 文書 B: 新しい仕様

のようなケースでも、モデルは

何とか両立する説明を生成しようとする

結果として、「どちらの仕様でもない、謎の折衷案」が出てくることがあります。これも立派なハルシネーションです。


3. リトリーバの評価指標と再ランキングの設計

RAGのハルシネーションは、生成前の時点でどこまで検知・抑制できるかがかなり効きます。

ここからは、リトリーバの評価と再ランキングの話が中心です。

3-1. リトリーバの評価: 何を測るか

リトリーバの評価でよく出てくるのが以下のような指標です。

  • Recall@k: 「正解ドキュメントが上位 k 件のどこかに含まれているか」
  • Precision@k: 「上位 k 件のうち、何件が正解ドキュメントか」
  • MRR(Mean Reciprocal Rank): 正解ドキュメントの順位の平均的な逆数

RAGのハルシネーション対策という観点だと、個人的には以下のように見ます。

  • Recall@k: 「そもそも必要な情報が候補の中に入っているか」
  • Precision@k: 「ノイズがどれくらい混ざるのか」

Recall は「k を増やせば上がる」性質があるので、

  • 検索自体の Recall をそこそこ高くしておき
  • 後段で再ランキングとフィルタリングをしっかりやる

という設計にすることが多いです。

3-2. 再ランキングで「ノイズを落とす」

再ランキングは、検索で取ってきた上位 N 件(たとえば 50 件)を、

  • 別のモデル(しばしばクロスエンコーダ系)
  • あるいはルールベース

で改めてスコアリングし、最終的に LLM に渡すのは 3〜5 件程度に絞る、というステップです。

疑似コード的にはこんなイメージです。

# ざっくり Python 風の疑似コード

candidates = vector_store.search(query_embedding, top_k=50)

# クロスエンコーダによる再ランキング
re_ranked = [
    (doc, cross_encoder_score(query, doc.text))
    for doc in candidates
]

re_ranked.sort(key=lambda x: x[1], reverse=True)

# 上位 n 件のみを LLM に渡す
final_docs = [doc for doc, score in re_ranked[:5]]

ここで 1つ注意点があります。クロスエンコーダや類似度スコアは、

  • 「クエリとの関連度」

は測ってくれますが、

  • 「情報が最新かどうか」
  • 「社内の正規ルールと合致しているかどうか」

までは見てくれません。

そのため、

  • 日付メタデータ
  • ドキュメントのステータス(draft / published / deprecated など)
  • 所属するシステムや組織

といった情報を、別の軸としてスコアに組み込む必要があります。

例えば擬似的にスコア合成を書くと:

def score(query, doc):
    relevance = cross_encoder_score(query, doc.text)  # 0.0 - 1.0
    freshness = freshness_score(doc.published_at)     # 0.0 - 1.0
    officialness = 1.0 if doc.is_official else 0.0

    return 0.7 * relevance + 0.2 * freshness + 0.1 * officialness

重みはプロダクトごとに要調整ですが、

「関連度だけ」で並び替えると、古いWikiがいつまでも上位に居座る

というのは実務でよくあるので、ここは最初から意識しておくと後々楽になります。

3-3. クエリ拡張とハルシネーション

クエリ拡張(Query Expansion)も、RAGでありがちなテクニックです。

  • 同義語や類義語を追加する
  • 関連するドメイン用語を加える
  • LLMで「良さそうな検索クエリ」を生成する

ただし、クエリ拡張もやりすぎると、

  • 広く浅く、関係ありそうな文書を大量に拾ってしまう
  • 元の質問の「意図」からズレた方向に検索が寄っていく

といった副作用があり、結果としてハルシネーションを増やすことがあります。

個人的には、

  • クエリ拡張は「Recall を底上げするための手段」と割り切る
  • 再ランキングと組み合わせて、Precision を取り戻す

という位置づけで考えると整理しやすいと感じています。


4. モデル側でどう抑えるか:帰納的バイアスとプロンプト設計

検索側をいくら頑張っても、完全なノイズ除去は現実的ではありません。

そこで重要になるのが、「ノイズを含んだコンテキストを渡しても、モデルが暴走しにくい状態を作る」工夫です。

4-1. 「コンテキスト外は答えない」を徹底させる

RAGの基本方針として、

コンテキストにないことは答えない

というものがあります。これはモデルの帰納的バイアスと逆行する指示なので、かなり強めにプロンプトで明示する必要があります。

ざっくりした例ですが:

あなたは社内ナレッジベースに基づいて回答するアシスタントです。

重要なルール:
- 回答は必ず、与えられた「コンテキスト」の情報に基づいてください。
- コンテキストに含まれない情報については、推測で補完してはいけません。
- コンテキストに必要な情報が見つからない場合は、
  「この資料からは分かりません」と正直に答えてください。
- 自分の一般的な知識ではなく、コンテキストを最優先してください。


[コンテキスト]
{{retrieved_documents}}

[ユーザーの質問]
{{query}}

ここでよくある罠として、

「一般的な知識とコンテキストが矛盾する場合はどうするか」

があります。例えば、社内ではあえて一般的なベストプラクティスと違う運用をしているケースなどです。

その場合は、プロンプトのどこかに

一般的な知識とコンテキストが矛盾する場合は、コンテキストを優先してください

と書いておくと、少なくとも方向性は伝わります。

4-2. コンテキストの「引用」を強制する

もう一段踏み込むために、

  • 回答の中で「どのコンテキストに基づいたか」を引用させる

という方法があります。

例:

回答を作成するときは、必ず次の形式で書いてください。

1. 回答
2. 参照したコンテキストの抜粋

「参照したコンテキストの抜粋」では、どの段落のどの文に基づいたかが分かるように引用してください。

こうしておくと、

  • モデル側の「勝手な補完」を減らせる
  • 後段のロギングやモニタリングで、どのドキュメントがどの回答に使われたか追跡しやすくなる

というメリットがあります。

もちろん、引用自体がハルシネーションになる(実際にはそこに書かれていないことを、それっぽく引用してしまう)ケースもゼロではありませんが、少なくとも

  • 「引用と回答が完全に乖離している」

という明らかな異常は検知しやすくなります。

4-3. ルールベースの帰納的バイアス

少し別の観点として、LLMにすべてを任せるのではなく、

  • そもそもこのクエリは RAG を使うべきなのか
  • どのドメインのドキュメントだけを使うべきなのか

といった判断を、ルールベースや軽量モデルで先に振り分けるという設計もあります。

例えば:

  • 「請求」「支払い」などのキーワードが含まれる質問は、必ず最新の決済システムのドキュメントのみを検索対象にする
  • 「採用」「求人」などの質問は、人事の公開ポリシー文書だけを検索する

といった制約を前段でかけておくと、モデルの帰納的バイアスが悪さをしづらくなります。


5. 実務でどう運用するか:モニタリングと継続改善

ここまでの話を現場に落とすと、ポイントは次の 3つくらいに集約されます。

  1. 検索・埋め込み層の品質を定量的に見る
  2. 生成の前後で「怪しい挙動」を自動検知する
  3. ログからのフィードバックループを作る

ここではざっくり全体像だけ触れておきます。

5-1. 検索の品質モニタリング

  • 代表的な問い合わせパターンをいくつか定義しておく
  • それぞれに対して、「これが返ってきてほしい」というゴールド文書セットを用意する
  • 定期的に Recall@k / Precision@k を計測する

という古典的な情報検索の評価が、RAGでもそのまま効きます。

RAGの難しいところは、

  • 生成の品質が悪いと「モデルのせいなのか、検索のせいなのか」が分かりにくい

点なので、検索の品質を独立してモニタリングしておくのはかなり重要です。

5-2. 生成の品質とハルシネーション検知

ハルシネーションの自動評価については、

  • FActScore
  • HaluEval

など、いくつか研究レベルの指標が提案されています。詳細な数式や実装には踏み込みませんが、共通しているのは

  • 生成された文を文単位・事実単位に分解する
  • それぞれについて「本当にソースに書かれているか」を別モデル等で検証する

という考え方です。

RAGの文脈だと、

  • ソース = 検索で取得した文書

と考えやすいので、

「回答のこの部分は、どのコンテキスト文と対応しているのか」

をチェックする自動テストを CI/CD に組み込む、という運用が現実的な選択肢になってきます。

5-3. ログからの継続的改善

実運用をしていると、どうしてもハルシネーションはゼロにはなりません。

そこで重要になるのが、

  • ハルシネーションが発生したログを後から分析できるか
  • それを検索・埋め込み・プロンプト・ドキュメント構造の改善に還元できるか

というフィードバックループです。

例えば:

  • ユーザーが「それは違う」「情報が古い」とフィードバックしたセッションを優先的に抽出
  • そのセッションで使われた検索結果とコンテキストを確認
  • 「そもそも正しいドキュメントが返ってきていない」のか
  • 「正しいドキュメントはあるが、再ランキングで落ちている」のか
  • 「正しいドキュメントを読んでいるのに、モデルが変なまとめ方をしている」のか

を分類するだけでも、改善の打ち手はかなり見えてきます。


6. うまくいかないケース・まだ分かっていないこと

最後に、あえてうまくいきづらいポイントや、自分自身もまだ整理しきれていないところを率直に書いておきます。

  • ドメインが広すぎる場合の RAG
    社内全体のドキュメントをひとつのベクトルストアに突っ込むと、どうしてもノイズが増えます。ドメイン分割・インデックス分割をどこまでやるかは、組織構造やシステム境界とも絡んで簡単には決めきれません。

  • 最新情報の即時反映
    仕様変更が頻繁にある環境では、ドキュメント更新とインデックス更新のラグがハルシネーションに直結します。どのくらいの頻度で再インデックスするか、イベント駆動でどこまで自動化するかは、まだ試行錯誤が多い領域です。

  • 「適度な創造性」の扱い
    FAQ やヘルプのような用途では、とにかく事実性優先でよいのですが、企画・アイデア出しのようなタスクではむしろ「少し盛ってほしい」場面もあります。タスクごとにどこまでハルシネーションを許容するか、ポリシーとしてどう切り分けるかは、技術というより組織の設計の話に近いです。


まとめと「明日やるなら」の一歩

この記事で書いてきたことを雑にまとめると、

  • RAG はハルシネーションを「消す」魔法ではなく、「検索・埋め込み・モデルの三位一体で制御する」設計問題
  • ハルシネーションの原因を、検索ノイズ・埋め込み誤差・モデルの帰納的バイアスに分けて見ると整理しやすい
  • リトリーバの評価と再ランキング、強めのプロンプト、ログに基づく改善ループが実務ではかなり効く

明日なにか 1つ手を動かすなら、今運用している(あるいは PoC 中の)RAG システムで、検索結果だけを眺めてみることをおすすめします。

  • 代表的な 10〜20 件の実クエリをピックアップ
  • 上位 10 件の検索結果だけを、人間の目でチェック
  • 「そもそもここでノイズが多いのか/生成側で変になっているのか」を分けて考える

これをやるだけでも、

  • どこから手を付けるべきか
  • どの指標をトラッキングすべきか

の解像度がかなり上がるはずです。

余力があれば、そこから一歩進めて、

  • 再ランキングの導入
  • コンテキスト引用の強制
  • ハルシネーションっぽいログの収集

あたりを少しずつ仕込んでいくと、RAGとハルシネーションの付き合い方がだいぶ楽になってきます。

FAcraft Tech Blog

Discussion