powered by TechFeed
表示モード
ハウツー

RAGシステムは「エラーを吐かずに間違える」— 検索失敗とハルシネーションをCIで自動検出する継続的評価パイプラインの作り方

7月16日、Towards Data Scienceが「Building Trustworthy Production RAG Systems Through Continuous Evaluation」と題した記事を公開した。この記事では、本番RAGシステムにおける検索失敗・ハルシネーション・性能劣化を継続的な評価パイプラインで検出・防止する実践的な手法について詳しく紹介されている。RAGシステムはエラーを吐かない。それが厄介だ。検索は何かしらチャンクを返し、モデルは流暢な文章を生成する。だが返ってきた答えが間違ったドキュメントに基づいていたり、3バージョン前の古い情報だったりしても、ログには何も残らない。本記事はこの問題に正面から向き合い、「ゴールデンデータセットの構築」から「CI組み込みの自動評価」まで、実際に手を動かせるレベルで解説している。Enterprise向けRAGシリーズの第4弾(第3弾はHybrid Search and Re-Ranking)という位置づけだ。

7月16日、Towards Data Scienceが「Building Trustworthy Production RAG Systems Through Continuous Evaluation」と題した記事を公開した。この記事では、本番RAGシステムにおける検索失敗・ハルシネーション・性能劣化を継続的な評価パイプラインで検出・防止する実践的な手法について詳しく紹介されている。

RAGシステムはエラーを吐かない。それが厄介だ。検索は何かしらチャンクを返し、モデルは流暢な文章を生成する。だが返ってきた答えが間違ったドキュメントに基づいていたり、3バージョン前の古い情報だったりしても、ログには何も残らない。

本記事はこの問題に正面から向き合い、「ゴールデンデータセットの構築」から「CI組み込みの自動評価」まで、実際に手を動かせるレベルで解説している。Enterprise向けRAGシリーズの第4弾(第3弾はHybrid Search and Re-Ranking)という位置づけだ。


まず「ゴールデンデータセット」なしには何も始まらない

最初のステップは評価ライブラリの導入ではなく、正解付きの質問セット(ゴールデンデータセット)の整備だ。これを飛ばして評価ツールをランダムなクエリに走らせても、システムが正しいのか間違っているのかはほぼ何もわからない。

各エントリに必要なフィールドは3つ。質問、正解、そしてどのドキュメントに答えが含まれているかだ。3番目のフィールドが肝で、「検索失敗(間違ったチャンクを取得した)」と「生成失敗(正しいチャンクを取得したが間違った答えを生成した)」を切り分けられる。これは異なるバグであり、修正方法も違う。

golden_set = [
    {
        "question": "What is the maximum file size for uploads?",
        "ground_truth": "25 MB per file on the free plan, 200 MB on paid plans.",
        "source_doc": "upload_limits.md",
        "category": "single_fact",
    },
    {
        "question": "Can I cancel my subscription mid-cycle and get a refund?",
        "ground_truth": "No, cancellations take effect at the end of the billing cycle. No partial refunds.",
        "source_doc": "billing_policy.md",
        "category": "single_fact",
    },
]

エントリ数は20〜30件で十分だが、categoryフィールドの設計が品質を左右する。単純な事実確認だけで構成されたデータセットは全問正解してもほぼ無意味だ。RAGが実際に本番でやらかすカテゴリを押さえる必要がある。

カテゴリ 内容
Multi-hop(複数チャンク推論) 複数チャンクを組み合わせて初めて答えられる質問
No answer expected(回答拒否が正解) 正しい挙動は「答えない」こと。最も近い文書を推測して答えるのはNG
Conflicting / stale docs(新旧矛盾) 同じポリシーの新旧バージョンが両方コーパスに存在するケース
Adversarial phrasing(表現ゆらぎ) ソース文書と異なる用語で同じことを聞いてくるケース

記事が特に強調しているのが「Conflicting / stale docs」だ。エンタープライズの文書ストアには誰もアーカイブしていない旧バージョンが必ず溜まっていく。その古いドキュメントを忠実に要約した答えは、faithfulnessスコアは高くても内容は完全に間違っている。このカテゴリをテストしない評価パイプラインは、そのリスクに対して完全に盲目だ。


RAGASで自動スコアリング——ただし「faithfulness高い=正解」は罠

基本的な動作確認(手動で答えを読む)を済ませたら、RAGAS(Retrieval Augmented Generation Assessment)で4つの指標を自動計測する。RAGASはRAGシステムの評価に特化したOSSのフレームワークで、検索品質と生成品質をそれぞれ独立して定量化できる点が特徴だ。

from ragas import evaluate
from ragas.metrics import (
    context_precision,
    context_recall,
    faithfulness,
    answer_relevancy,
)
  • Context Precision:取得したチャンクのうち、実際に関連していたものの割合(ノイズのペナルティ)
  • Context Recall:正解を導くのに必要な情報が取得できていたか
  • Faithfulness:答えが取得したコンテキストに基づいているか(ハルシネーション検出)
  • Answer Relevancy:答えが質問に対して適切に応答しているか

典型的なスコア例は以下の通り。

Metric Score
Context Precision 0.81
Context Recall 0.74
Faithfulness 0.88
Answer Relevancy 0.85

ここで最も危険な誤読が「faithfulness高い=答えが正しい」だ。faithfulnessは「取得したコンテキストに基づいているか」しか見ない。取得したコンテキスト自体が古い文書だった場合、その文書を忠実に要約した答えはスコアが満点でも内容は完全に誤りになる。RAGASの構造的な限界がここにある。


RAGASの限界を補う:カスタムLLMジャッジ

RAGASのデフォルトプロンプトはドメイン固有の要件(数値の正確さ、情報の鮮度、必須の免責事項など)を評価できない。そこでLLMをジャッジとして使い、独自の評価軸をプロンプトで定義する。

JUDGE_PROMPT = """
Compare the generated answer to the ground truth. Score 1-5 on each dimension.
...
1. numeric_accuracy - are all numbers and facts correct, not just plausible?
2. recency_awareness - if the source is outdated, does the answer flag
   uncertainty instead of stating it as current fact?
Return JSON only: {"numeric_accuracy": int, "recency_awareness": int, "reasoning": str}
"""

コストが高いため、全データに対して毎回実行しないことが重要だ。RAGASでは評価しきれないconflicting_docsno_answer_expectedのカテゴリに絞って適用する。


CIに組み込んでこそ「パイプライン」になる

以下のティアリング構成が現実的だ。

  • RAGAS(全ゴールデンセット):検索・プロンプトに触れるすべてのPR
  • カスタムジャッジ(フラグ付きカテゴリのみ):同上、ただし数件に絞る
  • 人間レビュー:週次、RAGASとカスタムジャッジのスコアが乖離したケースのみ
  • 本番トラフィックのサンプリング:週次、直近7日間のクエリから50件
def check_regression(current_scores, baseline_scores, threshold=0.03):
    regressions = [
        (metric, baseline_scores[metric], score)
        for metric, score in current_scores.items()
        if baseline_scores[metric] - score > threshold
    ]
    if regressions:
        raise SystemExit(f"Blocking merge — regressions found: {regressions}")
    print("No regressions. Safe to merge.")

このCIゲートが最も重要な部分だ。チャンキングの変更が特定カテゴリの質問を静かに壊していた場合、3週間後にサポートチケットとして顕在化するのではなく、PRの時点でビルド失敗として検出できる。

また、コーパスのドリフト検出も欠かせない。本番では新しい文書が追加され、古いものがアーカイブされ、実ユーザーのクエリはゴールデンデータセット作成時から徐々にずれていく。週次で本番ログをサンプリングしてRAGASを実行し、コード変更なしにスコアが急落した場合はコーパス側の変化を疑う。


まとめ

記事の最後に率直な言葉がある。「ゼロから始めるなら、6ステップを一度に構築しようとしないこと。20問の良質なゴールデンデータセットとRAGASスコアを手動で確認するだけで、今日本番稼働しているRAGシステムの大半より前に進んでいる」。

評価を「リリース前に一度やること」から「CIに静かに座って変化を検出し続けるインフラ」に昇格させる——それがこの記事の伝えたいことだ。

※編集部の考察:RAGの評価が難しいのは、失敗が「沈黙」として現れる点にある。本記事のアプローチが特に価値を持つのは、その沈黙をコード変更の粒度で可視化する仕組みを提示している点だ。LLMを活用したプロダクト開発が成熟フェーズに入りつつある今、こうした「評価のインフラ化」は品質保証の新たな標準になっていく可能性がある。

詳細はBuilding Trustworthy Production RAG Systems Through Continuous Evaluationを参照していただきたい。