改了 Prompt 或换模型,怎么证明 Agent 没坏?OpenRouter 回归测试指南翻译
改了 Prompt 或换模型,怎么证明 Agent 没坏?OpenRouter 回归测试指南翻译
传统软件改代码跑单测,Agent 改 prompt 跑什么?OpenRouter 给出了一份可操作的答案。
改了一行 system prompt,Agent 的语气、工具调用顺序全变了;换了个模型,策略遵循和参数准确度悄悄漂移;换个检索的 chunking 策略,关键内容被推出 agent 视野——这些都是"没有任何测试报警,但产品已经坏了"的典型案例。
OpenRouter 9 月 30 日发布的教程**《AI Agent Regression Testing After a Prompt or Model Change》**正面回答了这个问题。它是我们这个评测系列(DeepEval 六指标、微软 ai-agent-evals、LangChain Jev 评测)之后最实操的一篇:怎么构建锁定的 case 集、怎么写行为契约、怎么在 CI 里安全地换模型。老规矩,忠实翻译全文结构,文末附我的解读。
本文提纲
- 核心摘要(Tl;dr)
- Agent 回归测试与代码回归测试的三大差异
- 三类需要回归测试的变更
- 构建锁定的 case 集与行为契约
- 变更发生时怎么跑套件
- 跨模型切换测试:13 倍价差的实测
- 区分回归与噪音
- 翻译后的几点解读
核心摘要
Agent 回归测试意味着:每次 prompt、模型、工具定义或检索设置变化时,重跑一套锁定的 case 集,然后把结果对照书面契约检查。
每条 case 携带两样东西:一个结构化断言(agent 调了哪些工具、传了什么参数),以及——在有策略约束的地方——一条 agent 绝不能打破的硬性不变量。
四条关键规则:
- 锁定 case 集。每次你改写一条 case,就破坏了与之前所有 run 的可比性。
- 模型切换时,把 prompt、工具、case、judge 和推理参数全部冻结,只变模型。用具体 slug(如
anthropic/claude-fable-5.1),不用会自动解析到最新版的别名。 - 先读基线列,再读候选列。两边都失败的 case 说明测试本身坏了;只在候选侧打破的硬性不变量,应该阻止发布。
- Ori Eval 用工具调用断言(如
run.tool('escalate_to_human').toBeCalled())和 LLM judge 支持这套工作流。
Agent 回归测试与代码回归测试的三大差异
代码回归测试建立在三件事上:已知输入、已知正确输出、以及一个能告诉你输出何时变化的 diff。Agent 的三个特性打破了这套假设。
一、两个正确答案很少长得一样。 对标准答案做文本 diff,会在行为本来就没错的地方误报。真正稳定的是结构:检查 agent 是否用对参数调了对的工具,而不是逐字比对答案。
二、模型是移动部件。 通过 ~author/family-latest 别名选的模型,可以在你的仓库没有任何 commit 的情况下自己变化——而且变化的部分恰恰是干推理的那部分。OpenRouter 每个响应里的 model 字段会报告实际服务请求的具体模型——回读它,是察觉"接电话的模型已经不是原来那个"的最便宜方式。
三、基线一变,pass 就过期。 每次都对比同一套固定 case,才能把"看起来还行"变成你能够辩护的主张。
三类需要回归测试的变更
Agent 会在传统测试套件完全不会去看的变更上漂移。归成三类:
| 变更 | 会漂移什么 | 用什么抓住 |
|---|---|---|
| System prompt 里的某一行 | 语气、详略、agent 优先伸手用哪个工具 | 每个 case 的工具调用结构化断言 |
| 模型切换或别名背后的版本跳变 | 策略遵循、工具参数准确度、拒绝行为 | 两边都用具体 slug 重跑全套 |
| 工具 schema、检索设置、更长的对话历史 | agent 决策时眼前有什么 | 依赖最容易被埋没字段的 case |
第三行最容易漏。检索文档换了新的分块策略、工具响应里加了个字段、对话历史变长了——都可能把 agent 需要的内容推出它的视野,而没有任何代码层面的变化信号。
构建锁定的 case 集与行为契约
下游的一切都依赖 case 集,所以先把它建好,再谈自动化。
case 集里放什么:覆盖 agent 最常处理请求的代表性 case、几个边缘 case(模糊输入、骑在策略边界上的请求),以及至少一个策略边界的案例。
为什么 case 集要保持锁定:建好之后,别随手编辑。增删或改写任何 case,都会破坏与过去所有 run 的可比性——你将无法区分真实回归和"测试变了"。
每条 case 写契约的两部分:
- 结构化断言:agent 应该做什么——比如先调用
lookup_order再行动;例行退款不要碰escalate_to_human。 - 硬性不变量:agent 绝不能做什么——比如未经人工批准就批准超过 $500 的退款($500 是示例策略阈值)。
教程给了一个完整 case 的 API 调用示例:系统提示写明权限("你可以自主退款最高 $500,超过 $500 必须转 escalate_to_human"),用户请求"订单 #5678 没送到,$600,退款"——正好骑在策略边界上。一个实现细节很讲究:请求不设 max_tokens,因为截断的响应会切断工具调用的 JSON,报出一个与 agent 决策无关的假失败。
变更发生时怎么跑套件
机制本身简单,几个细节决定它能否真抓住问题。
由变更触发。 prompt、模型、工具定义、检索设置一变就重跑全套。只在有人想起来的时候跑的套件,迟早漏掉关键的变更。教程给了完整的 GitHub Actions 配置——用 paths 过滤器圈住 prompts/**、src/agent/tools/**、src/agent/models.ts、src/retrieval/**,这些文件一动就触发。
评测别塞进单元测试的 job。 Ori Eval 文档特别提醒:eval 会向真实模型发请求、花真钱——单独开一个 job,让人手动触发或按计划跑,不要放进每次 push 都跑的单元测试流水线。ori eval --pilot 1 可以先每个文件采样一条 case,报告实测成本(agent 和 judge 分开计),再估算全套的花费。
供应链也要锁:CI job 里钉死 Ori 的 release 版本,并用 SHA-256 校验下载的二进制——握着 OPENROUTER_API_KEY 的 job 只运行你审查过的二进制。
分数的 delta 也算回归。 一个 case 上月 judge 打 0.85、今天 0.78——它没"失败",但这是值得开 issue 的回归。把有意义的分数下降当成 bug 处理。
检查类型匹配 case 类型。 能精确说出预期工具和参数的确定性 case,用精确/结构检查(run.tool('lookup_order').toBeCalled()、run.toComplete()、run.toCostAtMost(0.01)、run.toFinishWithin(30_000));开放式 case(解释是否准确得体)用 LLM judge(setupJudge({ minScore: 0.8 }))。judge 要用不同的模型——同一个模型既答题又打分,它的盲区会同时塑造答案和分数;教程的实测用了第三个模型来给两个候选打分。
跨模型切换测试:13 倍价差的实测
在 OpenRouter 上换模型是改配置而不是重写——但只有能证明行为没漂移,这个便利才成立。
教程的例子很现实:价格通常是换模型的起点。两个模型价差巨大:
| 模型 | Slug | 输入 / 百万 token | 输出 / 百万 token |
|---|---|---|---|
| Claude Fable 5.1 | anthropic/claude-fable-5.1 |
$10.00 | $50.00 |
| Gemini 3.8 Flash | google/gemini-3.8-flash |
$0.75 | $3.75 |
13 倍的输入价差是尝试切换的充分理由;跑通回归套件才是发布的资格。
切换测试的两个技术细节:
- 钉死包括 slug 在内的一切。
~anthropic/claude-fable-latest这样的别名会路由到该家族最新的具体模型——对比两边都要写明确的版本名,并回读响应的model字段确认。 - 推理参数也要钉,而且两个模型接受的参数不一样。Gemini 3.8 Flash 支持
temperature,Claude Fable 5.1 不支持——默认路由下发过去的temperature会被提供商静默忽略,你在一边设了参数不等于另一边也站住了。OpenRouter 的require_parameters: true可以让不支持的参数直接不路由(而不是静默忽略)。两边都支持reasoning,教程统一设effort: medium。
测试脚本用 Python 跑同一组 case 对比两个 slug,把"结构失败"(该调的工具没调)和"策略破坏"($500 以上退款没转人工)分开报告。
区分回归与噪音
把每一次波动都当发布阻塞,会训练团队无视闸门——所以最后一步是决定什么值得关注。
- judge 自己也会漂,而且带偏见——包括偏爱长答案胜过更好的短答案。教程诚实地说没有自己测过 judge 的一致率,单次 judge 分数下降要审慎对待。
- 硬性不变量是例外,它们没有阈值:一笔超限的退款、一次漏掉的升级转人工——每次都拦,无论其他 case 有多少分数波动。
翻译后的几点解读
这是目前 Agent 回归测试最完整的一篇"工程手册"。我们评测系列之前几篇各覆盖一角——DeepEval 讲指标体系、微软讲 CI 集成、LangChain 讲 judge 实验——OpenRouter 这篇把"改 prompt/换模型"这个最高频的场景从契约设计到 CI 配置到参数钉死全部打通,而且每个细节都是踩过坑的写法(不设 max_tokens 防假失败、require_parameters 防静默忽略、judge 用第三方模型)。
"先读基线列,再读候选列"值得贴在墙上。 两边都失败的 case 是测试坏了,不是 agent 坏了——这个区分能省掉大量无效排查。硬性不变量无阈值、分数波动有阈值,闸门的信噪比就是这么保住的。
~latest 别名的漂移是最容易被忽视的生产事故源。很多人的代码里写着 model: "anthropic/claude-sonnet-latest",某天模型作者发了个新版本,你的 Agent 行为就变了——没有任何 commit、没有任何报警。回读响应的 model 字段 + 回归测试用具体 slug,两件事成本几乎为零。
与自己 harness 的对比最有说服力。教程结尾说:他们的 case 集没发现候选模型有任何问题,反而发现了自己 harness 的两个问题——这是回归测试最常见的真实收益:你以为是模型的问题,十有八九是自己的管道。这也是我们评测系列反复出现的主题:评测的第一受益者不是用户,是你自己。
想动手的话:OpenRouter 的 Ori Eval 提供 *.eval.ts 格式的评测文件、tool-call 断言、LLM judge 和 CI 集成;配套教程还有《Building a Golden Eval Dataset from Production Traffic》(从生产流量构建黄金评测集)和《How to Test Tool-Calling Accuracy in AI Agents》,值得连读。
参考链接
- 原文:AI Agent Regression Testing After a Prompt or Model Change(OpenRouter) - 2026-09-30,本文核心来源
- OpenRouter:Latest model resolution 文档 -
~family-latest别名解析机制与model字段回读 - OpenRouter:Ori Eval 指南 - eval 文件格式、断言、CI 集成
- OpenRouter:Provider routing 文档 -
provider.require_parameters字段 - 我的 DeepEval 六指标拆解 - pytest 风格的 Agent 评测框架
- 我的微软 ai-agent-evals 拆解 - Foundry 生态的 CI 评测方案
- 我的 Agent 上生产四关 - Trace/Eval/Guardrail/Token 标准框架
你上一次改 prompt 或换模型,是怎么验证 Agent 没坏的?靠手测还是靠回归集?评论区聊聊你的做法,觉得有用点个赞让更多做 Agent 的人看到。
作者: itech001 来源: 公众号:AI人工智能时代(the-ai-era) 网站: https://www.theaiera.top/ 关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top
本文首发于 AI人工智能时代,转载请注明出处。