OpenAI 兼容 API 的 Structured Outputs:JSON Schema 指南
逐字段说明 Responses 与 Chat Completions 如何使用严格 JSON Schema,并覆盖应用侧校验、拒绝与截断处理、路由兼容性测试。
Structured Outputs 让应用可以要求模型返回符合指定 Schema 的 JSON,而不只是通过 Prompt 要求“返回 JSON”。使用 OpenAI 兼容 API 时,同一份 Schema 可以通过 Responses 或 Chat Completions 发送,但两种端点的字段结构不同,而且仍需针对实际选择的模型和路由验证兼容性。
安全的生产做法包含三层:模型支持时使用 strict Schema 输出;应用继续解析并校验返回的 JSON;最后再对字段值执行确定性的业务规则。Schema 遵循可以消除大量格式错误,却不能证明模型给出的事实或语义一定正确。
Structured Outputs 不等于 JSON Mode
常见的 JSON 输出方式可以分为三类。
| 方式 | 能否产生合法 JSON | 是否强制符合指定 Schema | 典型用途 |
|---|---|---|---|
| 只在 Prompt 中要求 | 不保证 | 否 | 可以接受解析失败的原型 |
| JSON Mode | 在支持且正常完成时可以 | 否 | 结构较灵活、由应用自行校验的 JSON |
使用 strict: true 的 Structured Outputs |
在正确处理完成状态和拒绝的前提下可以 | 在支持的 JSON Schema 子集内可以 | 类型化提取和应用工作流 |
OpenAI Structured Outputs 指南建议在所选模型和端点支持时优先使用 Structured Outputs,而不是 JSON Mode。该指南也区分了两种不同任务:
- 如果模型需要用可预测的数据对象回答用户,使用结构化响应格式。
- 如果模型需要请求应用执行一个动作,使用 Function Calling。
本文聚焦第一种任务。后续的工具循环也可以复用严格参数 Schema,但它的 Call ID 和结果消息属于另一套契约。
先定义 Schema,再选择端点
以客服工单分流为例,应用需要四个字段:
category:billing、technical或account;priority:1 到 3 的整数;requires_human:布尔值;summary:进入客服队列的简短说明。
对应 JSON Schema 如下:
{
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["billing", "technical", "account"]
},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 3
},
"requires_human": {
"type": "boolean"
},
"summary": {
"type": "string"
}
},
"required": ["category", "priority", "requires_human", "summary"],
"additionalProperties": false
}
把所有属性设为必填,并把 additionalProperties 设为 false,可以为下游代码提供稳定对象。但这并不代表所有 JSON Schema 关键字都能跨提供商移植。OpenAI 支持文档中列出的一个子集,其他 OpenAI 兼容提供商可能支持不同子集,甚至完全不支持严格 Schema 模式。
通过 Responses API 发送 Schema
Responses API 把响应 Schema 放在 text.format 中。先设置 Modelflare API Key,再选择当前 API Key 可以访问、且已经验证支持 Structured Outputs 的模型。
export MODELFLARE_API_KEY="<YOUR_API_KEY>"
export MODEL_ID="<MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT>"
curl -sS https://modelflare.dev/v1/responses \
-H "Authorization: Bearer $MODELFLARE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"$MODEL_ID\",
\"input\": \"The customer was charged twice and wants a refund.\",
\"text\": {
\"format\": {
\"type\": \"json_schema\",
\"name\": \"support_ticket\",
\"schema\": {
\"type\": \"object\",
\"properties\": {
\"category\": {
\"type\": \"string\",
\"enum\": [\"billing\", \"technical\", \"account\"]
},
\"priority\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 3
},
\"requires_human\": { \"type\": \"boolean\" },
\"summary\": { \"type\": \"string\" }
},
\"required\": [\"category\", \"priority\", \"requires_human\", \"summary\"],
\"additionalProperties\": false
},
\"strict\": true
}
}
}"
Responses 返回的生成内容位于带类型的 Output Item 中。SDK 可能提供聚合输出文本或解析结果等便捷方法,但使用普通 HTTP 客户端时,不能假设结构化对象就是顶层字段。应先读取已完成的 Response Object,找到其中的文本输出,再把该文本解析为 JSON。
通过 Chat Completions 发送相同 Schema
Chat Completions 把响应 Schema 放在 response_format.json_schema 中。Schema 正文本身不变,只有外层包装不同。
curl -sS https://modelflare.dev/v1/chat/completions \
-H "Authorization: Bearer $MODELFLARE_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"$MODEL_ID\",
\"messages\": [
{
\"role\": \"user\",
\"content\": \"The customer was charged twice and wants a refund.\"
}
],
\"response_format\": {
\"type\": \"json_schema\",
\"json_schema\": {
\"name\": \"support_ticket\",
\"schema\": {
\"type\": \"object\",
\"properties\": {
\"category\": {
\"type\": \"string\",
\"enum\": [\"billing\", \"technical\", \"account\"]
},
\"priority\": {
\"type\": \"integer\",
\"minimum\": 1,
\"maximum\": 3
},
\"requires_human\": { \"type\": \"boolean\" },
\"summary\": { \"type\": \"string\" }
},
\"required\": [\"category\", \"priority\", \"requires_human\", \"summary\"],
\"additionalProperties\": false
},
\"strict\": true
}
}
}"
在 Chat Completions 中,JSON 文本通常位于 choices[0].message.content。需要解析 Content 字符串,不能把它当成已经类型化的对象。
两种包装方式可以归纳如下:
| 用途 | Responses API | Chat Completions |
|---|---|---|
| Schema 容器 | text.format |
response_format.json_schema |
| 格式类型 | text.format.type |
response_format.type |
| Schema 名称 | text.format.name |
response_format.json_schema.name |
| Schema 正文 | text.format.schema |
response_format.json_schema.schema |
| Strict 开关 | text.format.strict |
response_format.json_schema.strict |
| 结果位置 | 带类型的 Output Item / Output Text Helper | choices[0].message.content |
Modelflare 的 OpenAI 兼容层针对受支持的 OpenAI/Codex 路由,具备这两种包装之间的显式转换逻辑。但转换无法为原本不支持 Structured Outputs 的上游模型创造这一能力。其他以原始 Chat Completions 透传方式提供的模型家族,仍应以上游提供商的确切请求契约为准。
分别校验结构和语义
假设模型返回:
{
"category": "billing",
"priority": 2,
"requires_human": true,
"summary": "Customer reports a duplicate charge and requests a refund."
}
这个对象符合 Schema,但应用仍需执行两类不同检查。
结构校验
使用 JSON Schema Validator 或 SDK 的类型化 Helper,确认:
- 对象包含所有必填属性;
- 不包含意外属性;
- 每个字段都使用指定类型;
- Category 位于允许的 Enum 内;
- Priority 位于允许区间。
即使提供商承诺严格遵循,应用侧校验仍能隔离不支持的路由、集成错误、截断内容和未来契约变化,避免这些问题直接影响后续系统。
语义和业务校验
Schema 无法判断客户是否真的被重复扣费、Priority 2 是否正确,或退款是否必须由人工批准。这些结论需要权威业务数据和应用策略。
对于影响较大的工作流,应把模型对象视为建议分类:与权威记录进行比对,在确定性代码中执行权限和资金限制,并保留安全的审计引用。绝不能因为模型对象结构合法,就让它绕过鉴权、计费或安全边界。
处理未完成和异常输出
生产解析器不能只实现 Happy Path 的 JSON.parse。
拒绝
模型可能因为安全原因拒绝请求。OpenAI 的结构化输出契约能够区分拒绝与正常 Schema 输出。在查找 JSON 对象之前,先检查 Response Status 和拒绝表示,不要把拒绝报告成 Schema 解析失败。
截断和输出限制
如果生成在对象完成之前停止,Schema 无法补回缺失的结尾。需要检查端点完成状态和 Stop Reason。输出上限应足以容纳最大的合法对象,同时保持 Schema 和请求内容有界。
不支持的 Schema 关键字
Strict Output 实现通常只支持 JSON Schema 的一个子集。应从 Object、Array、Primitive Type、Enum、Required Field 和明确的 Additional Property 行为开始。在使用引用、递归结构、复杂 Union 或高级校验关键字之前,核对当前提供商文档。
Schema 首次使用延迟
部分提供商会预处理并缓存新 Schema,因此某个 Schema 的首次请求可能比后续请求更慢。应复用稳定且有版本的 Schema,而不是为每个请求生成全新 Schema;做延迟比较时,也应把首次使用与稳定阶段分开。
模型或路由不兼容
OpenAI 兼容端点可能可以接收普通 Chat Completions,却拒绝 json_schema、忽略 strict,或者把字段转发给不支持它们的模型。即使返回 200 和一段 JSON,也不能证明严格遵循。必须使用异常和边界输入进行测试,并验证最终对象。
生产前执行兼容性测试
针对准备启用的每个模型和路由,运行一份小型测试矩阵。
| 测试 | 需要记录的证据 |
|---|---|
| 最小必填对象 | Status、Model、Route、解析后的对象、校验结果 |
| 每个 Enum 值 | 每个允许值是否能够生成并解析 |
| 信息缺失 | 模型是否使用有界回退值,或要求补充信息 |
| 触发安全策略的输入 | 拒绝表示与应用处理方式 |
| 较低输出上限 | 完成状态与截断处理 |
| 不支持的 Schema 功能 | 返回明确错误,还是静默忽略约束 |
| Responses 与 Chat 包装 | 两者能否产生等价的应用对象 |
| 重复使用稳定 Schema | 首次使用与稳定阶段的时序差异 |
不要在 Schema、Prompt、区域、流式模式或输出上限不同的情况下比较模型,再把差异全部归因于模型质量。还应记录确切测试日期,因为提供商行为和模型可用性会变化。
测试工作流后再选择端点
如果应用已经使用带类型 Output Item、Responses 流式事件或更完整的 Responses 工具工作流,可以选择 Responses。如果应用已有稳定的 Messages 集成,且所选路由支持对应 response_format 契约,可以继续使用 Chat Completions。更广泛的选型方法可以查看 Responses API 与 Chat Completions。
无论选择哪个端点,都应:
- 为 JSON Schema 保留唯一、带版本的来源;
- 只转换端点包装,不改变 Schema 含义;
- 使用结果之前校验完整响应;
- 在结构校验之后执行确定性的业务规则;
- 分别监控解析、拒绝、截断和兼容性失败。
首次迁移客户端时,从 OpenAI 兼容 API 指南开始。处理事件解析和未完成的 Stream 时,参考 AI API 流式指南。先在模型与价格中确认当前模型和分组入口,再在 API Key 实际使用的路由上验证 Structured Outputs。
Structured Outputs 最大的价值,是把模型响应收敛成更可靠的应用接口。它不能替代事实核验、鉴权或计费逻辑;“OpenAI 兼容”也仍然需要逐项测试,不能仅凭 Base URL 推断完整能力。