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.7 是不是降智了?我要转战 Codex!

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

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

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

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

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

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

每一行左边都成立,Agent 的坑往往藏在右边。推理强度调低,对普通问答影响很小;但面对”读项目结构 → 定位调用链 → 改代码 → 跑验证”这类任务,少一点推理余量,就可能变成某一步没想透、某个文件漏查、某个假设没验证完。thinking 清理也是一样,原本只是”空闲一小时后清一次”,bug 导致它每一轮都继续清——模型每积累一点判断,下一轮又被抹掉,体感就像一个人接手了复杂任务,但工作笔记每隔一会儿就被撕掉前面几页,模型本身的能力能记住的时候还好,但记不住,就是很难完成任务了。

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

可以把这条链路画出来:

flowchart LR
    A[一个看起来很小的改动] --> B[模型看到的信息变少]
    B --> C[中间判断不够完整]
    C --> D[工具选择和执行路径开始漂]
    D --> E[重复、绕路、漏验证]
    E --> F[用户感知:怎么没以前好用了]

最麻烦的是,中间几步不一定立刻报错。系统还在跑,工具还能调,回答也能给,简单任务甚至更快了。问题只在复杂任务、长任务、多轮任务里慢慢露出来——这类问题也最容易被平均指标盖掉:整体成功率可能只掉一点,成本和延迟甚至还变好,但用户在复杂任务上的真实感受会非常明显。

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

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

Opus 4.7 相比前代更啰嗦一些(但这其实是好事,因为模型变得更啰嗦,也就意味着模型更愿意去思考更多的内容,提前了解清楚边界)。为了控制输出 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、翻参数、翻缓存逻辑,不如在每次动到这些地方的时候,多花一点评测成本把负收益提前看见。