工具 Schema 演进陷阱:当一个可选参数改变了你 Planner 的先验分布
在某个周二,一个全新的可选参数被添加到了工具描述中。这个改动很小——在 diff 中只有六行代码,没有破坏性的签名变更,没有更新调用者,也没有触及任何评估用例。PR 描述写着“为现有搜索工具添加了可选的 language 过滤器支持”。两名评审员批准了,随后上线。
一周后,成本仪表板显示,搜索工具的调用频率比之前的基准线增加了 18%。受影响的 agent 延迟也以大致相同的比例攀升。没人能指出哪一个评估用例失败了。新参数在使用时表现正常;在不使用时,也无关紧要。然而,planner 显然改变了它对何时使用该工具的看法——而评估套件(用于衡量工具的“正确性”)对于工具“频率”的变化却无话可说。
这就是工具 schema 演变陷阱,它几乎困扰着每一个在生产环境中发布 agentic feature 超过 18 个月的团队。这个陷阱是无形的,因为这种失败模式看起来并不像失败。没有调用中断,没有输出畸形。根据任何合理的定义,该 schema 都是向后兼容的。唯一改变的是模型的先验知识(prior )——即在 planner 步骤的参数中编码的隐含信念,关于这个工具对于流经 agent 的查询有多大的能力和适用性。工具描述是 prompt surface 的一部分。它们在每一轮对话中都会被读取。对描述的编辑就是对 prompt 的编辑。
工具描述就是 Prompt 补丁
大多数团队对待工具 schema 的方式就像对待 OpenAPI 规范一样:将其视为存在于代码中、由工程团队所有、并通过常规代码审查进行更改的机器可读契约。相比之下,prompt 被视为行为表面——它们有 A/B 测试框架,有评估门控,还有标注了促使每次编辑的失败模式的版本历史。
这种拆分在理论上是合理的。Schema 是结构化的,prompt 是自由格式的。Schema 由 agent 运行时确定性地消耗;prompt 则由模型以概率方式解释。两种产物,两个领域。
在实践中,模型在每一轮对话中都会读取这两个产物,它并不知道你的团队认为哪个是哪个。一条写着“当用户询问可用性时,使用此工具搜索产品目录”的工具描述,其改变先验知识的权重与 system prompt 中写着“当用户询问可用性时,调用搜索工具”的句子完全相同。模型无法区分它们。它们被连接到同一个上下文窗口中,由同一个 tokenizer 进行分词,并被同样的注意力头所关注。
这一推论对于大多数团队组织工作的方式来说是不太舒服的。负责 agent 行为(prompt)的团队并不拥有工具描述。而拥有工具描述的团队(工程团队)却没有针对它的行为测试套件。两者之间的交接陷入了缝隙,而缝隙两边的每一次编辑,在另一 边看来都是一次未经评估的 prompt 编辑。
为什么可选参数特别棘手
这种特定的失败模式——一个新的可选参数导致工具调用频率虚高——有一个值得命名的机制。当模型看到一个带有 query 和 filters 参数的工具时,它会根据描述和参数集形成一个关于该工具适用于哪些查询的先验。当你添加第三个可选参数(比如 language)时,三件事会同时发生。
首先,描述为了解释新参数而变得更长,而更长的描述往往会被理解为功能更强大的工具。其次,参数列表本身扩展了模型对该工具功能的心理模型;即使参数是可选的,它的存在也是一种能力信号。第三,周围的示例或提示(如果你为了演示新参数而添加了任何示例)会将 planner 推向与这些示例相似的查询,即使用户的实际查询与其无关。
这些影响都不是 bug。如果新参数真的扩大了工具的适用范围,那这些正是你想要的结果。但添加参数的团队很少考虑“适用范围”。他们考虑的是某个需要语言过滤的特定上游功能。他们预计在 95% 不受影响的调用中,工具的行为将保持不变。这个预期是错误的,而且评估套件无法告诉你真相。
工具描述的膨胀会加剧这种效应。Anthropic 的内部测试显示,58 个工具可能会消耗约 55,000 个 token,在复杂的目录中,组合 schema 可能会为每个请求增加数千个 token——即使是 agent 从不调用的工具也是如此。当工具选择准确率因目录膨胀从 43% 骤降至 14% 以下时,团队并不总能判断这种退化 是来自最新的编辑,还是来自过去一个季度累积的膨胀。
针对工具模式的语义化版本 (Semver)
捕获这类回归的纪律,始于像对待任何其他版本化 API 一样对待工具模式(tool schemas)——但对“破坏性变更”的解释要从行为角度出发,超越签名兼容性。
修改参数列表的模式编辑至少是一个次要版本(minor version)。改变措辞的描述编辑,如果可能改变规划器的先验倾向——例如“使用此工具进行”(use this for)对比“这可以”(this can)对比“你可以”(you may)——也至少是一个次要版本。真正琐碎的编辑,比如修复不改变含义的拼写错误,则是补丁版本(patch)。版本控制的目的不是为了搞合规性表演,而是在 PR 提交时标注哪些编辑需要行为评估门禁,而哪些不需要。
次要模式版本的评估门禁必须对频率进行评分,而不仅仅是正确性。团队需要一个关于每个工具在代表性生产流量中被调用频率的基准——称之为工具调用频率基准(tool-call frequency baseline)——如果编辑后在相同流量下的频率差异超过某个阈值(5% 是一个合理的起点;根据生产环境进行调整),门禁就会拦截该 PR。
这是目前大多数技术栈中所缺失的评估。工具调用评估通常关注模型是否为给定查询选择了正确的工具,以及它生成的参数是否符合模式。这两者都是正确性衡量标准。但两者都无法回答以下问题:规划器现在伸向某个工具的频率是否超出了应有的范围?或者一个原本作为最后 手段的工具,现在是否变成了第一直觉?
描述风格作为一种行为表层
除了版本控制,团队还需要一份描述风格指南,列出已知会改变规划器先验倾向的措辞。这份指南应该简短且具体,因为其效果是具体的。
“使用此工具进行 X”(Use this for X)读起来像是一条指令,会让规划器偏向于调用。 “这可以执行 X”(This can do X)读起来像是能力陈述,更为中性。“当你需要 X 时,你可以使用此工具”(You may use this when X)则进一步留有余地,让规划器偏向于不调用。偏向动词的能力列表(“搜索、过滤、排序”)读起来比偏向名词的能力列表(“一个产品搜索接口”)更广泛。嵌入在描述中的示例查询会比其前面的抽象能力描述更强烈地将规划器锚定在示例的表层形式上。
这一切都不是民间传说。在任何对比同一流量下两种工具描述并测量调用频率的 A/B 测试框架中,这都是可以观察到的。大多数团队观察不到这一点的原因是他们没有这种测试框架,而没有框架的原因是他们将工具描述视为代码而非提示词。解决方法是建立测试框架,在未通过行为评审的描述编辑上运行一次对比,让团队通过数据看到频率的变化。在那之后,这种纪律就会自我维持,因为打破过一次的工程师不想再打破第二次。
