返回博客列表

改了 Prompt 或换模型,怎么证明 Agent 没坏?OpenRouter 回归测试指南翻译

2026-09-30T23:30:00+08:00
OpenRouterAgent评测回归测试EvalPrompt模型切换CI/CD

改了 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 里安全地换模型。老规矩,忠实翻译全文结构,文末附我的解读。

本文提纲

  1. 核心摘要(Tl;dr)
  2. Agent 回归测试与代码回归测试的三大差异
  3. 三类需要回归测试的变更
  4. 构建锁定的 case 集与行为契约
  5. 变更发生时怎么跑套件
  6. 跨模型切换测试:13 倍价差的实测
  7. 区分回归与噪音
  8. 翻译后的几点解读

核心摘要

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 写契约的两部分:

  1. 结构化断言:agent 应该做什么——比如先调用 lookup_order 再行动;例行退款不要碰 escalate_to_human。
  2. 硬性不变量: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》,值得连读。

参考链接

你上一次改 prompt 或换模型,是怎么验证 Agent 没坏的?靠手测还是靠回归集?评论区聊聊你的做法,觉得有用点个赞让更多做 Agent 的人看到。


作者: itech001 来源: 公众号:AI人工智能时代(the-ai-era) 网站: https://www.theaiera.top/ 关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top

本文首发于 AI人工智能时代,转载请注明出处。

分享给朋友