深色模式
结构化输出与 JSON Mode
摘要:本文面向把大模型接入后端系统的工程师。逐档讲解「仅靠 prompt 要 JSON」「JSON Mode」「Structured Outputs(约束解码)」三者的保证边界,给出 OpenAI
response_format/ Anthropic tool use 的可复制代码、strict 模式的约束清单、以及解析失败时的重试与降级路径。适用模型:OpenAI gpt-4o-2024-08-06 及之后、o 系列;Anthropic Claude 3.5+(tool use)。功能与价格标注[版本相关]。
核心概念:三档约束强度
| 档位 | 机制 | 保证 | 失败模式 |
|---|---|---|---|
| Prompt-only | 在 system prompt 写「输出 JSON」 | 无保证,常包 markdown 或尾随文本 | 解析失败 / 幻觉键 |
| JSON Mode | 服务端标志强制语法合法 JSON | 合法 JSON,但不保证 schema | 缺字段、错类型、多键 |
| Structured Outputs | 约束解码(constrained decoding)+ schema | 严格匹配 schema(strict=true) | 仅在不安全/信息不足时显式拒绝 |
JSON Mode 最常见的生产坑
OpenAI JSON Mode(response_format: {"type":"json_object"},2023-11 引入)只保证 json.loads() 不报错,不保证键名、必填字段、类型。模型可能返回 {"tool_action":"search"} 而非你期望的 {"action":"search"},于是下游 dict["action"] 抛 KeyError——HTTP 仍是 200,没有任何报错信号。这是 agent 管线里 schema 类故障的首要来源。
架构与原理:约束解码如何工作
Structured Outputs(OpenAI 2024-08-06 GA)通过约束解码,使 token 生成过程只能产出符合 schema 的 token:不在 properties 里的键在采样前被 mask 到负无穷,结构层面不可能出现幻觉键。在 OpenAI 自己的复杂 schema 评测上,gpt-4o-2024-08-06 + Structured Outputs 达到 100% 匹配;对比 gpt-4-0613 不到 40%。该数字为官方评测,非通用基准。
服务端 vs 客户端校验
- 服务端(token 级):保证结构(键、类型、必填)。OpenAI Structured Outputs、Anthropic tool_use 属于此类。
- 客户端(值级):保证语义(范围、正则、业务规则)。strict 模式不支持
minLength/maxLength/pattern之类值级约束,需用 Pydanticfield_validator兜底。 - 两者互补:结构靠服务端,值靠客户端。
生产实践
选型建议
- 能用 Structured Outputs 就用它(最稳)。
- Anthropic 没有独立「JSON Mode」,用 tool use 让模型返回
tool_use块里的结构化 JSON。 - 老模型/开源模型不支持约束解码时,退回 JSON Mode + 强制客户端 Pydantic 校验与重试。
1. OpenAI Structured Outputs(strict)
python
from openai import OpenAI
client = OpenAI()
resp = client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "你是工单分类器,只依据给定文本输出 JSON。"},
{"role": "user", "content": "数据库主从延迟告警,持续 5 分钟。"},
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "ticket_classify",
"strict": True,
"schema": {
"type": "object",
"properties": {
"category": {"type": "string",
"enum": ["hardware", "network", "dependency", "account"]},
"severity": {"type": "integer"},
"summary": {"type": "string"},
},
"required": ["category", "severity", "summary"],
"additionalProperties": False,
},
},
},
temperature=0.0,
)
print(resp.choices[0].message.content)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
strict 模式硬约束(写错会报错):
- 每个嵌套对象都必须
additionalProperties: false(禁止多余键)。 required必须列出properties全部字段(保证字段必现)。- 不支持外部
$ref(用内联$defs);顶层必须是 object,不能是数组/基本类型。 - 有嵌套深度与属性数量上限
[版本相关,以官方文档为准]。
2. Anthropic tool use
python
import anthropic
client = anthropic.Anthropic()
msg = client.messages.create(
model="claude-3-5-sonnet-20240620",
max_tokens=1024,
tools=[{
"name": "classify_ticket",
"description": "对告警工单分类",
"input_schema": {
"type": "object",
"properties": {
"category": {"type": "string",
"enum": ["hardware", "network", "dependency", "account"]},
"severity": {"type": "integer"},
},
"required": ["category", "severity"],
},
}],
messages=[{"role": "user", "content": "支付接口超时 3 分钟。"}],
)
# 取 tool_use 块里的 input 即为结构化 JSON
data = next(b.input for b in msg.content if b.type == "tool_use")1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
3. 客户端 Pydantic 兜底(值级校验)
python
from pydantic import BaseModel, Field, field_validator
class Ticket(BaseModel):
category: str
severity: int = Field(ge=1, le=5)
summary: str
@field_validator("summary")
@classmethod
def not_empty(cls, v):
if not v.strip():
raise ValueError("summary 不可为空")
return v1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
验证
bash
# 1. 故意缺字段 / 错类型,确认 strict 模式拒绝或自动补齐
# 2. 跑 1000 次调用,统计解析成功率与 refusal 比例 [未实测,示意]
# 3. 对 refusal 分支确认下游有兜底,不会 5001
2
3
2
3
回滚与清理
schema 变更是破坏性变更
- 改
required/properties会让旧客户端解析失败。schema 应版本化,必要时双写过渡。 - 关闭 strict 退回 JSON Mode 会重新引入缺字段风险,务必同步保留客户端校验。
- 若切换模型到不支持 Structured Outputs 的版本,需回退到「JSON Mode + 重试」路径。
故障排查
| 现象 | 原因 | 处理 |
|---|---|---|
| HTTP 200 但 KeyError | 用了 JSON Mode 而非 Structured | 升级到 Strict / 加 Pydantic |
| 包 ```json 代码块 | prompt-only 或无 JSON Mode | 开启 JSON Mode / strict |
| 字段值非法(如 severity=9) | strict 不校验值级 | Pydantic 校验拒绝并重试 |
| finish_reason=length 截断 | max_tokens 过小 | 调大 max_tokens |
| 返回 refusal 而非数据 | 模型判定不安全/信息不足 | 下游走降级分支 |
安全与合规
- 结构化输出若承载 PII,需与 提示注入攻防 配合:外部内容可能诱导模型把不该输出的字段填进来。
- 不要信任模型「自报」的置信度数值作为风控依据,它不可靠。
成本与性能
- Structured Outputs 与 JSON Mode 的 token 单价与同模型普通调用一致(不额外计费),成本差异主要来自输出长度(schema 越大、字段越多,输出 token 越多)。
- 约束解码会在服务端做 schema 编译,首请求可能有极小开销,通常可忽略
[版本相关]。 - 反复重试会放大成本:建议配合 上下文压缩与缓存 缓存固定 system prompt,并对重试次数设上限(如 ≤2 次)。