测试集 & 回归 & CI:让机器替你把关
昨天(Day 51)你会算一整套指标了。但每次改动都靠"我记得手动跑一遍"太不靠谱——总有忘的一天,而那天往往就翻车。今天把评测自动化、制度化:攒一套稳定的 golden set 测试集,做回归测试保证老功能不被改坏,再接进 CI——指标不达标就自动拦住合并。这是阶段 9 的收官,也是"业余"和"专业"的分水岭。明天(Day 53)进入可信可观测阶段,学防幻觉的 Critic。
golden set:你的"标准菜样"档案
# golden set 常存成一个 jsonl / yaml 文件,随代码一起进 Git。这里用 Python 列表示意
GOLDEN = [
{"id": "refund_time", "q": "退款要多久到账",
"must_include": ["3-5 个工作日"], "note": "高频问题,必答对"},
{"id": "invoice", "q": "能开发票吗",
"must_include": ["可以", "发票"], "note": "别漏"},
{"id": "no_answer", "q": "你们卖不卖火箭",
"must_include": ["抱歉", "无法"], "note": "答不了要老实说,不能瞎编"},
]
# 每条都有稳定 id(方便追踪某题历史表现)+ 验收标准 + 备注(为什么收录)
no_answer 这条:问"你们卖不卖火箭",验收标准是回答里要出现"抱歉/无法"这类词。· 若 Agent 老实答"抱歉,我们不卖火箭" → 命中标准 ✅。
· 若它瞎编"卖,30 万一枚" → 不含验收词,判失败 ❌。这条题专门守着"答不了要老实说、不许编"这条底线——它就是你把某个线上教训"钉死"成的一颗钉子。
怎么攒出一套好测试集
👶 小白:多少条题才够?我一个人怎么攒得动?
👨🏫 老师:起步 30-50 条就非常有用了,别被"要几千条"吓住。关键做法:每次线上发现一个 bug,就把它变成一条测试题加进 golden set——这叫"把事故钉死",保证同一个坑不会踩第二次。测试集就这样随项目一天天长大。这也是面试加分点:你能说"我们的测试集是随线上问题持续沉淀的",一听就是干过实事的人。
回归测试:别把老菜做难吃了
CI 是什么:出餐口的质检员
接进 CI:不达标就拦住合并
.github/workflows/ 下),告诉机器人:"每次 PR,就装环境→跑我的评测脚本→脚本报错(exit code 非 0)就判失败"。评测脚本里,只要指标低于阈值就主动报错退出,CI 就会亮红灯拦住合并。# eval_gate.py —— 评测脚本:跑 golden set,不达标就"报错退出"让 CI 变红
import sys
score = run_eval(GOLDEN) # 假设返回完成率,比如 0.86
THRESHOLD = 0.90 # 及格线(质检员心里那条线)
print(f"完成率 {score:.0%},阈值 {THRESHOLD:.0%}")
if score < THRESHOLD:
print("❌ 低于阈值,拦住合并")
sys.exit(1) # 关键:退出码非 0 = 失败,CI 就报红
print("✅ 达标,放行")
sys.exit(0) # 退出码 0 = 成功
# .github/workflows/eval.yml —— 告诉 GitHub 每次 PR 自动跑上面的脚本
name: agent-eval
on: [pull_request] # 触发时机:有人发起 PR
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # 拉下代码
- uses: actions/setup-python@v5 # 装 Python
with: { python-version: "3.11" }
- run: pip install -r requirements.txt
- run: python eval_gate.py # 跑评测;它 exit 1 时本步失败 → PR 被拦
sys.exit(1) 表示"不达标",CI 看到非 0 退出码就判失败、亮红灯、锁住合并按钮。整套评测就此从"自觉行为"变成"制度强制"。阈值怎么定:太松没用,太严天天误拦
| 定阈值的方式 | 怎么理解 | 适用 |
|---|---|---|
| 绝对线:完成率 ≥ 90% | 低于这条就是不合格 | 核心质量红线 |
| 相对线:不比基线掉 > 2% | 允许微小波动,防误拦 | 会天然抖动的指标 |
| 分级:关键题一条都不能错 | 高危题设"零容忍"子集 | 安全/合规问题 |
| 多指标同时卡 | 完成率↑但成本翻倍也拦 | 防"拆东墙补西墙" |
👶 小白:LLM 输出本来就随机,同一份代码今天 90% 明天 89%,这不老误拦吗?
👨🏫 老师:好问题,这是 Agent 评测和传统单测最大的不同——指标天生会抖。三个应对招:① 判分尽量用 temperature=0 降随机(Day 50);② 用"相对基线容差"而不是死绝对值;③ 把最不能错的少数题单独拎成"零容忍子集",其余用容差。这样既拦得住真退步,又不会天天误报把团队逼疯。能讲清这一点,面试官会觉得你真的落地过评测。
想看真实工程里的评测闸门?去深潜
今日小结 + 动手 10 分钟
🧠 今天你应该能回答
- golden set 是什么?为什么它要"固定、进 Git、当资产维护"?
- 好测试集从哪四类来源攒?为什么"质量 > 数量"?
- 什么是"回归"?回归测试解决什么?判断能否合并该看什么?
- CI 是什么?它靠什么机制(退出码)拦住不达标的合并?
- 阈值为什么不能定死 100%?相对基线容差和零容忍子集各治什么?
✋ 动手 10 分钟:搭一个会"拦"的迷你闸门
把 L05 的 eval_gate.py 落地成能跑的版本(先用假 run_eval 演示"拦/放"两种结局):
import sys, random
GOLDEN = ["refund_time", "invoice", "no_answer"] # 你的 golden set(示意)
def run_eval(golden):
# 真实里这里跑 Agent + 判分;这里随机模拟一个完成率,好体验"拦/放"
return random.choice([0.86, 0.93])
score, THRESHOLD = run_eval(GOLDEN), 0.90
print(f"完成率 {score:.0%},阈值 {THRESHOLD:.0%}")
sys.exit(0 if score >= THRESHOLD else 1) # 达标 exit 0,否则 exit 1
python eval_gate.py; echo "退出码=$?" # 多跑几次:看 0.86 那次 退出码=1(会被 CI 拦)
观察 $?(上一条命令的退出码):0 表示放行、1 表示拦下。这正是 CI 判断"该不该拦你合并"的唯一依据。进阶:把 L05 的 eval.yml 放进你 Day 06 建的 GitHub 仓库 .github/workflows/,发个 PR,亲眼看它自动跑起来。