の回答に出典を付けるのは、いまや標準的です。ただし、出典を表示することと、検証できることは別です。
この回は、追跡できる形で出典を返す設計を扱います。実装の細部で、価値が大きく変わる領域です。
1. よくある不十分な実装
最も多いのが、渡した文書を全部並べるという実装です。
| 実装 | 問題 |
|---|---|
| 渡した文書を全部並べる | その記述の根拠かどうか不明 |
| モデルに出典を書かせる | 存在しない出典を出すことがある |
| 文書全体を示す | 該当箇所を探す手間が利用者に残る |
| 出典を示さない | 検証しようがない |
1行目が最も多い。5件渡して5件並べるなら、それは「参考にしたかもしれない文書の一覧」であって、出典ではありません。利用者はどれを見ればよいか分かりません。
2行目は EP.11(ガードレール)でも扱われている論点です。モデルはもっともらしい出典を作ることがあります。渡していない文書名が出る、実在しない条項番号が出る。必ず検証が要ります。
皮肉なことに、出典が付いていると、利用者は内容を確認しなくなります。「根拠が示されているから正しいだろう」と受け取る。だからこそ、出典の正確さは本文以上に重要です。
2. まず存在を検証する
最低限やるべきことは、出典として挙げられたものが、実際に渡した文書に含まれるかの確認です。これは決定的に判定できます。
import re
class CitationError(Exception): pass
def validate_citations(answer: str, provided_docs: list) -> dict: """回答中の出典が、渡した文書に含まれるかを確認する。
回答は [doc-001] のような形式で出典を含む前提。 """ provided_ids = {d["id"] for d in provided_docs} cited_ids = set(re.findall(r"\[(doc-[0-9a-z-]+)\]", answer))
# 1. 渡していない文書を出典にしていないか(作り話の検出) fabricated = cited_ids - provided_ids if fabricated: raise CitationError(f"存在しない出典: {sorted(fabricated)}")
# 2. 出典が1つも無いのに、断定的に答えていないか if not cited_ids and len(answer) > 50: return {"ok": False, "reason": "出典なしで回答している", "cited": cited_ids}
# 3. 渡したのに一度も使われなかった文書(検索の精度の手がかり) unused = provided_ids - cited_ids
return {"ok": True, "cited": cited_ids, "unused": unused}1番目が最も重要です。渡していない文書を出典にしているなら、それは作り話です。機械的に弾けるので、必ず入れてください。
3番目は副次的に有用です。渡したのに使われなかった文書の割合は、検索の精度の手がかりになります。毎回5件渡して1件しか使われないなら、上位2〜3件で足りているかもしれません。渡す件数を減らせれば、EP.08 で扱ったコストの削減にも直結します。
3. どの部分が、どの出典か
存在の検証ができたら、次は対応づけです。回答のどの部分が、どの文書に由来するか。
| 粒度 | 利用者の手間 | 実装 |
|---|---|---|
| 回答全体に1つ | どこが根拠か探す | 簡単 |
| 段落ごと | 近い範囲を見ればよい | 中 |
| 文ごと | 該当箇所が明確 | やや複雑 |
| 該当箇所を引用 | そのまま確認できる | 複雑 |
下2行が目指す形です。特に最下行 ── 元の文書の該当箇所をそのまま引用して並べる ── なら、利用者はその場で確認できます。
実装としては、モデルに文ごとの出典を書かせるか、回答の各文と渡した文書を照合するという方法があります。前者は簡単ですが誤りが混ざり、後者は確実ですが計算が要ります。
検索の段階で、その文書のどの部分が該当したかは分かっています。分割した単位を捨てずに保持しておけば、該当箇所の引用を出せます。文書全体を出典とするより、利用者の確認コストが桁違いに下がります。
4. 出典が示せない場合
重要な設計判断です。出典を示せない内容を、どう扱うか。
| 状況 | 扱い |
|---|---|
| 渡した文書に根拠がある | 出典を付けて答える |
| 複数の文書を組み合わせた推論 | 根拠となった文書を全部示す |
| 一般的な知識で補った | その旨を明示する |
| 根拠がない | 答えない |
3行目が判断の分かれ目です。渡した文書に書かれていないことを、一般的な知識で補うのを許すかどうか。社内文書の検索では、許さないほうが安全なことが多い。
4行目は LLM時代のテスト戦略 EP.08 でも扱いました。「答えられない」と言えることは、正しく答えることと同じくらい重要です。評価セットには、答えられない質問を必ず入れてください。
5. 表示の設計
検証できても、表示が悪ければ確認されません。利用者が確認しやすい形を考えます。
- 該当箇所を展開できる — クリックで本文が見える
- 元の文書へ移動できる — 全文を確認したいとき
- 更新日時を添える — 古い情報かの判断(EP.14)
- 信頼度を示す — 検索の一致度など
- 出典が1件だけの場合を目立たせる — 裏取りが薄い
1番目が最も効きます。別のページに移動しないと確認できない設計だと、確認されません。その場で展開できるだけで、確認の率が大きく変わります。
5番目は判断材料として有用です。複数の文書が同じことを言っているなら確からしい。1件だけなら、その1件が古い可能性もあります。裏取りの厚さを示すという発想です。
6. 何を測るか
出典の質は、測ることができます。指標設計の教科書 EP.21 で扱った考え方を、この領域に適用します。
| 指標 | 何が分かるか |
|---|---|
| 存在しない出典が出た割合 | 作り話の頻度(0であるべき) |
| 出典なしで答えた割合 | 根拠なしの回答 |
| 出典が展開された割合 | 確認されているか |
| 渡したが使われなかった割合 | 検索の精度 |
| 出典が1件だけの割合 | 裏取りの薄さ |
1行目は0であるべき指標です。検証を入れていれば0になります。0でないなら、検証が漏れている経路があります。
3行目は利用のされ方を示します。出典が一度も展開されていないなら、利用者は確認していません。これは信頼されている証拠でもあり、危険な状態でもあります。用途によって解釈が変わります。
出典を表示することと、検証できることは別。渡した文書を全部並べるのは出典ではない。まず存在の検証(渡していない文書を挙げていないか)を機械的に入れる ── これは決定的に判定できる。粒度は文単位、できれば該当箇所の引用まで。根拠がなければ答えない。そしてその場で展開できる表示にしないと、確認されません。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。