LLM-as-a-Judge
用更强的 LLM 评估其他模型的输出,是目前最实用的自动化评估方法。
原理
graph LR
A[待评估的回答] --> B[评估 LLM GPT-4o]
C[评估标准 Rubric] --> B
D[参考答案 可选] --> B
B --> E[评分 + 理由]
style B fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
为什么有效:GPT-4 级别的模型在评估任务上的表现已经接近人类专家水平,且成本远低于人工。
单项评分
"""
LLM-as-a-Judge 评估系统
"""
from openai import OpenAI
import json
class LLMJudge:
"""LLM 评估器"""
def __init__(self, model: str = "gpt-4o"):
self.client = OpenAI()
self.model = model
def score(
self,
question: str,
answer: str,
criteria: str,
reference: str = None,
scale: int = 5,
) -> dict:
"""
对回答进行单项评分
Args:
question: 用户问题
answer: 模型回答
criteria: 评估标准
reference: 参考答案(可选)
scale: 评分范围 (1-scale)
"""
ref_section = f"\n## 参考答案\n{reference}" if reference else ""
prompt = f"""请评估以下回答的质量。
## 用户问题
{question}
## 模型回答
{answer}
{ref_section}
## 评估标准
{criteria}
## 评分要求
请给出 1-{scale} 分的评分:
1 分 = 非常差
{scale // 2} 分 = 中等
{scale} 分 = 优秀
请以 JSON 格式返回:
{{"score": 数字, "reasoning": "详细理由"}}"""
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "你是严格公正的评估专家。"},
{"role": "user", "content": prompt},
],
temperature=0,
response_format={"type": "json_object"},
)
try:
result = json.loads(response.choices[0].message.content)
return {
"score": result.get("score", 3),
"reasoning": result.get("reasoning", ""),
"max_score": scale,
}
except json.JSONDecodeError:
return {"score": 3, "reasoning": "解析失败", "max_score": scale}
def pairwise_compare(
self,
question: str,
answer_a: str,
answer_b: str,
criteria: str,
) -> dict:
"""
成对比较:哪个回答更好?
比单项评分更稳定,因为比较比绝对打分更容易
"""
prompt = f"""比较以下两个回答,判断哪个更好。
## 用户问题
{question}
## 回答 A
{answer_a}
## 回答 B
{answer_b}
## 评估标准
{criteria}
请以 JSON 格式返回:
{{"winner": "A" 或 "B" 或 "tie", "reasoning": "详细理由"}}"""
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "公正比较两个回答,不要有位置偏见(不要因为 A 在前面就偏向 A)。"},
{"role": "user", "content": prompt},
],
temperature=0,
response_format={"type": "json_object"},
)
try:
return json.loads(response.choices[0].message.content)
except json.JSONDecodeError:
return {"winner": "tie", "reasoning": "解析失败"}
def multi_dimension(
self,
question: str,
answer: str,
dimensions: dict[str, str],
) -> dict:
"""
多维度评估
Args:
dimensions: {维度名: 评估标准描述}
"""
dim_desc = "\n".join(
f"- **{name}**: {desc}" for name, desc in dimensions.items()
)
prompt = f"""从多个维度评估以下回答。
## 用户问题
{question}
## 模型回答
{answer}
## 评估维度
{dim_desc}
请对每个维度给出 1-5 分,以 JSON 格式返回:
{{"dimensions": {{"维度名": {{"score": 分数, "comment": "简短评语"}}}}, "overall": 总分}}"""
response = self.client.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "你是多维度评估专家。"},
{"role": "user", "content": prompt},
],
temperature=0,
response_format={"type": "json_object"},
)
try:
return json.loads(response.choices[0].message.content)
except json.JSONDecodeError:
return {"dimensions": {}, "overall": 3}
# ==================
# 使用示例
# ==================
judge = LLMJudge()
# 1. 单项评分
result = judge.score(
question="什么是机器学习?",
answer="机器学习是人工智能的一个分支,通过数据训练模型来做预测。",
criteria="评估答案的准确性、完整性和易懂程度",
)
print(f"评分: {result['score']}/{result['max_score']}")
print(f"理由: {result['reasoning']}")
# 2. 成对比较
compare = judge.pairwise_compare(
question="Python 列表和元组有什么区别?",
answer_a="列表可变,元组不可变。",
answer_b="列表用方括号[]创建,可以修改内容;元组用圆括号()创建,创建后不能修改。元组因为不可变所以性能更好,适合作为字典的键。",
criteria="回答的完整性和实用性",
)
print(f"胜者: {compare['winner']}")
# 3. 多维度评估
multi = judge.multi_dimension(
question="如何设计一个 RESTful API?",
answer="使用 HTTP 方法(GET/POST/PUT/DELETE),统一资源路径,返回 JSON...",
dimensions={
"准确性": "技术内容是否正确",
"完整性": "是否覆盖了关键要点",
"实用性": "是否有实际可操作的建议",
"清晰度": "表达是否清晰易懂",
},
)
print(f"多维评估: {json.dumps(multi, ensure_ascii=False, indent=2)}")
减少评估偏差
LLM-as-a-Judge 常见的偏差及解决方法:
| 偏差类型 | 描述 | 解决方案 |
|---|---|---|
| 位置偏差 | 倾向选择第一个出现的答案 | 交换位置做两次比较 |
| 冗长偏差 | 倾向更长的答案 | 在 Prompt 中强调简洁 |
| 自我偏好 | GPT-4 偏向 GPT-4 的回答 | 使用不同模型交叉评估 |
| 确信度偏差 | 自信的错误答案得分高 | 要求验证事实依据 |
"""
减少偏差的成对比较
"""
class FairJudge(LLMJudge):
"""减少偏差的评估器"""
def fair_compare(
self, question: str, answer_a: str, answer_b: str, criteria: str
) -> dict:
"""
公平比较: 交换位置做两次,消除位置偏差
"""
# 第一次: A 在前
result1 = self.pairwise_compare(question, answer_a, answer_b, criteria)
# 第二次: B 在前
result2 = self.pairwise_compare(question, answer_b, answer_a, criteria)
# 翻转结果
if result2["winner"] == "A":
result2["winner"] = "B"
elif result2["winner"] == "B":
result2["winner"] = "A"
# 判定
if result1["winner"] == result2["winner"]:
final = result1["winner"]
confidence = "高"
elif result1["winner"] == "tie" or result2["winner"] == "tie":
final = result1["winner"] if result2["winner"] == "tie" else result2["winner"]
confidence = "中"
else:
final = "tie"
confidence = "低 (两次结果矛盾)"
return {
"winner": final,
"confidence": confidence,
"round1": result1,
"round2": result2,
}
批量评估
"""
批量评估与统计
"""
class BatchEvaluator:
"""批量评估"""
def __init__(self):
self.judge = LLMJudge()
def evaluate_dataset(
self,
dataset: list[dict],
dimensions: dict[str, str],
) -> dict:
"""
评估整个数据集
Args:
dataset: [{"question": ..., "answer": ..., "reference": ...}]
dimensions: 评估维度
"""
all_scores = {dim: [] for dim in dimensions}
all_scores["overall"] = []
for i, item in enumerate(dataset):
print(f"评估 {i + 1}/{len(dataset)}...")
result = self.judge.multi_dimension(
question=item["question"],
answer=item["answer"],
dimensions=dimensions,
)
dims = result.get("dimensions", {})
for dim_name in dimensions:
if dim_name in dims:
all_scores[dim_name].append(dims[dim_name].get("score", 3))
all_scores["overall"].append(result.get("overall", 3))
# 统计
summary = {}
for dim, scores in all_scores.items():
if scores:
summary[dim] = {
"mean": round(sum(scores) / len(scores), 2),
"min": min(scores),
"max": max(scores),
"count": len(scores),
}
print("\n=== 批量评估结果 ===")
for dim, stats in summary.items():
print(f" {dim}: 平均={stats['mean']}, 范围=[{stats['min']}, {stats['max']}]")
return summary
本章小结
- LLM-as-a-Judge 是目前最实用的自动化评估方法
- 三种评估模式:单项评分、成对比较、多维度评估
- 成对比较比绝对打分更稳定可靠
- 通过交换位置等技术减少评估偏差
- 批量评估时要注意统计分析和成本控制
下一章:学习基准测试和自动化测试流水线。