导致你的智能体重试机制失效两周的工具 Schema 迁移
弃用通知是在周二发出的。下游团队更改了其搜索工具的响应结构 —— results[].snippet 变成了 results[].excerpt,这是一个干净的重命名,有六周的窗口期,文档中有横幅提醒,还给工程邮件列表发了三封提醒邮件。每一个人类用户都迁移了。但 Agent 没有,因为 Agent 不读邮件。在 14 天的时间里,重试循环静默地解析新的有效负载,发现它正在寻找的字段缺失了,抛出了一个 KeyError,并将其计为可重试的故障。重试命中了相同的端点,得到了相同的新结构,抛出了相同的错误,在尝试三次后放弃,并向用户返回了一条致歉消息。重试预算仪表盘在那段时间里一直显示为绿色 —— 重试次数从未 耗尽,它们只是在 预算内永久失败。在该路径上,从工具层测得的成功率为零。但没有人察觉,因为没有触发报警。
这是 2026 年最让工程师头疼的失败形式:不是那种戏剧性的停机,而是隐蔽的契约漂移(Contract Drift)。在这种情况下,面向人类的迁移已经顺利完成,而面向 Agent 的迁移甚至从未开始,因为 没有人意识到还需要进行迁移。弃用流程完全按照设计运行,服务于它所设计的使用方。而 Agent 却是一个不在名单上的使用方。
这个故事最吸引人的部分在于,每个组件在隔离状态下的行为都是正确的。下游团队发出了通知。弃用窗口期很充裕。错误处理干净地捕获了解析失败。重试策略是有界限的。可观测性堆栈通过结构化日志捕获了每一次工具调用。每一个部分独立来看都能通过设计评审。失败发生在它们之间的缝隙中 —— 发生在于“弃用公告能传达到必须迁移的实体”这一假设,以及“绿色的重试预算意味着重试正在生效”这一假设。
Agent 未订阅的弃用通道
API 弃用运行在人类的基础设施之上。变更日志被发布,文档横幅出现,邮件发送到某人的收件箱,破坏性变更的日历邀请被接受。现在的行业最佳实践是在弃用警告和正式停止服务之间留出 6 到 18 个月的时间,这个窗口期的存在完全是为了让人们有时间阅读、确定优先级、安排计划并交付迁移工作。
调用相同端点的 Agent 只订阅了这些通道中的一个:响应体(Response Body)。它收不到邮件,也看不到横幅。虽然 Deprecation 和 Sunset HTTP 标头作为标准存在,但 Agent 的工具包装器(Wrapper)几乎肯定会在响应到达模型之前将它们剥离,因为包装器的编写初衷是提取 LLM 关心的部分 —— 即答案。弃用元数据停留在一个 Agent 层从未连接的侧信道中。
这在结构上与 Agent 错过人类操作员读取的其他操作信号的方式完全一致: 速率限制标头、Retry-After 提示、状态页横幅、弃用通知。模型精通 JSON,却对它旁边的一切视而不见。保护每个普通用户的迁移窗口对你的 Agent 用户没有任何保护作用,因为这种保护机制是“阅读你的邮件”。
第一个教训是管理层面的,而非技术层面的:Agent 依赖的每个工具都需要一个负责人,该负责人必须在该工具上游的弃用通知名单中。不是指“团队”,而是一个具体的人,其工作职责包括“监控此工具的契约”。如果你无法指名道姓,那么你的 Agent 就没有迁移路径,只能等待故障发生。
为什么重试循环让情况变得更糟
一个出于好意的重试策略,正是将“明显的故障”转化为“持续两周的静默故障”的罪魁祸首。
当响应结构发生变化时,第一次调用的解析器抛出了异常。Agent 套件在工具执行中看到了异常,将其归类为瞬态故障 —— 与网络超时归为同一类 —— 并进行了重试。第二次调用返回了相同的结构。第三次调用也返回了相同的结构。在三次重试后,Agent 放弃并将该工具报告为不可用,编排器随后通过绕过该工具来处理。用户得到了一个更糟糕的答案,而不是一个错误。
三件事共同导致了这种静默:
1. 重试策略将解析错误视为瞬态错误。 大多数重试库默认对任何异常进行重试,因为另一种选择是罗列每个可恢复的错误类别并不断更新该列表。Schema 不匹配并不是瞬态的 —— 在代码更改之前,每次重试都会以同 样的方式失败 —— 但重试层无法将其与网络波动区分开来。因此,它消耗了三次尝试和三组 Token 来证明同一件事。
2. 重试预算指标测量的是上限,而不是底限。 当每次调用都耗尽其三次重试机会时,“每小时使用的重试次数”仍保持在预算范围内 —— 三次毕竟只是三次。本应捕捉到这一点的仪表盘面板不是“已用重试次数”,而是“已恢复的重试”。从未恢复的重试是解析错误,而不是瞬态故障,你需要将它们分开。
3. 回退路径过于优雅。 编排器被设计为在工具不可用时平滑降级,当工具确实不可用时,这是正确的设计。但当工具总是失败时,这就是错误的设计,因为平滑降级与该工具根本不在计划中是无法区分的。Agent 静默地停止使用搜索工具,用户得到了更多自信满满的错误答案,却没有任何标记指出这一点。
能够捕捉到这一点的模式是按可恢复性层级对工具错误进行分类。网络超时是可恢复的 —— 重试它。带有 Retry-After 标头的 5xx 错误是可恢复的 —— 退避并重试。而针对已知 Schema 的解析错误在 Agent 侧是不可恢复的;Schema 改变了,重试多少次都无法修复。正确的行为是立即报警、显著地报错,并呈现预期结构与接收结构之间的差异(Diff),以便人类能清楚地看到哪个字段发生了变动。将这些层级混为一谈,正是导致长达 14 天静默故障的原因。
你以为你拥有的指标
“重试预算”(Retry budget)是那种听起来像安全带,最后却发现 只是个虚荣指标的数据。预算限制了你的支出上限,但它并不能告诉你这些支出是否换来了价值。仪表盘上显示“重试率:1.2%,远低于 10% 的阈值”,值班工程师扫一眼就滑过去了,因为面板显示一切正常——没有触发告警。
而在绿灯掩盖下的真实情况是:
- 一小部分调用正在不断重试。
- 所有的这些重试都失败了。
- 该路径上底层工具的成功率为零。
- 整体 Agent 任务的成功率下降了,但由于它是针对多种工具和多种任务类型取的平均值,这种下降仅表现为 1% 的波动,看起来就像是正常的噪声。
那段描述中的每一个指标都在被监测,但没有一个被组合成能发出“重试层在此路径上已无法实现恢复”信号的告警。最接近有效告警的应该是:对于任何 (tool, error_class) 组合,当重试恢复率(retry-recovery-rate)连续一小时跌至零时发出告警。这与“重试预算”的指标形态完全不同——它按工具和错误类型进行细分,并观察一个导数指标(是恢复率,而非绝对计数)。这也是那种构建起来很麻烦、极易被略过的指标,而这恰恰是为什么没人拥有它的原因。
更深层的教训是:任何在所代表的事物已损坏时仍能保持健康值的指标,都不是指标,而是装饰品。在 Agent 系统中,重试率、错误率和预算利用率都属于这一类。真正有效的指标是恢复率、端到端任务完成度以及基于质量评分的输出检查——这些指标在用户体验下降时会变红,而不是在某个计数器超过某个数字时变红。
