TL;DR
- LLM出力の品質評価は「rule-based → LLM-as-Judge → RAGASベース」の3手法を使い分ける
- 精度・コスト・速度のトレードオフで選択する。まずrule-basedから始めるのが現実解
- CI/CDへの組み込みは「評価実行 → 結果記録 → ゲート判定 → 通知」の4ステップで設計する
- 閾値は「初期値設定 → モニタリング → 調整」のサイクルで育てる。最初から完璧を求めない
- ハルシネーション検出にはRAGASのfaithfulness指標が有効(公式値: RAGASドキュメント)
本記事はシリーズ「AIコードレビューを導入する実践ガイド」の第2回です。
この記事の目的と成功基準
- 目的: LLMアプリの出力品質保証に悩むMLエンジニアに、評価手法の使い分け基準とEvalパイプラインの設計手順を提供する
- 想定読者: LLMをAPIとして使うアプリ開発経験があり、CI/CDの基礎知識を持つMLエンジニア・バックエンドエンジニア
- 成功基準: 「LLM 品質評価」「LLM Eval 自動化」クエリで検索上位10位以内(公開後60日)
なぜ品質ゲートが必要か
LLMを本番に投入したチームが共通して直面する問題がある。プロンプトを少し変えただけで、既存のユースケースが壊れる。モデルのバージョンがサイレントアップデートされて出力傾向が変わる。新しいユースケースを追加したら既存の精度が落ちた。
これらはソフトウェア開発で言う「リグレッション」だが、LLMの場合はユニットテストの構造が通用しない。入力に対して決定論的な出力が得られないからだ。
この問題への答えがEval(評価)パイプラインと品質ゲートだ。人間が定義した評価基準を自動化し、CI/CDのパイプラインに組み込むことで、品質の劣化を変更のたびに検出できる。
LLM本番運用チェックリストで整理したように、本番運用の監視軸にはtrace・eval・costの3つが必要だが、このうち**eval(出力品質の継続評価)**の設計が最も難易度が高い。本記事ではこのeval設計にフォーカスする。
評価手法の3分類
LLM出力の評価手法は大きく3つに分類できる。
1. Rule-based Eval(ルールベース評価)
正規表現・文字列マッチ・スキーマ検証など、決定論的なルールで評価する手法。
強み: 速い・安い・説明可能。結果に再現性がある。 弱み: 柔軟性がない。意味的な品質(自然さ・正確さ)の評価には不向き。 適用場面: フォーマット検証(JSON構造、必須フィールド)、禁止ワード検出、長さ制約
def rule_based_eval(output: str) -> EvalResult:
# JSONパース可否
try:
parsed = json.loads(output)
except json.JSONDecodeError:
return EvalResult(score=0.0, reason="invalid_json")
# 必須フィールド検証
required_fields = ["summary", "key_points", "confidence"]
missing = [f for f in required_fields if f not in parsed]
if missing:
return EvalResult(score=0.0, reason=f"missing_fields: {missing}")
# confidence値の範囲検証
confidence = parsed.get("confidence", -1)
if not (0.0 <= confidence <= 1.0):
return EvalResult(score=0.0, reason="confidence_out_of_range")
return EvalResult(score=1.0, reason="pass")
2. LLM-as-Judge(LLM評価者)
別のLLM(評価専用モデル)に評価を委ねる手法。OpenAI Evalsフレームワークでも中心的な手法として採用されている。
強み: 柔軟・意味的品質を評価できる。評価基準をプロンプトで定義できる。 弱み: コストが高い。評価者LLM自体のバイアスが入る。速度が遅い。 適用場面: 回答の有用性・トーン・論理的整合性の評価
JUDGE_PROMPT = """
あなたはLLM出力の品質評価者です。
以下の基準でユーザーの質問に対する回答を評価してください。
評価基準:
- 正確性: 回答は事実として正確か(1-5点)
- 有用性: ユーザーの質問に答えているか(1-5点)
- 簡潔性: 冗長でないか(1-5点)
質問: {question}
回答: {answer}
JSON形式で評価結果を出力してください:
{{"accuracy": <1-5>, "helpfulness": <1-5>, "conciseness": <1-5>, "overall": <1-5>, "reason": "<理由>"}}
"""
async def llm_judge_eval(question: str, answer: str) -> EvalResult:
response = await judge_client.chat(JUDGE_PROMPT.format(
question=question, answer=answer
))
scores = json.loads(response)
return EvalResult(
score=scores["overall"] / 5.0,
details=scores
)
LLM-as-Judgeのバイアスに注意: 評価者LLMは自分と同じモデルを高く評価する傾向(self-enhancement bias)があることが研究で報告されている(arxiv: 2309.15217)。評価モデルを本番モデルと変える、複数モデルで評価平均を取るなどの対策が推奨される。
3. RAGASベース評価(RAGアプリケーション特化)
RAG(Retrieval-Augmented Generation)アプリケーション向けの評価フレームワーク。RAGAS公式ドキュメントで提供されている指標群を使う。
強み: RAGのコンポーネント(検索・生成)を分解して評価できる。faithfulness(ハルシネーション検出)が強力。 弱み: RAGアプリ以外への適用は限定的。評価コストが高い。 適用場面: チャットボット・社内検索・Q&Aシステムなどのアプリケーション
主要指標:
| 指標名 | 計測対象 | 概要 |
|---|---|---|
| Faithfulness | 生成の正確性 | 回答がコンテキストに基づいているか(ハルシネーション検出) |
| Answer Relevancy | 回答の適切性 | 質問に対して回答が適切か |
| Context Precision | 検索精度 | 取得したコンテキストが質問に関連しているか |
| Context Recall | 検索網羅性 | 必要なコンテキストを取得できているか |
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
dataset = Dataset.from_dict({
"question": questions,
"answer": generated_answers,
"contexts": retrieved_contexts,
"ground_truth": reference_answers,
})
result = evaluate(
dataset=dataset,
metrics=[faithfulness, answer_relevancy, context_precision],
)
# result["faithfulness"] -> 0.0〜1.0のスコア
評価手法の使い分けマトリクス
どの手法を選ぶかは、ユースケース・コスト・精度要件で決まる。
| 評価手法 | 精度 | コスト | 速度 | 適用場面 |
|---|---|---|---|---|
| Rule-based | 低〜中 | 低 | 高速 | フォーマット検証・禁止ワード・長さ |
| LLM-as-Judge | 高 | 高 | 低速 | 意味的品質・トーン・論理整合性 |
| RAGAS | 高(RAG特化) | 高 | 低速 | RAGアプリのfaithfulness・relevancy |
推奨アプローチ: まずRule-basedでCI/CDに組み込み、余裕があればLLM-as-Judgeを追加する。RAGASはRAGアプリの場合に採用を検討する。コストと速度のトレードオフを考えると、全サンプルにLLM-as-Judgeを適用するのは高コスト。本番では一部サンプリングするか、重要なユースケースに絞って適用する。
LLMガードレール設計と同様、品質評価も単一手法では不十分で、複数手法の組み合わせが実践的な設計だ。
Evalパイプラインのアーキテクチャ
品質評価を一回やるだけでなく、継続的に自動化するための設計が「Evalパイプライン」だ。
4ステップのアーキテクチャ
[Step 1] 評価実行
├── テストデータセット(ゴールデンセット)のロード
├── 評価対象LLMへのリクエスト送信
└── 各評価手法(rule-based / LLM-as-Judge / RAGAS)の実行
[Step 2] 結果記録
├── スコアのデータベース保存(LangSmith / Weights&Biases / 自前DB)
├── 時系列での比較(前回との差分)
└── 評価ログの保存(デバッグ用)
[Step 3] ゲート判定
├── 設定した閾値との比較
├── Pass / Fail の判定
└── 閾値違反の場合はパイプラインを停止
[Step 4] 通知
├── Pass: CIログに結果を記録
└── Fail: Slackアラート / PRコメント / メール通知
GitHub ActionsでのEval CI設定例
# .github/workflows/eval-ci.yml
name: LLM Eval CI
on:
pull_request:
paths:
- 'prompts/**'
- 'src/llm/**'
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install ragas openai deepeval
- name: Run Eval Pipeline
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: python scripts/run_eval.py
- name: Check Quality Gate
run: python scripts/check_quality_gate.py --threshold-file eval_thresholds.json
# scripts/run_eval.py
import json
from eval_pipeline import EvalPipeline
def main():
pipeline = EvalPipeline(
test_dataset_path="tests/eval_dataset.jsonl",
output_path="eval_results.json"
)
results = pipeline.run([
"rule_based", # フォーマット検証
"llm_as_judge", # 意味的品質
])
with open("eval_results.json", "w") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
print(f"Eval completed: {results['summary']}")
if __name__ == "__main__":
main()
# scripts/check_quality_gate.py
import json
import sys
import argparse
def check_gate(results_path: str, threshold_path: str) -> bool:
with open(results_path) as f:
results = json.load(f)
with open(threshold_path) as f:
thresholds = json.load(f)
failures = []
for metric, threshold in thresholds.items():
score = results["metrics"].get(metric, 0)
if score < threshold:
failures.append(f"{metric}: {score:.3f} < {threshold}")
if failures:
print("QUALITY GATE FAILED:")
for f in failures:
print(f" - {f}")
return False
print("QUALITY GATE PASSED: all metrics above thresholds")
return True
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--threshold-file", required=True)
args = parser.parse_args()
passed = check_gate("eval_results.json", args.threshold_file)
sys.exit(0 if passed else 1)
閾値の決め方
Evalパイプラインで最も迷うのが閾値をどう設定するかだ。最初から「正解の閾値」を決めようとしても無理がある。
閾値設計の3フェーズ
フェーズ1: ベースライン計測(初期値設定)
プロダクションに近いテストデータで現在の状態を計測し、そのスコアを初期閾値にする。「今の状態が壊れたら検知する」ことを最初のゴールとする。
// eval_thresholds.json(初期値の例)
{
"format_validity": 0.95,
"llm_judge_overall": 0.65,
"faithfulness": 0.80
}
フェーズ2: モニタリング(観察期間)
2〜4週間、日々の変動を観察する。スコアが自然に揺れる範囲(ノイズ)を把握し、閾値が厳しすぎるか緩すぎるかを判断する。
- アラートが多すぎる: 閾値が厳しすぎる → 少し下げる
- 明らかな品質劣化を見逃す: 閾値が緩すぎる → 少し上げる
フェーズ3: 継続調整
プロダクトの成熟に応じて閾値を上げていく。人手レビューでのスコアとEvalスコアを定期的に照合し、Evalが実際の品質と相関しているかを確認する。
ゴールデンセット(テストデータセット)の管理
- 最低50件以上: サンプルが少なすぎると統計的ノイズが大きい
- 代表性: プロダクションのユースケース分布に近いデータを含める
- アップデートサイクル: 月1回程度、新しいユースケースを追加する
- 人手ラベル付き: ground_truthが必要な指標には正解ラベルを付ける
ハルシネーション検出の実装
RAGアプリにおけるハルシネーション(事実誤認)の検出は、RAGASのfaithfulness指標が最も実績のあるアプローチだ。
faithfulnessは「LLMが生成した回答が、取得したコンテキストに根拠を持つか」を測定する。スコア0〜1で、1に近いほどハルシネーションが少ない(経験則: 0.8以上を目標にすると適切な品質ゲートになる)。
from ragas.metrics import faithfulness
from ragas import evaluate
# faithfulnessのみ評価する例
result = evaluate(
dataset=test_dataset,
metrics=[faithfulness],
llm=evaluation_llm, # 評価専用モデル
)
faithfulness_score = result["faithfulness"]
if faithfulness_score < 0.8:
raise QualityGateError(
f"Faithfulness below threshold: {faithfulness_score:.3f} < 0.80"
)
RAGASを使わない場合の簡易ハルシネーション検出として、LLM-as-Judgeに「回答がコンテキストに基づいているか」を評価させる方法も有効だ。DeepEvalフレームワークではHallucinationMetricがこの用途に対応している。
LLMテスト戦略との接続
Evalパイプラインは、より広いAIテスト戦略の一部として位置付けるべきだ。
- ユニットテスト: rule-basedのフォーマット検証など、決定論的なテスト
- Eval(品質評価): LLM-as-Judge / RAGASによる意味的品質の継続評価
- 人手レビュー: 週次や月次でのサンプリングレビュー
- A/Bテスト: プロンプト変更時の本番での比較
この階層で考えると、Evalはユニットテストと人手レビューの中間に位置し、量と質のバランスを取る役割を持つ。
AIエージェントの成熟度モデルで見ても、品質評価の自動化はLevel 2(管理された自律)からLevel 3(高度な自律)に進む上での前提条件だ。
実装のロードマップ
初めてEvalパイプラインを導入する場合の推奨ステップ:
- Week 1: ゴールデンセット(テストデータ)を50件作成する
- Week 2: rule-based evalをCIに1本追加する(フォーマット検証から始める)
- Week 3〜4: ベースラインスコアを計測し、初期閾値を設定する
- Month 2: LLM-as-Judgeを追加し、より詳細な品質評価を始める
- Month 3〜: RAGASの導入(RAGアプリの場合)、閾値の調整
「完璧なEvalシステム」を最初から作ろうとしない。動く1本のEvalをCIに組み込むことが最初のゴールだ。
まとめ
LLM出力の品質ゲート設計は、以下の3点を押さえれば始められる:
- 手法の選択: rule-based → LLM-as-Judge → RAGASの順に導入する。まずrule-basedから
- パイプライン設計: 評価実行 → 結果記録 → ゲート判定 → 通知の4ステップ
- 閾値の育て方: 最初はベースラインを初期閾値に設定し、観察しながら調整する
品質評価の自動化は一度設計すれば長く使える投資だ。プロンプト変更・モデルアップデート・新機能追加のたびにリグレッションを心配する状態から抜け出せる。
LLM本番運用チェックリストでカバーするSLO/フォールバック/コスト管理と組み合わせることで、LLMアプリの本番運用基盤が揃う。
FAQ
LLMの出力品質はどうやって測る?
主に3つの手法を使い分ける。①rule-based eval(フォーマット検証・禁止ワード検出など決定論的ルール)、②LLM-as-Judge(別のLLMに有用性・正確性・トーンを評価させる)、③RAGAS(RAGアプリのfaithfulnessやrelevancyを測る専用フレームワーク)。コストと精度のトレードオフで選択し、まずrule-basedから始めるのが現実的だ。
RAGAS・LLM-as-Judge・rule-based evalはどう使い分ける?
フォーマット検証や長さ制約はrule-based、意味的な品質(有用性・論理整合性)はLLM-as-Judge、RAGアプリのハルシネーション検出はRAGASを使う。コスト面ではrule-based(低コスト)< RAGAS / LLM-as-Judge(高コスト)の順。全サンプルに高コスト手法を適用するのは現実的でないため、本番ではサンプリングするか重要ユースケースに絞る。
LLM評価をCI/CDに組み込む方法は?
「評価実行 → 結果記録 → ゲート判定 → 通知」の4ステップでパイプラインを設計し、GitHub Actionsに組み込む。プロンプトファイルやLLM関連コードの変更をトリガーに評価を実行し、スコアが閾値を下回ったらCIを失敗させてPRをブロックする。本記事のGitHub Actions設定例を参照。
品質ゲートの閾値はどう決めればよい?
現在の出力スコアを計測してベースラインを取り、そのスコアを初期閾値にする。「今の状態が壊れたら検知する」ことが最初のゴール。2〜4週間モニタリングしながら、アラートが多すぎれば下げ、品質劣化を見逃すなら上げる。最初から「正しい閾値」を決めようとせず、観察サイクルで育てる。
LLMのハルシネーションを自動で検出するには?
RAGアプリではRAGASのfaithfulness指標が最も実績がある。スコア0.8以上を閾値にするのが経験則として妥当だ(経験則)。RAGASを使わない場合は、LLM-as-Judgeに「回答はコンテキストに基づいているか」を評価させる方法か、DeepEvalのHallucinationMetricを使う方法が有効。
本記事はシリーズ「AIコードレビューを導入する実践ガイド」の一部です。
References
- RAGAS公式ドキュメント — faithfulnessをはじめとするRAG評価指標の定義と使い方
- DeepEval公式ドキュメント — LLM評価フレームワーク、HallucinationMetric等
- LangSmith評価ガイド — LLM-as-JudgeのCI/CD組み込みパターン
- OpenAI Evals — LLM評価フレームワークの設計思想
- Judging LLM-as-a-Judge(arxiv: 2309.15217) — LLM-as-Judgeのバイアスと限界に関する研究
