ClaudeでRAGの評価データセットを合成し、golden setに絞る手順
社内文書から質問・答え・根拠の引用をClaudeに作らせ、コード、LLM、人の三段で間引いてRAG評価用のgolden setにする手順とスクリプトです。
RAGを評価するには、質問と正解と「どの文書のどこが根拠か」の3点が組になったデータが要ります。社内文書に対して人が一から書くと数十問で力尽きるため、Claudeに候補を作らせて人が間引く方式が現実的です。
先に結論を書きます。Claudeが作るのは候補であって正解データではありません。候補の山から、答えが文書に無い質問と、読み方が2通りある質問を落とし、残りを凍結したものがgolden setです。この記事では落とす作業を3段(コード、Claude、人)に分け、動くスクリプトまで示します。
合成した質問をそのまま評価に使えない理由
LLMに文書を渡して質問を作らせると、それらしい質問がいくらでも出てきます。問題は質問の質が揃わないことです。
- 文書に答えが書かれていない質問が混ざる
- 「その件はいつからですか」のように、単体で読むと何を指すか分からない質問が混ざる
- 引用として示された根拠が、原文に存在しない言い回しになっている
この状態のデータでRAGを採点すると、RAGが悪いのか、データが悪いのかを切り分けられません。評価の数字が動いても、直す先が決まらなくなります。
Anthropicの評価ガイドは、評価の設計原則として実際のタスクの分布に似せることを挙げています。その際に入れるエッジケースの例として、無関係な入力、存在しない入力、人間でも判定が割れる曖昧なケースが並んでいます。合成データの検品は、この種のケースを意図して残すものと、事故として混入したものとに分ける作業とも言えます。
全体の流れ
候補の生成から凍結までの5段
- 1
文書をチャンクに分ける
質問の単位を決めます。1問は1チャンク(複数チャンクをまたぐ質問は別枠)から作ります。
- 2
質問、答え、根拠の引用を同時に作らせる
根拠は「原文そのままの抜き出し」に限定させます。
- 3
コードで根拠の実在を確かめる
引用が元のチャンクに文字列として含まれない候補は、その場で落とします。
- 4
Claudeで曖昧さと文書依存を検品する
単体で意味が定まるか、文書なしで答えられてしまわないかを判定させます。
- 5
人が残す候補を決めて凍結する
機械検品を通った候補だけをシートにして、人が採否を付けます。
人の作業を最後の1段に寄せるのが要点です。評価ガイドは採点方法の優先順を、コードによる採点、LLMによる採点、人による採点の順に並べ、人による採点は柔軟で質が高い一方で遅くて高いので可能なら避けるとしています。ここで人に任せるのは採点ではなく入力データの採否で、機械で落とせるものを落とし切ってから渡せば、見る件数が減ります。
先に質問の種類と配分を決める
何も指定せずに作らせると、1か所を見れば答えられる事実確認の質問ばかりになります。RAGのテストセット生成を扱うRagasのドキュメントは、質問を次の2軸で分けています。
| 軸 | 種類 | 内容 |
|---|---|---|
| 必要な文書の数 | 種類シングルホップ | 内容1つの文書から答えが取れる |
| 必要な文書の数 | 種類マルチホップ | 内容2つ以上の文書をつなぐ必要がある |
| 問い方 | 種類具体的(specific) | 内容事実を直接尋ねる |
| 問い方 | 種類抽象的(abstract) | 内容解釈や説明を求める |
Ragasの既定の配分は、シングルホップの具体的な質問が0.5、マルチホップの抽象的な質問と具体的な質問が各0.25です。この比率は社内文書にそのまま当てはまるとは限りませんが、先に配分を決めておく発想は流用できます。
社内文書向けには、次のような分け方が使いやすい形です。文書の種類によって比重を変えます。
| 種類 | 社内文書での例 | 生成方法 |
|---|---|---|
| 事実確認 | 社内文書での例経費精算の締め日はいつか | 生成方法1チャンクから生成 |
| 手順・条件 | 社内文書での例出張の事前申請が要るのは何円以上か | 生成方法1チャンクから生成 |
| 複数箇所の突き合わせ | 社内文書での例休職中の社会保険料は誰が払うか | 生成方法関連する2チャンクを渡して生成 |
| 該当なし | 社内文書での例文書に載っていない制度を尋ねる | 生成方法別枠(後述) |
マルチホップの質問は、関連するチャンクを組にして渡す工程が先に必要です。Ragasはこの組作りに、チャンクから固有表現を抜き、共通する語でつなぐ知識グラフを使っています。まず1チャンク由来の質問だけで始め、動いてから広げる進め方でも構いません。
質問と根拠の引用を同時に作らせる
生成の指示で最も効くのは、根拠の引用を原文からそのまま抜き出させることです。要約や言い換えを許すと、次の段でコードによる照合ができなくなります。
import json
import anthropic
client = anthropic.Anthropic()
MODEL = "claude-opus-5-5" # 手元で使うモデルIDに置き換える
GEN_PROMPT = """以下は社内文書の一節です。この一節だけを根拠に答えられる質問を{n}個作ってください。
条件:
- 質問は、この一節を読んでいない人が単独で読んでも意味が定まる形にする
("この制度" "先ほどの件" などの指示語を使わない)
- answer は一節の内容だけから書く
- evidence は一節からの原文そのままの抜き出しにする(言い換え・要約は禁止)
- type は fact(事実確認) か procedure(手順・条件) のどちらか
<passage id="{cid}">
{text}
</passage>
JSONの配列だけを出力してください。
[{{"question": "...", "answer": "...", "evidence": "...", "type": "fact"}}]"""
def generate(chunk_id: str, text: str, n: int = 3) -> list[dict]:
msg = client.messages.create(
model=MODEL,
max_tokens=2048,
messages=[{
"role": "user",
"content": GEN_PROMPT.format(n=n, cid=chunk_id, text=text),
}],
)
body = next(b.text for b in msg.content if b.type == "text")
items = json.loads(body[body.index("["): body.rindex("]") + 1])
for it in items:
it["chunk_id"] = chunk_id
return itemsここで構造化出力ではなく、プロンプトで指定したJSONを自分で取り出しているのは意図的です。後で評価対象のRAG側にCitationsを使う場合、Citationsは構造化出力と併用できず、併用すると400エラーになります。生成側と評価側で道具立てを揃えておくと、後から付け替えずに済みます。
機械で落とす:根拠の実在を確かめる
評価ガイドは、コードによる採点を最速で最も確実な方法とし、部分文字列が含まれるかを見る文字列一致を例に挙げています。この種の照合は、生成された候補の検品にもそのまま使えます。
import re
def norm(s: str) -> str:
return re.sub(r"\s+", "", s)
def evidence_exists(item: dict, chunk_text: str) -> bool:
ev = norm(item["evidence"])
return len(ev) >= 8 and ev in norm(chunk_text)空白と改行を無視して比べるのは、チャンク分割や整形で空白の入り方が変わるためです。最低文字数を課しているのは、「は」や「です」のような短い抜き出しが偶然一致して通ってしまうのを防ぐためで、8文字という値は目安です。
ここで落ちる候補は、Claudeが根拠を言い換えたか、存在しない一節を作ったものです。どちらも答えの正しさを保証できないので、中身を読まずに捨てられます。
Claudeで落とす:曖昧さと文書依存を判定させる
文字列一致では見抜けない2つの欠陥があります。単体で意味が定まらない質問と、文書がなくても答えられる質問です。この判定はClaudeに任せます。
評価ガイドは、LLMによる採点を速くて柔軟で大規模に回せる方法とする一方、まず信頼性を確かめてから拡大するよう求めています。同じガイドが挙げるコツは次の3つです。
- 詳細で明確なルーブリックを書く
- 出力は
correctかincorrectのような決まった形にする - 判定の前に推論させる(thinkingを有効にした採点モデルを使う)
これに沿って、判定は2値で返させます。
RUBRIC_AMBIGUOUS = """次の質問を、元の文書を見ずに読んでください。
質問の意味が1通りに定まり、何を答えれば良いかが分かる場合は correct、
指示語が指す先が不明、対象が複数読み取れる、前提が欠けている場合は incorrect としてください。
<question>{q}</question>
最終判定を <result>correct</result> または <result>incorrect</result> の形で出力してください。"""
RUBRIC_CLOSED_BOOK = """質問と、想定された正解を示します。
<question>{q}</question>
<gold>{a}</gold>
次の回答が、想定された正解と同じ内容を述べていれば correct、そうでなければ incorrect としてください。
<answer>{ans}</answer>
最終判定を <result>correct</result> または <result>incorrect</result> の形で出力してください。"""
def ask(prompt: str, max_tokens: int = 1024) -> str:
msg = client.messages.create(
model=MODEL,
max_tokens=max_tokens,
messages=[{"role": "user", "content": prompt}],
)
return next(b.text for b in msg.content if b.type == "text")
def judged_correct(text: str) -> bool:
return "<result>correct</result>" in text.lower()
def is_clear(q: str) -> bool:
return judged_correct(ask(RUBRIC_AMBIGUOUS.format(q=q)))
def answerable_without_doc(q: str, gold: str) -> bool:
closed = ask(f"次の質問に簡潔に答えてください。分からなければ「不明」と答えてください。\n{q}")
return judged_correct(ask(RUBRIC_CLOSED_BOOK.format(q=q, a=gold, ans=closed)))answerable_without_docは、質問だけをClaudeに渡し、文書なしで答えさせる検品です。これで正解と一致するなら、その質問は検索結果が何であっても答えられてしまい、RAGの検索精度を測れません。「労働基準法上の休憩時間の下限は」のような一般知識の質問がここで落ちます。逆に、社内固有の規程を尋ねる質問は通常ここでは落ちません。この検品は公式の手順ではなく、評価の目的(検索と回答を測る)から組み立てた工夫です。
落とし穴
検品の判定役と生成役を同じ指示で動かさない
生成と検品に同じモデルを使うのは問題ありませんが、検品のプロンプトに生成時の指示文を流用すると、生成側の癖をそのまま「合格」と判定しがちです。検品側は元の一節を見せず、質問だけを見せて判定させると、読み手の目線に近づきます。
人の目で間引く
ここまでを通った候補を、人が見るシートに書き出します。見る列を絞り、判断を1問あたり数秒で終えられる形にします。
import csv
def write_review_sheet(items: list[dict], path: str = "review.csv") -> None:
cols = ["chunk_id", "type", "question", "answer", "evidence", "keep", "note"]
with open(path, "w", newline="", encoding="utf-8-sig") as f:
w = csv.DictWriter(f, fieldnames=cols, extrasaction="ignore")
w.writeheader()
for it in items:
w.writerow({**it, "keep": "", "note": ""})人が見る観点は、機械が見落とす次の4つに絞れます。
| 落とす観点 | 具体例 | 機械で拾えるか |
|---|---|---|
| 答えが一節の一部にしか対応しない | 具体例手順が3つあるのに1つ目だけが正解として書かれている | 機械で拾えるか拾いにくい |
| 業務上、聞かれない質問 | 具体例規程の条番号だけを尋ねる | 機械で拾えるか拾えない |
| 古い情報・改訂前の内容 | 具体例廃止済みの制度に基づく質問 | 機械で拾えるか拾えない |
| 正解が文書の誤記に依存している | 具体例原文の誤字や矛盾をそのまま答えにしている | 機械で拾えるか拾いにくい |
業務上の質問かどうかは、現場の担当者が最も早く判断できます。シートは、担当者が1問数秒でkeep列にyかnを入れられる量にします。
評価ガイドは「量を優先する」と書き、質の高い少数の手採点より、やや信号が弱くても自動採点できる多数の質問を取る方向を示しています。そのうえで、人が見て残した候補は質の担保に回します。この2つは矛盾しません。量は機械で稼ぎ、人が見るのは機械が通した候補に限ります。人に判断させる件数を絞るために、前段の機械検品を厳しめにしておきます。
「該当なし」の質問は捨てずに別枠へ
答えが文書に無い質問は、golden setの本体からは外します。ただし、評価ガイドがエッジケースの例として無関係な入力や存在しない入力を挙げているとおり、「文書にないことを聞かれたときに、RAGが知らないと答えるか」も測る価値があります。
そこで、答えが無い質問はno_answerのラベルを付けて別のセットにします。正解は「文書には記載がありません」のような拒否の形にして、本体の採点とは別の指標で見ます。本体に混ぜないのは、検索の精度と、答えられない場合の振る舞いが別の問題だからです。
golden setとして凍結する
人がyを付けた候補を、バージョンを付けて保存します。1行に必要な項目は次のとおりです。
{"id": "g-0001", "chunk_id": "expense-rules#12", "type": "procedure",
"question": "出張の事前申請が必要になるのは何円以上の場合ですか。",
"answer": "5万円以上の場合に事前申請が必要です。",
"evidence": "出張費が5万円以上となる場合は、事前に申請すること。",
"source_version": "2026-10-01"}source_versionを持たせるのは、文書が改訂されたときに古くなった問いを見つけるためです。改訂のたびにevidence_existsを全行に再実行し、落ちた行は人の再確認に回します。
Citationsを評価対象のRAGに使っているなら、根拠の突き合わせを機械化できます。プレーンテキスト文書では、引用がchar_locationとして開始と終了の文字位置(0始まり、終了は含まない)で返ります。golden setのevidenceの文字位置を同じ形で持っておけば、RAGが示した引用の範囲と重なるかを検索側の指標にできます。引用の形式は文書の種類で変わるため、詳しくはCitationsの引用形式の違いにまとめています。
評価に使い始めたら、golden setはプロンプトの調整にも使い回さない点に気をつけます。評価ガイドが成功基準の例で、評価用に取り分けたデータ(held-out)に対して数値を測るとしているのと同じ考え方です。調整用の質問と最終確認用の質問は、凍結の時点で分けておきます。
次に読むとよいもの
golden setができたら、採点の方法と成功基準の置き方が次の課題になります。exact matchやLLMによる採点の選び方はClaude evalの成功基準を測る4つの実装にあります。検索側の構築、チャンクの切り方、Voyageの埋め込みモデルはClaude RAGの構築手順の領分です。
まとめ
合成した質問は候補の山で、使えるのは検品を通った一部だけです。落とす作業は、引用の実在をコードで、曖昧さと文書依存をClaudeで、業務上の妥当性を人で受け持たせると、人が見る件数を無理なく絞れます。答えが文書に無い質問は、本体から外したうえでno_answerの別枠として残すと、RAGが知らないと答えられるかも測れます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。