改坏了,在合并前就被拦住------AI 回归门禁实战(E04)
系列《AI 应用生产化手册》第 4 篇(共 30 篇)|配套开源项目:github.com/ChenYingbo/...
一、先看问题:"手动跑"= 靠自觉
E03 有了自动化评测,但马上遇到第 4 个问题:
- "手动跑"= 靠自觉------今天记得跑,明天忙起来就"先上线再说"。
- 改坏是悄悄发生的------一次"优化提示词"的 PR,评测通过率从 85% 掉到 60%,没人发现,直到用户投诉。
- 代码测试和 AI 质量是两套体系 ------
pytest拦代码错误,但拦不住"模型输出质量退化"。
解法:把评测变成"门禁"(Gate)------不通过就不让合并/上线。 让机器强制执行,而不是靠人的自觉。
核心认知:门禁的价值不在"拦住坏改动",而在"让每次改动都有质量证据"。
二、原理:三层防线 + 阈值设计
门禁放在哪?合并前,不是上线前
css
开发 → PR → CI(代码测试 + 评测门禁)→ 合并 main → 灰度上线
│
▼
失败:打回 + 看报告(失败集中在哪类)
上线前发现等于返工,合并前发现只是改一版。
防退化三层防线(缺一不可)
| 层 | 机制 | 拦什么 |
|---|---|---|
| 代码层 | pytest | 接口挂了、参数校验坏了 |
| 结构层 | --dry-run(评测集校验) |
评测集被改坏、缺字段 |
| 质量层 | 黄金集回归(--exit-on-fail) |
模型输出质量退化(核心) |
很多团队只做代码层,等于"代码没坏但 AI 变傻了"照样上线。
阈值怎么定(最容易做错的地方)
markdown
错误做法:拍脑袋定 0.9
正确做法:先跑基线 → 取「中位数 - 容差」
例:连续 5 次跑基线 0.83/0.85/0.84/0.86/0.84
→ 基线 ≈ 0.84,阈值定 0.80(留 4-5 个点抖动余量)
阈值的两个坑:定太高 → judge 抖动导致 flaky 门禁,团队学会"跳过 CI",门禁死亡;定太低 → 形同虚设。
flaky 治理(judge 有方差,门禁会"时而绿时而红")
| 手段 | 做法 | 代价 |
|---|---|---|
| 阈值留余量 | 基线中位数 - 5% 容差 | 漏掉小退化 |
| 固定 seed / 温度 0 | judge 更稳定 | 治标 |
| 双判分 | 两个模型判分一致才通过 | 成本 ×2 |
| 失败重跑 | 连续两次失败才判红 | 时间 ×2 |
| 分层门禁 | PR 只跑单元层;场景层留合并后 | 复杂度 |
先做"阈值留余量 + 分层",flaky 严重再上双判分。 误报率高的门禁会被团队用脚投票。
触发策略(省钱关键)
真实评测烧 token,不是每次 PR 都值得跑全量:
| 触发 | 跑什么 | 为什么 |
|---|---|---|
| 每次 PR | 代码测试 + dry-run | 快、免费、拦结构错误 |
| 合并 main | 黄金集全量回归 | 真正的质量门禁 |
| 改提示词/换模型 | 手动触发全量 | 有针对性 |
| 定时(每周) | 演进集 + 在线采样 | 防静默退化 |
三、动手:搭三层门禁
bash
git clone https://github.com/ChenYingbo/ai-prod-demo.git && cd ai-prod-demo
source .venv/bin/activate && pip install -r requirements-dev.txt
# 1. 代码层:pytest
pytest -q tests/ # 预期:125 passed
# 2. 结构层:评测集校验(无需 Key)
python -m evals.run_evals --dry-run
# 3. 质量层:黄金集回归(需要 API Key + demo 运行中)
uvicorn app.main:app --port 8000 # 另开终端
python -m evals.run_evals --metric correctness --exit-on-fail 0.8
# 通过率 ≥ 0.8 → 退出码 0;否则退出码 1(门禁拦截)
必做实验:复现"门禁拦住退化"
- 先跑一次基线,记录通过率
- 把提示词改坏(提示词仓库
prompts/chat-system/的模板改成"你只会说'我不知道'的助手") - 重跑门禁------通过率暴跌,
--exit-on-fail让命令失败(退出码 1) - 恢复提示词,重跑确认恢复
- 体会:这就是"改坏了在合并前被拦住",而不是上线后用户发现
四、真实踩坑(含"门禁差点误杀"案例)
- 阈值拍脑袋:先跑 5 次基线,用"中位数 - 容差"定,不要直接写 0.9
- 只做代码层门禁:pytest 过了就放行------AI 质量退化照样漏
- 黄金集被偷偷改:有人为了过门禁往里加简单题------黄金集要"锁死",改它走评审
- 门禁误报率高:judge 抖动 + 阈值贴太近 → 团队跳过 CI,门禁死亡
- CI 跑全量烧钱:PR 别跑全量,合并 main 再跑------触发器是成本设计
- PR 上跑 evals-gate 会失败 :GitHub 不给 PR 的 secrets------工作流用
if限制只在 push main / 手动触发跑 - (实测)门禁拦住的可能是"评测设计缺陷",不是模型退化 :真实跑分 23/30 = 0.77,低于 0.8 门禁 FAIL------查下来是判分器把"安全拒答"误判成"没回答";给边界 case 加规则判分后 29/30 = 0.97 通过。门禁红了先别急着改模型,先看是不是评测本身错了
五、小结
- 三层防线:代码(pytest)/ 结构(dry-run)/ 质量(黄金集回归)
- 阈值 = 基线 + 容差,不拍脑袋;flaky 先做"留余量 + 分层"
- 门禁红了,先查评测设计------0.77 → 0.97 的真实案例说明一切
明天(E05):《评测体系案例》------把 E01-E04 串成一套可交付的评测体系(评测集 + 执行器 + 门禁 + 报告模板)。 收藏 + 关注,每天一篇,30 天把 AI 应用送上生产。