RAGの回答に出典を付ける設計|「それらしい引用」を防ぐチャンクID・引用範囲・原文照合の作り方


社内RAGを公開してしばらくすると、ほぼ確実に来る要望があります。「回答の根拠を示してほしい」。回答の末尾に参照した文書名を並べるだけなら半日で終わります。ところが運用を始めると、出典に挙がった規程を開いても該当箇所が見つからない、書いてある番号の文書がそもそも存在しない、出典は正しいのに回答の数字がどこにも書かれていない、といった報告が届き始めます。出典は「それらしく付いている」ことがいちばん危険で、利用者は付いているというだけで確認をやめてしまうからです。この記事では、RAGの回答に出典を付ける部分だけに絞って、表示の粒度、実装方式の比較、壊れ方、機械的な検査、評価への組み込みまでを整理します。各社の組み込み機能については、公式ドキュメントで確認できた範囲だけを書きました。
「根拠を示して」は3つの別の要求
利用者が「根拠を示して」と言うとき、その中身は少なくとも3つに分かれます。ひとつめは、どの文書を見たのかを知りたいという要求です。ふたつめは、書いてあることを自分の目で確かめたいという要求です。みっつめは、回答のこの一文がこの箇所に本当に書いてあるのかを保証してほしいという要求です。
最初の要求は、検索でヒットした文書の一覧を出せば満たせます。2つめは、該当箇所へすぐ飛べる導線がなければ満たせません。3つめは、生成された文と引用箇所の対応関係を検査しないかぎり満たせません。多くのRAGが出典表示でつまずくのは、1つめの実装で3つめの期待に応えたつもりになっているためです。
この記事で扱うのは、2つめと3つめを満たすための設計です。検索精度そのものや、ハルシネーション対策の全体像は別の記事に譲ります。
あわせて読みたい
ハルシネーション対策の実務|検証フローの作り方と運用のコツ
出典表示の粒度を決める
最初に決めるのは、出典をどの細かさで示すかです。粒度は実装の難しさと、利用者が確認に要する手間の両方を決めます。
| 粒度 | 表示の例 | 利用者の確認手間 | 実装の要件 |
|---|---|---|---|
| 文書単位 | 「就業規則.pdf」 | 大きい。文書を開いて該当箇所を自分で探す | 検索結果の文書IDを持っていれば足りる |
| ページ・セクション単位 | 「就業規則.pdf 12ページ」「第5章 休暇」 | 中程度。開いたページの中を読む | 抽出時にページ番号や見出しパスをチャンクのメタデータに残す |
| 文字範囲単位 | 該当する一文をハイライトして表示 | 小さい。ハイライト箇所を読むだけ | 原文の版を固定し、チャンク内の開始・終了位置を保持する |
文書単位の出典は、実装が楽な分だけ検証の負担を利用者に回しています。数十ページの規程集が出典として挙がっても、利用者はまず該当箇所を探しません。確認されない出典は、付いていないのと実質的に同じか、安心感を与える分だけ悪い結果になります。
一方、文字範囲単位は原文の版管理とセットでないと成立しません。文書が改訂されて文字位置がずれると、保存しておいた開始・終了位置は別の文を指します。現実的な落としどころは、ページまたはセクション単位を基本とし、引用した文字列そのものを併記して、画面上で原文内を検索・ハイライトできるようにする構成です。位置の数値ではなく文字列を持っておけば、多少の版ずれがあっても原文側で探し直せます。
ヒント
前提: チャンクに「引ける住所」を持たせる
どの方式を選ぶにしても、出典はチャンクのメタデータからしか作れません。取り込みの段階で持たせておかなかった情報は、回答時にどう工夫しても出せません。最低限、次の項目を各チャンクに持たせます。
| 項目 | 用途 |
|---|---|
chunk_id | 回答側から参照する安定したID。再取り込みで意味が変わらないこと |
doc_idとdoc_version | どの文書のどの版かを特定する。旧版の引用を検知する材料 |
titleとurl | 表示用の文書名と、原文を開くためのリンク |
pageまたはsection_path | ページ番号、見出しの階層(例: 第5章/第2節) |
char_startとchar_end | 抽出後テキスト内での位置。文字範囲単位の表示に使う |
acl | 閲覧権限。検索時と表示時の両方で使う |
source_updated_at | 元文書の最終更新日時。回答に「いつ時点の情報か」を出す |
チャンクIDの安定性と版の扱いは、インデックス更新の設計と表裏一体です。IDが再取り込みのたびに変わると、ログに残った出典が後から辿れなくなります。PDFからページ番号や見出しを正しく取り出せるかは、ドキュメントパースの品質に依存します。
あわせて読みたい
RAGのインデックス更新・鮮度管理|「作ったら終わり」にしないための変更検知と削除反映の設計
出典を付ける3つの方式
実装方式は大きく3系統あります。それぞれに向く場面と、構造的に防げない壊れ方があります。
方式A: プロンプトでチャンクIDを付け、参照番号を返させる
検索したチャンクをプロンプトに入れるとき、それぞれに[c1]、[c2]のような短いIDを振り、「根拠にしたチャンクのIDを文末に付けて答えること」と指示する方式です。どのモデルでも動き、既存のRAGに後付けしやすいのが長所です。
以下の資料だけを根拠に質問に答えてください。
各文の末尾に、根拠にした資料のIDを[c1]の形式で付けてください。
資料に根拠がない内容は書かず、「資料からは分かりません」と答えてください。
[c1] (就業規則 第5章第2節 / 2026-04版)
年次有給休暇は、入社日から6か月経過した時点で付与する。...
[c2] (休暇取得ガイド / 2026-07版)
...
制約は、IDがモデルの出力テキストの一部でしかないことです。モデルは存在しない[c7]を書けますし、正しいIDを書いても、その資料に書かれていない内容を続けられます。後述する壊れ方のほぼすべてが、この方式では生で起こりえます。そのため方式Aを採るなら、出力を受け取ったあとの検査(IDの実在確認と原文照合)が必須になります。
IDは連番の短い記号にし、内部のchunk_idとはアプリケーション側で対応表を持つのが扱いやすいです。長いUUIDをそのまま見せると、モデルが一部を書き間違えたときに照合できなくなります。
方式B: 構造化出力で主張と根拠IDを対にする
回答を自由文ではなく、「主張」「根拠ID」「引用文字列」を持つオブジェクトの配列として返させる方式です。スキーマで形を固定できるため、パースの失敗がなくなり、主張ごとの検査が機械的に書けます。
{
"answer": [
{
"claim": "年次有給休暇は入社6か月後に付与されます。",
"evidence": [
{ "id": "c1", "quote": "入社日から6か月経過した時点で付与する" }
]
},
{
"claim": "申請は取得日の3営業日前までに行います。",
"evidence": [
{ "id": "c2", "quote": "取得希望日の3営業日前までに申請する" }
]
}
],
"unanswered": ["時間単位の有給休暇の上限日数"]
}
quoteを必須にしておくのが要点です。モデルに原文からの抜き書きを書かせれば、それが原文に実在するかを文字列照合で確かめられます。答えられなかった論点をunansweredのような欄で明示させると、出典のない主張が回答本文に紛れ込むのを減らせます。
ただし、構造化出力が保証するのは形だけです。idの値が実在するか、quoteが本当に原文にあるか、claimがquoteから導けるかは、スキーマでは検証されません。形が整っている分だけ正しく見えてしまう点にも注意が要ります。表示の段階では、この配列を自然な文章に組み立て直し、主張ごとに出典マーカーを付けます。
あわせて読みたい
構造化出力(Structured Outputs)とは何か|LLMの返答をプログラムで安全に扱うための仕組み
方式C: ベンダーの組み込み引用機能を使う
主要なLLMのAPIは、出典を返す機能を標準で持っています。ただし「何を指すか」が製品ごとに大きく違います。以下は2026年9月時点の各社公式ドキュメントで確認できた範囲です。
AnthropicのClaude APIには、Citationsという機能があります。公式ドキュメントによると、文書ブロックごとにcitations.enabled=trueを設定すると、回答が複数のテキストブロックに分かれることがあり、各ブロックが主張と、それを支える引用のリストを持ちます。対応する文書は3種類で、プレーンテキストは文単位にチャンク化されて文字位置(0始まり、終了位置は含まない)で、PDFは文単位にチャンク化されてページ番号(1始まり、終了ページは含まない)で、カスタムコンテンツは渡したブロックをそのまま単位としてブロック番号で引用が返ります。各引用にはcited_textとして引用された原文が入り、この欄は出力トークンに数えないと書かれています。RAG向けには、検索結果をsearch_resultブロックとして渡す方法も用意されています。source(URLまたは識別子)とtitleを必須とし、引用はsearch_result_locationとして、どの検索結果のどのテキストブロックを引いたかが返ります。ドキュメントは、テキストブロックが最小の引用単位で部分文字列は引かないため、細かく引用させたいなら内容を小さなブロックに分けるよう案内しています。
公式ドキュメントはプロンプトで引用させる方式との比較として、APIが引用をパースしcited_textを直接取り出すため「引用は提供された文書への有効なポインタを含むことが保証される」と説明しています。つまり存在しない箇所を指す引用は構造的に出ません。一方で制約もあります。引用できるのはテキストのみで、抽出可能なテキストを含まないスキャンPDFは引用できません。リクエスト内の文書は、すべて引用を有効にするか、すべて無効にするかのどちらかです。そして、Citationsは構造化出力と併用できず、引用を有効にした文書と構造化出力の指定を同時に送ると400エラーになると明記されています。方式Bと方式Cは、Claudeでは同じリクエスト内で両立しません。
OpenAIのResponses APIでは、ホスト型のfile searchツールがベクトルストアを検索し、回答テキストにfile_citationの注釈(annotations)が付きます。公式ガイドの例では、注釈はindex、file_id、filenameを持っています。APIの型定義はindexを「ファイル一覧内でのファイルのインデックス」と説明していますが、ガイドの例の値(992、1176)は回答テキスト上の位置のようにも見えるため、実際の意味は自分の環境で確かめてから使ってください。確実に言えるのは、ファイル単位の参照であることと、原文のどの文字列を根拠にしたかは注釈に含まれていないことです。検索結果そのものは既定では返らず、includeにfile_search_call.resultsを指定すると取得できると書かれています。引用箇所まで示したい場合は、この検索結果を保存し、アプリケーション側で回答と突き合わせることになります。ファイル属性によるメタデータフィルタも用意されており、権限や版での絞り込みに使えます。
GoogleのGemini APIでは、ai.google.devのドキュメントが新しいInteractions APIの例に移っており、従来のgenerateContent側のページは「Legacy」と表示されています。Interactions APIのFile Searchでは、回答テキストのブロックにfile_citationの注釈が付き、例ではfile_nameとsource、PDFのようにページを持つ文書ではpage_number、取り込み時に付けたメタデータとしてcustom_metadataが参照できると説明されています。Google検索によるグラウンディングでは、url_citationの注釈がstart_indexとend_indexで回答テキストの区間と出典URLを対応づけます。Legacyのページでは、同じ情報がgroundingMetadataのgroundingSupports(回答テキストの区間と、groundingChunks内の出典の番号の対応)として返ります。
| 機能 | 引用が指すもの | 原文の引用文字列 | 注意点 |
|---|---|---|---|
| Claude Citations(document) | 原文側の文字位置・ページ・ブロック | cited_textで返る | 構造化出力と併用不可。スキャンPDFは引用不可 |
| Claude search_result | 渡した検索結果内のテキストブロック | cited_textで返る | 引用は既定で無効(citations.enabledで有効化)。構造化出力と併用不可。ブロック単位でしか引かない |
| OpenAI file search | ファイル(indexの意味は実測で確認) | 注釈には含まれない | 検索結果はinclude指定で取得する |
| Gemini File Search(Interactions API) | ファイル、ページを持つ文書ではページ | 例では明示されていない | 取り込み時のメタデータを注釈で参照できる |
| Gemini Google検索グラウンディング | 回答テキストの区間と出典URL | 返らない(回答側の区間と出典URLのみ) | 検索候補(Search Suggestions)の表示要件は利用規約を確認する |
「回答のどこ」と「原文のどこ」を区別する
表で見落としやすいのが、位置の数値が何を指しているかです。Claudeの文字位置やページ番号は、原文側の位置です。Geminiのstart_index / end_indexは、回答テキスト側の位置、つまり「回答のこの部分に出典が付いている」ことを示すものです。後者だけでは、原文のどこを読めば確かめられるかは分かりません。UIで原文の該当箇所をハイライトしたいなら、原文側の位置を返す仕組みを選ぶか、検索結果を保存して自前で突き合わせる必要があります。
注意
SegmentのstartIndex / endIndexを「バイト単位」と定義しています。日本語はUTF-8で1文字が通常3バイトなので、文字列のスライスにそのまま使うと区間がずれます。英語のサンプルコードでは問題が表に出ません。新しいAPIの注釈やOpenAIのindexについても、何を指す値なのか、単位が文字・バイト・トークンのどれなのかを、日本語を含むテストデータで必ず実測してから使ってください。どれを選ぶか
組み込み機能は「存在しない箇所を指す引用」を減らす点で強力ですが、モデルやAPIの選択と結びつきます。方式Aと方式Bは、どのモデルでも同じ検査コードを使えます。実務では次のように考えると整理しやすくなります。
- 単一ベンダーで完結し、文書をリクエストに直接渡せる構成なら、原文側の位置と引用文字列を返す組み込み機能を第一候補にする
- 回答を後段のプログラムで処理する必要があり、構造化出力が欠かせないなら方式Bを採り、検査を自前で持つ
- 複数モデルを切り替える、または評価の比較を公平にしたいなら、方式Bの形式に揃えて検査を共通化する
どれを選んでも、次の節の壊れ方のうち少なくとも「主張を支えない引用」は残ります。ClaudeのCitationsのように引用先の実在を保証すると明記している機能でも、引用先がその主張を支えていることまでは保証しません。
出典の壊れ方5パターン
出典付きの回答は、次の5通りで壊れます。それぞれ検知の方法が違うので、分けて考えます。
| 壊れ方 | 例 | 主な検知方法 |
|---|---|---|
| 存在しないIDの捏造 | 渡したのはc1〜c5なのに[c7]が付く | IDの実在確認(全件・機械的) |
| 引用箇所は正しいが主張を支えていない | 引用は有給休暇の付与条件なのに、回答は日数を述べている | 主張と根拠の含意判定(抜き取り・LLM-as-a-Judge) |
| 複数チャンクの合成で出典が曖昧になる | 2つの文書の数字を足して答え、どちらか一方だけを出典にする | 主張単位の含意判定、合成を明示させる指示 |
| 古い版の文書を引用する | 改訂前の規程が検索に残っていて、そちらが引かれる | 版メタデータの照合、インデックスの鮮度監視 |
| 閲覧権限のない文書が出典に出る | 本文は伏せても、出典欄に機密文書のタイトルが表示される | 表示直前の権限再チェック |
存在しないIDの捏造
方式Aで最も頻繁に起きます。モデルはIDの形式を学んでいるので、それらしい番号を生成できます。検知は簡単で、回答に含まれるIDが今回プロンプトに渡したIDの集合に含まれるかを見るだけです。見つかったら、その主張を「出典なし」として扱います。黙って消すと、出典のない主張が出典付きの顔で残ります。
引用箇所は正しいが主張を支えていない
最も見つけにくく、最も害の大きい壊れ方です。引用した文字列は原文に実在し、話題も合っているので、利用者が引用を開いても「関係ある箇所だ」と感じて確認を終えがちです。たとえば「有給休暇は入社6か月後に付与する」という引用で、「初年度の付与日数は10日です」という主張を支えているように見せるケースです。話題は同じでも、日数はどこにも書かれていません。これは文字列照合では検知できず、主張と引用の意味的な関係を判定する必要があります。
複数チャンクの合成で出典が曖昧になる
「AとBを合わせるとこうなる」という推論は、RAGの価値そのものでもあります。問題は、合成した結論に片方の出典しか付かない場合や、どの部分がどの資料由来なのかが読み取れない場合です。対策は、合成した主張には関与したすべての根拠を付けさせ、「資料c1とc2から計算した」と書かせることです。構造化出力なら、evidenceを配列にしておけば自然に表現できます。
古い版の文書を引用する
改訂前の文書がインデックスに残っていると、モデルは正しく「引用」したうえで、古い情報を答えます。出典は原文に実在し、主張も引用から導けるので、ここまでの検査はすべて通ります。原因は出典付与ではなくインデックスの鮮度にありますが、出典の側でできることもあります。チャンクにdoc_versionとsource_updated_atを持たせ、同じdoc_idの新しい版が存在するなら出典として表示しない、または警告を付けることです。
閲覧権限のない文書が出典に出る
検索段階の権限フィルタが正しければ本来起きませんが、キャッシュや会話履歴を経由して他の利用者向けに取得したチャンクが混ざる、共有された回答画面を別の人が開く、といった経路で起きます。回答本文を伏せても、出典欄の文書タイトルだけで情報が漏れることがあります。「人事異動案_2027年度_部外秘」というタイトルは、それだけで十分な情報です。
あわせて読みたい
RAGのアクセス制御設計|社内文書の閲覧権限をベクトル検索にどう持ち込むか
対策1: 引用文字列を原文と機械照合する
最初に入れるべき検査は、引用文字列が原文に実在するかの照合です。安く、速く、全件に適用できます。組み込み機能でcited_textが返る場合は原文からの抜き出しなので、この検査は主に方式A・Bの出力に対して行います。
import unicodedata
import re
def normalize(s: str) -> str:
# 全角英数・記号の揺れ、改行や連続空白の違いを吸収する
s = unicodedata.normalize("NFKC", s)
s = re.sub(r"\s+", "", s)
return s
def verify_evidence(answer: dict, chunks: dict[str, str]) -> list[dict]:
"""chunks: 今回プロンプトに渡した {短いID: 原文テキスト}"""
results = []
for item in answer["answer"]:
for ev in item["evidence"]:
cid, quote = ev["id"], ev.get("quote", "")
if cid not in chunks:
status = "unknown_id" # 捏造されたID
elif not quote:
status = "no_quote" # 引用文字列がない
elif normalize(quote) in normalize(chunks[cid]):
status = "exact" # 原文に実在する
else:
status = "not_found" # 言い換え、または捏造
results.append({"claim": item["claim"], "id": cid, "status": status})
return results
実装上の注意がいくつかあります。第一に、正規化はUnicode正規化(NFKC)と空白の除去までに留めます。PDFから抽出した文字列は、改行位置や全角・半角が原文の見た目と違うことがよくあります。一方で、句読点を消したり類似度のしきい値を緩めたりしすぎると、言い換えや捏造まで通してしまいます。
第二に、not_foundを即座に誤りとは扱わないことです。モデルは指示しても原文を少し言い換えて引用することがあります。運用としては、not_foundの主張は画面上で出典マーカーを外して「出典を確認できませんでした」と注記する、または回答を再生成するといった扱いを決めておきます。どちらを選ぶかは、誤りのコストで決めます。
第三に、照合の対象は「今回プロンプトに渡したチャンク」に限定します。インデックス全体と照合すると、渡していない文書の文字列にたまたま一致して通ってしまいます。
メモ
対策2: 主張と根拠の含意を抜き取り検査する
引用が主張を支えているかは、意味の判定です。これには、LLMに判定させる方法(LLM-as-a-Judge)か、含意判定(NLI)用のモデルを使う方法があります。どちらも1件ごとに推論が走るため、全件に常時適用するかはコストと遅延で決めます。多くの場合、本番の回答から一定割合を抜き取って非同期に判定し、率の推移を監視する使い方から始めるのが現実的です。
判定プロンプトの要点は、判定対象を1つの主張と、それに付いた引用だけに絞ることです。回答全体と検索結果全体を渡して「正しいか」を問うと、判定が曖昧になります。
あなたは出典の検証担当です。以下の「引用」だけを根拠にしたとき、
「主張」が成り立つかを判定してください。一般常識や外部知識は使わないでください。
主張: 初年度の年次有給休暇は10日付与されます。
引用: 年次有給休暇は、入社日から6か月経過した時点で付与する。
次のいずれか1つをJSONで返してください。
- "supported": 引用だけから主張が導ける
- "partial": 主張の一部だけが引用から導ける(不足している要素を説明する)
- "not_supported": 引用からは主張が導けない
partialを用意しておくと、複数の要素を含む主張のうち一部だけが裏付けられているケースを拾えます。上の例では、付与のタイミングは引用から導けても日数は導けないので、partialまたはnot_supportedになるべきです。判定モデル自体も誤るので、判定結果の一部を人が見直し、人の判断との一致率を確かめてから指標として使います。判定モデルは回答生成と同じモデルにしないほうが、同じ思い込みを共有しにくくなります。
あわせて読みたい
LLM-as-a-Judge(LLMによる評価)とは|出力の良し悪しをLLMに採点させる仕組みとバイアスへの備え
対策3: 出典のない回答をどう扱うか
検査の結果、出典を確認できない主張が見つかったときの扱いは、事前に決めておく必要があります。選択肢は大きく3つです。
| 扱い | 向く場面 | 注意点 |
|---|---|---|
| 答えない(「資料からは分かりません」) | 規程、契約、手順など、誤りのコストが高い領域 | 答えない率が高すぎると使われなくなる。検索の取りこぼしと区別して記録する |
| 注記して表示する(「出典を確認できませんでした」) | 社内の一般的な調べもの、下調べ用途 | 注記が小さいと読まれない。出典付きの主張と見た目で区別する |
| 再生成する | 検査が安価で、応答時間に余裕がある場合 | 再生成しても通らないときの最終的な扱い(答えない、または注記)が別途必要 |
注意したいのは、「答えない」を増やすとそれ自体が品質問題になることです。答えない理由が「資料に書かれていない」のか「検索で見つからなかった」のかを区別して記録しておけば、前者は正しい挙動、後者は検索の改善課題として切り分けられます。
プロンプトで「資料に根拠がなければ答えない」と指示するだけでは、この扱いは徹底できません。指示はモデルの傾向を変えますが、保証はしません。最終的な扱いは、検査の結果を受けてアプリケーション側で決めるようにします。
対策4: 旧版と権限外の出典を表示前に止める
出典は、生成された直後ではなく、利用者に表示する直前にもう一度チェックします。生成から表示までの間に文書が改訂されたり、権限が変わったりすることがあるためです。特に回答を保存して後から再表示する機能や、回答を共有するリンクがある場合は必須です。
- 1
版を確認する
出典のdoc_idについて現在の最新版を引き、出典のdoc_versionと一致するかを見ます。一致しなければ「この出典は改訂前の版です」と注記するか、出典から外して再回答を促します。 - 2
表示する人の権限で再チェックする
回答を生成した人ではなく、いま画面を開いている人の権限で、出典の文書が閲覧可能かを確認します。閲覧できない出典は、タイトルも含めて表示しません。 - 3
出典を外した主張を扱い直す
権限や版の理由で出典を外した結果、出典のない主張が残るなら、対策3の扱いに回します。本文だけ残して出典欄を空にするのは避けます。 - 4
ログに記録する
どの理由で何件の出典を外したかを記録します。旧版の出典が頻繁に外れるなら、インデックスの鮮度に問題があるという信号です。
対策5: UIで原文へジャンプさせる
出典の価値は、利用者が実際に確かめられるかで決まります。確かめる手間を下げるためのUIの要点は次のとおりです。
- 出典マーカーを主張の直後に置く。回答末尾にまとめて並べると、どの文がどの出典に対応するかが分からない
- マーカーに触れると、引用文字列と文書名、版、ページをその場で表示する。別タブを開かなくても一次確認ができる
- 「原文を開く」で該当ページまたはセクションに直接移動し、引用文字列をハイライトする。PDFならページ指定で開き、HTMLならアンカーやテキスト検索を使う
- 原文照合を通らなかった主張や、含意判定で
not_supportedになった主張は、出典付きの主張と見た目で区別する - 「この出典は主張を支えていない」と報告できるボタンを付け、報告を評価データに回す
最後の報告ボタンは、評価データを集める仕組みとしても有効です。利用者が指摘した事例は、そのまま回帰テストのケースになります。
ヒント
評価指標を既存のevalに組み込む
出典の品質は、回答の正しさとは別の指標として測ります。回答が正しくても出典がずれていることも、出典が正しくても回答が言い過ぎていることもあるからです。
研究では、出典の品質を「再現率」と「適合率」に分けて測る考え方があります。Gaoらの論文(EMNLP 2023)が提案した評価基盤ALCEでは、引用の再現率(citation recall)を、回答の各文について、少なくとも1つの引用があり、かつ引用した文章をすべて連結したものがその文を含意する場合に1とし、全文で平均します。引用の適合率(citation precision)は、個々の引用について「無関係」かどうかを判定します。無関係とは、その引用単独では文を支えられず、かつその引用を除いても残りの引用で文が支えられる場合です。含意の判定にはNLIモデルを使っています。同論文は、ELI5データセットでは最良のモデルでも約半数の生成で引用による裏付けが完全ではないと報告しています。
また、評価ライブラリのRagasには、回答に含まれる主張のうち検索した文脈から導けるものの割合をFaithfulnessとして0から1で算出する指標があります。こちらは引用の対応ではなく、回答全体が文脈に忠実かを測るものです。出典の評価と組み合わせると、「文脈には忠実だが引用がずれている」状態を見分けられます。
実務では、これらを参考に、次の4つの指標を置くと扱いやすくなります。
| 指標 | 測り方 | 何が分かるか |
|---|---|---|
| ID実在率 | 回答中のIDのうち、渡したIDに含まれる割合 | 捏造の頻度。方式Aでは最初に見る |
| 引用一致率 | 引用文字列のうち、原文照合を通った割合 | 言い換えや捏造の頻度 |
| 引用の正確性(precision) | 付いた引用のうち、単独で主張を支えるか、合成主張で他の引用と合わせたときに欠かせないと判定された割合 | 主張を支えない引用の頻度 |
| 引用の網羅性(recall) | 主張のうち、出典が付き、かつその出典で支えられている割合 | 出典のない主張の頻度 |
- 1
評価セットに「正解の根拠」を足す
既存の評価セットの各質問に、期待する回答に加えて、根拠となるべき文書IDと該当箇所を記録します。最初は数十件で構いません。利用者の報告から集めた失敗事例を必ず含めます。 - 2
機械的な指標から入れる
ID実在率と引用一致率は、モデル呼び出しなしで計算できます。まずこの2つを毎回のevalで出し、プロンプトやチャンク設定を変えたときの回帰を検知できる状態にします。 - 3
含意判定の指標を足す
正確性と網羅性はLLM-as-a-Judgeで判定します。判定結果の一部を人が見直して一致率を確かめ、判定プロンプトを固定してから指標として扱います。判定プロンプトを変えたら、過去の数値とは比較しません。 - 4
根拠の取りこぼしを別に見る
評価セットに記録した正解の根拠が、検索結果に入っていたか、回答の出典に選ばれたかを分けて測ります。検索に入っていなければ検索の問題、入っていたのに引かれなければ生成の問題です。 - 5
本番の抜き取りと接続する
本番回答の抜き取り判定の結果を、同じ指標の定義で集計します。評価セットの数値と本番の数値が大きく離れたら、評価セットが実際の質問を代表していないという信号です。
指標を1つにまとめたくなりますが、分けておくほうが原因を特定しやすくなります。ID実在率が下がったならプロンプトやモデルの変更、引用一致率が下がったならチャンクの前処理の変更、網羅性だけが下がったなら検索件数や再ランキングの変更を疑う、という具合です。
あわせて読みたい
LLMアプリの評価(eval)の作り方|「動いた気がする」で止めないための実務手順
本記事の各社機能に関する記述は、執筆時点(2026年9月)のAnthropic(Claude API Docs)、OpenAI(API Docs)、Google(Gemini API Docs)の公式ドキュメントに基づきます。機能名、対応するファイル形式、返却されるフィールドは更新されることがあるため、実装時は必ず最新の公式ドキュメントを確認してください。評価指標の定義はGaoら(EMNLP 2023)およびRagasのドキュメントによります。本記事は一般的な設計方針の整理であり、特定製品の優劣を判断するものではありません。
よくある質問
ベンダーの組み込み引用機能を使えば、自前の検査は不要になりますか
出典は文書単位で十分ではありませんか
構造化出力と組み込みの引用機能は一緒に使えますか
引用文字列の照合で一致しなかったら、回答を捨てるべきですか
引用の評価にはどの指標を使えばよいですか
まとめ
RAGの出典付与は、表示の問題に見えて、実際にはデータ設計と検査の問題です。チャンクに版と位置と権限を持たせておかなければ、どんな方式を選んでも細かい出典は出せません。出典を出せるようになっても、それが主張を支えているかは別途確かめる必要があります。
原則を3つに絞るなら、次のとおりです。第一に、出典は利用者が確かめられる粒度で出すこと。確かめられない出典は、安心感だけを与えて確認を止めてしまいます。第二に、機械で確かめられることは全件で確かめること。IDの実在と引用文字列の実在は、安価に毎回検査できます。第三に、意味の判定は抜き取りで回し、指標として追うこと。主張を支えない引用は、測らなければ増えていても気づけません。
組み込みの引用機能は強力な部品ですが、何を保証し、何を保証しないかを理解して使う必要があります。「引用先が実在する」と「引用先が主張を支えている」の間にある距離を、自前の検査と評価で埋めてください。
RAGの出典付与チェックリスト
- 文書種別ごとに出典の粒度(文書/ページ・セクション/文字範囲)を決めた
- チャンクに安定したID、文書の版、ページまたは見出しパス、位置、権限、更新日時を持たせている
- 採用した方式(チャンクID/構造化出力/組み込み機能)が保証すること・しないことを整理した
- 組み込み機能の位置情報が、回答側と原文側のどちらを指し、単位が文字かバイトかを日本語データで実測した
- 回答中のIDが今回渡したIDに含まれるかを全件で検査している
- 引用文字列が原文に実在するかを、正規化したうえで全件照合している
- 主張と根拠の含意を抜き取りで判定し、判定結果の一部を人が見直している
- 出典を確認できない主張の扱い(答えない/注記/再生成)を決めている
- 表示の直前に、閲覧者の権限と文書の最新版で出典を再チェックしている
- 出典マーカーから原文の該当箇所へ移動し、引用文字列をハイライトできる
- 利用者が出典の誤りを報告でき、報告が評価データに回る
- ID実在率・引用一致率・正確性・網羅性を既存のevalで毎回算出している
あわせて読みたい
RAGとは何か|仕組み・向き不向き・導入判断の考え方
出典・参考
- Citations - Claude API Docs
- Search results - Claude API Docs
- Structured outputs - Claude API Docs
- File search - OpenAI API
- File Search - Gemini API (Google AI for Developers)
- Grounding with Google Search - Gemini API (Google AI for Developers)
- Grounding with Google Search - Gemini Generate Content API (Legacy)
- Generating content - Gemini API reference(Segment / CitationSource)
- Enabling Large Language Models to Generate Text with Citations(Gao et al., EMNLP 2023)
- Faithfulness - Ragas Documentation
関連する記事
RAGとは何か|仕組み・向き不向き・導入判断の考え方
社内文書をAIに答えさせる代表的な手法であるRAG(検索拡張生成)を解説します。検索と生成を組み合わせる仕組み、向いている用途と向かない用途、導入前に確認したい判断ポイントをまとめます。
ハルシネーション対策の実務|検証フローの作り方と運用のコツ
LLMがもっともらしい誤情報を生成するハルシネーションへの実務的な対策を解説します。発生をゼロにできない前提での検証フローの設計、用途別のチェックの深さ、プロンプトと仕組みでの抑制策をまとめます。
RAGのアクセス制御設計|社内文書の閲覧権限をベクトル検索にどう持ち込むか
社内文書RAGで「権限のない人が要約経由で機密を読めてしまう」事故を防ぐ設計。チャンクが元ファイルのACLを失う理由、事前フィルタ・事後フィルタ・インデックス分割・ACL同期の4パターン比較、権限フィルタとANNインデックスの再現率トレードオフを実装例とともに整理します。


