Day 52 / 共 68 天 · 阶段 9 评测(最重要)

测试集 & 回归 & CI:让机器替你把关

昨天(Day 51)你会算一整套指标了。但每次改动都靠"我记得手动跑一遍"太不靠谱——总有忘的一天,而那天往往就翻车。今天把评测自动化、制度化:攒一套稳定的 golden set 测试集,做回归测试保证老功能不被改坏,再接进 CI——指标不达标就自动拦住合并。这是阶段 9 的收官,也是"业余"和"专业"的分水岭。明天(Day 53)进入可信可观测阶段,学防幻觉的 Critic。

📍 你在阶段 9(评测 D49-52)的位置
D49 为什么评测最重要 D50 LLM-as-Judge D51 指标体系 D52 测试集&回归&CI
💡 用一个类比兜住今天(今天全程沿用「餐厅后厨」的世界观) 把 Agent 项目当一家餐厅后厨golden set 测试集=一份"标准菜样"档案,招牌菜该是什么味道全记在案;回归测试=每次改配方,先照着标准菜样把老菜重做一遍,确认没把招牌菜做难吃;CI=出餐口那位质检员,每一盘菜端出去前都尝一口,不合格当场拦下不许上桌;阈值=质检员心里那条"及格线"。今天你从"厨师全凭手感"升级到"后厨有质检制度"。
L01

golden set:你的"标准菜样"档案

🤔 痛点Day 49 说过"每次跑同一套题分数才可比"。可这套题从哪来?随手编几个、每次还不一样,那分数还是没法比。
💡 本质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(方便追踪某题历史表现)+ 验收标准 + 备注(为什么收录)
📝 举个例子:一条 golden case 怎么"验收"no_answer 这条:问"你们卖不卖火箭",验收标准是回答里要出现"抱歉/无法"这类词。
· 若 Agent 老实答"抱歉,我们不卖火箭" → 命中标准 ✅。
· 若它瞎编"卖,30 万一枚" → 不含验收词,判失败 ❌。这条题专门守着"答不了要老实说、不许编"这条底线——它就是你把某个线上教训"钉死"成的一颗钉子。
👶 "golden" 是什么意思?就是"黄金标准"——这套题和它的标准答案是可信赖的基准,像标尺一样。之所以叫黄金,是因为它珍贵且要长期维护:题目慢慢积累、答案人工确认过、轻易不改动。它是你整个评测体系的地基,地基稳,上面的分数才有意义。
L02

怎么攒出一套好测试集

🤔 痛点凭空想 100 道题,又累又不像真实用户会问的。攒出来的题库可能全是"你好""谢谢"这种没营养的,测不出真问题。
💡 本质好测试集不是"想"出来的,是从真实场景"捞"出来的:真实用户问过的、线上出过错的、你最怕它答错的,都收进来。像后厨的标准菜样,来自"顾客最常点的"和"以前被投诉过的",而不是厨师拍脑袋。
好测试集的四个来源 真实高频 用户最常问 的那些问题 线上事故 出过错的 case 收进来当"钉子" 边界/刁难 答不了的、 易诱导瞎编的 分层覆盖 各类功能都有 别偏科
图注:宁可 50 条精挑细选、覆盖真实与边界,也别要 500 条注水题。质量 > 数量。

👶 小白:多少条题才够?我一个人怎么攒得动?

👨‍🏫 老师:起步 30-50 条就非常有用了,别被"要几千条"吓住。关键做法:每次线上发现一个 bug,就把它变成一条测试题加进 golden set——这叫"把事故钉死",保证同一个坑不会踩第二次。测试集就这样随项目一天天长大。这也是面试加分点:你能说"我们的测试集是随线上问题持续沉淀的",一听就是干过实事的人。

L03

回归测试:别把老菜做难吃了

🤔 痛点你为了修 A 问题改了 prompt,A 好了——但你没发现,这一改把本来好好的 B、C 问题弄坏了。上线才被用户发现,尴尬又危险。
💡 本质"改一处、坏别处"叫回归(regression)——退步了。回归测试=每次改动都把整套 golden set 重跑一遍,确认老功能没被改坏。像改了糖醋里脊的配方后,把菜单上的招牌菜全部照标准菜样重做一遍尝一尝。
📝 举个例子:回归测试救场 你改 prompt 让它"回答更简短",refund_time 这题确实简洁了。但重跑 golden set 发现 no_answer 那题炸了——它现在对"卖不卖火箭"直接简短地瞎编"可以,30 万一枚"。回归测试当场逮住这个新坑,你在上线前就修掉了。没有它,这个编造问题要等用户投诉才暴露。
记住这条铁律:修一个问题时,最容易顺手弄坏两个。所以判断"改动是否可以合并",永远不是看"目标问题修好没",而是看"整套 golden set 有没有整体退步"。
L04

CI 是什么:出餐口的质检员

🤔 痛点回归测试要靠人记得跑。但人会忘、会偷懒、会想"就改一行应该没事"——而"应该没事"正是事故高发词。
💡 本质CI(持续集成,Continuous Integration) = 一个自动机器人:你每次把代码推上去、或发起合并请求(PR),它就自动帮你把测试全跑一遍,不合格就报红、拦住。像出餐口那位质检员——每盘菜必尝,不合格不许上桌,且从不"今天心情好放你过"。
CI:推代码 → 自动跑评测 → 拦或放 ① 你推代码 发起 PR ② CI 自动 跑 golden set ③ 对比阈值 达标? ✅ 放行 ❌ 拦下
图注:CI 把"人要记得跑测试"变成"机器强制跑测试"。最常见的 CI 平台是 GitHub Actions(回忆 Day 06 你已把项目推上 GitHub)。
👶 CI 和普通单元测试一样吗?机制一样(都是自动跑测试),但内容不同。普通单测判"函数返回对不对"(确定的对错);Agent 的 CI 跑的是"评测"——完成率/延迟/成本这些会波动的指标,所以不是判"全对",而是判"有没有低于及格线(阈值)"。这个区别下一讲展开。
L05

接进 CI:不达标就拦住合并

🤔 痛点怎么让 GitHub 在别人(或你自己)提 PR 时,自动跑评测、不达标就把合并按钮变灰?
💡 本质写一个 CI 配置文件(GitHub Actions 放在 .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 退出码就判失败、亮红灯、锁住合并按钮。整套评测就此从"自觉行为"变成"制度强制"。
L06

阈值怎么定:太松没用,太严天天误拦

🤔 痛点阈值定 100% 吧,结果指标本来就会小幅波动,每次都被拦,大家烦到直接把 CI 关了。定 50% 吧,等于没拦。到底怎么定?
💡 本质阈值要贴着基线定,还要留一点"波动容差"。常用做法不是定死一个绝对分,而是"不能比基线掉超过 X"——像质检员不要求每盘菜一模一样,但不许比标准菜样明显难吃。
定阈值的方式怎么理解适用
绝对线:完成率 ≥ 90%低于这条就是不合格核心质量红线
相对线:不比基线掉 > 2%允许微小波动,防误拦会天然抖动的指标
分级:关键题一条都不能错高危题设"零容忍"子集安全/合规问题
多指标同时卡完成率↑但成本翻倍也拦防"拆东墙补西墙"

👶 小白:LLM 输出本来就随机,同一份代码今天 90% 明天 89%,这不老误拦吗?

👨‍🏫 老师:好问题,这是 Agent 评测和传统单测最大的不同——指标天生会抖。三个应对招:① 判分尽量用 temperature=0 降随机(Day 50);② 用"相对基线容差"而不是死绝对值;③ 把最不能错的少数题单独拎成"零容忍子集",其余用容差。这样既拦得住真退步,又不会天天误报把团队逼疯。能讲清这一点,面试官会觉得你真的落地过评测。

L07

想看真实工程里的评测闸门?去深潜

🤔 痛点今天讲的是评测+CI 的"骨架"。真实生产项目里,评测、闸门、Critic、护栏是怎么系统地拼在一起,形成一套"可信工程"的?
💡 本质评测只是"可信"的入口。真正上生产的系统,会把评测、失败闸门、防幻觉审查、可观测串成一整套工程体系。gov-agents 就是一套以"可信/工程化"为核心的多 Agent 框架,把这些做成了实打实的代码。
🔗 想深入?去看《gov-agents 21 天精讲》→
建议看它的"评测与质量闸门""失败处理"相关章节,对照今天学的 golden set / 回归 / CI 阈值,看工业级实现多了哪些考量。看完记得回来继续 Day 53——我们要把"防幻觉"这个最硬核的可信话题单独讲透。
L08

今日小结 + 动手 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,亲眼看它自动跑起来。

明日预告 · Day 53:评测能"事后发现"错误,但有些错误(尤其是一本正经地编造)最好在输出前就拦住。明天进入阶段 10 可信可观测,学防幻觉 Critic:用双层校验、逐条核对证据 ID,专门拦住模型的编造——让你的 Agent 不敢"张口就来"。
← Day 51 · 指标体系 Day 53 · 防幻觉 Critic →