LLM評価(Evals)完全ガイド2026 - AIアウトプットの品質を測る実践手法
はじめに
「AIの回答は本当に正確か?」「プロンプト変更で品質は改善したか?」を測定する仕組みが**LLM評価(Evals)**です。
2026年、AIアプリが本番環境で使われるようになり、評価の仕組みを持つことは必須要件になっています。この記事では、LLMの品質評価の全体像と実装方法を解説します。
LLM評価の全体像
評価の階層
│
├── 1. タスク固有の評価(What)
│ ├── 正確性(Accuracy)
│ ├── 適切性(Relevance)
│ ├── 一貫性(Consistency)
│ └── 流暢さ(Fluency)
│
├── 2. 評価手法(How)
│ ├── 人手評価(Human Evaluation)
│ ├── 参照ベース自動評価(BLEU, ROUGE, BERTScore)
│ ├── LLM-as-Judge
│ └── タスク固有メトリクス
│
└── 3. 評価フレームワーク(With)
├── Promptfoo
├── LangFuse
├── RAGas
└── DeepEval
評価手法の種類
1. 人手評価
最も信頼性が高いが、スケールしない:
from dataclasses import dataclass
from enum import Enum
from datetime import datetime
class QualityScore(Enum):
EXCELLENT = 5
GOOD = 4
ACCEPTABLE = 3
POOR = 2
UNACCEPTABLE = 1
@dataclass
class HumanEvaluation:
question: str
model_output: str
accuracy: QualityScore # 正確性
relevance: QualityScore # 関連性
clarity: QualityScore # 明確さ
helpfulness: QualityScore # 役立つか
overall: QualityScore # 総合
feedback: str # コメント
evaluator: str
timestamp: datetime = None
def to_score(self) -> float:
scores = [
self.accuracy.value * 0.3, # 正確性に高いウェイト
self.relevance.value * 0.25,
self.clarity.value * 0.2,
self.helpfulness.value * 0.25
]
return sum(scores) / 5 # 0〜1に正規化
2. 参照ベース自動評価
# BERTScore(意味的類似度)
from bert_score import score
def evaluate_with_bertscore(predictions: list[str], references: list[str]) -> dict:
"""
BERTScoreで生成文と参照文の意味的類似度を計算
精度(P)・再現率(R)・F1スコアを返す
"""
P, R, F1 = score(predictions, references, lang="ja")
return {
"precision": P.mean().item(),
"recall": R.mean().item(),
"f1": F1.mean().item()
}
# 使用例
predictions = [
"TypeScriptはJavaScriptに静的型付けを加えた言語です。"
]
references = [
"TypeScriptはMicrosoftが開発した、JavaScriptのスーパーセットで型システムを持つ言語です。"
]
scores = evaluate_with_bertscore(predictions, references)
print(f"意味的類似度(F1): {scores['f1']:.3f}")
# ROUGE(テキスト要約の評価)
from rouge_score import rouge_scorer
def evaluate_with_rouge(prediction: str, reference: str) -> dict:
"""ROUGE-1, ROUGE-2, ROUGE-Lスコアを計算"""
scorer = rouge_scorer.RougeScorer(
['rouge1', 'rouge2', 'rougeL'],
use_stemmer=True
)
scores = scorer.score(reference, prediction)
return {
"rouge1_f1": scores['rouge1'].fmeasure,
"rouge2_f1": scores['rouge2'].fmeasure,
"rougeL_f1": scores['rougeL'].fmeasure,
}
3. LLM-as-Judge
import openai
import json
from pydantic import BaseModel
class JudgementResult(BaseModel):
score: int # 1-5
reasoning: str # 評価理由
strengths: list[str] # 良い点
weaknesses: list[str] # 改善点
def llm_judge(
question: str,
response: str,
rubric: str,
model: str = "gpt-4o"
) -> JudgementResult:
"""GPT-4oを審判として使い、LLMの出力を評価する"""
client = openai.OpenAI()
prompt = f"""以下の基準に従って、AIの回答を評価してください。
【評価基準】
{rubric}
【質問】
{question}
【評価対象の回答】
{response}
【評価】
スコア(1-5)と理由をJSON形式で返してください:
{{
"score": 1-5の整数,
"reasoning": "評価の説明",
"strengths": ["良い点1", "良い点2"],
"weaknesses": ["改善点1", "改善点2"]
}}"""
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"}
)
data = json.loads(response.choices[0].message.content)
return JudgementResult(**data)
# 使用例
result = llm_judge(
question="Pythonのデコレーターとは何ですか?",
response="デコレーターは関数を修飾するシンタックスシュガーです。@で始まります。",
rubric="""
5: 正確で網羅的、コード例があり、初心者にもわかりやすい
4: 正確で詳細、コード例がある
3: 基本的に正確だがやや不足
2: 部分的に正確
1: 不正確または不完全
"""
)
print(f"スコア: {result.score}/5")
print(f"理由: {result.reasoning}")
RAG評価(RAGas)
RAGシステム専用の評価フレームワーク:
pip install ragas
from ragas import evaluate
from ragas.metrics import (
faithfulness, # 忠実性:回答はコンテキストに基づいているか
answer_relevancy, # 回答関連性:質問に対して適切か
context_recall, # コンテキスト再現率:必要な情報が含まれるか
context_precision, # コンテキスト精度:余分な情報がないか
)
from datasets import Dataset
# 評価データの準備
data = {
"question": [
"TypeScriptのジェネリクスとは何ですか?",
"Next.jsのApp RouterはPages Routerと何が違いますか?",
],
"answer": [
"ジェネリクスは型をパラメータとして受け取れる機能で、再利用可能な型安全なコードを書けます。",
"App RouterはReact Server Componentsを採用しており、より柔軟なデータフェッチングが可能です。",
],
"contexts": [
[
"TypeScriptのジェネリクスは、型をパラメータとして受け取る機能です。<T>という記法で表されます。",
"ジェネリクスを使うと、型に依存しない汎用的なコードを書けます。"
],
[
"Next.js App RouterはReact 18のServer Componentsをベースにしています。",
"Pages Routerに比べて、より細かいデータフェッチング制御が可能です。",
],
],
"ground_truth": [
"ジェネリクスは型パラメータを使って汎用的なコードを書く機能です。",
"App RouterはServer ComponentsとClient Componentsを組み合わせて使います。",
]
}
dataset = Dataset.from_dict(data)
# 評価実行
results = evaluate(
dataset,
metrics=[
faithfulness,
answer_relevancy,
context_recall,
context_precision,
]
)
print(results)
# {'faithfulness': 0.92, 'answer_relevancy': 0.88, 'context_recall': 0.85, 'context_precision': 0.90}
DeepEval(包括的評価フレームワーク)
pip install deepeval
from deepeval import evaluate
from deepeval.metrics import (
AnswerRelevancyMetric,
FaithfulnessMetric,
ContextualPrecisionMetric,
HallucinationMetric,
ToxicityMetric,
BiasMetric,
)
from deepeval.test_case import LLMTestCase
# テストケースの作成
test_case = LLMTestCase(
input="Dockerのコンテナとイメージの違いを説明してください",
actual_output="コンテナはイメージのランタイムインスタンスです。イメージは静的なテンプレートで、コンテナはその動的な実行形式です。",
expected_output="イメージはアプリケーションと依存関係を含む静的なスナップショット。コンテナはイメージから起動した実行中のプロセス。",
retrieval_context=[
"Dockerイメージはアプリケーションのすべての依存関係を含む読み取り専用のテンプレートです。",
"Dockerコンテナはイメージのランタイムインスタンスで、実際のプロセスが動作します。"
]
)
# メトリクスの定義
metrics = [
AnswerRelevancyMetric(threshold=0.7),
FaithfulnessMetric(threshold=0.8),
HallucinationMetric(threshold=0.1), # 幻覚が低いほど良い
ToxicityMetric(threshold=0.01), # 毒性が低いほど良い
]
# 評価実行
results = evaluate([test_case], metrics)
print(f"全体合格: {results.test_cases[0].success}")
評価パイプラインの構築
from dataclasses import dataclass, field
from typing import Callable
import pandas as pd
from datetime import datetime
@dataclass
class EvalPipeline:
"""評価パイプラインを管理するクラス"""
name: str
metrics: list[Callable]
threshold: float = 0.7
results: list[dict] = field(default_factory=list)
def run(self, test_cases: list[dict]) -> pd.DataFrame:
for case in test_cases:
result = {
"question": case["question"],
"expected": case["expected"],
"actual": case["actual"],
"timestamp": datetime.now().isoformat()
}
# 各メトリクスを実行
for metric in self.metrics:
score = metric(
question=case["question"],
expected=case["expected"],
actual=case["actual"]
)
result[metric.__name__] = score
# 総合判定
metric_scores = [
result[m.__name__] for m in self.metrics
]
result["overall_score"] = sum(metric_scores) / len(metric_scores)
result["passed"] = result["overall_score"] >= self.threshold
self.results.append(result)
df = pd.DataFrame(self.results)
self._print_summary(df)
return df
def _print_summary(self, df: pd.DataFrame):
print(f"\n{'='*50}")
print(f"評価パイプライン: {self.name}")
print(f"{'='*50}")
print(f"総テスト数: {len(df)}")
print(f"合格数: {df['passed'].sum()}")
print(f"合格率: {df['passed'].mean()*100:.1f}%")
print(f"平均スコア: {df['overall_score'].mean():.3f}")
print(f"{'='*50}\n")
A/Bテストで最適なプロンプトを選ぶ
import openai
import numpy as np
from scipy import stats
def ab_test_prompts(
prompt_a: str,
prompt_b: str,
test_questions: list[str],
evaluator: Callable
) -> dict:
"""2つのプロンプトをA/Bテストで比較する"""
client = openai.OpenAI()
scores_a, scores_b = [], []
for question in test_questions:
# プロンプトAで生成
response_a = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": prompt_a},
{"role": "user", "content": question}
]
)
# プロンプトBで生成
response_b = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": prompt_b},
{"role": "user", "content": question}
]
)
# 評価
score_a = evaluator(question, response_a.choices[0].message.content)
score_b = evaluator(question, response_b.choices[0].message.content)
scores_a.append(score_a)
scores_b.append(score_b)
# 統計的検定(t検定)
t_stat, p_value = stats.ttest_rel(scores_a, scores_b)
return {
"prompt_a_mean": np.mean(scores_a),
"prompt_b_mean": np.mean(scores_b),
"improvement": (np.mean(scores_b) - np.mean(scores_a)) / np.mean(scores_a) * 100,
"p_value": p_value,
"significant": p_value < 0.05,
"winner": "B" if np.mean(scores_b) > np.mean(scores_a) else "A"
}
# 使用例
result = ab_test_prompts(
prompt_a="あなたは役立つアシスタントです。",
prompt_b="あなたはシニアエンジニアです。実際のコード例を必ず含めて答えてください。",
test_questions=[
"Reactのレンダリング最適化方法を教えてください",
"TypeScriptの型推論について教えてください",
],
evaluator=lambda q, a: 1.0 if "```" in a else 0.5 # コード例があれば高スコア
)
print(f"勝者: プロンプト{result['winner']}")
print(f"改善率: {result['improvement']:.1f}%")
print(f"統計的有意差: {result['significant']}")
評価のベストプラクティス
評価の原則
1. タスク固有の評価基準を定義する
→ 「良い回答」の定義を明確にしてから評価ツールを選ぶ
2. 複数の評価手法を組み合わせる
→ LLM-as-Judge + BERTScore + タスク固有メトリクス
3. ゴールデンデータセットを整備する
→ 「正解」のある評価セットを100件以上用意
4. 評価を継続的に実施する
→ プロンプト変更のたびにCI/CDで自動評価
5. 人手評価でキャリブレーションする
→ 自動評価のスコアが人間の判断と一致するか定期確認
コスト効率の良い評価戦略
# 評価の階層化(全件でなく代表サンプルで評価)
EVAL_STRATEGY = {
"fast_check": {
"frequency": "every_commit",
"sample_size": 50,
"metrics": ["keyword_match", "length_check"],
"model": "gpt-4o-mini", # 安いモデルで素早く
},
"quality_check": {
"frequency": "daily",
"sample_size": 200,
"metrics": ["llm_judge", "bertscore"],
"model": "gpt-4o",
},
"full_eval": {
"frequency": "weekly",
"sample_size": 1000,
"metrics": ["all"],
"model": "gpt-4o",
}
}
まとめ
LLM評価はAIアプリを継続的に改善するための基盤です。
評価導入のロードマップ:
- まずはシンプルなキーワードマッチで始める
- LLM-as-Judgeを追加して品質を測る
- RAGシステムにはRAGasを導入
- CI/CDに組み込んで自動化
- 人手評価でキャリブレーション
ツール選択の指針:
- Promptfoo: プロンプトのA/Bテスト、CI/CD統合
- RAGas: RAGシステム専用評価
- DeepEval: 包括的な評価フレームワーク
- LangFuse: 本番モニタリング+評価