失败通常发生在边界上
当 agent 调用工具而出错时,本能反应是怪模型。提示词不够清晰、推理太浅、上下文太嘈杂。但在实践中,这类失败中有很大一部分在模型开始推理之前就已经注定了:问题出在工具 schema 上。schema 是模型与系统之间的契约。如果这份契约含糊不清,模型就会用看似合理的猜测去填补空白,而工具会照单执行。
本文主张,接口设计应当被视为 agent 系统可靠性的首要杠杆,优先于提示词工程。提示词以概率方式塑造行为,schema 则以结构方式强制行为。
工具调用究竟在哪里失败
有三种模式造成了不成比例的错误工具调用。
参数名含糊。 一个叫 target、value 或 options 的字段,会诱使模型从上下文中推断含义。模型产出一个语法上合法、语义上错误的参数。工具接受了它。错误在之后才浮现,往往表现为下游数据问题,而不是一次失败的调用。像 target_user_id 和 retention_days 这样的命名并不啰嗦,它们是在消除歧义。
可选字段却没有默认值。 如果 schema 把某个字段标为可选,但工具既没有默认值也没有 null 处理路径,模型就必须编造一个值来完成调用。它一定会编。编出来的值看起来合理,但错得很难被发现。可选性是关于工具行为的声明,不是给模型的提示。
自由文本参数。 没有结构的字符串参数把意图压缩成了散文。工具无法校验散文。模型也无法被散文纠正。一个接受 "send the report to the team tomorrow morning" 的字段,等于把调度逻辑塞进了模型脑子里,在那里它既不可检视也不可测试。
这些不是推理失败,而是以推理失败的形式呈现出来的规格失败。
工具 schema 的设计规则

工具 schema 应当以对待公开 API 的同等严谨度来对待,因为它本来就是。
每个参数都要有类型、有边界,并在边界处校验。 类型是必要的,但不充分。一个必须取自固定集合的字符串应当是 enum。一个必须落在某个范围内的数字应当携带该范围,边界应当在执行前拒绝范围外的值。在边界处校验意味着工具永远不会看到它无法处理的参数,而模型在产出这样的参数时会收到确定性的信号。
前置条件写在描述里,而不是从上下文假定。 如果某个工具要求某个资源存在,或要求先前的调用已成功,这应当写进描述。模型并不能可靠地从对话历史中推断前置条件,也不应该要求它这么做。一句写着“需要从 open_session 获取的有效 session id”的描述是规格。一句写着“做那件事”的描述是许愿。
错误以类型化代码返回,并附带修复提示。 当调用未通过校验时,响应应当是机器可读的:一个 agent 可以据此分支的代码,外加一条关于怎样才能满足契约的简短提示。堆栈跟踪和自由文本错误消息把解析工作又推回给了模型,而这恰恰是 schema 本应消除的工作。
一个具体的机制:两阶段调用路径。模型依据 schema 产出参数。校验器检查类型、边界、enum 和前置条件,返回一个规范化后的参数对象,或一个类型化的拒绝。只有规范化后的对象才会到达工具实现。工具实现于是可以假定其输入是良构的,这消除了执行路径中的防御性分支,并使失败可归因于两处之一:模型的参数,或工具的行为。
为什么提示词无法补偿
人们很容易想写一段更长的系统提示词来解释工具的怪癖。这不可扩展,原因有三。
指令遵循会随上下文长度和工具数量而退化。 随着可用工具增多,模型对任何单条指令的注意力都会变薄。在长提示词中只陈述一次的规则,要与窗口中的其他一切竞争。而 schema 在每次调用时都被强制执行,无论存在多少工具。
schema 是被强制执行的,提示词是被遵守的。 这是核心的不对称性。校验确定性地运行。提示词遵循是一个随措辞、顺序和上下文而变的概率。当可靠性重要时,应当投资于那个每次都运行的机制。
schema 变更是可审查的产物。 对工具 schema 的 diff 就是对契约的 diff。它可以被审查、版本化、测试和回滚。

诚实的权衡:更严格的 schema 会牺牲灵活性。一个严格类型化的工具无法吸收一个规格松散的请求,一些合法的用例会在边界处被拒绝,而不是被优雅地处理。替代方案是一个接受一切、却以不可预测的方式失败的工具。对大多数生产系统而言,一次类型化的拒绝比一个静默的错误结果更便宜。在确实需要灵活性的地方,应当显式地建模,例如建模为类型化请求变体的可辨识联合,而不是一个无类型的字符串。
迁移与版本化
工具就是 API。它们需要弃用窗口、版本化命名,以及一段新旧 schema 都能解析的时期。一个通过描述习得了工具形态的 agent 不会瞬间适应破坏性变更,围绕它构建的评估也不会。
有两个指标应当与延迟和成本一起,进入一等公民的仪表盘:
- 单工具调用成功率。 按工具细分,而不是聚合。系统级的成功率会掩盖那个正在失败的工具。
- 参数拒绝率。 执行前被校验器拒绝的调用比例。拒绝率上升是一个早期信号,说明某个 schema 含糊、某个描述有误导,或者模型被要求给出一个它不可能知道的值。
这两个指标把 schema 问题与推理问题区分开来。一个拒绝率高、且拒绝代码清晰的工具,是在告诉你它的契约不清楚。一个调用成功率高、但之后产生错误结果的工具,是在告诉你它的校验过于宽松。这两种信号在提示词里都看不见。
这在实践中改变了什么
把工具接口当作 schema 问题,会改变工程投入的方向。schema 设计成为一项可审查的活动,有自己的测试。描述成为带有前置条件和后置条件的规格。错误成为接口的一部分,而不是事后补丁。提示词仍然重要,但它们在做自己擅长的事:塑造语气、策略和分解。它们不再承担强制执行契约的重担。
更宏观的一点是,agent 的可靠性主要是一种接口纪律。一个面对含糊接口也能推理得很好的模型,仍然会产出含糊的调用。一个面对精确接口推理得还算够用的模型,会产出可被检查、拒绝和纠正的调用。第二种系统才是可以被运维的系统。
封面照片:Jakub Pabis / Pexels。
