powered by TechFeed
表示モード
Deep Dive

RAGが「この文書に答えはない」と言うなら、4種類の証拠を示せ

9月2日、Angela ShiおよびKezhan Shiが「A RAG That Says "Not in This Document" Has to Show Four Kinds of Evidence」と題した記事を公開した。RAGシステムが「この文書には答えがない」と返すとき、その判定は4種類の証拠とセットでなければ意味をなさない——本記事はそう主張し、パーシング・質問解析・検索・生成の各レイヤーが何を証拠として出力すべきかを実装レベルで解説している。

9月2日、Angela ShiおよびKezhan Shiが「A RAG That Says "Not in This Document" Has to Show Four Kinds of Evidence」と題した記事を公開した。RAGシステムが「この文書には答えがない」と返すとき、その判定は4種類の証拠とセットでなければ意味をなさない——本記事はそう主張し、パーシング・質問解析・検索・生成の各レイヤーが何を証拠として出力すべきかを実装レベルで解説している。


RAGシステムが最も難しい場面のひとつは、答えが「存在しない」ときだ。LLMはどんな質問にも何かを答えようとする傾向があるため、「見つかりません」という正直な応答は、設計に組み込まなければ実現しない。しかも、ただ「わかりません」と返すだけでは不十分だ。ユーザーはその応答が「本当に探した結果か」「探し方が浅かっただけか」を区別できない。

本記事はEnterprise Document Intelligenceシリーズのボーナス記事として位置づけられており、同シリーズの前回記事群は著者のMediumページで参照できる。コンパニオンコードはGitHub(doc-intel/notebooks-vol1)で公開されている。


なぜ「No Answer」には証拠が必要か

記事が例示するのは、世界銀行のCommodity Markets Outlook(2025年4月号)——石油・農業・金属価格に関する63ページの四半期レポートだ。このレポートに対して「AIは世界でどれだけ電力を消費しているか?」と質問すると、システムは「この文書には答えがありません」と返す。

応答は正しい。しかしユーザーは「本当に全ページ調べたのか?」を確認できない。「見つからなかった」という判定は、それを裏付ける証拠セットとセットでなければ意味をなさない。

この記事はRAGを構成する4つのレイヤー(パーシング・質問解析・検索・生成)それぞれが、No Answerの判定に対して何を証拠として出力すべきかを整理している。


4種類の証拠、それぞれの役割

1. パーシング:「どこまで読んだか」の記録

パーシングの出力は単なるテキストの塊ではなく、line_df(行ごとのテキスト・ページ番号・バウンディングボックス・行タイプ)、image_dftoc_dfcross_ref_dfなどのDataFrameだ。No Answerの判定では、これらを集計するだけで証拠が得られる。

parse_coverage = {
    "pages_total":       len(out["page_df"]),
    "pages_with_text":   out["line_df"]["page_num"].nunique(),
    "images_total":      len(out["image_df"]),
    "toc_entries":       len(out["toc_df"]),
    "cross_refs_total":  len(out["cross_ref_df"]),
    "objects_registered": len(out["object_registry"]),
}
# CMO April 2025の場合: 63ページ、63ページにテキスト、426件の画像登録、
# TOCエントリ9件、本文内クロスリファレンス76件

特に重要なのが未解決のクロスリファレンスだ。「付録B参照」「図3.D参照」といった参照先がobject_registryに存在しない場合、それはパーシングの欠落を意味し、答えが隠れている可能性がある。CMOの場合は未解決クロスリファレンスがゼロだったため、「63ページ全て解析済み、欠落なし」と明示できる。

また、グラフの軸ラベルや凡例は画像内テキストとして埋め込まれているケースがある。image_dfにOCR結果を付加することで、line_dfだけでは見逃す情報もカバーできる。

2. 質問解析:キーワードの網羅性が証拠になる

検索は列挙したキーワードの質に依存する。「AI」だけ指定して「artificial intelligence」を忘れれば、そのフレーズで書かれた箇所は全て見逃す。

記事では専門家(ドメインエキスパート)がキーワードのバリアントを列挙するconcept_keywords_dfスキーマを採用している。たとえば「AI」概念なら:AIartificial intelligencemachine learningdeep learningneural networkGenAILLMlarge language modelなど。

専門家が網羅できているか不安な場合は、コーパストークンを埋め込みでクラスタリングし、シードキーワードの近傍にある語を候補として提示するアルゴリズム的な安全網も紹介されている。ただし決定権は専門家にある——アルゴリズムは候補を提示するだけで、採否は人間が判断する。このキーワードセットそのものが質問解析レイヤーの証拠だ。

3. 検索:Top-Kではなく全件スイープ

通常のRAG検索はTop-Kを返す。しかしNo Answerの証明には全数調査が必要だ。Top-10を見て「なかった」と言っても証明にならない。

def sweep_for_absence(line_df, concept_keywords_df):
    rows = []
    for concept_row in concept_keywords_df.itertuples():
        pattern = r"\b(" + "|".join(map(re.escape, concept_row.variants)) + r")\b"
        matched = line_df[line_df["text"].str.contains(pattern, regex=True, case=False, na=False)]
        for line in matched.itertuples():
            rows.append({
                "concept": concept_row.concept,
                "variant": extract_first_match(pattern, line.text),
                "page":    line.page_num,
                "line":    line.line_num,
                "snippet": line.text[:120],
            })
    return pd.DataFrame(rows)

返り値はスコアなしの全ヒット一覧だ。この結果の解釈は3段階になる:

  • ゼロヒット:その概念はドキュメントに全く登場しない → 不在の強い証拠
  • ヒットあり・共起なし:概念は別々のページに出るが交差しない → 中程度の証拠
  • 共起あり:同じページや段落に複数概念が出現 → 弱い証拠(生成レイヤーで判断が必要)

CMOの場合、7,829行に対してスイープした結果、「AI」「data center」はゼロヒット、「electricity」は30・31ページに7件のヒットがあったが、AIとの共起はゼロだった。

4. 生成:構造化された「No Answer」の出力

最後の生成レイヤーは、上記3つの証拠をまとめてユーザーに提示する。AnswerWithEvidenceの兄弟スキーマとしてAbsenceJustificationが定義されており、主なフィールドは次のとおりだ。

  • **answer**:常にnull(回答が存在しないことを型レベルで保証する)
  • **reason**:concept_not_found / concept_found_but_off_topic / ambiguous_questionのいずれか
  • **concept_hits**:各概念のヒット数サマリー(検索レイヤーの集計をそのまま受け渡す)
  • **closest_mentions**:最近傍スニペットのリスト(ゼロヒット時は空配列)
  • **absence_explanation**:「なぜ答えでないか」を一文で説明する自然言語フィールド
  • **suggested_requery**(オプション):質問が曖昧だった場合の再質問候補

たとえばCMOに対する「AIの電力消費量は?」という質問では、以下のような出力が期待される。

{
  "answer": null,
  "reason": "concept_not_found",
  "concept_hits": {
    "AI": 0,
    "data center": 0,
    "electricity": 7
  },
  "closest_mentions": [
    {"page": 30, "snippet": "electricity prices in Europe fell sharply in Q1..."}
  ],
  "absence_explanation": "AIや data centerへの言及はゼロ件。electricityは30〜31ページに7件登場するが、いずれもヨーロッパの電力価格に関する記述であり、AI消費との関連はない。",
  "suggested_requery": null
}

モデルはスキーマの内容を発明しない。パーシング・質問解析・検索の出力をそのまま受け取り、reasonの選択・closest_mentionsの選定・一文の理由説明・オプションの再質問提案だけを担う。


設計思想の核心

記事全体を通じて強調されているのは、「自信を持った誤答はバグであり、根拠のない不回答もほぼ同様に悪い」という点だ。No Answerを正当化するには、4つのレイヤーそれぞれが証拠を出力する構造が必要であり、どれか一つを省略すれば判定の信頼性は失われる。

詳細はA RAG That Says "Not in This Document" Has to Show Four Kinds of Evidenceを参照していただきたい。