跳到主要内容

你无法通过邮件给模型发送变更日志:为什么当调用者是 LLM 时,API 弃用机制会失效

· 阅读需 11 分钟
Tian Pan
Software Engineer

API 弃用是一种建立在接收者具备阅读能力这一假设之上的通信协议。你发布更新日志、向注册开发者发送邮件、添加 Deprecation 标头、提前六个月发出通知,并寄希望于另一端的人类能看到警告、提交工单,并在停用日期之前完成迁移。当你的最活跃调用者变成了语言模型的那一刻,这整套工作流程就悄无声息地失效了。

LLM 不会订阅你的开发者时事通讯。它没有 Slack 频道让人转发你的迁移指南。它在每一次调用中重新发现你的 API —— 或是通过被交付的工具描述,或是通过一份可能已经过时 18 个月的文档页面,亦或是基于其训练数据中对你 API 样子的记忆。这里没有一个你可以进行版本化、通知或传呼的持久客户端。每一次请求都是与一个实体的全新博弈,而这个实体既不记得你上一次的公告,也没有义务阅读你下一次的通知。

这并非假设。随着 Agent 逐渐成为内部和外部 API 的主要消费者,后端团队沿用了 15 年的弃用策略手册正在以一种特定的、可诊断的方式失效 —— 且大多数团队只有在发现一个“已弃用六个月”的端点仍在生产环境中为 Agent 提供服务、且没有路径使其停止时,才会意识到这一点。

你无法触达的调用者

传统的弃用机制依赖于一个非常基础的假设,以至于没人会特意把它写下来:存在一个稳定的客户端,且客户端背后是一个人。OpenAI 的弃用页面、RFC 8594 的 Sunset 标头、Zalando 的 API 指南 —— 它们描述的都是同一种模式。宣布更改,给出一个宽裕的时间窗口,指向迁移指南,然后由调用方代码的维护者完成工作。

LLM 调用者打破了这条链条上的每一个环节。

它没有稳定的身份。同一个逻辑上的“客户端”可能在一周的滚动更新中对应三个不同的模型版本,每个版本都有不同的训练数据和对你 API 的不同记忆。你无法固定它,无法列举它,也无法对比它昨天和今天所掌握信息的差异。

它没有收件箱。没有邮件地址,没有注册的 Webhook,没有控制台登录入口。你的迁移邮件无处可达。拥有 该 Agent 的人类可能会读到邮件 —— 但他们并不在 Agent 选择你已弃用端点的具体调用环节中,而且他们通常甚至不知道自己的 Agent 正在调用你。

它在调用时并不阅读你的文档。它只读取进入上下文窗口的那部分 API 片段:一个工具 Schema、一个检索到的文档块,或者干脆什么都没有,在这种情况下它会退回到记忆模式。针对不断演进的 API 进行代码生成的研究发现,即使 Prompt 中明确提供了更新后的信息,模型仍会默认使用记忆中的 API 模式 —— 已弃用的调用只是概率更高的 Token。你的最新文档输给了旧文档,因为旧文档存在于训练集中,并被强化了上万次。

因此,你给出的“六个月通知”只触达了一类受众:那些已经知道如何迁移的人类。而实际产生流量的调用者从未收到这份备忘录,因为根本没有它能接收的备忘录格式。

为什么模型会一直调用已废弃的端点

准确界定 LLM 是 如何 最终调用了你已弃用的内容是很有帮助的,因为每条路径都需要不同的修复方案。

路径一:训练数据记忆。模型是从 GitHub、Stack Overflow 以及在其知识截止日期之前存在的文档中学习你的 API 的。如果你去年将 POST /v1/charge 重命名为 POST /v1/payments last year,模型中仍然存有数千个旧名称的示例,而新名称的示例寥寥无几。对 LLM 在知识截止日期后的 API 上生成代码的研究发现,使用旧 API 和幻觉 API 是主要的失败模式 —— 超过 40% 的执行失败可追溯到错误的参数或捏造的行为。模型并不是粗心,它是在遵循统计规律。

路径二:过时的检索 (RAG)。你的 Agent 对文档语料库使用 RAG,而该语料库在弃用之前就已完成索引。模型严格按照你的要求执行 —— 将调用建立在检索到的文档之上 —— 而文档是错误的。如果弃用只更新了官方文档网站,而没有更新每一个下游索引,那么弃用信息实际上并未传播开来。

路径三:过时的工具 Schema。Agent 配置了 MCP 工具定义或 OpenAPI 规范,而这些定义仍然描述着已弃用的接口。这里有一个微妙之处:在 LLM 时代,工具的 描述 (description) 是其契约的一部分。更改参数描述的措辞会改变模型选择该工具的概率,即使底层代码未被触动。未能触达工具 Schema 的弃用是不可见的,因为 Schema 是模型唯一真正参考的内容。

共同点在于:在每一条路径中,模型调用你的旧端点是因为旧端点是其可用证据所描述的内容。“已弃用”是关于你 意图 的事实。而模型只看到你的 接口。如果接口仍然将端点呈现为活跃且可选的,那么无论你的更新日志怎么写,该端点就是活跃且可选的。

“废弃”只是一种心态,直到强制执行

Deprecation: true 请求头只是写给阅读日志的人类看的。生成下一个请求的 LLM 并不会将你的响应头解析为策略——充其量,它只会将其视为工具结果中的数据,而且它完全没有本能去把这些数据当作改变行为的指令。软废弃(Soft deprecation)——即“虽然还能用,但请停止使用”——假设调用者能感受到社会压力或会安排迁移工作。但模型既没有感觉,也不会安排。它在处理第 1 个请求和第 100 万个请求时,调用已废弃端点的热情是一模一样的。

这意味着对于 LLM 调用者来说,并不存在软废弃。这里没有基于信誉系统的中间状态。端点要么是可选的,要么是不可选的,而唯一决定这一点的,是模型与你的 API 之间的 强制执行层(enforcing layer)。具体来说,这一层就是你的工具网关(tool gateway)、你的 MCP 服务器或你的 API 网关——这些组件决定了向模型提供哪些工具,以及模型在调用工具时会发生什么。

这重新定义了整个废弃工作。工作内容不再是“宣布变更并等待”,而是“改变模型能看到和能做的事情”。有三种机制对模型调用者真正有效:

  • 从工具界面中移除。 如果已废弃的端点不再出现在交给模型的工具 schema 中,模型就无法选择它。这是最有效的控制手段,因为它直接作用于模型读取的唯一 API 表现形式。权衡之处在于时机——撤掉得太早,正在运行的智能体(agent)就会崩溃。
  • 让错误消息承担教学任务。 当废弃的调用确实发生时,响应是你与模型沟通的唯一保证渠道。模型会阅读工具结果。一个正文仅为“端点已移除”的 410 错误教不会模型任何东西。而如果响应说明了失败原因、原因何在,并提供了带有正确参数名称的准确替代调用,模型就能在下一轮对话中完成自我纠正——优秀的智能体循环(agent loops)能够做到这一点。
  • 在服务器端做兼容适配(Shim)。 透明地将已废弃的路径路由到新的实现上,这样在迁移界面的同时,旧的调用仍能产生正确的结果。这为迁移赢得了时间,且不会破坏智能体,代价是需要维护这段适配代码。

注意,这些手段没有一个是“发送通知”。你不是在通知调用者,而是在重塑调用者所处的环境。

为只阅读接口的受众设计废弃策略

一旦你接受了模型只看得到接口和工具结果这一事实,废弃就变成了一个目标明确的设计问题。由此可以直接得出以下几项原则。

将工具 schema 视为契约,并对其进行版本化。 MCP 生态系统一直趋向于此——使用元数据字段来标记 version(版本)、工具 deprecates(废弃)的内容以及最低协议版本,因为除此之外,没有其他地方可以承载面向模型的变更。最稳妥的演进策略是最枯燥的那种:只增加,绝不静默更改。新端点获得新的工具条目;旧端点按计划从界面中撤出,而不是就地打补丁。如果你必须修改描述,请将其视为行为变更并重新测试,因为你刚刚修改了契约。

为模型编写错误消息,而不是为系统管理员编写。 关于工具 API 的 schema 设计研究明确指出:通用的错误很少能引导成功的重试,而带有 schema 提示(预期的字段、允许的值、纠正措施)的错误能让模型在无需人工干预的情况下发出纠正后的调用。你的废弃错误是针对唯一重要读者的教学机会。值得在上面多花点笔墨。

计划强制关停,因为软关停不起作用。 对于人类调用者,你会依赖较长的软废弃窗口。而对于模型调用者,软窗口除了让流量积聚在已失效的端点上之外,毫无作用。真诚的设计是适配层(shim)加上有日期的强制关停:通过透明路由保持旧界面 可用,但停止 展示 它,并承诺一个你会在网关处真正执行的移除日期。

监测界面,而非邮件列表。 你无法询问智能体谁还在使用旧版本。但你可以观察自己的网关。跟踪哪些工具 schema 正在被提供,哪些已废弃的路径仍有流量,以及它们属于哪些智能体运营商。这些遥测数据——而不是注册开发者列表——才是你迁移进度的真实写照。

转变:废弃是环境变更,而非通知

更深层的教训不仅限于 API。十五年来,废弃一直是一个 社交 过程:你传达意图,给人类时间,并信任他们会采取行动。这之所以奏效,是因为调用者是能阅读、记忆和计划的人。

现在的调用者越来越不再是人。它是一个模型,只阅读你摆在它面前的东西,只记得它被训练过的内容,且从不计划。你无法通知它。你只能改变它所看到的——提供给它的工具 schema、它收到的错误消息、网关实际路由的端点。废弃不再是一条消息,而是你为模型的每一次调用所构建的环境属性。

领会到这一点的团队不再为读不懂邮件的受众写迁移邮件,而是开始将工具网关视为真正的废弃控制平面。变更日志(changelog)仍然重要——那是给拥有智能体的人类看的。但模型永远不会去读它。如果你想让模型停止调用某些东西,它唯一能听懂的语言就是接口本身。

References:Let's stay in touch and Follow me for more thoughts and updates