下线一个 Planner 已产生依赖的 Agent 工具
你从工具目录中注销了 lookup_account_v1,换上了 lookup_account_v2,并修改了系统提示词中的一个段落来指向新名称。测试通过了。三天后,支持工单开始提到助手“一直尝试调用不存在的东西”,或者——更令人不安的是——它用自信、看似合理的数字回答客户问题,却根本没有查询数据库。弃用并没有在通信层失败,它在规划器(planner)中失败了。
这是将工具弃用视为语法变更与将其视为行为迁移之间的差距。智能体不仅是在注册表中拥有你的函数;它还拥有数月的计划、多步配方(recipes)以及通过该函数作为检查点的 few-shot 示例。撤掉它更像是停用下游服务非正式硬编码的内部 API——只不过下游服务是一个你无法通过 grep 搜索其习惯的模型,而且当它偏好的工具消失时,它的兜底方案是编造一个。
工具退役(Tool retirement)是智能体工程中最容易导致 API 版本化策略失效且代价昂贵的部分。调用已删除端点的 REST 消费者会收到 404 和堆栈跟踪。而“调用”已删除函数的智能体可能会针对旧名称发出幻觉工具调用,被运行时拒绝,尝试使用幻觉出来的参数形状进行重试,最后——在耗尽重试预算后——给出一个流利的回答,满足用户可见的交互,同时悄悄跳过它本应执行的查询。通信层(wire-level)的失败很容易被发现。而行为上的失败才是真正上线的隐患。
模型究竟对你的工具内化了什么
当工程师认为“智能体正在使用 search_orders 工具”时,他们往往会想象一个清晰的依赖链:提示词 → 规划器 → 工具规范 → 工具调用。现实情况要复杂得多。模型对工具至少有四个重叠的表示,当你退役工具时,每一个都必须更新。
第一种是运行时注册表中的 schema。这是你唯一真正拥有并能进行原子更改的表示。第二种是模型在当前轮次中看到的上下文工具列表——通常是从注册表渲染的,但可能缓存你的提示词模板中,有时为了提示词缓存的稳定性而固定在特定版本,偶尔还包含着没人注意到的旧描述副本。第三种是系统提示词中的 few-shot 示例,它们演示了成功调用的规范形式:“要通过 SKU 查找订单,请调用带有 {sku, region} 的 search_orders。”这些示例是六个月前针对旧工具调整的,现在它们在可用工具方面对规划器撒了谎。
第四种,也是最令人不安的,是模型在预训练或微调过程中吸收的 与你的 API 模式类似的模式。如果你的工具命名很通用——比如 search、get_user、lookup——模型很有可能会产生这种形状的调用,而不管你的注册表里写了什么,因为在许多类似语境下,这是高概率的补全。重新训练注册表并不能消除这种先验(prior)。补救措施是结构性的:使用独特的名称、独特的参数形状,并针对已知会触发先验的提示词进行激进的 eval 覆盖。
前两个你可以通过一次部署来更新。第三个需要你找到代码库中引用旧工具的每个系统提示词和 few-shot 示例——而这正是大多数团队忘记查看的地方。第四个则需要你假设存在敌对的先验信念,并围绕它们进行设计。
阶段性退役模式
能够实现平稳退役的规范借鉴了十年前 protobuf 操作员处理字段弃用的方式:永不破坏,始终重叠,观察行为,然后移除。
标记弃用,不要移除。 第一步是更新注册表中的工具描述,用模型能读懂的平实语言声明弃用。“DEPRECATED(已弃用)——请使用 lookup_account_v2。此工具将于 YYYY-MM-DD 移除。”模型每一轮都会读取工具描述。这句话是你最廉价的行为引导,只需花费几个 token。关键在于,工具在此窗口期间仍然有效——调用它会返回相同的数据。你没有破坏任何东西;你只是在宣传变更。
使用不同名称双重注册新工具。 不要抵制诱惑,不要将 lookup_account_v2 作为同名的直接替代品发布。独特的名称为规划器提供了可以感知的“新鲜度”信号,并允许你构建对 比两者的 eval。新工具的描述应明确引用旧工具:“替代 lookup_account_v1。除了现在需要 region 外,协议完全相同。”这种交叉引用是规划器可以消费的提示目录,而无需你重写每个系统提示词。
带有遥测的软删除窗口。 在迁移期间,对已弃用工具的每次调用都会成功——并发出你的平台可以跟踪的弃用事件。你需要一个能回答两个问题的仪表板:哪些会话仍通过旧工具路由,以及哪些提示词产生了这种路由?第一个问题告诉你何时可以安全移除。第二个问题告诉你代码库中哪些 few-shot 示例和下游解析器仍需更新。弃用事件是元数据,而不是失败;它是你用来安排实际移除的信号。
带有结构化故障模式的硬删除。 当软窗口结束时,不要对旧工具的调用静默返回 404。返回一个模型可以恢复的结构化错误:“工具 lookup_account_v1 已于 YYYY-MM-DD 退役。请改用 lookup_account_v2。协议相同。”模型可以读取该错误并重新规划;而通用的异常则做不到。跳过这一步的团队会发现,他们的智能体通过对已失效的函数幻觉参数、陷入重试循环、最终在没有任何工具调用的情况下给出答案来响应移除。
这种模式的形状应该让人感到熟悉。它与任何版本化 API 运行的阶段性契约——弃用、双重发布、遥测、移除——是一样的。不同之处在于消费者:规划器比严苛的客户端更宽容(如果你足够坚定地指向它,它会采用新名称),但也更危险(当它无法采用时,它会自己发明一个)。
