跳转到主要内容

逐渐腐化的工具描述:当你的 Agent 仍在盲目调用时

阅读需 2 分钟Tian PanTian Pan

你的智能体已经悄悄出错六个月了,而你的错误率看起来却很正常。底层 API 发布了一个重命名的错误代码,将一个可选字段改为必填,并开始拒绝没有幂等性请求头(idempotency header)的调用。你智能体系统提示词(system prompt)中的工具描述 —— 那是去年第四季度从 Notion 页面粘贴过来的 —— 完全没有描述这些变化。智能体不断调用旧的参数结构,编排层不断捕获失败并使用同样错误的参数进行重试,而你遥测系统中的唯一信号只是略微升高的重试次数,且没有任何值班人员有足够的背景信息去调查它。

工具描述是接口契约。底层 API 发生变化的时刻,它们就开始老化。与强类型 SDK 不同,它们的失效是无声无息的 —— 模型只会发出更糟糕的调用。

这种失败模式之所以普遍存在,是因为团队中没有人将这些描述视为受版本控制的产物。发布 API 变更的后端工程师更新了 OpenAPI 规范,运行了 SDK 代码生成,并合并了迁移。智能体平台的工具定义存在于不同的文件中 —— 有时在不同的仓库,通常是提示词模板中的一个 YAML 块,偶尔是某人入职文档中的一个 Markdown 块。它是从六个月前的草稿中粘贴过来的。代码生成从未触及它。PR 模板没有提及它。值班手册(runbook)也没提到它。这种偏移随着一次次悄无声息的 schema 变更而累积,直到有一天客户投诉升级,迫使某人朗读提示词,才发现描述的是一个早已不存在的 API。

为什么这里的偏移是无声的,而不是震耳欲聋的​

带有陈旧类型签名的强类型 SDK 会在编译时报错。针对已更改契约的 HTTP 客户端会抛出 400 错误或反序列化错误,从而被监控捕获。LLM 的工具调用层面没有任何这类关口。

当描述说某个字段是可选的,而 API 后来将其改为必填时,模型会省略该字段,API 拒绝调用,编排层将其视为瞬时故障,重试策略会再次发出同样的错误参数。你的仪表盘显示 “工具 X 的重试率升高”。它们不会显示 “工具描述正在对模型撒谎”。模型无法在会话中从失败中学习 —— 它看到 400 错误,推断是服务器问题,向用户道歉,然后继续。

当 API 添加了一个描述中未提及的新可选参数时,模型永远不会传递它,API 的行为会发生细微变化 —— 参数默认值可能是六个月前合理的设置,但现在对客户的租户(tenant)来说是错误的。智能体会表现得很 “专业” 地做错事。没有错误,只有由于用户投诉而逐渐浮现的错误结果,且没有人能追踪到根本原因。

当 API 将错误代码从 RATE_LIMITED 重命名为 THROTTLED_BY_TENANT_POLICY 时,描述中 “如果你看到 RATE_LIMITED,请等待并重试” 的指令会悄无声息地停止触发。模型看到一个无法识别的错误代码,无法应用描述中编码的重试逻辑,要么放弃,要么幻化(hallucinate)出另一条恢复路径。编排层的指标最终会察觉到,但检测时间是以周计的,因为这种失败表现为用户报告的不稳定性,而不是警报。

这三者的共同属性是:描述是智能体推理层面(reasoning surface)的一部分,错误的描述在调用真正发生之前就污染了推理。

甚至不涉及 API 变更的偏移模式​

这个问题更难的版本是,当 API 本身保持不变时,周围的系统可能会发生偏移。大多数团队直到被坑了之后才会考虑到这一类问题。

下游服务引入了幂等键(idempotency-key)要求。智能体调用的 API 仍然接受旧的请求结构,但其背后的服务在缺失该键时的重试方式发生了变化,产生了重复的状态更改,看起来就像智能体在进行 “双重支付”(double-spending)。从未提及幂等性的工具描述不可能提示模型发送该键。修复工作在模型的上游,但症状看起来却像是一个智能体 bug。

速率限制(rate limit)收紧了。智能体并不知道每分钟的配额现在只有以前的一半,它继续发出工具描述中 “你可以随意调用” 语言所暗示的突发调用。一半的调用被退回。另一半以不可预测的顺序成功。用户看到的是一个 “有时有用” 的会话。

一项新的合规政策意味着调用现在需要在请求头中包含同意令牌(consent token)。API 在没有该令牌的情况下接受了调用,但记录了违规。团队在六周后审计调取日志时才发现。描述从来没有任何理由提及 “同意”,因为编写描述时 “同意” 机制还不存在。

在每种情况下,工具描述作为 API 契约的描述在技术上是正确的。但作为智能体在当前运行现实中应该进行的调用的描述,它是错误的。智能体进行推理的接口比 OpenAPI 规范更广泛,相应的偏移面(drift surface)也更广。

将描述视为编译产物的版本管理准则​

解决方法是停止将工具描述视为文档,并开始将其视为代码。具体而言:

从与 SDK 相同的单一事实来源生成描述。 如果 API 有 OpenAPI 规范,工具描述应在生成类型化客户端的同一个 CI 步骤中从中生成。描述的参数列表、类型约束和必填/可选标志都源自规范,而不是由人工阅读规范后转录。人工撰写的内容是 正文(prose)——该工具的 用途、Agent 何时应调用它、Agent 对结果应采取什么操作——这些正文与规范并列,并处于相同的审核流程中。

将描述与 API 一同版本化。 工具应遵循语义化版本(semver)。对描述形状(shape)的破坏性变更——重命名的参数、删除的字段、更严格的类型——应触发主版本号升级和新的工具名称(createInvoice_v2),而不是对现有描述进行静默修改。尚未升级的 Agent 会继续针对它们测试过的相同形状调用 createInvoice_v1。平台同时提供这两个版本,直到旧版本通过标准渠道被弃用。

将描述正文的变更视为破坏性变更。 这是最反直觉的一点。即使 JSON schema 完全相同,描述 正文 的改变也可能改变模型的工具选择决策。以前为“查找我最近的购买记录”调用 searchOrders 的模型,现在可能因为描述措辞的调整而调用 listInvoices。从 API 的角度来看,什么都没有改变;但从 Agent 的角度来看,其表面积(surface area)发生了偏移。CI 应将任何描述编辑视为潜在的破坏性变更,并在允许部署前运行评测(eval),证明在留存的 trace 语料库上工具选择的分布没有发生偏移。

添加契约偏移(contract-drift)的 CI 门控。 一个计划任务拉取线上 API 的 OpenAPI 规范,将其与生成已部署工具描述的规范进行比对,并在出现任何差异时使构建失败(或呼叫负责团队)。其信号是“API 变了而描述没变”,该门控强制团队要么重新生成描述,要么显式确认延迟处理并设置 TTL。

针对当前的 API 行为重放 Trace。 捕捉描述陈旧(rot)的评测不是一组静态提示词,而是针对今天的 API 重放的近期 Agent trace 语料库。重放会产生每次调用的增量(delta):“此参数形状不再通过验证”、“此参数现在是必填的”、“此响应字段已不再存在”。这些增量就是需要交付的描述更新清单。重放还能捕捉到静默失败模式,即调用仍然成功但语义行为发生了变化——评测对结果进行评分,而不仅仅是调用形状。

本地化(Vendor)描述文件。 将生成的描述存储在自己的仓库中(而不是在运行时从供应商的 MCP 服务器获取),意味着上游变更不会自动应用。你在 PR 中看到差异,刻意接受它,这样发生破坏的时刻是一个代码审查事件,而不是凌晨 3 点的报警。权衡之处在于陈旧性,而上述的契约偏移门控正是为了限定这种陈旧性。

契约归谁所有​

使这类问题变得棘手的组织性失败在于,没有一个单一团队端到端地拥有工具描述。后端团队拥有 API,平台团队拥有 Agent 运行时,产品团队拥有提示词和面向用户的行为。描述夹在三者中间,只有当某些东西坏到足以触发复盘时,才会有人关注它的归属。

我见过的有效模式是像对待 SDK 一样对待工具描述——将其视为一种生成的产物,其来源在 API 仓库中,其审核在 API 团队的 PR 模板中,其部署由阻断错误 SDK 发布的相同 CI 检查来门控。Agent 平台像消费类型化 SDK 的用户一样,将描述作为版本化的包来消费。当 API 团队发布破坏性变更时,描述的升级和 SDK 的升级是在同一个 PR 中完成的。当消费者使用旧版描述时,他们也在使用旧版 SDK,弃用时间表统一适用。

这种转变虽小但影响深远:描述不再是由最后编辑 Agent 系统提示词的人所拥有的提示词内容,而是成为了由拥有 API 的人所拥有的接口契约。发布 API 变更的团队现在负责在同一个提交(commit)中发布描述变更,而消费它的团队则获得了与 SDK 相同的版本保证。

这对你的路线图意味着什么​

如果你的 Agent 平台今天还没有契约偏移 CI 门控,那么你的描述在某种程度上已经陈旧了,而你并不知道陈旧了多少。第一件事是衡量:拉取你 Agent 暴露的每个工具的线上 OpenAPI 规范,重新生成描述,并将其与已部署的内容进行比对。每一个红色的差异都是一个潜在的 Agent 失败模式,你正在为此付出重试、错误路由调用以及用户投诉的代价,这些问题的根源看起来像是“模型搞错了”,但实际上是“描述告诉了模型错误的信息”。

更深层次的认识是,LLM 工具调用表面是接口契约存在的新场所,而行业尚未建立起像对待 SDK、gRPC 存根或 protobuf schema 那样对待它的肌肉记忆。早期做对这一点的团队,其 Agent 的失败模式将在 API 演进过程中平滑降级。而没做对的团队,未来两年将花在调试 Agent 上,而这些问题的根源在于模型上游,在于一个无人负责、针对已不存在的 API 编写的描述中。

工具描述是你 Agent 表面积的一部分。一个没有版本管理准则的 Agent 表面积,就是一个你选择不拥有其契约的表面积——最终,总有人会在你最不希望被发现的时候,发现这个选择带来的后果。

参考资料

保持联系,关注我获取更多内容

阅读需 10 分钟

工具行为漂移:Schema 没变,语义却变了

当 Schema 保持不变但工具的行为发生偏移时,你的 Agent 就会悄然退化。这是一份关于如何检测和遏制工具行为漂移的实战指南。

ai-agents
observability
阅读需 9 分钟

智能体状态差异对比 (Agent State Diff):为什么肉眼对比两条追踪路径无法规模化

同一个提示词的两次智能体运行几乎从未产生过完全相同的输出。仅在文本层面进行 Diff 会掩盖问题的真正原因。本文将探讨结构化 Diff 的必要条件以及如何构建此类系统。

ai-agents
observability
阅读需 9 分钟

静默成功:当你的 Agent 宣告完成但实际上什么也没发生

Agent 往往因为喋喋不休而失败。自信的文字掩盖了工具错误,而写入操作从未真正提交。解决方案是:将模型的声明降级为假设,将工具响应和操作后探测提升为权威信号,并衡量效果落地而非单次对话的成功。

insider
ai-agents
阅读需 11 分钟

确定性重放:如何调试永远不会以相同方式运行两次的 AI Agent

记录 Agent 执行期间的每个 LLM 调用、工具响应和时间戳,然后重放精确序列来复现故障——因为把 temperature 设为零并不能让你的多步 Agent 变得确定性。

ai-agents
debugging
阅读需 11 分钟

导致你的智能体重试机制失效两周的工具 Schema 迁移

为期六周的弃用窗口对每一位人类消费者都运作完美,却让智能体静默失效了整整十四天。本文探讨了重试预算、解析错误和优雅降级为何会交织成一场无人报警的故障,以及捕获这些问题的关键指标。

ai-agents
observability