Agent 产品的“牵一发而动全身”——改动必须配备评测

从 Claude Code 4 月 23 日的问题复盘报告出发,聊聊为什么 Agent 产品里每一处动到模型链路的改动(哪怕只是 prompt 里多加一个“字符”)都应该进行一轮完整的评测。

最近看了 Anthropic 在 4 月 23 日发布的关于 Claude Code 质量问题的复盘报告(原文地址),所以从此出发,聊一下我的一些关于 Agent 方面的经验。

其实把 Claude Code 的这几处改动单独拎出来,是会感觉到每一项都还挺合理的:

  • 默认推理强度从 high 调成 medium,能降成本、降延迟,eval 分数下降也没超出波动范围;
  • 空闲会话恢复后清理旧的 thinking,因为缓存已经失效,全部塞回上下文请求会有一堆没命中缓存的贵 token;
  • 工具调用之间限制输出长度,减少模型啰嗦、加快响应、让界面更干净。

这些改动要是放到普通聊天对话的模型上,大概率只会让用户体验更好,因为响应快了、token 少了、用户少看一堆不关心的中间过程。但对于 Claude Code 这种要读项目、查文件、调工具、改代码、跑验证的 工具型Agent 来说,这些”看起来只是体验优化”的小改动,其实都因为动到了模型完成任务的链路,进而导致在某些边界情况的变化,甚至是劣化。

最终用户的感知也印证了这一点:Claude Code 最近怎么越来越笨了?总是忘记事情、绕路、反复查同一个东西、出现幻觉了!Opus 4.6 是不是降智了?我要转战 Codex!

对此,我想表达一个观点(这其实也是不少做 Agent 的同行在走过坑之后的共识):Agent 产品里,任何会影响模型链路、上下文、推理预算、工具行为的改动,都不应该只靠”感觉问题不大”就上线,而是需要有明确且分层的评测指标兜底。

下面围绕三个层面展开:现象上,模型可能不是变笨了,是手里的线索少了;机制上,一条不起眼的 prompt 约束就能改掉 Agent 的行为边界;应对上,关键链路必须配上与之匹配的评测。

一、模型可能没变笨,是它手里的线索少了

线上一出问题,用户最自然的反馈就是”模型变笨了”。从用户体验上看,确实如此,因为任务没以前稳,回答没以前准,过程没以前顺。但从系统角度看,模型本身未必真的变差,也有可能是因为它拿到的信息变得不够了。

这次 Claude Code 的三项改动表面上是三件不同的事,但联系起来看,可以发现是指向同一件事情的——模型能用的有效上下文变少了:

改动本来想解决什么最后影响到了什么
默认 efforthigh 调成 medium降低延迟和成本复杂任务里的思考余量(用户虽然可以手动调回,但入口不明显,绝大多数人不会意识到要改)
空闲恢复后清理旧 thinking避免缓存失效后重建上下文导致 token 成本飙升多轮任务的连续性:用户目标、限制条件、已排除方向这些前面已经”想过”的东西,下一轮就拿不到了
工具调用之间限制 25 个词减少啰嗦、缩短等待中间状态被压没:前一步为什么选这个工具、下一步要验证什么、当前结果要用来干嘛,这条因果链断了

每一行左边都成立,但 Agent 的坑往往藏在右边。

推理强度调低,对普通问答影响很小;但面对”读项目结构 → 定位调用链 → 改代码 → 跑验证”这类任务,少一点推理余量,就可能变成某一步没想透、某个文件漏查、某个假设没验证完。

thinking 清理也是一样,原本只是”空闲一小时后清一次”,bug 导致它每一轮都继续清,也就是说模型每积累一点判断,下一轮又被抹掉,就像一个人接手了复杂任务,但工作笔记每隔一会儿就被撕掉前面几页,模型本身的能力能记住的时候还好,但记不住,就是总重复做一些已经做过的工作,也就很难完成任务了。

这一点在别的 Agent 上也能看到类似形态。比如 TRAE 的早先版本里,Agent 被限制了上下文长度,模型的思考塞不下被舍弃,然后就导致了下一次请求的时候,模型就只能继续反复补思考,最终的结果就是响应给用户的消息里堆满了”思考”,真正有用的结论和执行反而越来越少、甚至是完全没有。同一个模型,换一个 Harness,表现就是两个样子。

flowchart TD
    C(("模型下一步能用的有效上下文(目标 / 限制 / 已排除方向 / 中间判断)"))

    A["effort 降低"] -. "思考余量变少" .-> C
    B["清理旧 thinking"] -. "历史判断变少" .-> C
    D["限制中间输出"] -. "轮次交接变少" .-> C

    C --> S["普通问答 / 短任务<br/>消耗线索少,影响不明显"]
    C --> H["复杂 / 长任务 / 多轮任务<br/>需要持续复用前面的线索"]
    H --> R["用户看到的是:忘事、重复、绕路、幻觉"]

所以,一些看似合理的小优化,最后都会落到了同一种资源上:可被后续轮次继续使用的线索。简单任务用不到太多线索,甚至会显得更快;复杂任务一旦要连续引用前面的目标、限制和判断,线索少一点,表现就会滚雪球,明显变形。

二、一条 25 词的约束,就能改掉 Agent 的行为边界

如果说上一节讲的是现象,那这次复盘里最值得单拎出来看的,是一条 Prompt 约束:它本身相对于整个 Prompt 来说很小很小,却非常能说明”小改动改行为边界”这件事是怎么发生的。

Opus 新的版本相比之前更啰嗦一些(但这其实是好事,因为模型变得更啰嗦,也就意味着模型更愿意去思考更多的内容,提前了解清楚边界)。为了控制输出 token 和响应时间,cc 在 harness 层加了一条类似这样的约束:

Length limits: keep text between tool calls to ≤25 words.
Keep final responses to ≤100 words unless the task requires more detail.

意图很清楚:让中间过程更短、让最终回答更紧凑,省 token 也省时间。问题在于,Claude Code 的中间输出并不只是写给用户看的过渡语,它同时承担着把任务状态传给下一轮的职责。

举个更能体现 Agent 特征的例子。模型读完一段调用链之后,中间输出原本可能是:

当前栈里这个 handler 只在异步分支被调到,错误只发生在同步路径。下一步应该查同步路径里的参数归一化逻辑,验证是不是在那里丢了字段。

被压到 25 词以内,就只会剩:

继续查同步路径。

对用户来说两句差别不大;对下一轮模型来说差别巨大——前一句里的判断依据和验证假设都没了,动作还在,但”为什么这么做”消失了。下一轮拿不到这些,就只能重新判断一次,重复、绕路、漏验证都从这里开始。

所以这条约束其实一次动了三件事:

  1. 改了输出分布:模型被迫学会”少说”,而它”少说”的方式,通常是先砍掉对下一步最有用的推理铺垫;
  2. 改了中间状态:原本靠 token 之间承接的因果链,被压成孤立的动作点;
  3. 改了行为边界:执行路径变短、验证变少、偶尔出现”看起来结束了但其实没完成”的情况。

只看”中间输出更短了”这个指标,改动是成功的;但把它放回 Agent 的完整链路里看,等于悄悄把 Agent 的行为边界往内收了一圈。

问题就在这里:如果只是重写工具调用链或者换模型,大家一定会重视起来;但改一句”少说点”、调一下默认 effort、清一下历史 thinking,很容易被当成体验侧的小优化。可只要一个改动会影响模型看到什么、能想多久、怎么调用工具、怎么保留中间状态,它就不是纯工程细节,也不是纯 UI 体验,而是在改 Agent 的行为边界。

这类改动还有一个结构性难题:收益容易量化,但负收益只在复杂任务里才能慢慢显形。 响应更快、token 更少、输出更短、界面更清爽,这些都能立刻在面板上看到;而”复杂任务偶尔绕路""长任务偶尔忘事""多了几步工具调用却少了一次验证”,需要一批精心设计的任务集才能暴露出来。靠”这个改动看起来问题不大”来决定要不要上线,几乎一定会漏掉后者

三、所以,Agent 的方方面面都要有评测

上一节那条结论落到工程上,其实就是一句话:只要这个改动会改变模型链路,就要配一套能看见负收益的评测,而不是按普通功能上线。

“模型链路”覆盖面比名字听起来要宽——模型版本、默认参数、推理强度、system prompt、工具描述、上下文压缩、历史清理、缓存恢复、输出长度限制、结束条件,都算在内。它们不一定都叫”模型改动”,但都会影响模型最终怎么表现。

这里不是说要给每处改动都压上一套全量评测,那会把迭代速度拖死。更合理的做法是按风险分层,每类改动配一组小而尖锐的冒烟集:一边覆盖它最可能伤到的内部链路,一边对齐用户最在意的核心体验,比如:

改动类型链路侧至少应该看什么用户侧最该确认什么
模型或 effort 变化复杂任务成功率、任务步数、验证完成度、成本变化真实复杂任务还能不能稳定做完
system prompt 变化工具使用是否变形、是否更容易漏验证、是否过早结束回答是否可靠,是否过早给结论
输出长度限制中间状态是否还够用,后续轮次是否还能接住前文多轮任务是否还能接得住
上下文压缩或历史清理用户目标、限制、已排除方向、下一步计划是否保住长任务是否还记得目标和限制
缓存或空闲恢复逻辑跨时间窗口继续任务时,是否重复、断片、改错方向隔一段时间回来是否还能接着做
工具描述或参数变化工具是否选对、参数是否填对、失败后能否恢复该用的工具是否用对,失败后是否能恢复

整体流程大概是这样:

flowchart TD
    A["一次改动准备上线"] --> B{"会不会影响模型链路?"}

    B -->|不会| C["普通功能测试<br/>确认没有明显回归"]
    B -->|会| D{"影响的是哪一层?"}

    D --> E["参数 / effort / 模型版本"]
    D --> F["system prompt / 工具描述"]
    D --> G["上下文压缩 / thinking 清理 / 缓存恢复"]
    D --> H["工具调用格式 / 输出长度 / 结束条件"]

    E --> I["复杂任务冒烟集<br/>看能否稳定完成用户目标"]
    F --> J["工具行为评测<br/>看是否漏验证、误调用、过早结束"]
    G --> K["多轮连续性评测<br/>看目标、限制、已排除方向是否保住"]
    H --> L["执行过程评测<br/>看中间状态是否足够、路径是否变形"]

    I --> M{"结果是否异常?"}
    J --> M
    K --> M
    L --> M

    M -->|没有明显异常| N["小流量灰度<br/>继续观察核心场景反馈"]
    M -->|有异常| O["拆开做消融<br/>定位是哪一项改动带来的负收益"]

这是”横着看”——按改动类型决定测什么范围。还需要”竖着看”——对同一个任务,观察要分三层:

  • 信息层:用户目标、限制、已排除方向还在不在?工具结果里的关键证据后续还能不能被引用到?这一层出问题,后面大概率都不会稳。
  • 过程层:该查的文件有没有查?工具选对、参数填对了吗?有没有重复动作、绕路,或者还没验证就宣布完成?这一层出问题,最终结果偶尔蒙对也不安全。
  • 结果层:用户要的任务是否完成,代码或答案是否正确可信,成本、步数、耗时有没有异常上升。

三层不能压成一个大分数。否则最终结果一旦变差,排查时就只能靠猜:是上下文没保住?工具选错了?prompt 把模型压得太短?effort 不够?还是最后总结阶段才出的错?中间没有观察点,复盘时就只剩”体验下降”四个字。

评测本身也不只是事前拦截,事后同样靠它定位。Anthropic 在复盘里也提到,后续会扩大预发布环境的评测覆盖面、加大对用户反馈的关注,并承诺在 prompt 和 harness 改动上做更系统的验证,避免一次上一堆、事后无从归因。对应到工程实践,其实就是两条:

  • 事前:不要一次性把多个影响模型链路的改动叠在一起上线。effort、prompt、thinking 清理最好分批走,或至少在灰度阶段分开观察,否则出了问题只能被迫做事后拆解。
  • 事后:用回滚来做消融,而不是开会讨论。把可疑改动一个个拿掉,对同一批复杂任务重新跑评测,看指标有没有回来。怀疑是”≤25 words”这条伤到了复杂任务,就只回滚它,其他保持不变,看任务质量、工具重复次数、最终完成率有没有变化。

这套流程能跑起来的前提,是关键链路上本来就有一批贴近用户核心场景的稳定任务集——这反过来又回到了上面那个判断标准:凡是动到模型链路的改动,都得配评测。

写在最后

参数、prompt、上下文、缓存、工具链路,这些东西表面上分散,最后都汇到同一个地方——模型下一步到底怎么判断、怎么行动。与其等用户自己感受到”怎么变笨了”再回头翻 prompt、翻参数、翻缓存逻辑,不如在每次动到这些地方的时候,多花一点评测成本把负收益提前看见。