Kvmzen 博客
← 返回技术实践

OpenAI Structured Outputs 完整指南:如何让 GPT 稳定输出符合 JSON Schema 的 JSON 数据?

技术实践 ·约 12 分钟阅读

在代码编辑器中编写 JSON Schema 与结构化数据流水线

只要你把模型输出接到数据库、表单或下游 Agent,就不能再靠「提示词里写请返回 JSON」碰运气。OpenAI Structured Outputs 用受限解码把输出钉在你提供的 JSON Schema 上:字段不会丢、枚举不会乱编、类型不会在字符串和数字之间漂移。本文面向要做抽取、工单分类、评测打分和多步工作流的工程师,走完从 JSON Mode 升级到 strict: true 的完整路径。

100%
受支持模型上的 Schema 符合率
2
接入入口:Chat Completions / Responses
10
对象最大嵌套层数

JSON Mode 为什么不够稳

JSON Mode(type: "json_object")只保证「能被 JSON 解析器读进去」。它不保证键名、必填字段、枚举集合或数字类型。生产里最常见的三种翻车是:漏掉 severity、把 42 写成 "42"、在 enum 外发明一个 urgent-plus。下游一旦用强类型解析,整条流水线就会在随机样本上失败。

能力 JSON Mode Structured Outputs
输出合法 JSON
遵守你给的 JSON Schema 否(靠提示词) 是(受限解码)
开启方式 json_object json_schema + strict: true
典型模型 较早期 GPT-4o / GPT-3.5 等 gpt-4o-2024-08-06、gpt-4o-mini 及更新快照
拒答处理 仍可能吐出「看起来像 JSON」的拒绝话术 走独立的 refusal 字段

官方把 Structured Outputs 称为 JSON Mode 的演进:新项目应默认走 Schema,而不是在解析失败后再重试三次。若你同时在比较不同模型的 Token 账单,可以对照 Kimi K3 与 GPT-5.5 的 API 成本对比——结构化输出本身几乎不增加输出体积,真正拉开账单的是重试和过长推理。

两个入口,同一套 Schema 规则
Chat Completions 把 Schema 放在 response_format;Responses API 放在 text.format。字段路径不同,strictadditionalProperties: false 和「全部 required」这些约束完全一样。

最小可运行请求

下面这个工单抽取 Schema 覆盖了生产里 80% 的需求:字符串、枚举、整数,以及用 null 联合类型模拟可选字段。模型必须返回所有键;没有账号 ID 时显式给 null,而不是省略键。

Chat Completions · response_format
{
  "model": "gpt-4o-2024-08-06",
  "messages": [
    {"role": "system", "content": "把用户描述抽取成工单对象。"},
    {"role": "user", "content": "结账页用已保存卡会 500。今天开始。账号 acct_8842。"}
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "support_ticket",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "summary": { "type": "string" },
          "category": {
            "type": "string",
            "enum": ["billing", "bug", "account", "other"]
          },
          "severity": { "type": "integer" },
          "account_id": { "type": ["string", "null"] }
        },
        "required": ["summary", "category", "severity", "account_id"],
        "additionalProperties": false
      }
    }
  }
}

Responses API 的等价写法是把同一份 Schema 放进 text.formattypenameschemastrict 作为兄弟字段。首次用某个 Schema 时,服务端会编译约束,延迟可能略高;之后同一 Schema 会被缓存,后续请求回到正常水平。

笔记本上的代码与结构化 API 调试环境
把 Schema 当成接口合同:先在本地用同一份定义跑单测,再交给模型生成

strict Schema 必须满足的规则

打开 strict: true 之后,你提交的不再是「宽松 JSON Schema」,而是官方支持的子集。不合规会直接 400,而不是「尽量遵守」。

  • 根必须是 object:根节点不能是 anyOf 或数组。Zod 的 discriminatedUnion 若生成顶层 anyOf,需要包一层对象,例如 { "result": ... }
  • 每个 object 都要 additionalProperties: false:包括嵌套对象。漏一个就会被拒绝。
  • properties 里出现的键必须全部进入 required:可选语义用 "type": ["string", "null"]anyOfnull
  • 支持的类型:string、number、integer、boolean、object、array、enum、anyOf
  • 字符串约束patternformatemaildate-timeuuidipv4 等)。
  • 数字与数组minimum / maximum / multipleOfminItems / maxItems
  • 明确不支持allOfnotif/then/elsedependentRequired 等组合关键字。
  • 规模上限:合计最多约 5000 个对象属性、10 层嵌套;所有属性名、定义名、枚举值字符串总长不超过 12 万字符;枚举值总数最多 1000。
微调模型更严
在微调模型上,patternformatminLengthminimumminItems 等约束目前仍可能不受支持。先在基础快照上验证 Schema,再决定是否微调。

工具调用 vs 回复正文

同一套 strict 也能用在函数 / 工具参数上。差别是意图:工具 Schema 描述「模型要调用什么」;response_format / text.format 描述「模型要回复用户什么形状」。抽取、评分、生成 UI 状态用后者;查天气、写文件、跑命令用前者。若你在对比终端 Agent 与 GUI 操控能力,可参考 Claude Code 与 OpenAI Computer Use 的关系——那是另一层「怎么行动」,本文解决的是「行动前后的数据合同」。

用 SDK 直接解析成对象

手写 JSON Schema 容易漏 required。官方 SDK 提供 Pydantic / Zod 助手:从类型生成 Schema,并在响应侧直接给出解析后的对象。下面是 Python 的典型写法。

Python · Pydantic
from typing import Literal, Optional
from pydantic import BaseModel
from openai import OpenAI

class SupportTicket(BaseModel):
    summary: str
    category: Literal["billing", "bug", "account", "other"]
    severity: int
    account_id: Optional[str]

client = OpenAI()
completion = client.beta.chat.completions.parse(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "system", "content": "抽取工单。"},
        {"role": "user", "content": "结账页 500,账号 acct_8842。"},
    ],
    response_format=SupportTicket,
)
ticket = completion.choices[0].message.parsed
if completion.choices[0].message.refusal:
    raise RuntimeError(completion.choices[0].message.refusal)
print(ticket.category, ticket.severity)
把 Schema 当成单一事实来源
同一份 Pydantic / Zod 模型同时服务 API 请求、单元测试和下游校验。不要在提示词里再抄一遍字段列表,否则文档会先于代码腐烂。

报错、拒答与工程清单

上线前按这张清单过一遍,能消掉绝大多数「本地 curl 能跑、流水线却 400」的问题。

  • 400 Unsupported schema:缺 additionalProperties: false、有未 required 字段、根是 anyOf,或用了 allOf
  • 模型拒答:安全策略触发时,内容不会塞进 Schema;读取 refusal,在 UI 里展示,不要当 JSON 解析失败重试。
  • 可选字段变成空字符串:合同里写了 null 就要接受 null;下游入库前把 null 映射成 SQL NULL,不要再二次猜测。
  • 枚举过宽:把业务状态收成 5–8 个值;上百个枚举既烧上下文,也逼近 1000 条上限。
  • 键顺序:输出键顺序与 Schema 中声明顺序一致,流式消费时可以按字段增量解析。
  • 提示词仍有用:Schema 管形状,提示词管语义。例如「severity 1–5,5 为全站故障」要写在 system 里,整数范围也可以用 minimum / maximum 再锁一层。

落地时建议固定三件事:Schema 进 Git;对拒答、400、超时分别打点;用真实工单做一组回归样本,而不是只用「Hello 返回 JSON」这种玩具输入。结构化输出让解析层稳定之后,你才能把精力放到分类准确率和延迟上,而不是每周修一次正则。

在 Mac mini 上跑抽取流水线,环境更省心

Structured Outputs 的调试循环很短:改 Schema、跑一小批样本、看解析对象。macOS 上 Python、Node.js、Docker 和 Homebrew 开箱即用,不必先搭 WSL。Apple Silicon 统一内存让你同时开编辑器、本地校验脚本和长上下文评测,不至于在类型检查时把机器拖进 swap。

Mac mini M4 待机大约 4W,适合把回归样本集整晚挂着跑;macOS 崩溃率低,Gatekeeper 与 SIP 也降低了依赖脚本被污染的风险。和同价位 Windows 盒子比,长期无人值守跑 Agent 评测时,稳定性和电费都更友好。

若你希望有一台一直在线、专门跑 JSON 回归与模型对比的节点,了解 Kvmzen 的套餐方案,把 Schema 合同从笔记本搬到固定规格的云端 Mac 上。

限时特惠

不只是一台 Mac,是你在云端的开发基地

独享算力 · 全球节点 · 按月订阅 · 无需购置硬件

返回首页
限时优惠 点击查看套餐