RAGをどう評価する? — LLM採点で試すRAGAS的指標
はじめに
ルミナイR&Dチームの栗原です。
これまでの記事では、レビュー論文 “Retrieval-Augmented Generation for Large Language Models: A Survey” (Gao et al., 2024) を手がかりに、
- Fig.2 / Fig.3:RAG の基本フローと
Naive / Advanced / Modular RAG の違い - Fig.4:Prompt / Fine-tuning / RAG の「守備範囲」と使い分け方
- Fig.5:Iterative / Recursive / Adaptive Retrieval という、エージェント寄りRAGのフロー
をざっくり整理してきました。
そろそろ出てくるのが、次の悩みです。
「RAG をそれっぽく組めたけど、
これってどのくらい“いい”の?」
- Retriever を差し替えたら本当に良くなったのか?
- ハイブリッド検索+再ランクは効いているのか?
- 「なんか良さそう」以上のことが言えない…
このモヤモヤに答えるのが、RAG の評価です。
本記事では、Gao et al. の Evaluation まわりの議論と、既存ツール(RAGAS / TruLens など)の発想を参考にしつつ、
- 何を評価すべきか(評価軸)
- どんな評価方法があるか(人手 / 自動 / LLM-as-judge)
- Python + OpenAI で動く 最小限の「LLMによる採点スクリプト」
をまとめます。
参照:“Retrieval-Augmented Generation for Large Language Models: A Survey” — Gao et al., 2024
この記事で学べること
- RAG を評価するときに見るべき 4 つの観点
- 人手評価・自動指標・LLM による採点(RAGAS的アプローチ)の違い
- Python + OpenAI API だけで試せる、RAG 出力の採点スクリプト(ミニ版RAGAS)
1. 「RAGが良い」とは何か?評価軸を分解する
まずは「何を測るのか」を整理します。
ここでは、RAG の評価軸をざっくり 4つ に分けて考えます。
1.1 RAG評価の4つの観点(図1)
ざっくり整理すると、こんな感じのマトリクスになります。
表1. RAG 評価の4つの観点
| 観点 | ざっくり意味 | 典型的な指標例 |
|---|---|---|
| Answer Correctness | 質問に対して、内容がどれくらい正しいか | EM, F1, 5段階スコア |
| Faithfulness (忠実さ) | 回答がコンテキスト文書にどれくらい忠実か | LLM採点・RAGAS の忠実度スコア |
| Context Relevance | 取得したチャンクが質問とどれくらい関係あるか | nDCG, MRR, 人手ラベル |
| Retrieval Quality | 「正解チャンク」を上位に持って来れているか | Recall@k, Hit@k, Precision@k |
それぞれもう少し言い換えると:
-
Answer Correctness
- ユーザー視点で一番分かりやすい軸。「答えとして合ってる?」
-
Faithfulness
- 回答がちゃんと渡したドキュメントの範囲で完結しているか。
コンテキストにない内容を「それっぽく」作っていないか。
- 回答がちゃんと渡したドキュメントの範囲で完結しているか。
-
Context Relevance
- 取得チャンクの良し悪し。「そもそも関係ありそうなところを引けてる?」という話。
-
Retrieval Quality
- 正解アノテーションがある前提で、「正解チャンクのランク」がどうなっているか。
RAG を評価するときは、この 4 つのどれを重視したいのかを最初に決めておくと、
あとで「指標が増えすぎて訳が分からない問題」を防げます。
1.2 ユースケースごとに優先する軸が違う
同じ RAG でも、用途によって重要な軸が変わります。
- 社内 FAQ ボット
→ Answer Correctness / Faithfulness が最重要。
多少チャンク選びが雑でも、答えが正しければよいケースが多い。 - 社内検索UI(「どの文書を読めばいいか」を教える用途)
→ Context Relevance / Retrieval Quality 寄りの評価が効いてくる。 - 自動レポート生成・ドラフト生成
→ Answer Correctness & Faithfulness に加えて、
「読みやすさ」「論点の抜け漏れ」のような、人間的な評価軸も必要。
この記事の後半で書く LLM-as-judge(LLM に採点させる方式) は、
- Answer Correctness
- Faithfulness
の 2 つを一度にざっくり見る、というイメージで使います。
2. 評価の3パターン:人手 / 自動指標 / LLM-as-judge
次に、「どうやって評価するか」の話です。
ざっくり分けると、手段は 3 つあります。
- 人手評価(human evaluation)
- 自動指標(EM / F1 / nDCG / Recall@k …)
- LLM-as-judge(LLM に採点させる)
2.1 人手評価:もっとも信頼できるが、スケールしにくい
一番ストレートなのは、人間が直接見る方法です。
- 質問
- RAG の回答
- (あれば)コンテキスト文書
を見ながら、次のような基準でラベリングします。
- 正しさ:◎ / ○ / △ / ✕
- 忠実さ:
- コンテキストから明らかに導ける
- グレー(推測入ってそう)
- コンテキストと矛盾している
メリット
- もっとも直感的で、信頼しやすい
- 評価コメントも一緒に残せば、改善のヒントになりやすい
デメリット
- コストが重い(人件費・時間)
- サンプル数を増やしにくい
- 評価者によって基準がぶれやすい
→ 現実的には、少数サンプルでのスポットチェック に落ち着きがちです。
2.2 自動指標:速いが、扱えるタスクが限られる
次に、既存の自動指標です。
- QA タスクなら:
- ゴールドアンサーとの EM(Exact Match) / F1 など
- Retrieval タスクなら:
- 正解文書IDに対する nDCG / MRR / Recall@k / Hit@k
メリット
- 大量サンプルを一気に評価できる
- 値が明確で、実験の比較に使いやすい
デメリット
- ゴールドアンサーや正解ドキュメントIDが必要
- 「言い換え」「部分的に正しい回答」の扱いが難しい
- Faithfulness(忠実さ)までは測りづらい
RAG の現場では、「Retrieval 部分の A/B テスト」には自動指標、
「生成(LLM)の出力品質」には次の LLM-as-judge を組み合わせる、という構成が多いです。
2.3 LLM-as-judge:RAGAS的な発想
3つめが、LLM に採点させる 方法です。
RAGAS や TruLens などのライブラリが採用している発想でもあります。
ざっくり流れはこうです。
- 質問(question)
- RAG の回答(prediction)
- RAG に渡したコンテキスト文書(contexts)
をすべて LLM に渡し、
- 回答の正しさ(correctness)
- 文脈への忠実さ(faithfulness)
を 1〜5 のスコアなどで採点してもらいます。
メリット
- 言い換えやニュアンスをある程度理解してくれる
- 人手評価に近い感覚を、そこそこのスケール で回せる
- 評価コメントも一緒に生成させられる
デメリット
- 採点する LLM のクセ(モデル・プロンプト)に依存する
- 本番と同じモデルで採点すると「自画自賛」になりがち
- 絶対値としての「80点だから良い」とは言いづらい(比較用だと割り切る必要)
このあとのセクションでは、
- 小さな評価用データセット(question / ground_truth / contexts)
- 既存の
answer(question, mode="rag")(前回までの RAG 実装を想定)
を前提にして、
Python + OpenAI API だけで動く「LLM採点スクリプト(correctness+faithfulness)」
を、ミニ版 RAGAS として書いていきます。
3. LLMに採点させるミニ評価スクリプト(RAGASもどき)
ここからは実際に、LLM-as-judge で RAG を評価する最小スクリプトを書いてみます。
前提として:
- すでに前回までのコードなどで
answer(question: str, mode="rag") -> strが定義されている想定です
(なければダミー関数を作ってください) - 小さな評価用データを Python 内にベタ書きします
3.1 評価用データセットを作る
まずは、数件で良いので 「質問+正解ラベル+コンテキスト」 のセットを用意します。
# 評価用の小さなサンプル(実際には自分のドメインに合わせて書き換えてください)
EVAL_DATA = [
{
"id": "q1",
"question": "新製品AのAPI認証方式は何ですか?",
"ground_truth": "OAuth2",
"contexts": [
"新製品Aでは、2025年10月以降、APIの認証方式をOAuth2に統一した。",
"旧バージョンではAPIキー認証も一部で利用されていた。"
],
},
{
"id": "q2",
"question": "社内データを外部サービスに送る場合のルールを教えてください。",
"ground_truth": "情報セキュリティ部門の承認が必要",
"contexts": [
"社内データを外部に送信する場合は、必ず情報セキュリティ部門の承認を得ること。",
],
},
]
3.2 RAGシステムを一通り走らせる
次に、各質問に対して自分の RAG システムを実行し、予測結果を集める部分を書きます。
from typing import Dict, Any, List
# 例: 以前の記事の RAG 実装
# from my_rag_impl import answer
def answer(question: str, mode: str = "rag") -> str:
"""ここではダミー。実際は自分の RAG 実装をインポートしてください。"""
return f"【ダミー回答】{question}"
def run_rag_on_eval(eval_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
rows = []
for ex in eval_data:
pred = answer(ex["question"], mode="rag")
rows.append(
{
"id": ex["id"],
"question": ex["question"],
"ground_truth": ex["ground_truth"],
"contexts": ex["contexts"],
"prediction": pred,
}
)
return rows
results = run_rag_on_eval(EVAL_DATA)
print(results[0])
ここまでで、各サンプルについて
- question
- ground_truth
- contexts
- prediction(RAG の回答)
が 1 つの dict にまとまったリスト results が得られます。
3.3 採点プロンプトを設計する
続いて、OpenAI API を使って correctness(正しさ) と faithfulness(忠実さ) を 1〜5 の整数で採点させます。
import os
import json
from openai import OpenAI
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
JUDGE_SYSTEM = """\
あなたはRAGシステムの評価者です。
与えられた「質問」「生成された回答」「コンテキスト文書」を読み、
次の2つを5段階(1〜5)で評価してください。
1. correctness: 回答が質問に対してどれくらい正しいか
- 1: ほぼ誤り
- 3: 部分的には正しいが不十分
- 5: ほぼ完全に正しい
2. faithfulness: 回答の内容がコンテキストにどれくらい忠実か
- 1: コンテキストと矛盾している or ほぼ全てが推測
- 3: 一部はコンテキスト由来だが推測も混ざる
- 5: ほとんどがコンテキストから導ける内容
出力は必ず、次のJSON形式1行で出してください:
{"correctness": int, "faithfulness": int}
"""
def score_one(example: Dict[str, Any]) -> Dict[str, Any]:
ctx_lines = "\n".join(f"- {c}" for c in example["contexts"])
prompt = f"""[質問]
{example['question']}
[回答]
{example['prediction']}
[コンテキスト]
{ctx_lines}
上記を読み、採点してください。
"""
resp = client.responses.create(
model="gpt-4o-mini",
input=[
{"role": "system", "content": JUDGE_SYSTEM},
{"role": "user", "content": prompt},
],
max_output_tokens=200,
temperature=0.0,
)
text = resp.output_text.strip()
scores = json.loads(text)
return {
**example,
"correctness": scores["correctness"],
"faithfulness": scores["faithfulness"],
}
ここで大事なのは:
- system プロンプト側で 評価基準と出力形式をかなり厳密に指定していること
- 出力を
json.loadsでパースする前提なので、形式のブレを許さないようにしていること
です。
3.4 全サンプルを採点して、平均スコアを見る
最後に、すべてのサンプルに対して score_one を回し、平均スコアを計算します。
import statistics as stats
scored = [score_one(ex) for ex in results]
avg_corr = stats.mean(r["correctness"] for r in scored)
avg_faith = stats.mean(r["faithfulness"] for r in scored)
print(f"平均 correctness: {avg_corr:.2f}")
print(f"平均 faithfulness: {avg_faith:.2f}")
# 1件分だけ中身を見てみる
print("--- サンプル1件目 ---")
print(f"Q: {scored[0]['question']}")
print(f"Prediction: {scored[0]['prediction']}")
print(f"Correctness: {scored[0]['correctness']}")
print(f"Faithfulness: {scored[0]['faithfulness']}")
これだけで、
- 自分の RAG 構成(Retriever / 再ランク / 圧縮 など)を変えたときに、
- correctness / faithfulness の平均スコアがどう変わるか
を 簡単に A/B 比較できるようになります。
本格的にやるなら、この結果を pandas の DataFrame にして CSV で保存したり、
LangSmith / Weights & Biases などにログ送信して可視化してもよいと思います。
4. LLM-as-judgeの落とし穴と、実務での使い方
最後に、LLM 採点を使うときの注意点と、現場での落としどころを簡単にまとめます。
4.1 よくある落とし穴
-
本番と同じモデルで自分を採点している
- → 多少「自画自賛」になりやすい
- 余裕があれば、評価には別のモデル(or バージョン)を使うと安全です。
-
評価プロンプトを変えるとスコア分布も変わる
- → JUDGE_SYSTEM の内容も「評価仕様」の一部として固定・管理しておくのがおすすめです。
-
絶対値を信じすぎる
- → 「correctness 4.2 だから良い/悪い」と見るより、
構成 A と B の差分を見るためのツールと割り切る方が現実的です。
- → 「correctness 4.2 だから良い/悪い」と見るより、
4.2 実務での使い方のイメージ
- 開発初期:
- 少数の
EVAL_DATAと LLM-as-judge を使って、
「BM25のみ vs ベクトルのみ vs ハイブリッド」などをざっくり比較。
- 少数の
- 本番直前:
- クリティカルな質問セットだけは、人手評価も混ぜてダブルチェック。
- 本番運用:
- ログからサンプリングした Q&A を、定期的に LLM-as-judge でスコアリングし、
「最近スコアが落ちていないか?」をモニタリング。
- ログからサンプリングした Q&A を、定期的に LLM-as-judge でスコアリングし、
RAG の評価は、どうしても
- 指標が多い
- 実装も重い
方向に行きがちですが、まずはこの記事のようなミニ版から始めて、
- 小さな
EVAL_DATAを用意する - 既存の
answer(question, mode="rag")に対して LLM 採点を回す - 構成を変えたときのスコア差を見る
というループが回せるようになると、RAG のチューニングがかなりやりやすくなるはずです。
【現在採用強化中です!】
- AIエンジニア
- PM/PdM
- 戦略投資コンサルタント
▼代表とのカジュアル面談URL
Discussion