【AI】LLM 应用评测体系:从 LLM-as-a-Judge 到生产级 Eval
1. 传统测试为什么不够用
假设你写了一个函数:
def add(a, b):
return a + b
assert add(1, 2) == 3 # 通过这个断言永远成立。但换成 LLM 应用:
result = llm.invoke("帮我把这句话改得更专业:这个方案我觉得不太行")
assert result == "???"你写不出这个断言。因为:
- 输出空间是无限的:同一句话有无数种「更专业」的表达
- 同一输入两次输出可能不同:采样温度、模型版本更新都会导致变化
- 「好」是主观的:什么算「更专业」,取决于业务标准
这不是「测试写不好」的问题,而是评测对象从「输出值」变成了「输出分布」。评测体系必须重新设计。
2. 三层评测体系
一个能落地的 LLM 评测体系,通常分三层,成本从低到高,稳定性从高到低:
| 层级 | 评测对象 | 判定方式 | 执行时机 | 成本 |
|---|---|---|---|---|
| L1 确定性断言 | 结构化输出、工具调用 | 精确匹配 / Schema 校验 | 每次提交 | 极低 |
| L2 语义评测 | 内容质量、答案正确性 | LLM-as-a-Judge、规则匹配 | 每次合并 | 中 |
| L3 端到端指标 | 用户价值 | 人工标注、线上指标 | 发版 / 持续 | 高 |
关键原则:能用 L1 解决的,绝不上升到 L2;能用 L2 解决的,不要天天跑 L3。
很多团队一上来就搭一套全自动 LLM 评测流水线,结果发现又贵又不稳,最后没人看。正确的做法是从确定性断言开始,把能确定的部分先确定下来。
3. L1:确定性断言能覆盖什么
LLM 应用里,其实有相当大一部分是可以确定性验证的:
3.1 结构化输出
如果要求模型输出 JSON,那就能用 Schema 校验:
from pydantic import BaseModel, ValidationError
class Extracted(BaseModel):
name: str
email: str
company: str | None = None
def test_extraction():
raw = agent.run("从这段简历里提取姓名、邮箱和公司:...")
try:
obj = Extracted.model_validate_json(raw)
except ValidationError as e:
pytest.fail(f"Schema 校验失败: {e}")
assert "@" in obj.email3.2 工具调用序列
Agent 该调什么工具、什么顺序,这是可以断言的:
def test_agent_tool_sequence():
trace = agent.run_with_trace("查一下北京今天的天气")
tools = [step.tool for step in trace if step.type == "tool_call"]
assert "get_weather" in tools
assert tools.index("get_weather") < len(tools) # 至少调用过一次
assert "send_email" not in tools # 不该调用的别调最后那条「不该调用的别调」尤其重要——它是安全回归测试。
3.3 关键词与禁止词
对于内容生成,可以用规则做粗筛:
FORBIDDEN = ["我无法", "作为一个AI", "抱歉"]
def test_no_meta_refusal():
out = agent.run("帮我写个请假邮件")
for word in FORBIDDEN:
assert word not in out, f"出现元话语: {word}"这类断言很粗糙,但零成本、零延迟、零波动,作为第一道闸门非常划算。
3.4 性能预算
def test_latency_budget():
start = time.time()
agent.run("简单问答:今天星期几")
assert time.time() - start < 5.0延迟回归同样会毁掉产品体验,但经常被忽略。
4. L2:LLM-as-a-Judge 的正确用法
语义质量无法用规则判定,于是有了「用模型评模型」。这个思路可行,但坑很多。
4.1 基础实现
JUDGE_PROMPT = """你是一个严格的评测员。根据以下标准评估回答质量。
【用户问题】
{question}
【参考答案】
{reference}
【待评回答】
{answer}
请按 1-5 分打分,并说明理由。先输出 <score>分数</score>,再输出 <reason>理由</reason>。
"""4.2 三个必须知道的偏差
位置偏差(Position Bias)
在成对比较时,评委倾向于选择第一个或第二个选项。缓解方法:双向评测——同一对答案交换顺序评两次,只有两次都一致才认可结果。
长度偏差(Verbosity Bias)
更长的回答往往得分更高,即使内容并没有更好。缓解方法:在评分标准里明确写「简洁性也是评分维度」,或者在评测时对长度做归一化处理。
自偏好(Self-Preference)
用 GPT 评 GPT 的输出,会系统性偏高。缓解方法:评委模型与生成模型不同源,或者用多个评委投票。
4.3 评分校准
LLM 给出的分数不能直接信。上线前必须做一次人工校准:
- 人工标注 50~100 条样本,给出真实分数
- 让 Judge 评同一批样本
- 计算相关性(如 Spearman 相关系数)
- 相关性低于 0.7,说明评分标准或 prompt 需要重写
没有校准过的 Judge,本质上是在测一个随机数。
4.4 成对比较优于绝对打分
实践经验:让模型回答「A 和 B 哪个更好」比「给这个回答打几分」要稳定得多。
def pairwise_judge(question, answer_a, answer_b, model):
# 正向
r1 = model.invoke(pairwise_prompt(question, answer_a, answer_b))
# 反向(交换顺序,消除位置偏差)
r2 = model.invoke(pairwise_prompt(question, answer_b, answer_a))
if r1.winner == "A" and r2.winner == "B":
return "A"
if r1.winner == "B" and r2.winner == "A":
return "B"
return "tie"这是一个非常实用的模式:用一致性换掉偏差。
5. 评测集怎么建
评测集的质量,直接决定评测体系的价值。三个来源:
5.1 黄金集(Golden Set)
人工精心标注的高质量样本,作为基准。规模不需要大——50 到 200 条高质量样本,胜过 1 万条脏数据。
构建要点:
- 覆盖主要场景,也覆盖边界情况
- 包含「应该拒绝」的负样本
- 每条样本带明确的验收标准
5.2 合成集(Synthetic Set)
用强模型批量生成测试用例。适合快速扩大覆盖面,但要小心:合成数据往往比真实数据「干净」,容易高估系统能力。
5.3 生产回流(Production Mining)
从线上日志里挖掘真实 case,尤其是:
- 用户点了「踩」或重新生成的
- 用户手动修改了输出的
- Agent 报错或超时的
- 人工客服介入的
这一类的价值最高,因为它们反映真实分布。建议建立固定流程:每周从生产日志挖 10 条,人工标注后进黄金集。
6. L3:生产指标
离线评测再好,也要看线上结果。常用的生产指标:
| 指标 | 含义 | 采集方式 |
|---|---|---|
| 采纳率 | 输出被用户直接使用的比例 | 前端埋点 |
| 重试率 | 用户不满意重新生成的比例 | 前端埋点 |
| 编辑距离 | 用户对输出做了多少修改 | 前后文本 diff |
| 人工升级率 | 转人工的比例 | 业务系统 |
| 任务完成率 | 多步任务最终成功的比例 | 会话级追踪 |
| 成本 / 请求 | 单次请求的 token 成本 | 网关统计 |
其中**「编辑距离」**是一个被低估的指标。用户对输出的修改量,是内容质量的直接体现——不需要用户主动反馈,就能自动采集。
7. CI 集成
把评测接进 CI,才能真正防回归。几个工程细节:
7.1 分层触发
on:
pull_request:
paths: ['prompts/**', 'src/agent/**']
jobs:
l1-assertions:
# 每次 PR 都跑:快、便宜、必过
runs-on: ubuntu-latest
steps:
- run: pytest tests/l1 -x
l2-judge:
# 只对相关 PR 跑,且做采样
if: contains(github.event.pull_request.changed_files, 'prompts')
runs-on: ubuntu-latest
steps:
- run: python evals/run_l2.py --sample 507.2 门禁阈值
不要用「平均分」做门禁,容易被少数高分样本拉平。建议:
gate = {
"l1_pass_rate": 1.0, # L1 必须全过
"l2_win_rate": 0.55, # 新版本相对基线的胜率
"regression_max": 3, # 允许最多 3 条回归
}特别注意:胜率要用成对比较算,直接用平均分对比是不靠谱的(分数尺度不稳定)。
7.3 成本控制
LLM 评测本身也烧钱。控制手段:
- L1 全量,L2 采样(按变更范围选样本)
- 用便宜的模型做初筛,只有边界样本才用强模型
- 缓存评测结果(同一个回答不重复评)
- 夜间跑全量,PR 只跑子集
7.4 版本快照
评测必须记录完整上下文,否则结果无法解释:
{
"run_id": "2026-11-05-001",
"model": "xxx-v2",
"prompt_version": "git:abc123",
"dataset_version": "golden-v7",
"judge_model": "yyy",
"results": { "l1_pass": 48, "l1_total": 48, "l2_win_rate": 0.62 }
}模型、prompt、数据集、评委,四者任一变化,评测结果都不可直接比较。
8. 五个常见误区
误区一:只测「能不能用」,不测「什么时候不该用」
负样本(该拒绝的请求、该触发的安全策略)比正样本更容易被忽略,也更容易出事故。
误区二:把评测当一次性任务
模型会更新、prompt 会改、用户分布会变。评测集必须持续维护,否则半年后就完全失真。
误区三:追求全自动
完全自动的评测会逐渐偏离人类判断。人类标注必须保留——哪怕一周只标 20 条。
误区四:只看平均分
平均分掩盖尾部风险。一个 95% 正确率的系统,如果 5% 的错误是「给了错误的医疗建议」,那它依然不可上线。要看 P95 / 最差样本。
误区五:评测和产品指标脱节
离线涨了,线上没动——这说明评测集不代表真实分布。要定期做离线指标与线上指标的相关性分析。
9. 小结
LLM 评测的本质,是在非确定性系统上重建确定性。
可行的路径是:
- 先把能确定的确定下来(L1 断言,零成本高价值)
- 再用模型评模型,但必须校准(L2,成对比较 + 双向评测)
- 最终回到线上指标验证价值(L3,编辑距离、采纳率)
记住一句话:
没有评测的 AI 应用,不是「上线了」,只是「发出了请求」。
评测体系不是发版前的一次性动作,而是和代码一起演进的长期资产。它决定了你敢不敢改 prompt、敢不敢换模型、敢不敢快速迭代。