JSON 模式/结构化输出报错怎么办?response_format 避坑
让 AI 返回结构化 JSON 是很常见的需求,但这个能力在各个平台上的实现方式差异很大,报错信息也五花八门。
JSON 模式报错通常分三类:
- 平台/模型不支持 --- 你用的模型或 API 端点根本没这个功能
- Schema 校验失败 --- 模型输出了 JSON 但不符合你定义的格式
- 输出被截断 --- JSON 写到一半被 max_tokens 截断,变成无效内容
搞清楚报错的真实原因,才能对症下药。
1. 三类报错的典型场景
场景一:平台/模型不支持 JSON 模式
JSON 模式不是所有模型都支持,不同平台的叫法和开启方式也不一样:
| 平台 | 开启方式 | 支持的模型 | 注意 |
|---|---|---|---|
| OpenAI | response_format: {"type": "json_object"} |
GPT-4o、GPT-4o-mini、GPT-4-turbo 等多数现代模型 | 必须确保消息中有 "json" 字样 |
| OpenAI Structured Outputs | response_format: PydanticModel 或 {"type": "json_schema", "json_schema": {...}} |
GPT-4o-2024-08-06 及之后,支持 strict | 只保证格式,不保证内容正确 |
| Claude | output_config: {"format": {"type": "json_schema", "schema": {...}}} |
Claude Sonnet 4.5、Opus 4.1、Haiku 4.5(2025年后) | 需要 beta header(老版)或新版 API |
| DeepSeek | response_format: {"type": "json_object"} |
deepseek-chat、deepseek-v4-flash、deepseek-v4-pro | 必须同时在 prompt 中引导输出 JSON 格式 |
如果你在不支持的模型上开启了 JSON 模式,通常会收到类似这样的错误:
json
{
"error": {
"message": "model does not support response_format",
"type": "invalid_request_error",
"code": "model_not_supported"
}
}
解决方式: 确认当前使用的模型是否在支持列表中,必要时切换模型。
场景二:Schema 校验失败(模型输出了 JSON 但格式不对)
这种情况最常见。模型确实返回了 JSON,但字段类型、数量、嵌套结构与你的定义不符。
典型错误:
json
{
"error": {
"message": "Invalid schema for function 'extract_contact': "
"'email' is not of type 'string'.",
"type": "invalid_request_error",
"code": "invalid_function_parameters"
}
}
或者模型自己输出了格式错误的 JSON:
python
# 模型可能输出这样的内容(不完整或含 markdown 包裹)
"""
Here is the JSON:
{
"name": "张三",
"age": 28
}
"""
json.loads() 直接报错:
python
import json
raw = response.choices[0].message.content
json.loads(raw) # ❌ JSONDecodeError: Expecting property name enclosed in double quotes
场景三:输出被截断
设置 max_tokens 过小时,模型生成的 JSON 只写了一半就停了:
python
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
response_format={"type": "json_object"},
max_tokens=100 # ❌ 太小了
)
# finish_reason = "length"
# content = '{"name": "张"
finish_reason == "length" 是判断截断的关键信号。
2. 各平台 JSON 模式详解
2.1 OpenAI:json_object 和 json_schema
json_object 模式(基础):
python
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL")
messages = [
{"role": "system", "content": "你是一个数据提取助手,只返回 JSON。"},
{"role": "user", "content": "提取:姓名张三,年龄28岁,邮箱 zhang@example.com"}
]
response = client.chat.completions.create(
model="YOUR_MODEL",
messages=messages,
response_format={"type": "json_object"},
max_tokens=500
)
result = json.loads(response.choices[0].message.content)
print(result)
必须满足的前提条件(很多人踩的坑): 消息中必须出现 "json" 字样(大小写不敏感)。如果 system prompt 完全没有提到 JSON,API 直接报错:
json
{
"error": {
"message": "messages must contain 'json' in some form "
"to use response_format with type json_object",
"type": "invalid_request_error"
}
}
json_schema / Structured Outputs 模式(严格,2024年6月推出):
python
from pydantic import BaseModel
class Contact(BaseModel):
name: str
email: str
age: int | None = None
response = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[{"role": "user", "content": "张三,zhang@example.com,28岁"}],
response_format=Contact,
max_tokens=500
)
contact = response.choices[0].message.parsed
print(contact.name, contact.email)
parse() 方法返回 Pydantic 模型实例,Schema 自动转换,解析失败直接抛异常。
如果你不想用 Pydantic,直接传 JSON Schema:
python
response = client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=messages,
response_format={
"type": "json_schema",
"json_schema": {
"name": "Contact",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"age": {"type": "integer"}
},
"required": ["name", "email"],
"additionalProperties": False
}
}
},
max_tokens=500
)
Structured Outputs 的重要限制:
- 只能保证格式,不能保证内容正确(模型可能"格式正确地胡说八道")
- 不兼容并行 Function Calling
- Schema 必须满足严格模式要求(所有字段 required,
additionalProperties: false)
2.2 Claude:output_config.format
Claude 在 2025 年 11 月正式发布 Structured Outputs,通过 output_config.format 控制输出格式:
python
from anthropic import Anthropic
import json
client = Anthropic(api_key="YOUR_API_KEY")
response = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
messages=[
{
"role": "user",
"content": "从以下文本中提取联系人信息:张三,zhang@example.com,想了解企业版套餐,希望安排下周二下午2点的演示。"
}
],
output_config={
"format": {
"type": "json_schema",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"plan_interest": {"type": "string"},
"demo_requested": {"type": "boolean"},
"demo_time": {"type": "string"}
},
"required": ["name", "email", "plan_interest", "demo_requested"],
"additionalProperties": False
}
}
}
)
result = json.loads(response.content[0].text)
print(result)
Claude 注意事项:
- 老版 beta API 用
betas=["structured-outputs-2025-11-13"]+output_format,新版(2025年11月后)直接用output_config,无需 beta header extended_thinking(扩展思考)模式下不支持强制工具调用- 同样只保证格式,不保证内容准确性
2.3 DeepSeek:response_format + prompt 双重引导
DeepSeek 的 JSON Output 依赖 response_format 参数,但同时要求 prompt 中包含 JSON 格式示例:
python
from openai import OpenAI
import json
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.deepseek.com"
)
system_prompt = """用户会提供一段文本,请你提取其中的问题与答案,按以下格式输出:
EXAMPLE JSON OUTPUT:
{
"question": "问题内容",
"answer": "答案内容"
}
只输出 JSON,不要任何其他文字。"""
user_prompt = "世界上最高的山是什么?珠穆朗玛峰。"
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
response_format={"type": "json_object"},
max_tokens=500
)
raw = response.choices[0].message.content
result = json.loads(raw)
print(result)
# {"question": "世界上最高的山是什么?", "answer": "珠穆朗玛峰"}
DeepSeek JSON 模式的坑:
- 必须在 prompt(system 或 user)中给出 JSON 格式示例,否则模型可能生成无限空白
max_tokens建议设大一些(500 以上),DeepSeek 有概率返回空 content(官方已知问题,正在优化)- 如果
finish_reason == "length",说明 JSON 被截断了,需要增大 max_tokens
3. 统一封装:一个能跑的 JSON 模式调用器
下面给出一个覆盖 OpenAI / Claude / DeepSeek 三家的统一调用封装,包含了完整的错误处理:
python
import json
import time
from typing import Any
def structured_output(
client,
model: str,
prompt: str,
schema: dict,
max_tokens: int = 1024,
retries: int = 3
) -> dict[str, Any] | None:
"""
通用的结构化输出调用,自动处理截断和解析错误。
支持 OpenAI (json_object/json_schema)、Claude (output_config)、DeepSeek (json_object)。
"""
for attempt in range(retries):
try:
response = client.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": f"请以 JSON 格式返回,包含以下字段:{json.dumps(schema, ensure_ascii=False)}。只输出 JSON,不要解释。"},
{"role": "user", "content": prompt}
],
response_format={"type": "json_object"},
max_tokens=max_tokens
)
message = response.choices[0].message
# 检查截断
if response.choices[0].finish_reason == "length":
raise JSONTruncatedError(
f"max_tokens={max_tokens} 不足,JSON 被截断。"
"请增大 max_tokens 后重试。"
)
raw = message.content
# 清理 markdown 包裹
raw = raw.strip()
if raw.startswith("```"):
lines = raw.splitlines()
raw = "\n".join(lines[1:-1] if lines[-1].strip() == "```" else lines[1:])
raw = raw.strip()
# 解析 JSON
result = json.loads(raw)
# 可选:Schema 后验校验
_validate_schema(result, schema)
return result
except JSONTruncatedError:
# 截断错误,增加 max_tokens 重试
max_tokens = int(max_tokens * 2)
if attempt == retries - 1:
raise
except json.JSONDecodeError as e:
if attempt == retries - 1:
print(f"[警告] JSON 解析失败: {e}")
print(f"原始内容: {raw}")
return None
except Exception as e:
print(f"[错误] {e}")
return None
return None
class JSONTruncatedError(Exception):
"""JSON 输出被截断时抛出"""
pass
def _validate_schema(result: dict, schema: dict) -> None:
"""简单 Schema 后验校验:检查必填字段和类型"""
required = schema.get("required", [])
properties = schema.get("properties", {})
for field in required:
if field not in result:
raise ValueError(f"缺少必填字段: {field}")
for field, expected_type in properties.items():
if field in result:
if "type" in expected_type:
expected = expected_type["type"]
actual = type(result[field]).__name__
# 简单类型映射
type_map = {"string": str, "integer": int, "number": (int, float), "boolean": bool, "array": list, "object": dict}
expected_cls = type_map.get(expected)
if expected_cls and not isinstance(result[field], expected_cls):
print(f"[警告] 字段 {field} 类型不符,期望 {expected},实际 {actual}")
使用示例:
python
from openai import OpenAI
client = OpenAI(api_key="YOUR_API_KEY", base_url="YOUR_BASE_URL")
schema = {
"required": ["name", "email"],
"properties": {
"name": {"type": "string"},
"email": {"type": "string"},
"phone": {"type": "string"}
}
}
result = structured_output(
client=client,
model="YOUR_MODEL",
prompt="联系人:张三,电话 13800138000,邮箱 zhangsan@example.com",
schema=schema,
max_tokens=512
)
print(result)
4. 常见坑
坑 1:OpenAI 消息中没有 "json" 字样
response_format: {"type": "json_object"} 要求消息中至少有一条包含 "json"(大小写不敏感)。如果你的 system prompt 写的是「严格按照以下格式返回数据」,而不提 JSON,API 会直接报错:
messages must contain 'json' in some form to use response_format with type json_object
解决: system prompt 第一行加上「请以 JSON 格式返回」或类似的描述。
坑 2:Schema 有未定义的字段
模型返回了 Schema 里没有的字段,普通 json_object 模式不会报错(它只保证 JSON 合法,不保证格式)。如果需要严格校验,用 Structured Outputs 或自己写校验逻辑。
坑 3:把 markdown 代码块当纯 JSON 解析
模型经常把 JSON 包裹在 markdown 代码块里:
Here is the result:
```json
{"name": "张三"}
直接 `json.loads()` 会报 `JSONDecodeError`。需要在解析前清理代码块。
### 坑 4:DeepSeek 的 `finish_reason="stop"` 但 content 为空
DeepSeek 文档明确提到:使用 JSON Output 时,API 有概率返回空的 content(`finish_reason="stop"` 但 content 为空)。这不是截断,是已知问题。官方正在优化。临时解法:检测到空 content 时重试。
### 坑 5:Claude 扩展思考模式(extended_thinking)下不能强制工具调用
如果你在 Claude 请求中开启了 `thinking.type = "enabled"`,就不能同时使用强制 JSON 格式输出。两者互斥。需要根据场景选择:需要推理时关闭 JSON 强制模式,需要结构化输出时关闭扩展思考。
---
## 快速排错表
| 错误信息/现象 | 原因 | 解决方式 |
|---|---|---|
| `messages must contain 'json' in some form` | OpenAI json_object 要求消息中有 "json" 字样 | 在 system/user prompt 中加入"JSON"、"json"等字样 |
| `model does not support response_format` | 使用的模型不支持 JSON 模式 | 切换到支持的模型(GPT-4o、Claude 4.x、DeepSeek-v4 等) |
| `finish_reason="length"` | max_tokens 太小,JSON 被截断 | 增大 max_tokens |
| content 为空但 finish_reason="stop" | DeepSeek 已知 bug,概率性返回空 | 检测到空 content 时重试 |
| `JSONDecodeError` | 模型输出了 markdown 包裹或非法 JSON | 解析前清理代码块,或用 Structured Outputs 强制格式 |
| Schema 校验失败(字段缺失/类型错误) | 模型输出了不符合 Schema 的 JSON | 用 Structured Outputs(严格模式)保证格式,或后验校验+重试 |
| Claude extended_thinking 模式下无结构化输出 | 扩展思考和强制 JSON 格式互斥 | 关闭 thinking 模式,或在 thinking 完成后再用 JSON 模式 |
| DeepSeek JSON Output 无限生成空白 | prompt 中没有引导 JSON 格式示例 | 在 system prompt 中加入 JSON 格式示例 |
---
## 配置检查清单
接入 JSON 模式前,逐项确认:
**消息内容**
- [ ] system prompt 中明确提到 "JSON"(OpenAI / DeepSeek 必须)
- [ ] prompt 中包含期望输出格式的示例(DeepSeek 必须)
- [ ] 没有其他指令与 JSON 模式冲突(如"用自然语言解释")
**API 参数**
- [ ] `response_format` 参数格式正确(`{"type": "json_object"}` 或 `{"type": "json_schema", "json_schema": {...}}`)
- [ ] `max_tokens` 设置足够大(至少 500,建议 1024+)
- [ ] 使用了支持 JSON 模式的模型版本
**解析逻辑**
- [ ] 有清理 markdown 代码块的逻辑
- [ ] 有处理 `finish_reason="length"` 的逻辑(增大 max_tokens 重试)
- [ ] 有处理 content 为空的逻辑(DeepSeek 场景下重试)
- [ ] 有 JSON Schema 后验校验(可选但推荐)
**Claude 专用**
- [ ] 使用新版 `output_config.format` 而非旧版 beta header + `output_format`
- [ ] 没有同时开启 `extended_thinking` 和强制 JSON 输出
- [ ] JSON Schema 满足要求(`additionalProperties: false` 等)