核心摘要

约束解码在大模型生成 Token 时强制执行输出语言。它可以让结果符合受支持的 JSON Schema、语法、正则或选项集合,但不能保证字段值真实或操作安全。生产系统需要完整输出契约:约束编译、拒答与截断处理、应用层校验、可观测性,以及高风险流程的失败关闭策略。

约束解码改变了什么

约束解码改变的是格式控制发生的位置。仅靠提示词,是请求模型主动配合;生成后校验,则是在模型已经输出后拒绝或修复;约束解码直接阻止不合法的下一 Token 被采样。

普通解码在第 t 步从模型概率分布采样:

text
P(token_t | prompt, token_1 ... token_t-1)

约束解码会把该分布与当前语法状态允许的 Token 求交集:

text
allowed = grammar.valid_tokens(generated_prefix)
masked_logits[token not in allowed] = -infinity
token_t = sample(masked_logits)

关键是当前语法状态。引擎不是只在最后检查字符串能否解析,而是持续判断当前前缀能否继续扩展成约束语言中的合法结果。

flowchart LR A["JSON Schema 或语法"] --> B["编译约束"] C["模型 logits"] --> D["Token 屏蔽"] B --> D D --> E["采样合法 Token"] E --> F["推进语法状态"] F --> D E --> G["完成结构化输出"]

提示词、JSON 模式、Schema 与校验

这些机制解决的问题不同,不能互相替代。

机制 能约束什么 主要失败方式
提示词格式要求 模型遵循示例的意愿 出现说明文字、缺字段、类型错误
JSON 模式 通常保证 JSON 可解析 可能返回任意合法形状
Schema 引导解码 受支持的结构约束 结构正确但字段值错误
应用层校验 领域规则与跨字段规则 需要维护确定性逻辑
证据校验 主张支持与来源 需要来源和评估策略

结构化输出是应用目标,JSON 模式和约束解码是实现机制。JSON Schema 描述契约,而厂商的解码后端决定能执行哪些子集。

OpenAI Structured Outputs 文档说明了受支持模型的严格 JSON Schema 输出。NVIDIA NIM 则记录了 guided JSON、正则、上下文无关文法和固定选项,同时提示不同后端的支持范围与回退行为。因此,只说某个平台“支持结构化输出”并不足以证明兼容性。

Schema 如何变成 Token 规则

Schema 引导生成通常经历四层处理:

  1. 规范化约束。 解析引用,验证受支持关键字,拒绝歧义或不支持的结构。
  2. 编译识别器。 把 Schema 或语法转换为有限状态表示、下推自动机或优化匹配器。
  3. 映射到分词器 Token。 一个 Token 可能表示一个字符、多个字符或 Unicode 序列的一部分,引擎要判断其文本是否为合法延续。
  4. 屏蔽并推进。 每一步阻止非法 Token,完成采样后更新识别器状态。

由此会产生实际边界:

  • Schema 本身合法,却超出厂商支持子集;
  • 同一正则在某个后端可编译,在另一个后端失败;
  • 分词器词表可能让简单约束产生较高计算成本;
  • 空对象可能满足 JSON 模式,却不满足应用所需字段;
  • 流式消费者收到的是暂时无法解析的前缀;
  • 安全拒答或 Token 上限可能让请求没有产生 Schema 实例。

设计生产级输出契约

生产契约必须定义正常路径之外的行为。Schema 应保持狭窄,并显式表达不确定数据,不能迫使模型编造值。

json
{
  "type": "object",
  "properties": {
    "ticket_id": { "type": ["string", "null"] },
    "priority": {
      "type": "string",
      "enum": ["low", "medium", "high", "unknown"]
    },
    "evidence_ids": {
      "type": "array",
      "items": { "type": "string" }
    }
  },
  "required": ["ticket_id", "priority", "evidence_ids"],
  "additionalProperties": false
}

执行器还要完成确定性校验:

typescript
type Ticket = {
  ticket_id: string | null;
  priority: "low" | "medium" | "high" | "unknown";
  evidence_ids: string[];
};

function validateForEscalation(
  ticket: Ticket,
  visibleEvidenceIds: Set<string>,
): void {
  if (ticket.priority === "high" && ticket.evidence_ids.length === 0) {
    throw new Error("high_priority_requires_evidence");
  }
  for (const id of ticket.evidence_ids) {
    if (!visibleEvidenceIds.has(id)) {
      throw new Error("unavailable_evidence");
    }
  }
}

解码器无法保证这两条规则。它不知道调用者有权查看哪些证据,Schema 也不能证明高优先级判断有事实支持。

延迟与吞吐权衡

约束解码可以减少修复重试,但也会增加推理路径工作,应同时测量收益和成本。

成本 观测指标 缓解方式
Schema 编译 冷启动延迟、缓存命中率 规范化并缓存已审查 Schema
Token 屏蔽 每个输出 Token 的耗时 使用简单约束和优化后端
语法分支 合法 Token 集合大小 使用有界枚举和浅层对象
强制输出变长 输出 Token 与完成延迟 让契约保持任务专用
减少重试 解析与 Schema 失败率 比较端到端成功率

不要假设约束越严格就一定越快。有些后端优化了常见 JSON Schema 路径,复杂正则或语法组合则可能回退到较慢实现。测试必须覆盖实际模型、分词器、后端、Schema 家族、流式模式和生产并发。

失败模式与安全降级

以下结果必须被视为不同状态:

结果 正确处理
不支持的 Schema 在部署或请求校验阶段拒绝
约束编译失败 返回类型明确的基础设施错误
模型拒答 保留为一等返回状态
达到 Token 上限 标记输出不完整,不能按成功解析
结构合法但语义无效 返回领域错误或进入人工复核
后端静默丢弃约束 通过一致性探针检测并失败关闭

对于只展示给人工的低风险草稿,降级为提示词 JSON 可能可以接受;如果结果会触发支付、权限变更、数据库写入或外部消息,则不能降级。无法执行契约时必须停止操作。

测试与可观测性

约束层测试应与模型质量测试分开。

结构一致性测试

  • 最小、典型和最大合法对象;
  • Unicode、转义文本、空数组和长字符串;
  • 不支持的关键字与递归 Schema;
  • 拒答、取消、超时和截断;
  • 流式拼接与消费者中途断开;
  • 后端升级与分词器变更。

语义测试

  • 类型正确但值错误;
  • 字段互相矛盾;
  • 编造标识符;
  • 引用未授权资源;
  • 缺少证据;
  • 对抗文本要求模型修改 Schema。

遥测应记录 Schema 版本与摘要、后端、模型修订、编译结果、首 Token 延迟、输出 Token 延迟、结束原因、拒答状态、校验结果和降级路径。默认不要记录敏感原始负载。

上线决策清单

当下游代码需要狭窄的机器可读契约,且实际后端能稳定支持时,适合使用约束解码。最终输出只是供人阅读的自然语言时,不必强行增加这层复杂度。

上线前应确认:

  1. 厂商支持的 Schema 或语法子集。
  2. 约束像生产代码一样被版本化和审查。
  3. 拒答、截断和不支持 Schema 的结果协议。
  4. 解析后继续验证领域规则与权限。
  5. 在生产并发下测试冷、热 Schema。
  6. 为后端升级添加结构一致性探针。
  7. 高影响操作禁止静默降级。

工具执行还要结合函数调用指南中的宿主授权模型。一般 JSON 校验可参考 JSON Schema 校验指南

常见问题

约束解码能强迫模型回答吗?

不能。安全拒答、信息不足、取消和 Token 上限仍然存在。响应协议应显式表示这些状态,而不是把它们强塞进正常对象。

语法一定比 JSON Schema 更好吗?

取决于目标语言。JSON Schema 适合类型化应用对象;上下文无关文法适合非 JSON 的 DSL;正则只适用于有界正则语言,嵌套结构会很难维护。

应该动态生成 Schema 吗?

只应在严格边界内使用。动态 Schema 会扩大编译缓存、攻击面和兼容矩阵。优先使用经过审查的模板、有界参数、大小限制和规范化。

约束解码可以与函数调用结合吗?

可以。很多厂商用 Schema 约束生成工具参数,但模型仍只是提出操作,可信代码必须在执行前校验并授权精确调用。

如何发现厂商静默降级了约束?

运行无限制模型容易违反的反向探针,记录后端与功能开关,并独立校验每个响应。请求返回成功不代表约束确实被执行。

参考资料