文章

Structured Outputs 实战:让大模型稳定输出 JSON,不只是加一句"请返回 JSON"

本文系统讲解大模型结构化输出的工程落地方法,覆盖 JSON Schema、约束解码、Schema 设计、评估指标、常见误区与上线检查,适合构建 Agent、工具调用和数据抽取流程。

为什么”请返回 JSON”不够用

很多 LLM 应用都会经历同一个阶段:一开始只需要自然语言回答,后来接入了工具、工作流、数据库、RPA 或前端页面,模型输出就必须被机器稳定消费。此时,Prompt 里加一句”请严格返回 JSON,不要输出多余文字”通常只能解决一部分问题。

常见故障包括:

  • 模型在 JSON 前后加解释文字,导致解析失败
  • 字段名轻微漂移,例如 user_id 变成 userid
  • 类型错误,例如应该是数组却返回字符串
  • 枚举值不稳定,例如 pendingwaitwaiting 混用
  • JSON 格式合法,但业务语义错误

这也是 Structured OutputsConstrained Decoding 进入工程视野的原因。OpenAI 将 Structured Outputs 描述为让模型响应遵守开发者定义的 JSON Schema,并明确区分了它与 JSON mode:JSON mode 主要保证 JSON 有效,而 Structured Outputs 进一步保证 Schema adherence。官方建议尽可能优先使用 Structured Outputs,而不是只依赖 JSON mode。

核心原理:把”格式要求”变成解码约束

普通生成流程里,模型每一步都会从词表中选择下一个 token。Prompt 里的”请返回 JSON”只是软约束,模型仍然可能生成不符合结构的 token。

约束解码的思路是:在每一步生成时,根据 JSON Schema、正则表达式或语法规则,动态计算”当前状态下哪些 token 仍然合法”,再屏蔽不合法 token。模型不是先自由生成再事后修 JSON,而是在生成过程中就被限制在合法结构内。

可以把它理解成一个状态机:

  1. Schema 定义目标结构
  2. 解码器根据当前位置计算可选 token 集合
  3. 模型只在合法 token 中选择
  4. 输出完成后天然满足格式规则

这类机制在 Agent、工具调用、信息抽取、代码配置生成、自动表单填充等场景非常有用。vLLM 提供了 Structured Outputs 能力,支持通过 xgrammar 或 guidance 作为后端;llama.cpp 则支持 GBNF grammar,并提供 JSON Schema 到 GBNF 的子集转换;Outlines 定位为结构化文本生成库,支持 JSON Schema、正则和上下文无关文法。

Structured Outputs 适合解决什么问题

1. 工具调用参数稳定

Agent 调用工具时,最怕模型把参数写错。例如:

{
  "name": "query_order",
  "arguments": {
    "order_id": "A20260629001",
    "include_refund_status": true
  }
}

如果 order_id 缺失,或者 include_refund_status 变成字符串,后端就要额外写大量容错逻辑。使用结构化输出后,可以把参数类型、必填字段、枚举值提前固定下来。

2. 数据抽取结果可入库

从合同、邮件、客服记录中抽取字段时,业务系统通常需要稳定字段。比如:

{
  "customer_name": "string",
  "policy_no": "string",
  "risk_level": "low | medium | high",
  "missing_fields": ["string"]
}

这类任务中,格式稳定性直接影响后续入库、检索、统计和人工复核。

3. 前端组件渲染

有些应用会让模型返回 UI 配置,例如卡片、表格、筛选项、图表参数。前端不应该靠正则猜测模型意图,而应该消费一个稳定 Schema。

4. 批处理自动化

批量跑文档处理、摘要、分类、审核时,结构化输出可以降低失败重试率,也更容易做结果比对和质量监控。

但是:格式正确不等于答案正确

这里有一个容易忽视的误区:Schema 合法率不是业务准确率

约束解码可以让输出满足 JSON Schema,但不能保证模型理解了任务。例如,一个发票抽取任务中,模型可以输出合法 JSON,却把金额、税号或日期填错。结构没错,业务仍然错。

2025 年的 JSONSchemaBench 工作指出,受约束解码已成为结构化输出的重要技术,但系统评估仍然必要。2026 年关于 “constraint tax” 的研究也提醒,小模型在强结构约束下可能出现”格式更合法,但答案准确率下降”的情况。这个结论对生产环境很重要:不能只看 JSON parse 成功率。

因此,生产监控至少要拆成以下几类指标:

指标说明
格式合法率能否被 JSON parser 解析
Schema 合法率是否满足 JSON Schema
字段完整率required 字段是否都存在
业务准确率字段值是否符合人工标注或业务规则
可执行准确率工具调用是否真的能成功执行
错误但合法率结构合法,但业务语义错误的比例
重试率第一次输出失败后需要修复或重新生成的比例

Schema 设计的工程建议

字段名要表达业务含义

字段名本身也是模型理解任务的一部分。不要写:

{ "a": "string", "b": "string" }

更好的做法是:

{ "policy_holder_name": "string", "claim_event_date": "string" }

OpenAI 文档也建议使用清晰、直观的 key,并为重要字段提供清楚的 titledescription。字段名越贴近业务语义,模型越不容易把值放错位置。

Schema 不要一次设计得过大

复杂 Schema 会带来三个问题:

  1. 模型更难理解每个字段的语义
  2. 约束解码的状态空间更复杂
  3. 后续排查时难以定位错误来源

更稳妥的做法是分阶段输出:先分类,再抽取;先判断是否适用,再生成详细结构;先让模型自由推理,再让它把结论封装成结构化结果。

枚举值要少而稳定

枚举值太多会让模型陷入”看起来都差不多”的选择。生产系统中建议把枚举值控制在必要范围内,并在 description 中解释每个值的业务含义。

例如:

{
  "risk_level": {
    "type": "string",
    "enum": ["low", "medium", "high"],
    "description": "low 表示无需人工复核,medium 表示建议抽检,high 表示必须人工复核"
  }
}

业务校验不要全部交给模型

Schema 适合表达结构规则,但不适合承载所有业务规则。比如”保单生效日期不能晚于终止日期""金额必须等于分项之和""证件号需要通过校验位”,这些规则应该由后端校验。

推荐链路是:

模型结构化输出 → JSON Schema 校验 → 业务规则校验 → 失败原因回传 → 必要时重试或转人工

一套可落地的结构化输出流程

第一步:定义最小可用 Schema

不要一开始就设计全量字段。先找出业务真正需要自动化消费的字段。

{
  "type": "object",
  "properties": {
    "intent": {
      "type": "string",
      "enum": ["query", "create", "update", "unknown"]
    },
    "confidence": { "type": "number" },
    "reason": { "type": "string" }
  },
  "required": ["intent", "confidence", "reason"],
  "additionalProperties": false
}

第二步:准备失败样例

结构化输出上线前,至少准备这些测试集:

  • 正常输入
  • 缺字段输入
  • 模糊表达输入
  • 多意图输入
  • 含噪声输入
  • 恶意提示注入输入
  • 超长上下文输入

不要只用 demo prompt 测试。结构化输出真正出问题,通常不是格式,而是边界输入下的语义判断。

第三步:区分”生成失败”和”业务失败”

建议将错误类型拆开,分别记录:

错误类型含义
parse_errorJSON 解析失败
schema_error不符合 Schema
validation_error业务规则不通过
execution_error工具执行失败
semantic_error结构合法但语义错误

这样线上排障会简单很多。如果只记录”LLM failed”,后面很难判断是 Prompt、Schema、模型、业务规则还是工具接口的问题。

第四步:建立降级策略

结构化输出不是上线后就结束。生产环境至少要有三类兜底:

  • 自动重试:仅在格式或轻微字段错误时触发
  • 修复模型:将错误 JSON 和错误原因交给模型修复,但限制次数
  • 人工复核:涉及金额、合同、理赔、风控等高风险场景时必须保留人工入口

常见误区

误区一:用了 Structured Outputs 就不用写校验

错。Structured Outputs 主要解决结构问题,不替代业务校验。金额、日期、权限、状态流转仍然要由后端规则兜底。

误区二:Schema 越细越好

不一定。Schema 太复杂会降低可维护性,也可能压缩模型表达空间。尤其是小模型,过强约束可能影响答案质量。工程上应该先小范围验证,再逐步扩展。

误区三:只监控 JSON 解析成功率

JSON parse 成功率太表层。更关键的是 Schema 合法率、字段准确率、工具执行成功率和错误但合法率

误区四:把自然语言解释完全删掉

在某些复杂判断任务中,保留 reason 字段有助于人工复核和排查。但 reason 不应参与强业务决策,只能作为解释材料。

上线检查清单

上线前建议逐项确认:

  • Schema 是否只包含当前业务真正需要的字段
  • 字段名是否清晰,description 是否能帮助模型理解
  • required 字段是否过多
  • 是否禁用了不必要的 additionalProperties
  • 枚举值是否稳定、互斥、数量可控
  • 是否有 JSON Schema 校验和业务规则校验
  • 是否记录 parseschemavalidationexecutionsemantic 等错误类型
  • 是否建立重试、修复、人工复核策略
  • 是否有边界样例和回归测试集
  • 是否分别监控格式合法率和业务准确率

结论

Structured Outputs 的价值不在于”让模型看起来更像 API”,而在于把 LLM 从自然语言接口推进到可工程化集成的组件。它能降低解析失败和字段漂移的概率,让 Agent、工具调用、数据抽取和批处理任务更稳定。

但它不是银弹。格式合法只是第一层,真正决定系统质量的是 Schema 设计、业务校验、评估样例、错误分类和兜底策略。一个成熟的结构化输出系统,应该同时追求可解析、可校验、可执行、可追责

主要参考资料

常见问题

Structured Outputs 和 JSON mode 有什么区别?
JSON mode 主要保证输出是合法 JSON,Structured Outputs 进一步要求输出遵守指定 JSON Schema,更适合工具调用、数据抽取和自动化流程。
约束解码能否完全解决大模型输出错误?
不能。约束解码能显著提升格式合法性,但不能保证字段语义、事实正确性和业务规则正确性,仍需要评估、校验和兜底。
生产环境应该如何设计结构化输出 Schema?
应使用清晰字段名、必要描述、严格类型、少量枚举和显式 required 字段,并分别监控格式合法率、业务准确率和修复重试率。
用了 Structured Outputs 还需要写业务校验吗?
需要。Structured Outputs 主要解决结构问题,不替代业务校验。金额、日期、权限、状态流转等仍需后端规则兜底。