JSON模式结构化输出报错

JSON 模式/结构化输出报错怎么办?response_format 避坑

让 AI 返回结构化 JSON 是很常见的需求,但这个能力在各个平台上的实现方式差异很大,报错信息也五花八门。

JSON 模式报错通常分三类:

  1. 平台/模型不支持 --- 你用的模型或 API 端点根本没这个功能
  2. Schema 校验失败 --- 模型输出了 JSON 但不符合你定义的格式
  3. 输出被截断 --- 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 模式的坑:

  1. 必须在 prompt(system 或 user)中给出 JSON 格式示例,否则模型可能生成无限空白
  2. max_tokens 建议设大一些(500 以上),DeepSeek 有概率返回空 content(官方已知问题,正在优化)
  3. 如果 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` 等)
相关推荐
一技安身1 小时前
【信创】外网打包ragflow镜像并导入内网银河麒麟V10arm64服务器
运维·服务器
1314lay_10071 小时前
asp文件的服务器控件要使用CssClass,否则会失效。导致上传服务器之后,按钮禁用时的效果出问题了,没有正确应用样式
服务器
秋饼1 小时前
Spring AI 2.0 接入 DeepSeek V4.1 Flash 生产级实战
java·ai·技术分享·后端开发
IT枫斗者枫哥1 小时前
Java 分批导出仍然 OOM?用 32 MiB 堆复现三种 CSV 写法
java
T01156181 小时前
全栈项目实战手记|艺培场馆课时预约小程序全项目开发历程完整复盘总结
运维·服务器·小程序
库玛西1 小时前
深度解构高并发利器:纯事件驱动 Reactor 模式的设计哲学与工程实践
服务器·开发语言·c++·笔记·tcp/ip
她说..1 小时前
MySQL JSON 处理学习文档
学习·mysql·json
shehuiyuelaiyuehao1 小时前
算法43,外观数列,模拟算法+双指针
java·算法
程序员-Benothing1 小时前
Linux 用户与用户组管理:useradd usermod groupadd 实战
linux·运维·服务器