# vLLM 0.31.0 的工具约束分三档，能解析不等于参数合规

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

- Published: 2026年10月31日
- Author: PiRouter Team, Engineering
- Tags: agents, models, reliability
- Canonical: https://pirouter.ai/zh/blog/vllm-tool-strictness-schema-not-semantics

---
复盘10月5日发布的 vLLM v0.31.0：能解析出工具名和参数，不代表参数已经符合工具声明的 schema。该固定版本的 `--tool-strict-level` 提供 `auto`、`function`、`parameter` 三档服务端下限，再与每个工具自己的 `strict` 叠加；其中 `function` 不会自动把所有工具的参数变严格。下面的矩阵针对支持结构标签的解析路径，不是所有模型、parser 和解码后端的通用保证。

排查这类错误时，先别急着换模型。一次调用究竟在哪层受到约束，可能比模型是否“会用工具”更接近问题所在。

## 一次库存工具调用，错在参数而不是 JSON

假设应用提供了 `reserve_stock`，用于预留库存。它要求商品编号为字符串、数量为整数，仓库只能选 `east` 或 `west`：

```json
{
  "type": "object",
  "properties": {
    "sku": {"type": "string"},
    "quantity": {"type": "integer"},
    "warehouse": {"type": "string", "enum": ["east", "west"]}
  },
  "required": ["sku", "quantity", "warehouse"],
  "additionalProperties": false
}
```

现在设想解析器返回的参数是：

```json
{"sku": "A-17", "quantity": "2", "warehouse": "north"}
```

这是一个合法 JSON，但数量写成了字符串，仓库又不在枚举里。工具名可以对，调用标记可以闭合，业务代码仍会拒绝执行。这个例子是自拟假设，不是本站模型测试输出。

![PiRouter自拟库存工具参数示例，quantity字符串与warehouse枚举不符合声明](/blog/images/vllm-tool-strictness-schema-not-semantics-fig-assumed-parameters.png "PiRouter编辑自拟的库存工具假设示例：JSON可解析但参数类型与枚举不符，不是模型输出、日志或官方UI。规则依据：[vLLM v0.31.0工具调用文档：约束解码行为](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/docs/features/tool_calling.md)")

这里至少有两个需要分别配置的层：调用外壳，也就是模型特有标记与函数名；参数 schema，也就是类型、必填字段和枚举等声明。vLLM 的固定版文档明确把调用义务、结构标签激活和参数 schema 约束分开处理。（[v0.31.0 工具调用文档：约束解码行为](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/docs/features/tool_calling.md)）

本文核对的是 10 月 5 日正式发布的 [vLLM v0.31.0](https://github.com/vllm-project/vllm/releases/tag/v0.31.0)，源码固定到提交 `db9527a46873454610df6dbedf79a36d6bf1a7f6`，不是浮动的 latest。10 月 20 日的[运营方工程报道](https://www.ubicloud.com/blog/fixing-tool-call-accuracy-in-vllm)也讨论了配置对工具调用的影响；其准确率数据不作为本站复测结论。

## 三档下限与逐工具 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` 的工具**。如果调用方原本依靠这一项保持参数宽松，服务端改档就可能改变接口合同。（[固定源码：逐工具严格性解析](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/vllm/tool_parsers/structural_tag_registry.py)）

调用义务还要另看。`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 路径](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/vllm/tool_parsers/abstract_tool_parser.py)）

![PiRouter编辑制作的vLLM parser能力、全局门控及独立JSON约束边界检查卡](/blog/images/vllm-tool-strictness-schema-not-semantics-fig-parser-boundaries.png "PiRouter依据固定源码编辑整理的检查清单：工具结构标签门控与独立JSON约束分开，auto不强制工具调用；72个resolver case不是模型准确率。这不是运行轨迹或完整服务测试。来源：[vLLM v0.31.0工具调用文档](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/docs/features/tool_calling.md)、[固定源码：结构标签门控与独立JSON路径](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/vllm/tool_parsers/abstract_tool_parser.py)")

还要留意请求同时带工具和 `response_format` 的情况。固定代码对请求的 `tool_choice="auto"` 分支有明确回退：如果 xgrammar 不支持相应的结构化输出约束，会警告并保留输出格式约束，而不是继续保证工具也受约束。若该组合的格式受支持，则工具调用与指定格式构成两个可选分支，非工具回复仍按指定格式生成，不等于可自由输出纯文本。看到某种 JSON 输出正常，不足以证明工具这一支同样开启了严格性。（[固定源码：auto 与结构化输出的组合分支](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/vllm/parser/abstract_parser.py)）

## 72 个 case 验证的是解析选择，不是模型准确率

为了核对矩阵，本批研究执行了一项很窄的检查：从固定提交中原样提取四个纯解析函数与枚举，用标准库 dataclass 模拟工具对象的 `strict` 和复制协议。结果是 **72 个 resolver case 与 5 项枚举检查通过**。

它覆盖了 Chat／Responses 形状、三档下限、不同工具选择、`strict` 缺省／真假和混合工具，还检查了原请求对象没有被原地修改。这支持的是解析逻辑如何选择严格性，不是完整客户端协议已经兼容。

本批没有导入整个 vLLM 模块，没有运行上游 pytest，没有编译 xgrammar，也没有做语法接受串、真实模型生成或服务集成测试。全局门控与 parser 接线只是静态查源。上游确实有对应的[结构标签与严格性测试定义](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/tests/tool_parsers/test_structural_tag_registry.py)，但读过测试不等于运行通过。

所以，这 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 开了吗”；要问这次请求走了哪条路径、哪一个工具的哪一层真正受到了约束。

---

## Sources

### github.com

- [vLLM v0.31.0工具调用文档：约束解码行为](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/docs/features/tool_calling.md)
- [vLLM v0.31.0](https://github.com/vllm-project/vllm/releases/tag/v0.31.0)
- [固定源码：逐工具严格性解析](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/vllm/tool_parsers/structural_tag_registry.py)
- [固定源码：结构标签门控与独立 JSON 路径](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/vllm/tool_parsers/abstract_tool_parser.py)
- [固定源码：auto 与结构化输出的组合分支](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/vllm/parser/abstract_parser.py)
- [结构标签与严格性测试定义](https://github.com/vllm-project/vllm/blob/db9527a46873454610df6dbedf79a36d6bf1a7f6/tests/tool_parsers/test_structural_tag_registry.py)

### ubicloud.com

- [运营方工程报道](https://www.ubicloud.com/blog/fixing-tool-call-accuracy-in-vllm)
