LLM 当裁判?先懂它的 4 个偏心------自动化评测实战(E03)
系列《AI 应用生产化手册》第 3 篇(共 30 篇)|配套开源项目:github.com/ChenYingbo/...
📊 本文配套项目实测 :同一套 30 条评测集,真实模型跑分经历了 0/30 → 23/30 → 29/30(97%) 的校准之旅------每一步都是一个真实的坑,正文里逐个拆。
一、先看问题:手工打分撑不住
E02 有了 30 条评测集,但手工打分立刻遇到现实问题:
- 30 条 × 每次改提示词都要重打一遍 = 噩梦------10 分钟打分 × 每天改 5 次 = 一天全搭进去。
- 打分靠人脑,标准必然漂移------上午 4 分下午 3 分,报告没法跨天比较。
- 没有报告格式------打完了,没法回答"这版比上版好还是差、好在哪里"。
解法:把"人打分"换成"判分器打分"------自动化执行 + 统一判分标准 + 结构化报告。
核心认知:评测执行框架 = 评测集(E02)× 判分器(本篇)× 报告。判分器决定"什么叫对",是整个体系里最容易出偏差、也最值得研究的环节。
二、原理:判分器是怎么工作的
评测工具选型(为什么选 DeepEval)
| 工具 | 形态 | 优势 | 短板 |
|---|---|---|---|
| DeepEval | Python 库 | 指标丰富、可定制 judge、LLM-as-judge 与规则断言都支持 | 依赖较重;API 版本迭代快 |
| promptfoo | CLI/YAML | 配置驱动、上手快、生态广(含 RedTeam) | 复杂场景要写配置表达式 |
| Ragas | Python 库 | RAG 场景指标专精 | 偏 RAG,通用问答场景少 |
四种断言:从"硬规则"到"LLM 裁判"
| 断言类型 | 例子 | 适用 | 缺点 |
|---|---|---|---|
| 精确匹配 | assert answer == "2" |
确定性输出 | 语义正确但措辞不同 → 误杀 |
| 包含匹配 | assert "RAG" in answer |
关键词存在性 | 太弱,容易假绿 |
| 语义相似 | embedding 余弦 > 0.8 | 摘要、翻译 | 阈值难定 |
| LLM-as-judge | "这个回答准确吗?1-5 分" | 开放问答、代码、判断类 | 有偏差,需校准 |
没有一种断言全对。生产体系 = 硬规则管"必须满足的"(安全/格式),LLM 裁判管"语义上对不对"(质量)。
LLM-as-judge 的四大偏差(必懂)
| 偏差 | 现象 | 校准方法 |
|---|---|---|
| 位置偏差 | 答案 A 在前就更可能被判好 | 互换顺序跑两次 |
| 长度偏差 | 越长越像"好答案" | 判分 prompt 显式约束"忽略长度" |
| 自我偏好 | 模型偏爱自己生成的风格 | judge 模型 ≠ 被测模型 |
| 严格度漂移 | 今天严明天松 | 用基准问题定期校准 judge |
最重要的工程纪律:judge 模型 ≠ 被评测模型。 用同一个模型生成又判分,等于运动员自己当裁判。
评测执行架构(runner 模式)
javascript
评测集 JSON ──► runner(逐条执行)──► /api/chat(被测系统)
│ │
▼ ▼
拿到答案 ──────────────► 判分器(GEval / 相关性)
│
▼
报告 JSON(逐条分数 + 汇总)
│
▼
E04:CI 门禁直接消费报告
runner 的职责:加载评测集 → 调被测系统 → 判分 → 写报告 →(可选)按阈值退出。 这套结构 E04 原样复用,只是把"手动跑"换成"CI 里跑"。
三、动手:跑第一份自动化评测
bash
git clone https://github.com/ChenYingbo/ai-prod-demo.git && cd ai-prod-demo
source .venv/bin/activate && pip install -r requirements.txt
# 1. 先跑 dry-run:校验评测集结构(不需要 API Key)
python -m evals.run_evals --dry-run
# 预期:评测集校验:共 30 条 / 各类别数量 / 边界 case: 8 / dry-run 通过 ✓
# 2. 启动 demo(另开终端,需要 API Key)
uvicorn app.main:app --port 8000
# 3. 真实评测:用 DeepSeek 当裁判(便宜、中文友好)
JUDGE_MODEL="deepseek/deepseek-chat" python -m evals.run_evals --all
报告怎么看(三个层次)
- 总览 :
passed/total------第一次跑别指望 100%,目标是从"不知道多差"变成"知道差在哪" - 逐条失败:失败的集中在哪几类?对照 E02 的 category 字段
- 分布规律 :边界 case 的失败率 vs 普通题------如果边界题全挂,说明系统缺"拒答/澄清"能力
动手调一版(体会判分器价值)
改一行系统提示词(app/routers/chat.py),重跑评测对比通过率------没有评测框架时,你根本不敢说这行改动是变好了还是变坏了;现在你有证据了。
四、真实踩坑(含 0/30 → 29/30 的校准实录)
- deepeval 4.x API 漂移(实测) :
evaluation_params从字符串改成了枚举对象SingleTurnParams,传字符串直接报错'str' object has no attribute 'value'------第一次全量跑,30/30 全 FAIL,不是模型问题,是判分器 API 变了 - 方法名也变了(实测) :
is_success()在 4.x 改名is_successful()------版本升级要对着文档核对 - naive judge 误判"安全拒答"(实测) :注入题被系统正确拦截,判分器却判"没回答问题"FAIL------边界 case 的判分要单独设计规则(注入→必须拦截、超范围→必须拒答),加规则后 23/30 → 29/30
- judge 和被评模型用同一个:等于自评,分数虚高------judge 单独配置
- 中文判分标准不写进 criteria :GEval 的 criteria 用英文模板,对中文回答判分偏差大------criteria 用中文写
- 把"通过率 100%"当目标:评测是为了暴露问题,第一版 60% + 知道差在哪,比 100% 假绿强
- 一次跑太多指标:每个指标一次 LLM 调用,30 条 × 2 指标 = 60 次调用------先跑 correctness,稳定了再加
校准后的最终结果:29/30(97%),门禁 0.8 通过------剩余 1 个是"模型 API 不可用怎么办"的泛泛回答,属于提示词引导问题,不是评测问题。
五、小结
- 四种断言 + judge 四大偏差:位置/长度/自我偏好/严格度漂移
- judge ≠ 被测模型:这条纪律能救你于自欺
- 边界 case 要单独设计判分规则:naive judge 会把"安全拒答"判成"没回答"
明天(E04):《回归门禁》------把评测跑进 CI,让"改坏了"在合并前就被拦住。 收藏 + 关注,每天一篇,30 天把 AI 应用送上生产。