文章

Structured Outputs 生产实战:用 Constrained Decoding 让大模型稳定输出 JSON

系统解析结构化输出在大模型应用中的核心价值,讲清 JSON Schema、约束解码、业务验证、失败回退和监控指标,帮助工程团队降低解析失败率,提升接口稳定性与上线可控性。

背景问题:不是所有”能解析”的 JSON 都能上线

很多大模型应用在原型阶段看起来很顺利——提示词里写一句”请返回 JSON”,模型大多数时候也的确会给出类似 JSON 的文本。问题通常出现在接入真实业务之后:

  • 字段偶尔缺失
  • 类型偶尔变化(stringnumber,或反过来)
  • 枚举值被模型自由改写
  • 字符串里混入解释性文本
  • 异常输入下模型干脆输出一段自然语言说明

这些问题在人工查看时并不明显,但在工程系统中会直接演变成 JSON 解析失败字段映射失败工具调用参数错误工作流状态机异常。如果系统依赖重试来修复格式,延迟和成本会被成倍放大;如果直接容错吞掉错误,又会把不确定性传导给下游服务。

Structured Outputs 的价值在于:把”请按格式回答”从一句软提示,升级为更接近接口契约的运行时约束。它通常以 JSON Schema、正则表达式、上下文无关文法(CFG)或函数参数 schema 的形式描述输出结构,再通过 Constrained Decoding 在生成过程中限制模型只能选择当前结构允许的 token。

这篇文章讨论的重点不是某个具体 API 的参数,而是生产环境中如何理解、选型、验证和监控结构化输出能力。


核心原理:Constrained Decoding 如何让输出遵守结构

大模型生成文本时,本质是在每一步根据上下文预测下一个 token。普通生成流程会计算整个词表的概率分布,然后根据采样策略选出一个 token。约束解码 在这个流程中插入了一层结构检查:

  1. 根据 JSON Schema、regex 或 grammar 构建解析状态机;
  2. 在每一步生成前,根据当前 parser state 推导哪些 token 仍然可能形成合法输出;
  3. 对不合法 token 做 mask,使其不可被采样;
  4. 采样合法 token 后,更新 parser state;
  5. 重复上述过程,直到生成完整结构。

可以把它理解成:模型负责内容,解码器负责边界。模型仍然决定字段值、文本内容和推理结果,但输出的格式外壳被 schema 或 grammar 严格限定。


Structured Outputs 不是 JSON Mode

二者常被混为一谈,但层次不同:

能力JSON ModeStructured Outputs
保证输出是合法 JSON
保证字段完整性❌ 不保证✅ 基于 schema 约束
保证字段类型正确❌ 不保证
保证枚举值合法❌ 不保证
保证对象层级符合业务结构❌ 不保证
保证业务语义正确

OpenAI 官方文档也明确区分了二者:JSON mode 关注合法 JSON,Structured Outputs 关注 schema adherence(对 schema 的遵守程度)。

⚠️ 重要提醒:schema 合规 ≠ 业务正确。例如模型返回的金额字段类型是 number,并不表示金额一定来自真实合同;返回的 risk_level 是枚举之一,也不表示分类一定准确。生产系统仍然需要业务验证、权限验证和任务级评估。


工程落地:从 Schema 到稳定接口

1. 先定义输出契约,而不是先写提示词

结构化输出应从接口契约开始设计。推荐在动手前写清楚:

  • 哪些字段是必须的(required);
  • 哪些字段允许为空;
  • 哪些字段必须是枚举;
  • 哪些字段需要保留原文证据;
  • 哪些字段只是模型判断,不能直接执行;
  • 哪些字段需要后端二次验证。

一个规模适中、职责单一、可上线的 schema 往往比一个复杂、嵌套极深、包含大量可选字段的 schema 更可靠。字段越多,模型需要同时满足的语义约束越多;结构越深,解码器和验证器的复杂度也越高。

{
  "type": "object",
  "additionalProperties": false,
  "required": ["category", "confidence", "evidence"],
  "properties": {
    "category": {
      "type": "string",
      "enum": ["billing", "technical", "account", "other"],
      "description": "The primary support category."
    },
    "confidence": {
      "type": "number",
      "minimum": 0,
      "maximum": 1,
      "description": "Model confidence between 0 and 1."
    },
    "evidence": {
      "type": "string",
      "description": "Short source phrase from the user request."
    }
  }
}

2. 在提示词中解释字段语义

约束解码保证结构,但字段值仍由模型生成。vLLM 文档也明确指出:虽然可以直接传入 JSON Schema,但 在 prompt 中说明 schema 及字段如何填充,会显著改善输出质量

因此,不要只把 schema 丢给推理服务,还要在系统提示词中说明:

  • 每个字段的业务含义;
  • 不确定时应该填什么(默认值 / null / 兜底枚举);
  • 是否允许猜测;
  • 是否必须引用原文证据;
  • 输出是否会被后续流程自动执行。

这一步的目标是减少”结构正确但语义错误”的结果。

3. 将结构化输出放进完整验证链路

生产系统中建议把输出处理拆成四层:

  1. 解码层约束:通过 JSON Schema、grammar 或 regex 限制输出结构;
  2. 解析层校验:确保结果能被稳定反序列化为目标对象;
  3. 业务层校验:检查金额范围、日期合法性、权限边界、枚举含义、跨字段逻辑一致性;
  4. 动作层保护:对会触发外部副作用的字段做人工确认、白名单或二次审批。
type ModelResult = {
  category: "billing" | "technical" | "account" | "other";
  confidence: number;
  evidence: string;
};

function validateBusinessRules(result: ModelResult): string[] {
  const errors: string[] = [];
  if (result.confidence < 0 || result.confidence > 1) {
    errors.push("confidence_out_of_range");
  }
  if (!result.evidence || result.evidence.length < 3) {
    errors.push("missing_evidence");
  }
  if (result.confidence < 0.6 && result.category !== "other") {
    errors.push("low_confidence_requires_safe_category");
  }
  return errors;
}

4. 自建推理服务要关注解码器后端

使用托管模型时,Structured Outputs 通常是 API 参数层面的选择。如果使用自建推理服务,就要关注运行时是否支持结构化输出,以及底层后端如何实现。

vLLM 支持 choiceregexjsongrammarstructural_tag 等多种结构化输出形式,并支持以 xgrammarguidance 作为后端引擎。Outlines 也强调同一套结构化生成接口可以对接 OpenAI、Ollama、vLLM 等推理入口,支持 JSON Schema、正则和上下文无关文法。

选型时不要只看”是否支持 JSON Schema”,还要压测:

关注点说明
冷启动延迟schema 编译是否拖慢首次请求
TPOT 影响token mask 计算是否会显著增加每 token 耗时
流式输出稳定性parser state 在流式场景下是否可靠
高并发缓存grammar cache 在高并发下是否命中有效
Tokenizer 差异不同模型的 tokenizer 是否存在边界差异
Schema 兼容性复杂 schema 是否触发后端不支持的关键字

适用场景与边界

✅ 最适合的场景

Structured Outputs 最适合那些”输出必须被程序消费”的场景:

  • 信息抽取:从邮件、合同、客服对话中抽取结构化字段;
  • 分类路由:把用户请求分流到不同队列或工具;
  • Agent 工具参数:生成可验证的函数调用参数;
  • UI 生成:把答案拆成卡片、步骤、风险提示等结构;
  • 数据清洗:将半结构化文本转成统一对象;
  • 审核系统:输出风险类型、证据片段和建议动作。

❌ 不太适合的场景

如果输出主要是长篇创作、开放式解释或强主观判断,过强的 schema 可能压缩表达空间,反而降低答案质量。此时可以只结构化关键元数据,把自然语言主体保留为字符串字段。


常见误区

误区一:Schema 越复杂越安全

复杂 schema 会增加约束解码的计算负担和模型语义填充的难度。大量可选字段、深层嵌套、复杂联合类型,在工程上更难测试,也更容易出现”字段存在但含义不稳定”的问题。

💡 更好的做法:按业务阶段拆分 schema——先让模型输出关键决策字段,再由后续步骤补充细节。

误区二:格式合规就可以自动执行

格式合规只是第一道门。任何会影响用户账户、订单、支付、权限、风控、理赔、医疗、法律判断的结果,都必须经过业务规则或人工确认。模型返回的 JSON 不能直接等同于可信事实。

误区三:失败时无限重试

结构化输出失败时,重试可以作为短期兜底,但不能成为唯一方案。无限重试会拉高延迟和成本,还可能掩盖 schema 设计问题。更稳妥的策略:

  • 小次数重试(建议 ≤ 3 次)
  • 降级到更宽松的 schema
  • 回退到 JSON mode
  • 转人工或返回可解释的错误

误区四:只看 Parse Failure,不看质量指标

parse failure 下降不代表产品质量提升。还要监控:

  • 字段准确率
  • 证据命中率
  • 分类一致性
  • 人工复核通过率
  • 用户纠错率

上线检查清单

Schema 设计

  • 字段名清晰,不使用模糊缩写
  • 关键字段都有 description
  • 对象默认禁止未声明字段(additionalProperties: false
  • 枚举值稳定,并有 other / unknown 兜底
  • schema 有版本号,并保留兼容策略
  • 大 schema 拆成多个小步骤独立测试

推理与性能

  • 记录 schema 编译耗时
  • 记录每 token mask 计算耗时
  • 对比开启和关闭约束后的 TTFT、TPOT、吞吐
  • 流式输出场景单独压测
  • 不同模型、不同 tokenizer 单独验证
  • 缓存 grammar 或 schema 编译结果

质量与安全

  • 保留一套固定评测集
  • 同时评估 schema match rate 与任务准确率
  • 对高风险字段增加证据要求
  • 对自动执行动作增加后端权限检查
  • 记录原始输出、解析结果、验证错误和回退路径
  • 灰度发布,支持按路由关闭结构化输出

监控指标体系

建议至少监控以下指标,并与模型版本、schema 版本、业务路由、请求长度、输出长度一起打标签,否则问题难以定位:

指标含义
structured_output_schema_match_rateSchema 匹配率
structured_output_parse_failure_total解析失败总数
structured_output_validation_failure_total业务校验失败总数
structured_output_schema_compile_msSchema 编译耗时(毫秒)
structured_output_mask_compute_msToken mask 计算耗时(毫秒)
structured_output_retry_total重试总数
structured_output_fallback_total降级回退总数
structured_output_task_accuracy任务准确率

总结

Structured Outputs 是连接大模型”自由生成”与业务系统”确定性消费”之间的关键桥梁。它不能解决所有问题——幻觉、语义错误、业务逻辑仍需额外的验证机制——但它能把格式层面的不确定性降到最低,让工程团队把精力集中在真正的业务质量上。

实施路径可以概括为:先定义契约 → 选型压测 → 四层验证 → 持续监控 → 灰度上线。遵循这一路径,结构化输出的投产成功率将大幅提升。


参考资料

  1. OpenAI Structured Outputs
  2. vLLM Structured Outputs
  3. Outlines Documentation
  4. XGrammar: Flexible and Efficient Structured Generation Engine for Large Language Models
  5. Generating Structured Outputs from Language Models: Benchmark and Studies / JSONSchemaBench
  6. SGLang: Efficient Execution of Structured Language Model Programs

常见问题

Structured Outputs 和 JSON mode 的区别是什么?
JSON mode 通常只保证模型输出是合法 JSON;Structured Outputs 会基于 JSON Schema 等约束进一步要求字段、类型和结构符合约定,但仍需要业务语义校验。
约束解码会不会影响推理性能?
会带来 token mask、语法状态更新和 schema 编译等额外开销。复杂 schema、深层嵌套、流式输出和高并发场景需要重点压测。
有了约束解码是否还需要后端校验?
需要。约束解码解决格式与结构问题,不能保证业务事实、枚举含义、权限边界、金额范围或跨字段逻辑完全正确。
Structured Outputs 能完全避免幻觉吗?
不能。它能约束输出格式和字段结构,但不能保证字段内容一定真实。要降低幻觉,需要结合检索证据、引用约束、业务验证、评测集和人工复核。