你交付了一个工具,让你的 Agent 可以获取用户个人资料。描述中写道:“通过用户 ID 检索用户信息。”六周后,后端团队将 user_id 重命名为 customer_uuid 并添加了一个必填的 tenant_id 字段。没有人更新工具的 Schema。你的 Agent 继续调用旧的签名,收到 400 错误,将空结果解释为“未找到用户”,并“热心地”创建了一个重复记录。
日志中没有错误。没有触发任何报警。Agent 全程都非常自信。
这就是工具文档问题:Schema 漂移将陈旧的描述变成了隐性故障向量。这可能是当今生产环境 AI 系统中最被低估的可靠性风险,而且你的 Agent 运行的时间越长,情况就越严重。
为什么工具描述不仅仅是文档
大多数工程师将工具描述视为注释——可选的,如果有帮助当然好,但并非关键的承重部分。这是一个错误。
当 LLM 调用工具时,它只有两个信号可以参考:Schema(参数名称和类型)和描述(解释工具功能以及如何调用的自然语言)。模型无法访问实现代码。它无法检查 user_id 是否最近被重命名。它无法知道上周二是否添加了一个新的必填字段。它完全是根据你提供给它的 Schema 进行推理。
这意味着工具描述在精确意义上就是 API 合约:它们是模型用来做出所有调用决策的规范。一项 2025 年的分析发现,97% 的工具描述至少包含一个质量问题——模糊的功能说明、缺失的参数格式、含糊的枚举值。当这些描述与实际实现发生偏离时,你就会面临双重失败:描述从一开始就不够精确,而现在它又是错误的。
危险不在于 Agent 抛出错误。危险在于 Agent 不抛错。它继续运行,返回看似合理的结果,直到最后有人注意到不对劲——通常是几周后,当损失已经成倍增加时,问题才会浮出水面。
Schema 漂移在实践中是如何发生的
其机制非常平凡,这也是问题得不到解决的部分原因。
后端工程师为了提高查询准确性添加了一个必填字段。该字段已记录在 API 变更日志中。但没有人把更新工具描述列入清单。Agent SDK 是独立部署的。模型 Prompt 中的描述仍然显示旧的内容。
或者是一个弃用周期(Deprecation Cycle):旧参数名称在一段时间内仍然有效,因此集成测试通过了。等到旧名称不再被接受时,Agent 的故障模式已经从“错误结果”转变为“硬错误”——这至少是可见的,但在转变之前的静默期已经造成了实质性的损害。
一个相关的故障是保留语义但改变格式的字段重命名:user_id(整数)变为 customer_uuid(UUID 字符串)。Agent 仍然会为所谓的“用户标识符”发送一个值,但它发送的是一个整数。后端返回了错误记录的结果,或者根本没有结果,而 Agent 会围绕空响应进行推理,而不是将其标记为 Schema 错误。
不可见性是核心。与缺失的导入或错误的端点调用不同,Schema 漂移的执行过程非常顺畅,以至于 Agent 不会停止——它只是在错误的前提下以高度自信继续运行。
“文档即合约”的准则
解决方法不是写更好的描述。而是将描述变更视为破坏性的 API 变更——句号。
这意味着在你的工程流程中需要落实几件具体的事情:
描述变更需要经过与接口变更相同的审查关卡。 将“通过 ID 获取用户个人资料”修改为“返回旧版客户记录”的 PR,需要像修改签名一样接受严格审查。这种变更改变了模型用于决定何时以及如何调用工具的语义合约。
版本化是必须的。 当你需要以可能破坏现有调用方(包括你的 Agent)的方式更改工具的行为或 Schema 时,正确的做法是让 get_user_v2 与 get_user_v1 并存。旧版本在发布期间保持活跃。缓存了旧 Schema 的 Agent 在你迁移时仍能继续工作。
参数名称是合约的一部分。 模型会逐字解释参数名称,并利用其训练先验来猜测格式。名为 id 的参数接收到的输入将与名为 customer_uuid 的参数不同。当你重命名参数时,你就改变了描述合约——你必须像沟通 REST API 中的重命名字段一样沟通这一变更。
工具描述的变更日志是强制性的。 这个描述最后一次更改是什么时候?具体改了什么?为什么改?在调试隐性故障时,这些问题需要答案;而且这些答案需要在 PR 合并之前给出,而不是事后。
每一层的自动验证 文档规范是必要的,但还不够。尽管初衷良好,描述仍会随时间发生偏差。真正能捕捉到这些偏差的是执行时的自动验证。
在后端执行之前进行 Schema 验证 是最廉价的关卡。在将 Agent 生成的参数传递给 API 之前,请根据预期 Schema 验证 JSON。缺失必填字段、错误的类型、未知的键 —— 这些都是可以确定性地检测出来的。返回 Agent 可以推理的结构化错误(例如 "Missing required field: tenant_id, expected a UUID"),而不是让它尝试自行解释的原始 400 错误。
从你的实现中生成 Schema,而不是与实现并列编写。 如果你的 API 已经有了 OpenAPI 规范,请根据该规范自动生成工具定义。这样,工具 Schema 和 API 合约就是同一个工件,而不是两个需要保持同步的工件。对 API 的每一次更改都会自动传播到工具描述中,从而消除整类由于不同步导致的失败。
在生产环境中跟踪 Schema 的一致性。 在每次工具调用执行前,记录原始的参数负载。按工具、按天汇总 Schema 验证错误率。当你部署后端更改时,观察该错误率。针对特定工具的 missing_field 或 invalid_type 错误激增,会准确地告诉你什么在什么时候损坏了。如果没有这种仪表化监控,你将无法区分“Agent 在胡言乱语生成参数”和“后端已更改但没人更新描述”这两种情况。
在 Staging 环境中对你的 Agent 运行合成探测。 定期执行已知的正确任务,并验证工具调用序列和参数形状是否符合预期。当后端更改破坏了工具一致性时,合成探测能在其进入生产环境之前在 Staging 环境中将其拦截。
结构性原因:工具没有像代码一样进行版本管理 这个问题的根源在于大多数团队管理 AI 系统的方式存在结构性缺陷。Prompt、工具 Schema 和模型版本处于“经过审查的代码”与“随意更新的配置”之间的灰色地带。无论是 CI 流水线还是产品更新日志,都没有被设计成用来跟踪它们的形式。
当你的 Agent 工具描述作为字符串存在于 Python 字典或 YAML 配置中时,对于那些能够捕捉 API 层签名更改的过程来说,它们是不可见的。它们不会出现在 API 更新日志中。当它们与实现发生偏差时,不会触发集成测试失败。它们存在于一种带有文档性质的模糊地带,没有人正式负责保持它们的更新。
解决方案是将工具描述作为部署流水线中的一等公民。将它们与它们所描述的服务一起存储在版本控制系统中。添加一个 CI 步骤,根据 OpenAPI 规范验证 Schema。要求任何涉及工具或其封装 API 的 PR 都要包含审查清单项。在 Agent 发布说明中标记描述更改,就像你标记接口更改一样。
大规模下的表现 拥有大型 Agent 工具集(跨多个服务的数十或数百个工具)的团队面临着这一问题的放大版。研究表明,当工具数量超过 20 个时,由于模型的工具选择逻辑遇到歧义和路由混淆,Agent 的性能会显著下降。陈旧的描述使情况变得更糟:模型现在是在基于从过时到严重误导不等的描述中选择工具。
在大规模应用中,“文档即合约”的规范需要自动化。一些团队使用的测试框架将每个工具定义都视为一个测试:当底层的 API 规范发生更改时,工具定义测试会在 CI 中失败,直到描述被更新并经过审查。另一些团队则运行每晚的偏差检测任务,将实时的工具 Schema 与当前的 API 规范进行对比,并为发现的任何分歧创建工单。
核心见解是,你不能指望人类在 API 更改时记得更新描述。审查过程捕捉的是有意的更改。而自动化捕捉的是你不知道已经发生的偏差。
将工具描述视为承重结构 陈旧工具描述的失败模式并不剧烈。没有崩溃,没有报警,没有明显的信号表明出了问题。Agent 继续运行,继续产生输出。它甚至在大多数时候可能是正确的。失败在那些不正确的案例中悄然累积 —— 检索了错误的记录,错误的参数被默默拒绝,在过时的合约之上构建了看似自信的推理。
解决这个问题需要转变思维模型:工具描述不是给人类看的文档。它们是模型用来做出所有调用决策的规范。它们是承重结构。当它们与实现所接受的内容发生偏差时,Agent 的推理虽然完整,但是建立在错误的前提之上 —— 这相当于 AI 领域的“对错误的输入进行了精确的计算”。
防止这种情况发生的工程规范,与在任何分布式系统中防止 API 合约违约的规范并无二致:版本化、显式的变更管理、自动化的合规性检查,以及一种组织准则 —— 即更改合约需要与更改其描述的代码相同的严谨性。区别在于,当合约断裂时,你的 API 客户端会通过错误进行回击。而你的 Agent 只会带着自信,继续进行错误的工具调用。
会员专享
余下内容仅对会员开放。 会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
— 完整文章,包含未公开存档的部分 — 可落地的工作框架,附带权衡与决策依据 — 新文章抢先看,先于公开发布 登录以继续阅读→ 随时取消 · 一次订阅,畅读全部