Golden Dataset:让Agent回归测试成为CI门禁
开篇:改了一行 prompt,全线翻车
我改过一行提示词:把「查报价」的指令从"再核对一次收件人"改成"直接发送"。理由很正当——少一步校验,快 200 毫秒。当天晚上,客户 003 收到了 007 的报价单。
最终答案全绿,过程全错。 上一篇《LLM-as-Judge:怎么校准你的裁判》讲的是:判断对错的裁判本身必须被校准,校准靠 20 条 golden set。这一篇回答那篇留下的问题:校准过的裁判,要天天上班,还要按预算出庭——20 条样本只够校准,不够当门禁。Agent 改一行 prompt 就可能全崩,没有回归测试的 Agent 就是走钢丝。解法四个字:门禁化。
一、为什么 Agent 特别需要回归测试
传统软件改代码,有编译期、单元测试、集成测试层层兜底。Agent 没有这种待遇——prompt 就是 Agent 的行为代码,改一行,影响的不是一行逻辑,是整条轨迹:
| 你改了什么 | 可能崩在哪 |
|---|---|
| 加一句「尽量精简」 | 工具调用参数被吞,下游全错 |
| 换一个模型版本 | 输出风格变了,裁判集体翻案 |
| 调一个 system 约束 | 白名单失效,越权访问 |
| 改一个工具描述 | Agent 开始用错工具 |
更麻烦的是:Agent 的输出有不确定性。同一个输入,两次运行轨迹可能不同,传统「断言输出等于期望」的单元测试根本不适用。这就是为什么我们需要 golden dataset——不是断言,是行为基线:把「过去犯过的错」固化成「未来的防线」。
二、Golden Dataset 三要素
上一篇 F1《别只测答案——轨迹评估才是 Agent 测试的真相》讲过:只测答案对 Agent 是盲的,发错人的那次,答案也是「已发送」。所以 golden set 的每一条样本必须含三个要素:

- 输入(input):一条真实的触发场景,比如「查客户 007 的报价历史并发送」
- 期望轨迹(expected trajectory):应该调哪些工具、按什么顺序——
search_contact → get_quote_history → send_email,多一步、少一步、跳步都算错 - 期望输出(expected output):答案必须覆盖的关键事实点,比如「收件人=007」「内容=007的报价」
三要素缺一不可:只测输出,轨迹错了(不该发送却发送了)你发现不了;只测轨迹,事实点错了(收件人串位)你也发现不了。两者合起来,才是完整的防线。
三、保姆级:把 golden set 变成 CI 门禁
下面是我系统里实际跑的回归门禁(简化版,保存成 golden_regression.py 直接能跑,全程确定性,不需要 API Key):
# golden_regression.py — Golden Dataset 回归门禁演示
# 三要素:input / expected_trajectory / expected_facts
# 门禁:通过率 < 90% 直接 BLOCK(阻塞合并/发布)
GOLDEN = [
# input, 期望轨迹(工具序列), 期望事实点(输出必须覆盖)
{"input": "查客户 007 的报价历史并发送",
"trajectory": ["search_contact", "get_quote_history", "send_email"],
"facts": ["收件人=007", "内容=007的报价"]},
{"input": "查客户 003 的报价历史并发送",
"trajectory": ["search_contact", "get_quote_history", "send_email"],
"facts": ["收件人=003", "内容=003的报价"]},
{"input": "只查客户 012 的报价历史,不要发送",
"trajectory": ["search_contact", "get_quote_history"],
"facts": ["无发送动作"]},
{"input": "给客户 007 发一封问候邮件",
"trajectory": ["search_contact", "send_email"],
"facts": ["收件人=007", "内容=问候"]},
{"input": "查客户 019 的联系方式",
"trajectory": ["search_contact"],
"facts": ["联系方式已返回"]},
{"input": "查客户 007 的报价历史并按模板发送",
"trajectory": ["search_contact", "get_quote_history", "send_email"],
"facts": ["收件人=007", "内容=007的报价", "模板=标准报价"]},
]
def run_agent_v1(input_text):
"""旧版 Agent:行为正确"""
cid = input_text.split("客户 ")[1][:3]
traj = ["search_contact"]
if "报价历史" in input_text:
traj.append("get_quote_history")
if ("发送" in input_text or "发一封" in input_text) and "不要发送" not in input_text:
traj.append("send_email")
if traj[-1] == "send_email":
if "问候" in input_text:
out = f"收件人={cid},内容=问候,已发送"
elif "模板" in input_text:
out = f"收件人={cid},内容={cid}的报价,模板=标准报价,已发送"
else:
out = f"收件人={cid},内容={cid}的报价,已发送"
else:
out = "无发送动作,联系方式已返回"
return traj, out
def run_agent_v2(input_text):
"""新版 Agent:改了一行逻辑 —— 发送分支不再核对「不要发送」,收件人写死 007"""
cid = input_text.split("客户 ")[1][:3]
traj = ["search_contact"]
if "报价历史" in input_text:
traj.append("get_quote_history")
# 改动点 1:无条件追加发送动作(忽略「不要发送」)
traj.append("send_email")
# 改动点 2:收件人写死 007(串位 bug)
if "问候" in input_text:
out = f"收件人=007,内容=问候,已发送"
elif "模板" in input_text:
out = f"收件人=007,内容={cid}的报价,模板=标准报价,已发送"
else:
out = f"收件人=007,内容={cid}的报价,已发送"
return traj, out
def evaluate(item, traj, out):
"""三要素打分:轨迹序列必须一致 + 输出事实点必须全覆盖"""
if traj != item["trajectory"]:
return False, f"轨迹不符: 期望{item['trajectory']} 实际{traj}"
missing = [f for f in item["facts"] if f not in out]
if missing:
return False, f"输出缺失事实点: {missing}"
return True, "通过"
def regression(agent, threshold=0.90):
passed = 0
fails = []
for i, item in enumerate(GOLDEN, 1):
traj, out = agent(item["input"])
ok, msg = evaluate(item, traj, out)
if ok:
passed += 1
else:
fails.append((i, msg))
rate = passed / len(GOLDEN)
return passed, rate, fails
def gate(name, passed, rate, fails, threshold=0.90):
print(f"\n=== {name} ===")
print(f"样本: {len(GOLDEN)} | 通过: {passed} | 通过率: {rate:.0%}")
if rate >= threshold:
print(f"✅ PASS: 通过率 {rate:.0%} >= {threshold:.0%},允许合并/发布")
else:
print(f"❌ BLOCK: 通过率 {rate:.0%} < {threshold:.0%},禁止合并/发布")
for i, msg in fails:
print(f" 样本#{i}: {msg}")
if __name__ == "__main__":
p1, r1, f1 = regression(run_agent_v1)
gate("v1(旧版 Agent)", p1, r1, f1)
p2, r2, f2 = regression(run_agent_v2)
gate("v2(改了一行逻辑后)", p2, r2, f2)
运行:
python3 golden_regression.py
这是我的真实运行输出:
=== v1(旧版 Agent) ===
样本: 6 | 通过: 6 | 通过率: 100%
✅ PASS: 通过率 100% >= 90%,允许合并/发布
=== v2(改了一行逻辑后) ===
样本: 6 | 通过: 3 | 通过率: 50%
❌ BLOCK: 通过率 50% < 90%,禁止合并/发布
样本#2: 输出缺失事实点: ['收件人=003']
样本#3: 轨迹不符: 期望['search_contact', 'get_quote_history'] 实际['search_contact', 'get_quote_history', 'send_email']
样本#5: 轨迹不符: 期望['search_contact'] 实际['search_contact', 'send_email']

三个失败样本,正好暴露两类错误,一一对应三要素:
- 样本#2:输出错——收件人串位成 007,期望事实点「收件人=003」缺失。输出级断言抓不到(它以为发送成功),事实点比对抓到了。
- 样本#3、#5:轨迹错——明明说了「不要发送」「只查联系方式」,Agent 却追加了发送动作。轨迹序列比对抓到了。
这就是门禁的威力:v2 这行改动,门禁前没人发现,门禁后 50% 直接打回。 错误在合并前被发现,成本是开发侧改一次;在用户那里被发现,成本是信任清零。
四、我的实战:publish_gate 四道门
脚本单独跑没有意义,要接进发布管道才起作用。我的内容流水线发布前有四道门,回归测试挂在最后一道:

# ci_gate.sh — 把 golden 回归挂进 CI(简化版)
# 任何一步非零退出 → 阻塞合并/发布
python3 validate_article.py check article.md || exit 1
python3 check_series_continuity.py check || exit 1
python3 article_checker.py article.md || exit 1
python3 golden_regression.py --threshold 0.90 || exit 1
echo "✅ 四门全过,允许发布"
这四道门是真实在跑的:validate_article 查格式完整性(字数/配图/禁杜撰),check_series_continuity 查系列预告逐字符匹配,article_checker 做 C1-C8 深度审计,publish_gate 做最终放行。门禁真拦过事——配图引用带空格导致图片上传失败、系列预告标题不精确匹配、文章包含禁用的杜撰内容,这些都在门禁层被拦下过。拦下的那一刻你会感谢门禁:因为它把「发布后才发现」变成了「发布前就发现」。
五、按预算出庭:回归的成本控制
F2 钩子说「校准过的裁判,要天天上班,还要按预算出庭」——回归测试最大的反对声音永远是成本。我的做法是分层:
| 层级 | 样本量 | 跑法 | 成本 |
|---|---|---|---|
| 冒烟集 | 20 条 | 每次提交都跑 | 秒级,几乎免费 |
| 全量集 | 500 条 | 发布前/每日跑 | 分钟级,预算可控 |
| 生产回流 | 持续增长 | 失败样本自动回灌 | 越跑越准,越跑越贵但值得 |
三个省钱原则:① 样本去重——同类型错误只留一条代表样本,不重复造轮子;② 分层抽样——冒烟集覆盖高频路径,全量集覆盖边界场景;③ 失败回灌——生产里新翻车的案例,24 小时内补进 golden set,让它成为下一轮回归的样本。golden set 不是一次性资产,是越用越厚的复利资产——这正是 F2 讲的「golden set 是资产,不是负担」的工程化版本。
进阶思考:回归的本质是把过去的错误变成未来的防线
门禁跑通之后,我重新理解了「回归测试」这件事。
回归测试的本质不是「测试」,是记忆。传统测试验证「现在的代码对不对」,回归测试验证「过去的错误有没有复发」。你的 golden set 每增加一条,就等于系统多了一条「不二过」的记忆。这跟系列三《从SOP到组织免疫力》讲的完全同构:错误 → 入账 → 修复 → 回灌规则——个人层面是 error-ledger,组织层面是 SOP,Agent 层面就是 golden dataset。三层用的是同一个 Loop Engineering 内核。
这还解释了为什么「门禁不是限制,是自由」:知道改了不会崩,才敢频繁改。 没有回归测试的团队不敢动 prompt,因为每改一次都是赌;有 golden 门禁的团队敢每天迭代,因为风险被量化、被拦截在合并之前。真正的工程素养,不是不犯错,是让错误以最低成本发生——在 CI 里发生,而不是在生产里发生。
结尾
今天你学到的是:Agent 改一行 prompt 就可能全崩,golden dataset 三要素(输入、期望轨迹、期望输出)+ 90% 门槛 = CI 门禁。回归测试不是测试,是把过去的错误变成未来的防线——冒烟集天天跑,全量集发布前跑,失败样本回灌复利增长。
行动号召很简单:今晚就给 Agent 建 6 条 golden set——挑 6 个你最常用的场景,写下输入、期望轨迹、期望事实点,跑一遍 golden_regression.py。然后挂上门禁,通过率低于 90% 就阻塞合并。你会第一次看见:改动还没上线,错误就被拦住了。
下一篇,我们把评估体系从「门禁」拉到「生产复盘」——《可观测性三件套的生产复盘——Gate、Audit、Correction 是怎么转起来的》:轨迹留痕、审计账本、纠正闭环,三件套在生产里到底怎么配合,才能让 Agent 越跑越稳。
关于作者:魏无记,AI 和数智化实践者。专注 Agent 工程化与 Loop Engineering 研究以及数智化转型。公众号持续更新 Agent 工程化实战系列和数智化转型相关知识实践——每篇都是保姆级教程照做就行。

浙公网安备 33010602011771号