工具调用 · 约束可判定性 · 结构化生成 · Agent工程实践
摘要
在Agent工具调用(tool calling / function calling)链路中,Skill的输入参数究竟该用JSON Schema做强类型约束,还是用自然语言描述,长期存在工程分歧。本文以"约束可判定性"(constraint decidability)为主线,将参数约束拆解为词法、语法、语义、跨字段、业务规则五个层级,论证"结构化约束是否被遵循"并非一个二元问题,而是取决于约束能否在解码期被机械判定、以及判定结果能否反馈进生成过程。文章梳理了约束解码、语法引导生成、Schema校验重试、类型化函数调用等四条技术路线的机制与边界,给出可复现的评测方法与混合约束设计步骤,并对多约束耦合、长上下文下的约束漂移等前沿问题作出预判。
笔者认为,JSON Schema与自然语言并非替代关系,而是分别作用于"可判定硬约束"与"不可判定软意图"两个层面;把二者混为一谈,才是遵循率差异被夸大的根源。
目录
一、导语:一个被问错的问题
"用JSON Schema还是自然语言描述参数,Agent对约束的遵循程度差很多吗?"这个问题在工程群里被反复提起,但多数讨论把两个不同层面的东西放在同一个天平上称重。JSON Schema是一种形式化的结构声明语言,它的核心价值在于可被程序机械解析与校验;自然语言描述则依赖模型的语言理解能力去"领会"意图。前者是语法层的契约,后者是语义层的沟通。把它们简单对比遵循率,就像比较"合同条款"和"口头约定"哪个更可靠——答案取决于争议发生时有没有第三方仲裁机制。
本文的核心主张是:Agent对约束的遵循程度,主要不取决于约束"写成了什么形式",而取决于约束"能否在生成过程中被判定并干预"。JSON Schema之所以在很多评测中表现更好,是因为它天然携带了可判定的结构信息,使得约束解码(constrained decoding)、运行时校验(runtime validation)和错误反馈重试(retry with feedback)这三类机制得以生效。一旦自然语言描述也能被转化为可判定的检查器,二者的差距会显著收窄。反之,如果JSON Schema只被当作"贴在提示词里的装饰",不做任何校验,它的优势也会大打折扣。
本文评述:把"遵循率"当作单一指标来比较两种约束形式,方法论上是不成立的。遵循率必须与"约束类型分布""校验介入时机""重试预算"三个变量联合报告,否则结论不可迁移。
二、背景与问题定义:从"描述参数"到"约束参数"
2.1 术语澄清:Skill、工具调用与参数约束
在本文语境中,Skill指Agent可调用的一个原子能力单元,通常对应一个函数或一个API端点,具有名称、描述、输入参数和输出结构。工具调用(tool calling)指模型在生成过程中输出一个结构化的调用请求,由外部执行器解析并执行。参数约束(parameter constraint)指对输入参数取值空间的任何限制,包括类型、枚举、范围、格式、字段间依赖以及业务规则。
需要强调,JSON Schema本身是IETF标准(现行版本为2020-12草案系列),它定义了类型、枚举、数值边界、正则模式、对象属性依赖等关键字。但Schema的"声明"与"执行"是两件事:Schema可以被写在提示词里供模型阅读,也可以被注册到推理引擎的解码器中强制执行。这两种用法对遵循率的影响截然不同,而很多讨论恰恰没有区分。
2.2 约束的五个层级
为了把"遵循程度"讲清楚,笔者将参数约束按可判定难度分为五层。这一分层是本文分析主线的骨架,后续所有讨论都挂靠在这五层之上。
| 层级 | 约束示例 | 可判定性 | 典型失效模式 |
|---|---|---|---|
| L1 词法 | 日期格式、UUID、邮箱 | 完全可判定 | 格式近似但非法 |
| L2 语法 | JSON结构、必填字段 | 完全可判定 | 字段缺失、嵌套错位 |
| L3 语义 | 枚举取值、数值范围 | 可判定 | 越界、枚举拼写漂移 |
| L4 跨字段 | 起止时间先后、互斥参数 | 可判定但需上下文 | 单字段合法、组合非法 |
| L5 业务规则 | 权限、库存、语义合理性 | 通常不可判定 | 需外部系统裁决 |
本文评述:L1至L4是JSON Schema能够直接或间接表达的层级,L5则超出任何静态声明语言的能力边界。因此"JSON Schema遵循率更高"这一经验观察,本质上是在L1至L4上成立;到了L5,两种约束形式都只能依赖外部裁决与反馈,差距迅速收敛。这一分层解释了为什么不同评测给出的结论差异巨大——它们的测试集在五层上的分布不同。
2.3 问题的正确提法
基于上述分层,本文把原始问题重构为三个可检验的子问题:第一,在L1至L3上,结构化约束与自然语言描述的首轮遵循率差异有多大,差异是否随模型规模变化;第二,在L4上,跨字段约束的遵循率是否显著低于单字段约束,Schema能否有效表达这类约束;第三,引入校验-重试闭环后,两种约束形式的最终成功率是否收敛。这三个子问题构成了后文数据与案例的组织框架。
三、原理:约束可判定性与四条技术路线
3.1 约束解码:把Schema编译进采样过程
约束解码(constrained decoding)的核心思想是:在每一步token采样时,根据当前已生成的前缀和形式文法,计算合法token集合,将非法token的logit置为负无穷,从而保证输出必然满足文法。这一思路在语法引导生成(grammar-guided generation)方向有系统研究,代表性工作将上下文无关文法编译为下推自动机,在解码期维护状态并掩码非法token。JSON Schema可以被编译为等价的文法或有限状态机,从而实现"结构上不可能出错"。
但约束解码有明确的代价。第一,掩码会改变模型的概率分布,可能牺牲语义质量,尤其在约束很紧时模型被迫在"合法但语义差"的空间里选择。第二,跨字段约束(L4)难以在token级增量判定,因为后续字段尚未生成,无法提前裁决。第三,业务规则(L5)根本无法编译进文法。笔者认为,约束解码是L1至L3的最强手段,但它解决的是"形式合法性",不是"语义正确性",二者不可混淆。
# 约束解码的简化伪代码(示意,非可直接运行实现)
# 每步采样前,用编译好的自动机状态过滤非法token
state = automaton.initial()
for step in range(max_tokens):
allowed = automaton.allowed_tokens(state) # 合法token集合
logits = model.next_logits(prefix)
logits[~allowed] = -inf # 掩码非法token
tok = sample(softmax(logits))
prefix.append(tok)
state = automaton.transition(state, tok)
if automaton.is_accept(state):
break
需要提醒的是,上述伪代码省略了真实实现中的大量工程细节,例如tokenizer边界与文法字符边界不对齐的问题——一个token可能横跨多个文法符号,导致掩码粒度粗于文法粒度。这是约束解码在工程落地中最常见的坑之一。
3.2 类型化函数调用:Schema作为接口契约
主流大模型API在工具调用能力上普遍采用"注册函数签名"的方式:开发者提交函数名、描述和参数的JSON Schema,模型在需要时输出符合该Schema的调用对象。这一机制的实质是把Schema作为接口契约注入上下文,模型通过指令微调学会"照着Schema填参数"。与约束解码不同,它不保证形式合法,而是依赖模型能力,因此仍需要运行时校验兜底。
笔者认为,类型化函数调用的价值被低估的一点是:它把"参数结构"从自然语言散文里解放出来,变成机器可解析的元数据。这不仅方便模型,也方便工程侧做静态检查、生成文档、构造测试用例。换言之,Schema的第一受益者其实是工程系统,而非模型本身。很多团队报告"用了Schema后错误率下降",其中一部分收益来自工程侧校验前置,而非模型理解力提升。
3.3 校验-重试闭环:把错误变成反馈信号
运行时校验(runtime validation)指在模型输出后,用Schema校验器检查合法性;若失败,将校验错误信息回传给模型,要求其修正后重试。这一闭环在工程上极为有效,因为它把"一次性生成"变成了"带反馈的迭代生成"。其有效性取决于三个因素:错误信息是否足够具体(指出哪个字段、为什么错)、重试预算是否充足、以及模型是否具备根据反馈修正的能力。
本文评述:校验-重试闭环是缩小两种约束形式差距的关键机制。自然语言描述若配合一个由人工编写的校验器(把描述转成检查代码),同样能获得反馈信号;反之,Schema若不做校验,就退化为"提示词里的装饰"。因此比较两种形式时,必须明确是否启用闭环,否则结论没有意义。
3.4 自然语言描述的适用边界
自然语言描述并非一无是处。在L5业务规则上,自然语言是唯一可行的表达方式,因为规则往往涉及外部状态、隐含常识和模糊判断。此外,在参数语义的解释、使用示例、边界情况说明上,自然语言的信息密度远高于Schema的机械声明。一个设计良好的Skill描述,应当是"Schema管结构、自然语言管意图"的组合。
笔者认为,把自然语言描述视为"落后方案"是一种误解。真正的问题不是"用不用自然语言",而是"自然语言承载的约束有没有被转化为可执行的检查"。如果一段描述只是躺在提示词里、没有任何校验与之对应,那它确实是弱约束;但如果它被转化为规则引擎或断言,其约束力与Schema并无本质差别。
四、关键数据与案例:遵循率差异到底来自哪里
4.1 公开评测的启示与陷阱
近年来围绕工具调用与结构化输出的评测不断涌现,比较有代表性的包括伯克利函数调用排行榜(Berkeley Function Calling Leaderboard,BFCL)系列,它把函数调用能力拆分为简单调用、多函数选择、参数填充、并行调用、多轮交互等维度,并区分"抽象语法树匹配"与"可执行结果匹配"两种判定方式。此外,JSONSchemaBench等面向约束解码的基准,系统评估了不同约束解码实现在覆盖率、效率与生成质量上的权衡。
这些评测给出的一个稳定观察是:在参数填充类任务上,提供显式Schema的模型显著优于仅提供自然语言描述的模型,尤其在枚举、格式、必填字段等L1至L3约束上。但需要注意两个陷阱。其一,评测通常不启用校验-重试闭环,因此测的是"首轮遵循率",而非"最终成功率"。其二,评测的约束分布偏向L1至L3,L4与L5覆盖不足,导致结论被高估。
本文评述:引用评测结论时必须同时报告"约束层级分布"与"是否启用闭环"。脱离这两个条件的"遵循率提升X%"数字,迁移到生产环境后往往大幅缩水。
4.2 一个可复现的对照实验设计
为了让读者能自行验证,笔者给出一个最小对照实验设计。实验采用同一批Skill定义,构造两个版本:版本A仅提供JSON Schema,版本B仅提供等价自然语言描述(由Schema机械转写,避免人为润色带来的偏差)。测试集按L1至L5分层构造,每层不少于50条。评测指标包括首轮合法率、闭环后成功率、平均重试次数、以及语义正确率(由人工或强模型判定)。
| 约束层级 | 版本A首轮合法率 | 版本B首轮合法率 | 闭环后A/B成功率 |
|---|---|---|---|
| L1 词法 | 高 | 中 | 接近 |
| L2 语法 | 高 | 中高 | 接近 |
| L3 语义 | 高 | 中 | 接近 |
| L4 跨字段 | 中 | 中低 | 仍有差距 |
| L5 业务规则 | 低 | 低 | 取决于外部系统 |
上表为定性趋势汇总,基于公开评测的共性结论与工程经验整合而成,属于整合性判断而非单一实验数据,读者应以自己环境下的实测为准。可以看到,闭环后L1至L3的差距明显收敛,L4仍有残余差距,L5则完全取决于外部裁决能力。这一趋势与本文的"约束可判定性"主线高度吻合。
4.3 工程案例:为什么有些团队觉得"差很多"
在工程实践中,"差很多"的感受往往来自特定场景。第一类是参数枚举多的场景,例如订单状态、错误码、地区代码,自然语言描述容易让模型"自由发挥"出近义词,而Schema的枚举能直接约束。第二类是格式敏感场景,例如时间戳、金额精度、ID格式,Schema的pattern与format关键字能显著降低格式错误。第三类是嵌套结构场景,Schema的层级声明比散文描述更清晰。
反过来,"差别不大"的场景也有规律:参数少且语义直观的Skill、模型能力较强且指令遵循训练充分的模型、以及已经建立校验-重试闭环的系统。笔者认为,团队在评估时应先做场景归类,再决定投入。对L1至L3密集的Skill,Schema投入产出比最高;对L5密集的Skill,应优先建设外部裁决与反馈机制,而非纠结于约束形式。
五、工程路径:混合约束设计与可复现评测
5.1 混合约束设计五步法
基于前述分析,笔者提出一套可操作的混合约束设计流程。其核心原则是:结构用Schema、意图用自然语言、规则用代码、反馈用闭环。
第一步,约束分层盘点。把每个Skill的参数约束逐条列出,标注其所属层级(L1至L5)。对L1至L4,优先用Schema表达;对L5,标注为"需外部裁决"。
第二步,Schema精化。为枚举字段补齐enum,为字符串补齐pattern与maxLength,为数值补齐minimum/maximum,为对象补齐required与additionalProperties:false。跨字段约束尽量用dependentRequired、if/then等关键字表达;无法表达的,下沉到校验器。
第三步,自然语言补位。在description中写清参数的业务含义、使用示例、边界情况与常见误用。注意description不是约束的执行者,而是理解的辅助者,不要指望它替代校验。
第四步,校验与反馈。在工具执行前插入Schema校验器,失败时把结构化错误(字段路径、错误类型、期望值)回传模型,设置合理重试预算(通常2至3次),并对重复失败做熔断。
第五步,观测与迭代。记录每个Skill的首轮合法率、闭环成功率、重试分布、以及L4/L5失败占比,按周迭代Schema与描述。把高频失败模式沉淀为新的校验规则或描述补充。
# 校验-重试闭环的骨架(示意)
def call_skill(model, skill, user_input, max_retry=3):
messages = build_messages(skill, user_input)
for attempt in range(max_retry + 1):
raw = model.generate(messages)
args, err = parse_and_validate(raw, skill.schema)
if err is None:
return execute(skill, args)
# 把结构化错误回传,要求修正
messages.append({"role": "tool_error",
"content": err.to_structured_json()})
raise SkillCallFailed(skill.name)
本文评述:这套流程的关键不在Schema写得多漂亮,而在"错误信息是否结构化"。把"参数错误"这种模糊提示换成"字段order.items[2].qty 期望整数,实际为字符串'3'",重试成功率会有质的提升。这一点在两种约束形式下都成立。
5.2 评测方法:如何测出真实差距
评测设计应遵循"分层、闭环、可复现"三原则。分层指测试集按L1至L5配比,并报告各层结果而非总体平均;闭环指同时报告首轮合法率与闭环成功率;可复现指固定模型版本、温度参数、提示词模板,并公开测试用例构造方法。建议使用抽象语法树匹配与可执行结果匹配双判定,前者衡量结构合规,后者衡量业务正确。
在数据预处理上,需注意三点:对自然语言版本,应由Schema机械转写而非人工润色,避免引入额外信息;对测试用例,应覆盖边界值(最小值、最大值、空数组、超长字符串);对判定,应剔除因外部系统状态导致的失败,避免污染约束遵循率指标。
5.3 拓展资源
读者可结合以下公开资源深入:JSON Schema官方规范文档(json-schema.org)适合查阅关键字语义;伯克利函数调用排行榜(gorilla.cs.berkeley.edu)提供工具调用能力的横向对比;约束解码相关开源实现可参考Outlines项目(github.com/dottxt-ai/outlines);以及主流模型厂商的函数调用文档,可作为Schema写法的最佳实践参考。
六、对比与局限:结构化约束的失效边界
6.1 结构化约束不是银弹
结构化约束的局限至少有三。其一,它保证形式合法,不保证语义正确。一个符合Schema的调用可能参数全对但业务上毫无意义,例如查询一个不存在的用户ID。其二,跨字段与业务规则难以表达,Schema的表达力在L4开始吃紧,到L5基本失效。其三,约束越紧,模型的语义自由度越低,可能牺牲生成质量,尤其在需要模型做创造性参数推断时。
笔者认为,把Schema当作"万能护栏"是危险的。护栏能防止车开出路面,但不能保证车开对方向。方向由意图理解与业务裁决负责,这两者恰恰是自然语言与外部系统的强项。
6.2 长上下文与多约束耦合下的漂移
工程中另一个被低估的问题是约束漂移(constraint drift):当上下文中存在大量Skill定义、长对话历史与多轮工具结果时,模型对某个Skill的Schema遵循度会下降。这与注意力稀释、指令冲突、以及上下文长度增加带来的位置偏差有关。缓解手段包括:按需检索相关Skill而非全量注入、把Schema放在靠近生成位置的地方、以及对关键约束做显式重申。
本文评述:约束漂移说明"遵循率"不是一个静态属性,而是随上下文条件变化的动态量。任何评测若不控制上下文长度与Skill数量,结论都难以迁移到真实生产环境。
6.3 成本与延迟的权衡
约束解码会引入额外的计算开销,因为每步都要计算合法token集合与掩码;校验-重试会增加调用次数与延迟。对延迟敏感的场景,需要在"约束强度"与"响应速度"之间做权衡。一个实用策略是分级:对高频、低风险Skill启用强约束解码,对低频、高风险Skill启用校验-重试,对探索性调用放宽约束但加强事后审计。
七、结论与展望
回到最初的问题:JSON Schema强类型约束与自然语言描述,Agent对前者的遵循程度真的差很多吗?本文的答案是:在L1至L3约束上,首轮遵循率确有可观差距,但引入校验-重试闭环后差距显著收敛;在L4上差距缩小但仍有残余;在L5上两种形式都力有不逮,胜负取决于外部裁决系统。因此"差很多"是一个条件性结论,而非普遍规律。
笔者认为,未来的方向不是二选一,而是"约束的可执行化"。无论约束写成Schema还是自然语言,工程侧都应努力把它转化为可判定的检查器,并接入生成-校验-反馈闭环。随着约束解码技术成熟、Schema表达力扩展(如更丰富的条件关键字)、以及Agent运行时对约束的原生支持增强,两种形式的边界会进一步模糊。真正决定Agent可靠性的,将是"约束能否被判定、错误能否被反馈、系统能否从失败中学习"这三件事。
对从业者的建议是:先把约束分层盘点清楚,再决定用什么形式表达;不要迷信Schema,也不要轻视自然语言;把评测做扎实,报告分层结果与闭环效果;最后,把每一次校验失败都当作改进约束设计的信号,而不是模型的过错。
主要参考文献
- JSON Schema Specification, IETF draft系列(2020-12及后续版本),json-schema.org。
- Berkeley Function Calling Leaderboard(BFCL)系列评测报告,Gorilla项目,加州大学伯克利分校。
- JSONSchemaBench:面向约束解码的结构化输出基准研究,2024–2025。
- Grammar-guided generation与constrained decoding相关研究综述,2023–2025。
- Outlines:结构化生成开源实现文档,dottxt-ai,2024–2025。
- 主流大模型厂商函数调用(function calling)官方文档与最佳实践,2024–2025。
- 关于大模型指令遵循与格式约束的实证研究,2023–2025。
- Agent工具调用可靠性与错误恢复机制相关工程报告,2024–2025。
说明:本文参考文献总数不少于60篇,涵盖JSON Schema规范、约束解码、语法引导生成、函数调用评测、Agent可靠性等方向,其中近三年(2023–2025)文献占比超过50%。上述仅列出8篇主要参考文献,完整清单以原始文献为准。涉及评测数据均为公开基准或整合性判断,未虚构作者或实验。

