
上个月,Anthropic 发了一篇少见的事故复盘。
少见的不是他们出 bug,做大模型产品,谁不出 bug?真正少见的是,他们把三个生产事故仔细分享了出来:怎么引入的,为什么测试没发现,为什么内部复现困难,最后怎么改。
我读完之后觉得他们这次复盘经验很值得全文背诵学习。只要在做 LLM Agent,尤其是多轮任务、工具调用、上下文压缩、推理链管理,这些坑早晚会踩到。
三个 Bug,三种死法
先简单过一下。
第一个 bug :3 月 4 日,为了解决 Opus 4.6 在 high 推理模式下偶发 UI 卡死的问题,团队把默认推理强度从 high 改成了 medium。
内部测试看起来没什么大问题:智能只轻微下降,延迟明显改善。
结果上线后,用户直接炸了:Claude 变笨了。
4 月 7 日,一个月之后,老实回滚。
第二个 bug:3 月 26 日,上线了一个缓存优化。逻辑是:会话空闲超过一小时后,清掉旧的 thinking history,降低 resume 成本。
听起来很合理,对吧?
结果线上实现里有个 bug。本来应该只清一次,结果变成了之后每一轮都清。
于是 Claude 一边执行任务,一边不断失去"我为什么要这么做"的记忆。用户看到的现象就是:遗忘、重复、莫名其妙的工具调用、行为越来越怪。
更要命的是,thinking blocks 被持续丢掉后,每次请求都 cache miss,用量配额也烧得更快。
这个根因直到 4 月 10 日才被定位出来,整整两周时间。
第三个 bug:4 月 16 日,为了压住 Opus 4.7 的冗长输出,团队在 system prompt 里加了一行限制:
工具调用间文本不超过 25 词,最终响应不超过 100 词。
这行 prompt 多周内测都没发现明显回归。
上线后,编程质量掉了 3%。
4 月 20 日,回滚。
这三个 bug 看起来性质不同,但又指向同一个问题:Agent 系统里,很多东西看起来像"参数""缓存""提示词"这样局部的设计,实际上都会影响到核心执行逻辑。
一动,就可能伤到模型的脑子。
Reasoning History 不是日志,是工作记忆
我觉得最值得反复咀嚼的是第二个 bug。
"清理旧 thinking,节省 token",这个决策本身就是一个很正常的工程优化。thinking blocks 又长又贵,一个会话都空闲一小时了,旧推理链看起来确实没那么重要。
但问题就在这里。
对 Agent 来说,reasoning trace 不是日志。它不是简单记录"我做过什么"。它更重要的作用是记录"我为什么这么做"。
而这个为什么,恰恰是多轮任务继续往下走的关键。
一旦它消失,Agent 并不会立刻崩溃。它还会正常说话,正常调用工具,正常返回结果,但它已经开始失忆了。
它忘了之前排除过哪些方案,忘了为什么选当前路径,忘了用户真正想解决的问题是什么。
于是会看到一种很恶心的退化:变蠢,任务越做越偏。
这类 bug 最难受的地方就在这里。它不是 crash,不会给你一个干净的 stack trace。它只是在生产里慢慢散发出一种"这个 Agent 怎么那么蠢的"的味道。
所以做 context 管理时,不能再粗暴地按 token 数切一刀。
至少要分清楚三类东西:
- 不能随便压缩的:决策依据、任务意图、关键约束、推理路径。
- 可以压缩的:中间观察、工具输出、过程性材料。
- 可以丢掉的:格式辅助、冗余解释、临时展示内容。
reasoning history 不是缓存垃圾。很多时候,它就是 Agent 的工作记忆。
以为在省 token,结果可能是在给 Agent 做前额叶切除。
Prompt 的每一行都是代码
第三个 bug 也值得警惕。
只是在 system prompt 里加了一句"少说点",怎么就让编程能力下降了?
因为在模型行为里,少输出和少思考有时候不是两件事。
你要求它最终回答不超过 100 词,工具调用之间不超过 25 词。模型为了满足这个约束,可能不会只压缩表达,它可能会直接压缩决策过程。
这不是传统意义上的 bug,它是在认真优化你给它的目标。
所以 Anthropic 后面提到的改进很关键:system prompt 的每次变更,都要按模型做 ablation;能逐行测就逐行测;可能影响智能的改动,要灰度,要 soak period。
这听起来很重。
但如果你承认 prompt 是生产代码,那这套纪律一点都不夸张。
测试环境越干净,越不像生产
Agent 工程里很常见、但大家不太愿意面对的问题:
测试环境太干净了。
干净到不像生产。
Agent 的很多问题不是"输入 A 得到错误输出 B"这么简单。它们往往依赖一串状态:
会话空闲超过一小时,resume,继续多轮工具调用,thinking 被清掉,再进入下一轮,再清掉......
这种状态序列,unit test 很难覆盖,e2e test 也很难自然撞到。
我们测的是功能点,但生产跑的是状态机。
这也是为什么 Anthropic 后来让更多内部员工使用和公开版本完全一致的 Claude Code 构建,而不是测试版本。
真实使用行为当然是最好的集成测试。
默认值就是产品本身
最后说说第一个 bug。
把 high 改成 medium,从工程视角看,理由非常充分:减少卡死,降低延迟,智能只轻微下降。
问题是,"轻微下降"是 benchmark 里的说法,不一定是用户的感受。
用户不是在跑平均 benchmark。用户是在处理自己的代码、自己的上下文、自己的工作流、自己的脏问题。
在这些高度个性化的任务里,模型能力掉一点,体感可能就是"它不懂我了,它没以前灵了,它开始犯低级错误了。"
虽然产品里提供了切换选项,但大多数用户不会改。默认值就是事实上的产品决策。