RAG 上級講座 #5 引用でハルシネーションを減らす

読了 5分

第4回までで検索を磨きました。今回は第1回で分けた失敗の残り半分、生成の失敗の番です。正解のチャンクをコンテキストに入れてあげたのに答えが間違うケース、そしてさらに厄介なことに、チャンクにない内容をもっともらしく作り出すケースです。後者をハルシネーション(hallucination、モデルが根拠のない内容を事実のように生成する現象)と呼びます。RAG においてハルシネーションは信頼の問題であり、10回うまく答えても、一度のもっともらしい嘘がサービスを崩します。

根拠の中だけで答えさせる #

最初の防御線はシステムプロンプトです。核心は二つのことを明確にすることです。根拠の外の知識を使わないこと、そして根拠になければ「ない」と答えること

grounded_system.py
SYSTEM = """あなたは社内文書ベースの Q&A ボットです。

ルール:
- 答えは必ず、提供された文書チャンクの内容だけを根拠にすること。一般知識で補わないこと。
- 文書チャンクに答えがなければ、推測せず「提供された文書からは該当する内容が見つかりませんでした」と答えること。
- 文書チャンク同士で内容が矛盾していれば、矛盾しているという事実をそのまま伝えること。
"""

二つ目のルールが特に重要です。「わからない」が許されないと、モデルは空欄をでっち上げてでも埋めます。わからないと答える権利を明示的に与えることがハルシネーション抑制の半分です。このルールがあると、副次効果として診断もしやすくなります。「見つかりませんでした」という答えが増えるなら、それは生成ではなく検索を直せという信号です。

citations — 文ごとに出典を付ける #

プロンプトで「出典を示せ」と指示することもできますが、モデルが出典表記そのものをハルシネーションするという逆説が生まれます。Claude には、これを構造的に解決する citations 機能があります。チャンクを普通のテキストではなく document ブロックとして渡し、引用を有効にすると、応答の各文がどの文書のどの箇所に基づいたのかを、API が構造化データとして一緒に返してくれます。

rag_with_citations.py
def answer_with_citations(question: str, chunks: list):
    documents = [
        {
            "type": "document",
            "source": {"type": "text", "media_type": "text/plain", "data": c["text"]},
            "title": f'{c["metadata"]["source"]}{c["metadata"]["section"]}',
            "citations": {"enabled": True},
        }
        for c in chunks
    ]
    response = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=2048,
        system=SYSTEM,
        messages=[{"role": "user", "content": documents + [{"type": "text", "text": question}]}],
    )
    return response

第2回でチャンクに付けておいたメタデータが、ここで title として使われます。応答では、テキストブロックごとに引用情報が付いてきます。

render_citations.py
def render(response) -> str:
    out = []
    for block in response.content:
        if block.type != "text":
            continue
        out.append(block.text)
        for cite in (block.citations or []):
            out.append(f"  [出典: {cite.document_title}\"{cite.cited_text[:50]}\"]")
    return "\n".join(out)

cited_text は、モデルが作り出した表記ではなく、実際の文書からそのまま持ってきた原文の箇所です。ユーザーにとっては検証可能な答えになり、運用者にとってはハルシネーション検出器になります。引用が一つも付いていない断定的な文は、疑うべき対象です。

引用を品質ゲートとして使う #

引用データは、見せるための用途で終わりません。答えを送り出す前の検査装置として使えます。

citation_gate.py
def is_grounded(response, min_ratio: float = 0.5) -> bool:
    """本文テキストのうち引用が付いた割合が基準未満なら、疑わしい回答として扱う。"""
    cited, total = 0, 0
    for block in response.content:
        if block.type != "text":
            continue
        total += len(block.text)
        if block.citations:
            cited += len(block.text)
    return total == 0 or cited / total >= min_ratio

基準に届かない答えは、そのまま出す代わりに「関連する文書を十分に見つけられませんでした」に置き換えたり、より保守的なプロンプトで再生成する分岐を設けたりできます。粗い仕掛けですが、最も危険なタイプの答え(根拠なしに断定する答え)をふるい落とすには、この程度でも効果があります。

チャンクが多いときの注意点 #

生成の失敗のもう一つの原因は、コンテキストに入れるチャンクの量です。関連度の低いチャンクまで大量に入れると、モデルがその中からもっともらしい(しかし間違った)根拠を見つけ出すことが起きます。第4回のリランキングは、ここでも意味があります。少ないが正確なチャンクのほうが、多いが中途半端なチャンクより生成品質に有利です。citations を有効にすると、この問題も観察可能になります。見当違いのチャンクが繰り返し引用されるなら、検索候補を減らすか、リランキングの基準を上げる番です。

よくつまずくところ #

  • 「わからない」という答えを封じる — 「必ず答えよ」という指示はハルシネーション製造機です。わからないと答える道を開けておき、その頻度を検索改善の信号として使います。
  • 出典表記をプロンプトだけで指示する — モデルが書いた「[出典: …]」という文字列は、出典そのものがハルシネーションかもしれません。検証可能な出典が必要なら、citations のような構造化された機能を使います。
  • 引用表示をそのまま露出するcited_text と位置情報は素材です。ユーザー画面には、脚注や折りたたみ式の出典リストのような読みやすい形に加工して見せます。

まとめ #

今回は、生成の失敗とハルシネーションを扱いました。

  • 根拠の外の知識の禁止と「わからないと答える権利」をシステムプロンプトに明示することが、基本の防御線です。
  • citations 機能は、文ごとに実際の原文の箇所を出典として返してくれます。ユーザーには検証手段、運用者にはハルシネーション検出器です。
  • 引用の割合を品質ゲートとして使えば、根拠のない断定的な回答を送り出す前にふるい落とせます。

これで改善の道具は揃いました。残るのは、これらすべての変更を安心して繰り返せるようにする土台、評価の体系化です。次回の「RAG 上級講座 #6 RAG 評価パイプラインを作る」では、第1回のベースラインを本格的な評価体系に育てます。

X