跳到主要内容
Agent Eval

第4章:LLM-as-Judge 方法论 —— G-Eval、Bias 与校准

LLM-as-Judge 三种范式、G-Eval 评分细则、4 类已知 bias(position/verbosity/self-preference/token)、校准方法、何时该用何时该避

LLM-as-Judge G-Eval Bias Pairwise Pointwise Calibration

不是所有任务都有”标准答案”——开放对话、创意写作、风格质量、复杂语义,verifier 写不出来。这时候只能让 另一个 LLM 当裁判。但 LLM Judge 不是公正的——它有 4 类系统性 bias,会让你的评测数字误导决策。本章把 LLM-as-Judge 的范式、bias、校准方法、何时该用何时该避全讲清,让你用得明白。

📑 目录


1. 什么时候必须用 LLM Judge

1.1 三类必须场景

场景为什么 verifier 不行
开放对话答案没有唯一形式,文本复杂语义判断
创意 / 风格”好不好”主观,无标准答案
复杂多跳推理推理过程评估难规则化

1.2 三类不该用

场景为什么用 verifier 更好
数学题答案唯一,EM verifier 99.9% 准
代码 generationunit test 决定对错
API 调用调用成功 / 状态变化可程序化检查

🍎 铁律:有 verifier 选 verifier,没办法才用 LLM Judge。LLM Judge 引入二阶噪声(judge 自己也有错),应是兜底而非首选。


2. 三种 Judge 范式

2.1 ① Pointwise Judge(单点打分)

Judge 看一个回答,直接给 1-5 / 0-10 分

Prompt 示例:

评分这个客服回答的质量,1-5 分:
- 1: 完全不相关
- 5: 完全准确且专业

User question: ...
Agent response: ...

Score:

优点:简单,可大规模跑 缺点:绝对分数极不稳定——同一答案不同 prompt / 模型可能差 1-2 分

2.2 ② Pairwise Judge(两两对比)

Judge 看 A 和 B 两个回答,选哪个更好

Prompt:

对比下面两个回答,哪个更好?

Q: ...
A: ...
B: ...

Output: A / B / Tie

优点:相对判断比绝对打分稳定(ChatBot Arena 用这种) 缺点:n² 比较成本高,扩展难

2.3 ③ Reference-based Judge(参考答案)

Judge 看到 ground truth,判断 prediction 是否等价

Prompt:

Reference answer: 鲁迅
Predicted answer: 周樟寿
Are they equivalent? Yes / No

适用:有 reference 但需要语义判断(同义词、不同表达)。介于 verifier 和 LLM Judge 之间。

2.4 哪个范式选哪个

场景推荐
大批量评分(1000+ 题)Pointwise(便宜)
模型对决(找哪个更好)Pairwise(更稳)
有 reference 的语义判断Reference-based
高 stakes(决策)Pairwise + 多 judge 投票

3. G-Eval:让 LLM 用细则评分

Liu et al., 2023

3.1 思想

不要让 Judge 自由打分,给它一个”评分细则(rubric)“

给定 rubric:
  Relevance:
    1 - 完全不相关
    2 - 部分相关但偏题
    3 - 基本相关
    4 - 高度相关
    5 - 完全切题

LLM Judge 按 rubric 评分,并 step by step 解释为什么

3.2 关键技巧

① Chain-of-thought 评分

Step 1: 列出回答的关键内容
Step 2: 对照 rubric 每一档的特征
Step 3: 给出最匹配的分数 + 理由

让 LLM 显式推理,比直接给数字稳得多

② Token probability 加权

不只看输出的数字,而是看 LLM 输出 1-5 各自的 token 概率,加权平均:

score=i=15iP(output=i)\text{score} = \sum_{i=1}^{5} i \cdot P(\text{output}=i)

更细粒度,降低离散数字的方差。

3.3 DeepEval 中的 G-Eval

from deepeval.metrics import GEval
from deepeval.test_case import LLMTestCase

correctness = GEval(
    name="Correctness",
    criteria="Determine if 'actual output' factually matches 'expected output'.",
    evaluation_steps=[
        "Check if all facts in expected output are present in actual output",
        "Identify any factual contradictions",
        "Score severely (low) if hallucinations exist",
    ],
    evaluation_params=[LLMTestCaseParams.INPUT, LLMTestCaseParams.ACTUAL_OUTPUT, LLMTestCaseParams.EXPECTED_OUTPUT],
)

test_case = LLMTestCase(input="...", actual_output="...", expected_output="...")
correctness.measure(test_case)
print(correctness.score, correctness.reason)

4. 4 类已知 Bias

4.1 ① Position Bias(位置偏见)

LLM Judge 倾向选第一个或最后一个

实验:同一对回答 (A, B),交换顺序变成 (B, A) 跑两次:

顺序 (A, B) → 选 A (60%)
顺序 (B, A) → 选 B (55%)
真实差距:可能就是 10% 而非 60-55=5%

防御:每对都跑两次(swap),取平均。

4.2 ② Verbosity Bias(冗长偏见)

LLM Judge 倾向选更长的回答

A: "答案是 42。"(简洁)
B: "经过仔细思考,我认为这个问题需要多角度分析...最终答案是 42。"(啰嗦)

LLM Judge 经常选 B,即使 A 更优

防御:rubric 显式声明”长度不应影响评分”+ 控制回答长度近似。

4.3 ③ Self-Preference Bias(自我偏好)

GPT-4 当 Judge 时倾向选 GPT-4 写的回答;Claude 选 Claude 的

研究发现:模型 judge 自己输出时给分高 5-15%

防御:用第三方 model 当 judge(评测 GPT 模型用 Claude judge,反之亦然)。

4.4 ④ Token Bias / Format Bias

Judge 对 markdown / code block / 列表等格式有偏好

A: "巴黎是法国首都。"
B: "**巴黎**:
- 法国首都
- 人口 200 万
- ..."

格式漂亮的 B 经常被选,即使内容相同

防御:rubric 说”不评格式只评内容”;或在 judge 前对两个回答统一 strip 格式。

4.5 综合校准前后对比

Bias校准前误差校准后(swap+rubric+third-party judge)
Position5-10%< 1%
Verbosity10-20%2-3%
Self-preference5-15%< 2%
Format5%< 1%

🌟 校准是 LLM Judge 必须的工程——直接用就是耍流氓。


5. 校准方法

5.1 校准 4 件套

① Position swap

每对评测都跑两次:(A, B) 和 (B, A),取一致的判断,不一致就标记 “tie”。

def swap_robust_judge(a, b):
    score_ab = judge_pair(a, b)
    score_ba = judge_pair(b, a)
    if score_ab == "A" and score_ba == "B":
        return "A wins (consistent)"
    elif score_ab == "B" and score_ba == "A":
        return "B wins (consistent)"
    else:
        return "Tie / Inconsistent"

② 多 judge 投票

用 3 个不同的 LLM(GPT / Claude / Gemini)各自评,多数票决定。

③ Human gold 校准

抽 50-100 题人工标注,作为 judge accuracy 基准:

Human label:    [A, A, B, A, B, ...]
Judge output:   [A, B, B, A, B, ...]

Agreement = 0.85
Cohen's Kappa = 0.7   (评估一致性,>0.6 算好)

如果 agreement < 0.7,改 prompt 或换 judge model。

④ Rubric + CoT

显式 rubric + chain-of-thought,而非”自由打分”。

5.2 校准代码示例

from deepeval.metrics import GEval

# 1. 定义 rubric + CoT
metric = GEval(
    name="Helpfulness",
    criteria="Score helpfulness 1-5",
    evaluation_steps=[
        "Identify user intent",
        "Check if answer addresses intent",
        "Penalize verbosity / format flair",
        "Score based on substance only",
    ],
    model="gpt-5",   # 第三方 judge,不用被评的同一个 model
)

# 2. 跑 swap-robust pairwise(自己写)
def evaluate_pair(case_a, case_b):
    score_a = metric.measure(case_a).score
    score_b = metric.measure(case_b).score
    return score_a - score_b   # > 0 → A 好

# 3. 多 judge 投票
votes = [
    evaluate_pair_with_model(a, b, "gpt-5"),
    evaluate_pair_with_model(a, b, "claude-4-opus"),
    evaluate_pair_with_model(a, b, "gemini-2.5-pro"),
]
final = majority(votes)

6. LLM Judge vs Verifier 边界

6.1 决策表

任务特征选 Verifier选 LLM Judge
答案唯一形式
可以执行验证(unit test、tool result)
数值 / 字符串精确匹配
开放式回答
风格 / tone / 创意
多种正确答案
需要语义同义判断partial
高 stakes 决策✅(更可靠)(要多 judge 投票)

6.2 混合策略(2026 推荐)

先 verifier,失败再 LLM Judge:

def hybrid_eval(response, ref):
    # 1. 严格 verifier
    if rule_based_verifier(response, ref):
        return 1.0
    # 2. 字符串近似(拼写、format 差异)
    if fuzzy_match(response, ref):
        return 0.9
    # 3. LLM judge 兜底
    return llm_judge(response, ref)

兼顾速度 + 准确度

6.3 LLM Judge 的成本

Judge model$/eval(短回答)
GPT-4o-mini~$0.0005
GPT-4o~$0.005
Claude Sonnet 4.5~$0.005
Claude Opus 4.7~$0.025

跑 1000 题 × 多 judge 投票 × swap = ~$50-300——比 verifier 贵 100 倍


7. 实战:跑一组 ChatBot Arena 风格评测

7.1 完整脚本

"""
对比 GPT-5 和 Claude Sonnet 4.5 在客服任务上的胜率。
"""
import asyncio
from openai import AsyncOpenAI
from anthropic import AsyncAnthropic

oai = AsyncOpenAI()
anthr = AsyncAnthropic()

async def get_response(model, prompt):
    if model.startswith("gpt"):
        r = await oai.chat.completions.create(
            model=model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7,
        )
        return r.choices[0].message.content
    else:
        r = await anthr.messages.create(
            model=model,
            max_tokens=2048,
            messages=[{"role": "user", "content": prompt}],
        )
        return r.content[0].text

async def judge(question, answer_a, answer_b, judge_model):
    """Pairwise judge with rubric."""
    prompt = f"""You are an expert evaluator. Compare the two answers.

Question: {question}

Answer A: {answer_a}

Answer B: {answer_b}

Rubric:
- Helpfulness (does it solve the user's problem?)
- Accuracy (factual correctness)
- Conciseness (no unnecessary verbosity — DO NOT prefer longer answers)

Step 1: Briefly evaluate Answer A
Step 2: Briefly evaluate Answer B
Step 3: Output exactly one of: "A wins" / "B wins" / "Tie"
"""
    if judge_model.startswith("gpt"):
        r = await oai.chat.completions.create(
            model=judge_model,
            messages=[{"role": "user", "content": prompt}],
            temperature=0.0,
        )
        text = r.choices[0].message.content
    else:
        r = await anthr.messages.create(
            model=judge_model, max_tokens=1024,
            messages=[{"role": "user", "content": prompt}],
        )
        text = r.content[0].text
    
    if "A wins" in text: return "A"
    elif "B wins" in text: return "B"
    else: return "Tie"

async def evaluate(prompts, model_a="gpt-5", model_b="claude-sonnet-4-5"):
    judges = ["claude-opus-4-7", "gpt-5", "gemini-2.5-pro"]
    
    results = []
    for prompt in prompts:
        # 1. 拿两个 model 的回答
        ans_a = await get_response(model_a, prompt)
        ans_b = await get_response(model_b, prompt)
        
        # 2. 多 judge swap-robust
        votes = []
        for j in judges:
            v_ab = await judge(prompt, ans_a, ans_b, j)
            v_ba = await judge(prompt, ans_b, ans_a, j)
            # swap consistent
            if v_ab == "A" and v_ba == "B":
                votes.append("A")
            elif v_ab == "B" and v_ba == "A":
                votes.append("B")
            else:
                votes.append("Tie")
        
        # 3. 多数票
        final = max(set(votes), key=votes.count)
        results.append({"prompt": prompt, "winner": final, "votes": votes})
    
    # 统计胜率
    a_wins = sum(1 for r in results if r["winner"] == "A")
    b_wins = sum(1 for r in results if r["winner"] == "B")
    ties = sum(1 for r in results if r["winner"] == "Tie")
    total = len(results)
    
    print(f"{model_a} vs {model_b}:")
    print(f"  {model_a} 胜率: {a_wins/total:.1%}")
    print(f"  {model_b} 胜率: {b_wins/total:.1%}")
    print(f"  Ties:        {ties/total:.1%}")
    return results

# 运行
prompts = [...]   # 100 条客服真实 prompt
asyncio.run(evaluate(prompts))

7.2 期望产出

gpt-5 vs claude-sonnet-4-5:
  gpt-5 胜率:        38%
  claude-sonnet-4-5 胜率: 42%
  Ties:              20%

🌟 关键设计点(都已加入上面脚本):

  • 多 judge 投票(3 个不同厂商)
  • Swap robust(每对跑 AB 和 BA)
  • Rubric 明确”不要偏好长回答”
  • temperature=0.0 减少 judge 噪声

✅ 自我检验清单

  • 何时用 LLM Judge:能列出 3 类必须 / 3 类不该的场景
  • 三种范式:能默写 Pointwise / Pairwise / Reference-based,各适用场景
  • G-Eval 思想:能解释”rubric + CoT + token prob”为什么比直接打分稳
  • 4 类 Bias:能默写 Position / Verbosity / Self-preference / Format
  • Position swap:能写代码做 swap-robust pairwise
  • Self-preference 防御:能解释为什么”评测 GPT 用 Claude judge”
  • Cohen’s Kappa:能解释为什么 agreement 0.85 加 kappa 才是好 judge
  • 混合策略:能写”verifier 优先 + LLM Judge 兜底”代码
  • 成本估算:能算 1000 题 × 多 judge × swap 的总 token cost
  • 完整脚本:能默写一段含 4 件套校准的 LLM Judge 评测

📚 参考资料

论文

框架

工业实践

  • AI Multiple LLM Eval Tools:博文
  • ZenML: 8 Best DeepEval Alternatives:博文