背景问题:为什么”请输出 JSON”不够用
在大模型应用里,很多场景并不需要一段自然语言,而是需要一份能被程序直接消费的结构化结果。例如:
- 从邮件中抽取客户姓名、订单号和问题类型;
- 把客服对话分类成固定枚举;
- 为 Agent 生成下一步工具调用参数;
- 从合同、保单、发票里抽取字段并落库;
- 给前端页面返回一个可渲染的对象结构。
早期做法通常是在 Prompt 里写一句:“请严格输出 JSON,不要输出多余解释。“这在 Demo 阶段看起来够用,但进入生产后会遇到几个典型问题:
- 模型偶尔会在 JSON 外面包一层 Markdown 代码块;
- 必填字段缺失,或者字段名拼错;
- 枚举值不在业务允许范围内;
- 数组、嵌套对象、nullable 字段在复杂场景下不稳定;
- 下游解析失败后只能重试,增加延迟和成本。
OpenAI 的 Structured Outputs 文档明确把它定义为让模型响应遵守给定 JSON Schema 的能力,并说明它相对于 JSON mode 的关键差异:JSON mode 只保证有效 JSON,而 Structured Outputs 还要求 Schema adherence。vLLM、Outlines、Guidance、XGrammar、SGLang 等开源体系也都围绕 structured generation / constrained decoding 提供了工程实现。
这篇文章讨论的核心问题是:结构化输出不是更强硬的 JSON Prompt,而是一套把格式约束前移到解码过程的工程机制。
核心原理:从”生成后修复”到”生成时约束”
Prompt 约束的本质问题
只靠 Prompt 时,模型仍然是在完整词表上自由采样。所谓”请输出 JSON”只是自然语言指令,不能阻止模型生成非法 token。输出结果是否能解析,依赖模型本身的指令遵循能力、上下文质量、采样参数和任务难度。
这种方式的问题不是完全不可用,而是可靠性不可证明。你可以用解析器、正则、重试、修复模型来补救,但这些都发生在生成完成之后。一旦输出已经坏掉,下游系统只能承担额外成本。
Constrained Decoding 的基本思想
Constrained Decoding 的思路是:在每一步生成 token 时,根据 Schema、正则或语法规则计算当前状态下哪些 token 合法,然后屏蔽非法 token,只允许模型在合法集合里选择。
可以把它理解成三层结构:
| 层级 | 职责 |
|---|---|
| 约束描述层 | JSON Schema、regex、choice、context-free grammar、EBNF |
| 状态机 / 语法执行层 | 根据当前已生成内容判断下一步合法 token |
| 模型解码层 | 在合法 token 集合中继续采样或贪心选择 |
这样做的直接收益是:模型不会在格式层面偏离结构。例如,当 Schema 要求字段 priority 只能是 low | medium | high 时,解码器可以在对应位置只允许这些值相关 token 出现。
JSON Schema 为什么成为主流接口
JSON Schema 适合结构化输出,不只是因为 JSON 常见,还因为它能表达业务接口里常见的约束:
- 字段类型:
string、number、integer、boolean、array、object - 必填字段:
required - 枚举值:
enum - 嵌套结构:
properties、items - 附加字段限制:
additionalProperties - 简单范围:
minimum、maximum、minLength、maxLength
OpenAI 的 Structured Outputs、vLLM 的 structured_outputs、Outlines 的 JSON generation、Guidance 的 JSON Schema 生成,都把 JSON Schema 作为重要入口。JSONSchemaBench 论文也指出,JSON Schema 已经成为约束解码框架中标准化程度很高的结构化数据格式。
工程实现:不同框架解决的是同一个问题吗
OpenAI Structured Outputs:托管 API 下的 Schema 约束
OpenAI Structured Outputs 的适用场景很直接:开发者给出 JSON Schema,模型返回符合结构的响应。文档中区分了两类用法:
| 用法 | 适用场景 |
|---|---|
| function calling | 模型要调用业务工具、函数、数据库或 UI 操作时 |
| response_format / text.format | 模型最终回复本身需要结构化时 |
这对应用开发很重要。比如”创建订单”更像 function calling,因为模型要把参数交给业务函数;而”抽取合同中的关键字段并返回 JSON”更像 response_format,因为模型的最终输出就是结构化对象。
vLLM structured_outputs:服务端推理框架里的约束解码
vLLM 文档说明,它支持通过 structured_outputs 生成结构化结果,并支持 choice、regex、json、grammar、structural_tag 等参数。旧字段如 guided_json、guided_regex 已经迁移到新的 structured_outputs 形式。
这说明结构化输出已经不是单独的上层 SDK 功能,而是逐渐进入推理服务框架的标准能力。对于自部署模型,vLLM 的价值在于可以把结构化输出放在 OpenAI-compatible server 里统一提供,而不是让每个业务系统自己写解析和重试逻辑。
Outlines:用类型定义结构,用解码保证结构
Outlines 的定位更偏”结构化生成库”。它强调直接在生成过程中保证结构,而不是生成之后再解析。它支持 JSON Schema、正则和上下文无关语法,并能和 OpenAI、Ollama、vLLM、Transformers 等后端结合。
一个常见工程模式是:用 Pydantic 或类型定义业务对象,再让结构化生成库把这个对象转换为约束。这样可以减少”接口文档一份、Prompt 一份、后端 DTO 一份”带来的不一致。
Guidance 与 XGrammar:约束执行效率是生产关键
Guidance 的文档提到,JSON Schema 可以视作一种上下文无关语法,并可用于约束 LLM 生成。它还介绍了 fast-forwarding:当语法约束下某些 token 已经确定时,不必让模型逐 token 生成,可以直接填充,减少前向计算。
XGrammar 则更关注结构化生成的效率、可移植性和低开销。其项目介绍强调通过 constrained decoding 确保结构正确,支持 JSON、regex、自定义 CFG,并面向不同硬件和推理引擎集成。
这类优化说明一个事实:结构化输出的瓶颈不只在模型本身,还在每步 token mask 的计算成本。 当服务端批量很大、Schema 很复杂、输出很长时,约束执行层可能成为新的性能瓶颈。
工程落地:一条更稳的结构化输出链路
1. 先区分”格式正确”和”业务正确”
Constrained Decoding 可以提升格式合规性,但不能保证业务语义一定正确。例如:
{ "claim_type": "medical", "confidence": 0.98, "reason": "用户提到了住院费用" }
这段 JSON 可能完全符合 Schema,但 claim_type 是否正确仍然要看原文证据、业务规则和模型理解。上线时要把结构化输出拆成两层校验:
- 结构校验:是否符合 JSON Schema;
- 业务校验:字段值是否和原文、数据库、规则引擎一致。
不要因为 Schema 通过,就直接执行高风险动作。
2. Schema 要面向模型生成,而不是只面向数据库
很多团队会直接把数据库表结构或后端 DTO 转成 JSON Schema,这通常不够理想。原因是数据库字段更关注存储,而模型生成更关注理解和可判定性。
更推荐的 Schema 设计原则:
- 字段名尽量语义清楚,例如
customer_complaint_summary比desc更好; - enum 值保持稳定、短小、互斥;
- required 字段不要过多,避免模型在证据不足时被迫编造;
- 对不确定字段显式允许
null或unknown; - 给高风险字段增加
evidence或source_span,方便人工复核; - 不要把过深嵌套一次性塞给模型。
2026 年关于 Schema key wording 的研究提出了一个值得注意的现象:Schema key 的措辞本身可能成为一种隐式指令通道,影响模型在约束解码下的行为。这意味着字段名不是纯技术细节,而会影响生成质量。
3. 复杂任务不要只靠一次结构化生成
对于复杂业务,建议拆成多阶段:
原文输入 -> 证据片段抽取 -> 字段候选生成 -> 结构化输出 -> 业务规则校验 -> 低置信度进入人工复核
不要让模型一次性完成”阅读长文档 + 推理 + 抽取 + 分类 + 校验 + 输出最终动作”。结构化输出越靠近最终业务动作,越需要中间证据链。
4. 失败处理要显式设计
即使使用 Structured Outputs,也要设计失败兜底。常见失败包括:
- 当前模型或后端不支持某些 JSON Schema 特性;
- Schema 太复杂导致解码变慢;
- 模型在合法 token 空间里仍然选出语义差的答案;
- 安全拒答导致输出结构与正常路径不同;
- 框架升级后字段名或参数形式变化。
推荐做法是把失败分级:
type StructuredOutputStatus =
| "ok"
| "schema_validation_failed"
| "business_validation_failed"
| "model_refusal"
| "timeout"
| "unsupported_schema"
| "fallback_used";
业务系统不要只拿一个 JSON.parse() 的异常来判断全部失败原因。
适用场景:哪些地方应该优先使用结构化输出
强适用场景
结构化输出最适合这些场景:
- 信息抽取:从文本中抽取字段、日期、金额、联系人、保单号;
- 分类与路由:把用户问题映射到固定业务队列;
- Agent 工具调用:生成函数参数、API 参数、数据库查询条件;
- 前端 UI 渲染:输出卡片、表单、步骤条需要的结构;
- 批处理自动化:大量文档处理时减少解析失败率。
谨慎使用场景
下面这些场景不应只依赖结构化输出:
- 法律、医疗、保险理赔等高风险判断:需要规则引擎和人工复核;
- 复杂数学推理:结构合规不代表推理正确;
- 长链路 Agent 自动执行:工具参数正确不代表动作安全;
- Schema 极复杂的接口:可能带来延迟、兼容性和维护成本;
- 模型本身能力不足的任务:约束会限制格式,但不会补齐知识与推理能力。
常见误区
误区一:用了 JSON Schema 就不需要后端校验
错误。JSON Schema 主要解决格式和局部约束,不替代业务规则。后端仍需校验权限、状态、金额范围、数据一致性和幂等性。
误区二:Schema 越严格越好
不一定。Schema 越严格,模型越可能在证据不足时被迫填充看似合法但语义错误的值。对于不确定信息,应允许 null、unknown 或单独输出 missing_fields。
误区三:结构化输出一定降低延迟
不一定。它减少了解析失败和重试,但约束解码本身可能引入 token mask、状态机推进和 Schema 编译开销。Outlines 强调编译一次、复用约束;XGrammar 则专门优化结构化生成开销。这些都说明性能需要单独压测。
误区四:字段名只是给程序看的
不是。字段名、枚举值、描述信息都可能影响模型行为。尤其在小模型、复杂推理和多语言任务中,Schema 命名需要像 Prompt 一样认真设计。
上线检查清单
Schema 设计
- 字段名是否清晰、稳定、无歧义
- enum 是否互斥,是否覆盖 unknown
- required 字段是否过多
- 是否禁止不需要的额外字段
- 是否保留证据字段或置信度字段
- 是否有 Schema 版本号
推理服务
- 当前模型是否支持结构化输出
- 当前推理框架是否支持所用 JSON Schema 特性
- Schema 编译是否可缓存
- 高并发下 token mask 开销是否可接受
- 超时、拒答、空输出是否有兜底
业务链路
- 是否保留原文和模型输出审计日志
- 是否有结构校验和业务校验两层
- 是否有人工复核队列
- 是否能回放失败样本
- 是否能灰度不同 Schema 和模型版本
观测指标
建议至少记录这些指标:
schema_valid_rate
business_valid_rate
parse_failure_rate
fallback_rate
model_refusal_rate
p50_latency
p95_latency
p99_latency
output_token_count
retry_count
manual_review_rate
不要只看”JSON 解析成功率”。真正影响生产质量的是结构合规、业务正确、延迟成本和人工介入率的组合。
FAQ
结构化输出能不能完全替代 Prompt?
不能。结构化输出解决的是格式约束,Prompt 仍然负责任务目标、上下文解释、判断标准和业务边界。更好的做法是:Prompt 负责”应该判断什么”,Schema 负责”必须输出成什么结构”。
为什么有时约束解码会影响回答质量?
因为它会把模型限制在合法 token 集合里。如果合法集合和模型原本认为高概率的语义路径冲突,模型可能走向格式正确但语义较弱的输出。2026 年的 Draft-Conditioned Constrained Decoding 研究也讨论了硬约束可能带来的 projection tax,并尝试用先生成草稿、再约束生成的方式缓解。
小模型适合做结构化输出吗?
适合,但要更谨慎。小模型更容易出现”格式能对、语义不稳”的情况。建议从分类、短字段抽取、固定枚举等低复杂任务开始,并配合业务校验和人工抽检。
小结
结构化输出的价值不是让 Prompt 看起来更工整,而是把”格式正确”从概率事件变成工程约束。它尤其适合 Agent 工具调用、信息抽取、业务分类和前端 UI 数据生成。
但它也有清晰边界:Schema 合规不等于语义正确,格式稳定不等于业务安全,约束解码不等于零成本。
生产落地时,推荐采用这条原则:
Prompt 定义任务边界 → Schema 定义输出契约 → Constrained Decoding 保证格式 → 业务校验保证可用 → 人工复核兜底高风险场景
这样结构化输出才不是一个”更硬的 JSON Prompt”,而是可以被测试、观测、灰度和回滚的生产能力。
主要参考资料
- OpenAI API Docs: Structured model outputs
- vLLM Documentation: Structured Outputs
- Outlines Documentation
- Guidance GitHub: Generating JSON
- XGrammar GitHub
- SGLang Structured Outputs Documentation
- Generating Structured Outputs from Language Models: Benchmark and Studies / JSONSchemaBench
- Schema Key Wording as an Instruction Channel in Structured Generation under Constrained Decoding
- Draft-Conditioned Constrained Decoding for Structured Generation in LLMs