文章

结构化输出不是 JSON Prompt:Constrained Decoding 的工程边界

本文从 JSON Schema、约束解码、vLLM 与 Outlines 等工程实现出发,解释结构化输出如何提升可靠性,以及上线时需要关注的延迟、Schema 设计与异常兜底。

背景问题:为什么”请输出 JSON”不够用

在大模型应用里,很多场景并不需要一段自然语言,而是需要一份能被程序直接消费的结构化结果。例如:

  • 从邮件中抽取客户姓名、订单号和问题类型;
  • 把客服对话分类成固定枚举;
  • 为 Agent 生成下一步工具调用参数;
  • 从合同、保单、发票里抽取字段并落库;
  • 给前端页面返回一个可渲染的对象结构。

早期做法通常是在 Prompt 里写一句:“请严格输出 JSON,不要输出多余解释。“这在 Demo 阶段看起来够用,但进入生产后会遇到几个典型问题:

  1. 模型偶尔会在 JSON 外面包一层 Markdown 代码块;
  2. 必填字段缺失,或者字段名拼错;
  3. 枚举值不在业务允许范围内;
  4. 数组、嵌套对象、nullable 字段在复杂场景下不稳定;
  5. 下游解析失败后只能重试,增加延迟和成本。

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 常见,还因为它能表达业务接口里常见的约束:

  • 字段类型:stringnumberintegerbooleanarrayobject
  • 必填字段:required
  • 枚举值:enum
  • 嵌套结构:propertiesitems
  • 附加字段限制:additionalProperties
  • 简单范围:minimummaximumminLengthmaxLength

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 生成结构化结果,并支持 choiceregexjsongrammarstructural_tag 等参数。旧字段如 guided_jsonguided_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_summarydesc 更好;
  • enum 值保持稳定、短小、互斥;
  • required 字段不要过多,避免模型在证据不足时被迫编造;
  • 对不确定字段显式允许 nullunknown
  • 给高风险字段增加 evidencesource_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() 的异常来判断全部失败原因。

适用场景:哪些地方应该优先使用结构化输出

强适用场景

结构化输出最适合这些场景:

  1. 信息抽取:从文本中抽取字段、日期、金额、联系人、保单号;
  2. 分类与路由:把用户问题映射到固定业务队列;
  3. Agent 工具调用:生成函数参数、API 参数、数据库查询条件;
  4. 前端 UI 渲染:输出卡片、表单、步骤条需要的结构;
  5. 批处理自动化:大量文档处理时减少解析失败率。

谨慎使用场景

下面这些场景不应只依赖结构化输出:

  1. 法律、医疗、保险理赔等高风险判断:需要规则引擎和人工复核;
  2. 复杂数学推理:结构合规不代表推理正确;
  3. 长链路 Agent 自动执行:工具参数正确不代表动作安全;
  4. Schema 极复杂的接口:可能带来延迟、兼容性和维护成本;
  5. 模型本身能力不足的任务:约束会限制格式,但不会补齐知识与推理能力。

常见误区

误区一:用了 JSON Schema 就不需要后端校验

错误。JSON Schema 主要解决格式和局部约束,不替代业务规则。后端仍需校验权限、状态、金额范围、数据一致性和幂等性。

误区二:Schema 越严格越好

不一定。Schema 越严格,模型越可能在证据不足时被迫填充看似合法但语义错误的值。对于不确定信息,应允许 nullunknown 或单独输出 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”,而是可以被测试、观测、灰度和回滚的生产能力。

主要参考资料

常见问题

结构化输出和 JSON mode 有什么区别?
JSON mode 主要保证输出是合法 JSON;结构化输出还会要求结果遵守指定 Schema,例如必填字段、枚举值和对象结构。
Constrained Decoding 是否一定比提示词更好?
它更适合强格式约束场景,但不等于语义正确。复杂 Schema、低概率合法路径和高并发场景仍可能带来延迟与质量问题。
上线结构化输出时最重要的检查项是什么?
应同时检查 Schema 复杂度、模型支持范围、失败兜底、延迟开销、字段命名、版本兼容和业务校验链路。