vLLM 0.31.0 的工具约束分三档,能解析不等于参数合规
固定 vLLM v0.31.0 源码,解释 auto、function、parameter 与逐工具 strict 的作用矩阵。工具调用外壳、参数 schema 和调用义务各有分支;72 个纯解析检查不等于模型或语法引擎实测。

复盘10月5日发布的 vLLM v0.31.0:能解析出工具名和参数,不代表参数已经符合工具声明的 schema。该固定版本的 --tool-strict-level 提供 auto、function、parameter 三档服务端下限,再与每个工具自己的 strict 叠加;其中 function 不会自动把所有工具的参数变严格。下面的矩阵针对支持结构标签的解析路径,不是所有模型、parser 和解码后端的通用保证。
排查这类错误时,先别急着换模型。一次调用究竟在哪层受到约束,可能比模型是否“会用工具”更接近问题所在。
一次库存工具调用,错在参数而不是 JSON
假设应用提供了 reserve_stock,用于预留库存。它要求商品编号为字符串、数量为整数,仓库只能选 east 或 west:
{
"type": "object",
"properties": {
"sku": {"type": "string"},
"quantity": {"type": "integer"},
"warehouse": {"type": "string", "enum": ["east", "west"]}
},
"required": ["sku", "quantity", "warehouse"],
"additionalProperties": false
}现在设想解析器返回的参数是:
{"sku": "A-17", "quantity": "2", "warehouse": "north"}这是一个合法 JSON,但数量写成了字符串,仓库又不在枚举里。工具名可以对,调用标记可以闭合,业务代码仍会拒绝执行。这个例子是自拟假设,不是本站模型测试输出。

这里至少有两个需要分别配置的层:调用外壳,也就是模型特有标记与函数名;参数 schema,也就是类型、必填字段和枚举等声明。vLLM 的固定版文档明确把调用义务、结构标签激活和参数 schema 约束分开处理。(v0.31.0 工具调用文档:约束解码行为)
本文核对的是 10 月 5 日正式发布的 vLLM v0.31.0,源码固定到提交 db9527a46873454610df6dbedf79a36d6bf1a7f6,不是浮动的 latest。10 月 20 日的运营方工程报道也讨论了配置对工具调用的影响;其准确率数据不作为本站复测结论。
三档下限与逐工具 strict,要放进同一张矩阵
先分清两个名字相同、位置不同的 auto。请求里的 tool_choice="auto" 让模型自行决定是否调用工具;服务端的 --tool-strict-level auto 则决定怎样沿用请求提供的严格性。它们不是同一个开关。
以下比较两个工具 A、B 的几种声明组合。在 tool_choice="auto"、结构标签路径可用、全局门控开启,且未另设 response_format 或其他 structured_outputs 约束的前提下,解析规则如下:
| 服务端下限与工具声明 | 结构标签是否激活 | 哪些工具进入参数严格分支 |
|---|---|---|
auto,所有工具均未设 true | 不激活 | 无 |
auto,A 为 true、B 非严格 | 激活调用外壳 | 仅 A |
function,所有工具均未设 true | 激活调用外壳 | 无 |
function,A 为 true、B 非严格 | 激活调用外壳 | 仍仅 A |
parameter,包括显式 false | 激活调用外壳 | 所有工具 |
把 A 换成前面的库存预留工具,再加一个未声明严格的商品搜索工具 B,就能看到作用域差别:在默认下限下,A 的声明足以让这次请求激活结构标签,但搜索工具的参数不会因此采用 A 的规则。把服务端改成 function,也不等于把搜索参数补成严格模式。
如果两项工具都没声明严格,差别则发生在更早的一步:默认下限不会为这个自动选择请求激活结构标签,function 才会把外壳这一层抬起来。排查“明明传了工具定义,为何仍然自由生成”时,应先看这一道门,而不是只检查定义里的字段是否齐全。
因此,给 A 加 strict 不会让邻居 B 一起严格。function 可以在客户端没有任何严格工具时抬高外壳约束,但它不会替客户端补齐全部参数要求;原本已经严格的工具也不会被它放松。
parameter 的影响更强:解析逻辑把所有工具视为严格,包括客户端显式写了 strict: false 的工具。如果调用方原本依靠这一项保持参数宽松,服务端改档就可能改变接口合同。(固定源码:逐工具严格性解析)
调用义务还要另看。tool_choice="auto" 不强制调用工具;未另设输出格式约束时,即使激活工具约束,也允许纯文本回复。另设输出格式时,非工具回复仍需满足该格式。提高到 parameter 不会强迫模型一定调用库存工具。结构标签路径里的 required 或指定函数会激活调用外壳,但参数仍按逐工具 strict 与下限决定。none 或没有工具时,则不生成工具结构标签。
这张表描述的是严格性解析选择,不是保证每个模型专有格式、每个 schema 关键字都具备相同的运行时约束能力。
flag 不是万能开关,要沿 parser 与后端走完整条路
自动工具选择需要 --enable-auto-tool-choice,以及与模型匹配的 --tool-call-parser。三档下限要作用到这里讨论的结构标签路径,还要求所选 parser 和后端具备相应能力。
固定版本中,KimiK2ToolParser 声明了 structural_tag_model = "kimi",是一个具名的源码例子。这证明存在对应接线,不等于任意 Kimi 权重、模板、量化格式和部署组合都已经验证兼容。
服务端参数也不是只写在帮助文本里:固定代码将它传给 renderer 和 derenderer,再由 ParserManager 转为枚举,传入解析类。但这条静态调用链只能证明配置接上了,不能代替实际服务测试。
另一个优先级更高的门是 VLLM_ENFORCE_STRICT_TOOL_CALLING,默认开启。设为 false 时,工具解析器不再附加结构标签,优先于服务端下限。即使你选择 parameter,也不能绕过这个关闭条件。
但它不是“关闭 vLLM 全部 schema 约束”。固定源码还保留了独立的、从工具 schema 派生 JSON 约束的 required/指定函数路径,这一路不由该环境变量关闭。不能把结构标签路径里“非严格工具参数较宽松”的规则,套到所有 parser 的所有调用方式。(固定源码:结构标签门控与独立 JSON 路径)

还要留意请求同时带工具和 response_format 的情况。固定代码对请求的 tool_choice="auto" 分支有明确回退:如果 xgrammar 不支持相应的结构化输出约束,会警告并保留输出格式约束,而不是继续保证工具也受约束。若该组合的格式受支持,则工具调用与指定格式构成两个可选分支,非工具回复仍按指定格式生成,不等于可自由输出纯文本。看到某种 JSON 输出正常,不足以证明工具这一支同样开启了严格性。(固定源码:auto 与结构化输出的组合分支)
72 个 case 验证的是解析选择,不是模型准确率
为了核对矩阵,本批研究执行了一项很窄的检查:从固定提交中原样提取四个纯解析函数与枚举,用标准库 dataclass 模拟工具对象的 strict 和复制协议。结果是 72 个 resolver case 与 5 项枚举检查通过。
它覆盖了 Chat/Responses 形状、三档下限、不同工具选择、strict 缺省/真假和混合工具,还检查了原请求对象没有被原地修改。这支持的是解析逻辑如何选择严格性,不是完整客户端协议已经兼容。
本批没有导入整个 vLLM 模块,没有运行上游 pytest,没有编译 xgrammar,也没有做语法接受串、真实模型生成或服务集成测试。全局门控与 parser 接线只是静态查源。上游确实有对应的结构标签与严格性测试定义,但读过测试不等于运行通过。
所以,这 72 个 case 不能换算成“模型 schema 准确率 100%”,也没有测出性能开销、任务成功率或某个量化版本的表现。固定到正式版本,让配置解释有了可核对的依据;它没有把证据范围扩大到未测的地方。
先决定哪些工具必须严格,再测自己的 schema
回到库存工具。如果问题是调用标记或函数名不稳定,应先检查结构标签前提,再考虑 function;如果服务端要统一要求所有工具的参数严格,应评估 parameter;它会覆盖客户端显式设置的 strict: false,改档前应列出受影响的调用方并逐一回归。如果只有部分工具需要严格,逐工具声明比假设它会传染给整个请求更准确。
schema 本身也要适合严格路径。固定文档建议采用严格 schema 风格:对象设置 additionalProperties: false,把 properties 中的字段列入 required,可选字段通过允许 null 表达。这是兼容性建议,不是任意 schema 写法或关键字都已经测试过的保证。
为库存工具准备验证样例时,可以把目标拆成两组:一组检查数量类型、仓库枚举、缺失字段和额外字段;另一组使用格式完全正确但不存在的商品编号,或超过实际库存的数量。前一组用于观察参数约束有没有按预期生效,后一组检查业务是否拒绝错误动作。两组不能合成一个笼统的“工具调用成功率”。
还应留一条无需调用工具的问题,例如“解释预留库存是什么意思”。当请求的 tool_choice="auto" 且未另设输出格式约束时,纯文本回答是合法分支,不应因没有工具调用就被判为配置失效。这样才能分清“本来不必调用”与“应该调用却走错路径”。
执行前,应用仍应按 schema 验证参数,再确认商品存在、库存够用,以及用户是否获准预留。这里不是再讲一遍抽象的“语法不等于语义”,而是把责任落回具体位置:服务端选择约束范围,客户端声明参数合同,业务层校验事实与权限。
上线前记录版本、parser、tool_choice、服务端下限、逐工具 strict、全局变量和输出格式组合,再用自己的模型与工具验证。别只问“strict 开了吗”;要问这次请求走了哪条路径、哪一个工具的哪一层真正受到了约束。