大模型如何可靠返回合格的 JSON:从提示词到 Structured Outputs
大模型如何可靠返回合格的 JSON:从提示词到 Structured Outputs
摘要: 本文系统梳理大模型生成 JSON 的四种方案,并给出一套生产级处理流程。内容覆盖提示词 JSON、JSON Mode、JSON Schema、Function Call、流式拼接、Schema 校验、业务校验、失败重试与供应商兼容,可作为结构化输出功能的设计和排查参考。
关键词: 大模型、JSON、Structured Outputs、JSON Schema、Function Call、流式输出
文章目录
- 大模型如何可靠返回合格的 JSON:从提示词到 Structured Outputs
前言
在业务系统中,“让大模型返回 JSON”看起来只是加一句提示词,真正上线后却经常遇到以下问题:
- JSON前后夹带解释文字;
- 使用 Markdown代码围栏包裹;
- 字段缺失或字段名变化;
- 数字被返回成字符串;
- 枚举值不符合约定;
- 嵌套结构不完整;
- 流式响应尚未结束就开始解析;
- 达到输出 token上限,JSON在中途被截断;
- 模型返回合法 JSON,但业务含义错误;
- 模型或中转服务不支持
response_format。
本文从协议和工程两个层面说明如何获得可靠的 JSON输出,并给出一套可以直接落地的生产流程。
适合哪些读者
- 正在对接大模型 API 的后端或全栈开发者;
- 需要从文本中抽取稳定结构化数据的开发者;
- 正在实现 Agent、Function Call 或 MCP 工具调用的开发者;
- 遇到 JSON 截断、字段漂移或模型不遵循格式问题的开发者。
阅读后可以获得什么
- 理解“合法 JSON”“符合 Schema”和“业务正确”的区别;
- 知道不同模型能力下应该选择哪一种 JSON 返回方案;
- 掌握完整请求、响应、校验、重试与降级流程;
- 避免把截断 JSON、工具参数或模型幻觉直接写入业务系统。
1. 先给结论
按可靠性从低到高,可以把方案分为四级:
| 级别 | 方案 | 格式可靠性 | 适用场景 |
|---|---|---|---|
| 1 | 提示词要求 JSON | 低 | 临时脚本、兼容不支持结构化输出的模型 |
| 2 | response_format: json_object |
中 | 只要求合法 JSON,不强制固定字段 |
| 3 | response_format: json_schema + strict |
高 | API返回、数据抽取、稳定业务结构 |
| 4 | Function Call / Tool Call | 高 | 模型需要触发程序动作或调用工具 |
生产环境推荐:
优先使用 json_schema + strict
→ 不支持时降级到 json_object
→ 仍不支持时使用提示词 JSON + 本地 Schema校验
→ 涉及执行动作时使用 Function Call
无论使用哪种方式,客户端都必须执行:
检查响应状态
→ 检查 finish_reason
→ 拼接完整流式内容
→ JSON解析
→ Schema校验
→ 业务语义校验
→ 失败时有限重试或降级
response_format 能提高结构可靠性,但不能突破上下文窗口、单次输出上限,也不能保证数据事实正确。
核心原则: 模型负责生成候选结构,程序负责验证。任何模型返回的 JSON 在进入数据库、调用工具或触发业务动作前,都必须经过确定性代码校验。
2. 什么叫“合格的 JSON”
一个可用于生产业务的 JSON结果至少要通过四层检查。
2.1 语法合法
下面是合法 JSON:
{
"name": "张三",
"age": 28,
"active": true,
"tags": ["开发", "AI"],
"remark": null
}
常见的非法情况:
{name: "张三"} 字段名没有双引号
{'name': '张三'} 使用了Python单引号
{"name": "张三",} 末尾多余逗号
```json ... ``` 外层带Markdown围栏
以下是结果:{"name":"张三"} 前面夹带说明文字
2.2 结构符合 Schema
语法合法不代表结构正确。例如业务要求 age 是整数,模型却返回:
{
"name": "张三",
"age": "二十八"
}
它是合法 JSON,但不符合业务 Schema。
2.3 业务语义正确
即使结构正确,也可能不符合业务规则:
{
"name": "张三",
"age": -20
}
因此还要校验年龄范围、时间先后关系、订单金额、状态迁移等业务约束。
2.4 内容完整
如果模型达到输出上限,可能返回:
{"name":"张三","projects":[{"name":"项目A"},{"name":"项目B"
此时不仅 JSON非法,而且响应通常伴随:
finish_reason: length
客户端不能把这种内容当作普通 JSON解析失败,而应先识别为输出截断。
3. 方案一:只用提示词要求 JSON
这是兼容性最好、可靠性最低的方案。
3.1 请求示例
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "你是数据抽取助手。只返回一个合法JSON对象,不要使用Markdown代码围栏,不要添加解释文字。字段必须为name、age和skills。"
},
{
"role": "user",
"content": "张三今年28岁,擅长Python和SQL。"
}
],
"max_tokens": 1000,
"temperature": 0,
"stream": false
}
理想响应:
{
"id": "chatcmpl-json-001",
"object": "chat.completion",
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{\"name\":\"张三\",\"age\":28,\"skills\":[\"Python\",\"SQL\"]}"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 77,
"completion_tokens": 29,
"total_tokens": 106
}
}
注意,message.content 是一个字符串,客户端仍需要调用 JSON解析器:
import json
raw_content = response["choices"][0]["message"]["content"]
data = json.loads(raw_content)
3.2 更可靠的提示词模板
你是结构化数据生成器。
输出规则:
1. 只返回一个JSON对象。
2. 不要使用Markdown代码围栏。
3. 不要添加解释、标题、前缀或后缀。
4. 字段名称必须严格使用:name、age、skills。
5. age必须是整数,未知时返回null,不要猜测。
6. skills必须是字符串数组,没有内容时返回空数组。
7. 不允许输出未定义字段。
目标结构:
{"name":"string","age":"integer|null","skills":["string"]}
提示词能提高成功率,但不是协议级约束。模型仍可能返回错误格式,所以必须在客户端验证。
4. 方案二:JSON Mode
支持 JSON Mode的 OpenAI兼容接口通常使用:
{
"response_format": {
"type": "json_object"
}
}
4.1 完整请求
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "返回JSON对象,字段为name、age和skills。不要添加解释。"
},
{
"role": "user",
"content": "张三今年28岁,擅长Python和SQL。"
}
],
"response_format": {
"type": "json_object"
},
"max_tokens": 1000,
"temperature": 0,
"top_p": 1,
"n": 1,
"stream": false
}
4.2 响应
{
"id": "chatcmpl-json-002",
"object": "chat.completion",
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{\"name\":\"张三\",\"age\":28,\"skills\":[\"Python\",\"SQL\"]}"
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 61,
"completion_tokens": 29,
"total_tokens": 90
}
}
JSON Mode一般保证结果可以解析为 JSON对象,但不一定保证:
- 必须包含所有字段;
- 字段类型正确;
- 没有额外字段;
- 枚举值符合要求;
- 数值位于合理范围;
- 数据事实正确。
因此JSON Mode仍然需要本地 Schema校验。
5. 方案三:JSON Schema Structured Outputs
如果模型支持 Structured Outputs,应优先使用 json_schema 和严格模式。
5.1 完整请求
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "从用户文本中提取人员信息。未知字段返回null,不要推测。"
},
{
"role": "user",
"content": "张三今年28岁,擅长Python和SQL。"
}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "person_information",
"description": "从文本中提取的人员信息。",
"strict": true,
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "人员姓名。"
},
"age": {
"anyOf": [
{"type": "integer"},
{"type": "null"}
],
"description": "人员年龄,未知时为null。"
},
"skills": {
"type": "array",
"description": "明确提到的技能。",
"items": {
"type": "string"
}
}
},
"required": ["name", "age", "skills"],
"additionalProperties": false
}
}
},
"max_tokens": 1000,
"temperature": 0,
"top_p": 1,
"n": 1,
"stream": false
}
5.2 成功响应
{
"id": "chatcmpl-json-003",
"object": "chat.completion",
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{\"name\":\"张三\",\"age\":28,\"skills\":[\"Python\",\"SQL\"]}",
"refusal": null
},
"finish_reason": "stop",
"logprobs": null
}
],
"usage": {
"prompt_tokens": 198,
"completion_tokens": 29,
"total_tokens": 227
}
}
5.3 为什么还要本地校验
即使服务端声称支持严格 Schema,也仍应本地校验,原因包括:
- 中转网关可能忽略或改写
response_format; - 实际模型可能不支持严格模式;
- 模型供应商可能只支持 JSON Schema子集;
- 流式拼接、网络中断或SDK兼容问题可能破坏内容;
- Schema约束不了所有业务语义。
6. JSON Schema可以描述什么
常见基础类型:
| 类型 | 示例 |
|---|---|
object |
{"name":"张三"} |
array |
["Python","SQL"] |
string |
"张三" |
number |
18.5 |
integer |
18 |
boolean |
true |
null |
null |
常用结构关键字:
properties
required
additionalProperties
items
enum
const
anyOf
$defs
$ref
标准 JSON Schema还定义了大量约束:
minimum / maximum
minLength / maxLength
pattern / format
minItems / maxItems / uniqueItems
oneOf / allOf / not
但是模型服务通常只支持 JSON Schema的一个子集。接入时应以具体供应商文档和实际测试为准。
6.1 可空字段
在严格模式中,常见做法是字段仍然必填,但允许值为 null:
{
"anyOf": [
{"type": "string"},
{"type": "null"}
]
}
这比完全省略字段更适合稳定的数据接口:调用方始终知道字段存在,只需要判断值是否为 null。
6.2 枚举
{
"type": "string",
"enum": ["pending", "running", "completed", "failed"]
}
枚举适合状态、分类和固定选项,可以减少模型自由发挥。
6.3 嵌套对象数组
{
"type": "object",
"properties": {
"order_id": {"type": "string"},
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sku": {"type": "string"},
"quantity": {"type": "integer"},
"price": {"type": "number"}
},
"required": ["sku", "quantity", "price"],
"additionalProperties": false
}
}
},
"required": ["order_id", "items"],
"additionalProperties": false
}
Schema越复杂,输入 token成本越高,模型生成错误和供应商不兼容的概率也越高。能用简单结构解决时,不要设计过深的嵌套。
7. 方案四:Function Call
如果JSON用于描述“应用接下来要执行什么动作”,应使用 Function Call,而不是要求模型把命令放进普通 content。
7.1 请求
{
"model": "example-model",
"messages": [
{
"role": "system",
"content": "你是订单查询助手。需要查询订单时使用工具。"
},
{
"role": "user",
"content": "查询订单 H20260718001。"
}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_order",
"description": "根据订单号查询订单。",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "完整订单号。"
}
},
"required": ["order_id"],
"additionalProperties": false
}
}
}
],
"tool_choice": "auto",
"parallel_tool_calls": true,
"max_tokens": 1000,
"temperature": 0,
"stream": false
}
7.2 响应
{
"id": "chatcmpl-tool-json-001",
"object": "chat.completion",
"model": "example-model",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_order_001",
"type": "function",
"function": {
"name": "get_order",
"arguments": "{\"order_id\":\"H20260718001\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 128,
"completion_tokens": 28,
"total_tokens": 156
}
}
function.arguments 通常仍是 JSON字符串,应用必须解析、验证、审批,再执行工具。Function Call提高了参数结构可靠性,但不代表模型有执行权限。
8. 一套生产级处理流程
业务请求
│
▼
探测模型能力
│
▼
选择 json_schema / json_object / 提示词 JSON
│
▼
计算输入与输出 token 预算
│
▼
发送模型请求
│
├─ 请求失败 ──> 分类网络、鉴权、限流和参数错误
│
▼
检查 finish_reason
│
├─ length ─────────> 判定截断,禁止直接修补半截 JSON
├─ content_filter ─> 处理拒绝或安全拦截
├─ tool_calls ─────> 解析并校验工具参数
└─ stop
│
▼
提取完整 content
│
▼
JSON 解析
│
▼
JSON Schema 校验
│
├─ 失败 ──> 有限重试或能力降级
│
▼
业务语义校验
│
├─ 失败 ──> 有限重试或返回业务错误
│
▼
返回可信业务对象
8.1 能力探测
不要假设所有文本模型都支持 response_format。可以为每个模型维护能力信息:
{
"example-model": {
"supports_json_object": true,
"supports_json_schema": true,
"supports_strict_schema": true,
"supports_tools": true,
"max_output_tokens": 16384,
"context_window": 131072
}
}
如果缺少官方能力接口,应通过小请求测试并缓存结果。不能在每个业务请求中先故意触发一次 400 来探测能力。
8.2 输出预算
结构化输出仍受 token限制:
输入 token + 输出 token <= 上下文窗口
输出 token <= 模型单次输出上限
输出 token <= 请求指定上限
对于可能返回大量数组的数据,优先使用分页、限制条数或先返回摘要。不要让模型一次生成数万条记录。
8.3 检查停止原因
在解析 content 前先检查:
choice = response["choices"][0]
finish_reason = choice.get("finish_reason")
if finish_reason == "length":
raise RuntimeError("模型输出达到长度限制,JSON可能不完整")
if finish_reason == "content_filter":
raise RuntimeError("模型输出被安全策略终止")
if finish_reason == "tool_calls":
raise RuntimeError("当前响应是工具调用,不是普通JSON内容")
if finish_reason != "stop":
raise RuntimeError(f"未知结束原因: {finish_reason}")
不同服务商可能使用 max_tokens、MAX_TOKENS、incomplete 或其他原始值,客户端适配层应先归一化。
8.4 JSON解析
import json
content = choice["message"].get("content")
if not isinstance(content, str) or not content.strip():
raise ValueError("模型没有返回JSON文本")
try:
data = json.loads(content)
except json.JSONDecodeError as exc:
raise ValueError(
f"JSON解析失败: line={exc.lineno}, column={exc.colno}, message={exc.msg}"
) from exc
不建议默认使用正则截取第一个 { 到最后一个 }。这种“修复”可能掩盖协议错误、误截嵌套对象,甚至错误接受被截断的数据。
8.5 Schema校验
使用 Python jsonschema:
from jsonschema import Draft202012Validator
PERSON_SCHEMA = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": ["integer", "null"]},
"skills": {
"type": "array",
"items": {"type": "string"},
},
},
"required": ["name", "age", "skills"],
"additionalProperties": False,
}
validator = Draft202012Validator(PERSON_SCHEMA)
errors = sorted(validator.iter_errors(data), key=lambda error: list(error.path))
if errors:
details = [
{
"path": ".".join(str(part) for part in error.path) or "$",
"message": error.message,
}
for error in errors
]
raise ValueError(f"JSON Schema校验失败: {details}")
也可以使用 Pydantic,将解析、类型检查和业务模型结合:
from pydantic import BaseModel, ConfigDict, Field
class PersonInformation(BaseModel):
model_config = ConfigDict(extra="forbid")
name: str = Field(min_length=1)
age: int | None = Field(default=None, ge=0, le=150)
skills: list[str]
person = PersonInformation.model_validate(data)
8.6 业务语义校验
Schema只能验证结构,不能证明内容真实。例如订单总额应等于明细之和:
calculated_total = sum(item["quantity"] * item["price"] for item in data["items"])
if abs(calculated_total - data["total_amount"]) > 0.01:
raise ValueError("订单总额与明细计算结果不一致")
对身份证号、库存、订单状态和权限信息,应以数据库或确定性代码为准,不能仅相信模型生成值。
9. 可直接复用的 Python封装
下面的代码展示核心校验流程。模型SDK调用部分以函数参数传入,便于替换不同供应商。
import json
from collections.abc import Callable
from typing import Any
from jsonschema import Draft202012Validator
class StructuredOutputError(RuntimeError):
pass
class OutputTruncatedError(StructuredOutputError):
pass
class SchemaValidationError(StructuredOutputError):
pass
def parse_structured_response(
response: dict[str, Any],
schema: dict[str, Any],
) -> dict[str, Any]:
choices = response.get("choices")
if not isinstance(choices, list) or not choices:
raise StructuredOutputError("响应中没有choices")
choice = choices[0]
finish_reason = choice.get("finish_reason")
if finish_reason == "length":
raise OutputTruncatedError("模型达到输出长度限制")
if finish_reason == "content_filter":
raise StructuredOutputError("输出被安全策略终止")
if finish_reason == "tool_calls":
raise StructuredOutputError("收到工具调用,而不是结构化content")
if finish_reason != "stop":
raise StructuredOutputError(f"不支持的finish_reason: {finish_reason!r}")
message = choice.get("message") or {}
refusal = message.get("refusal")
if refusal:
raise StructuredOutputError(f"模型拒绝请求: {refusal}")
content = message.get("content")
if not isinstance(content, str) or not content.strip():
raise StructuredOutputError("响应content为空")
try:
data = json.loads(content)
except json.JSONDecodeError as exc:
raise StructuredOutputError(
f"JSON语法错误: line={exc.lineno}, column={exc.colno}, {exc.msg}"
) from exc
validator = Draft202012Validator(schema)
errors = sorted(validator.iter_errors(data), key=lambda error: list(error.path))
if errors:
details = "; ".join(
f"{'.'.join(str(part) for part in error.path) or '$'}: {error.message}"
for error in errors
)
raise SchemaValidationError(details)
return data
def request_structured_data(
send_request: Callable[[dict[str, Any]], dict[str, Any]],
request_body: dict[str, Any],
schema: dict[str, Any],
max_attempts: int = 2,
) -> dict[str, Any]:
last_error: Exception | None = None
for attempt in range(1, max_attempts + 1):
try:
response = send_request(request_body)
return parse_structured_response(response, schema)
except OutputTruncatedError:
raise
except (StructuredOutputError, SchemaValidationError) as exc:
last_error = exc
if attempt == max_attempts:
break
request_body = {
**request_body,
"messages": [
*request_body["messages"],
{
"role": "user",
"content": (
"上一次输出未通过结构校验。请重新生成完整JSON,"
"严格遵守Schema,不要添加解释文字。"
),
},
],
}
raise StructuredOutputError(
f"结构化输出在{max_attempts}次尝试后仍失败: {last_error}"
)
重要原则:
- 截断错误不应把半截 JSON交给模型“猜着修复”;
- 重试次数必须有限;
- 涉及费用或副作用的调用要保证幂等;
- 日志记录错误类型、模型、请求ID和 token用量,但不要记录密钥和敏感原文。
10. 流式 JSON如何处理
流式返回时,每个数据块只是字符串片段:
data: {"choices":[{"index":0,"delta":{"content":"{\"name\":"}}]}
data: {"choices":[{"index":0,"delta":{"content":"\"张三\",\"age\":"}}]}
data: {"choices":[{"index":0,"delta":{"content":"28}"}}]}
data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]
正确方式:
parts: list[str] = []
finish_reason: str | None = None
for event in stream:
choice = event["choices"][0]
delta = choice.get("delta") or {}
if isinstance(delta.get("content"), str):
parts.append(delta["content"])
if choice.get("finish_reason") is not None:
finish_reason = choice["finish_reason"]
if finish_reason != "stop":
raise RuntimeError(f"流式响应未正常结束: {finish_reason}")
data = json.loads("".join(parts))
不要对每个增量片段分别调用 json.loads()。在最后一个片段到达前,整个字符串通常都不是完整 JSON。
如果需要边生成边展示,可以展示原始文本进度,但只有完成拼接和校验后,才能把结果提交给业务系统。
11. 失败重试应该怎么做
11.1 可以重试的情况
- 短暂网络错误;
- 服务端
429或5xx; - JSON语法错误;
- Schema字段缺失或类型错误;
- 输出包含额外说明文字;
- 模型偶发没有遵循结构要求。
11.2 不应盲目重试的情况
- API Key错误;
- 模型明确不支持
response_format; - 输入已经超过上下文窗口;
- Schema本身不被服务商支持;
- 输出达到固定 token上限且请求内容没有缩小;
- 安全策略明确拒绝;
- 同一个有副作用工具已经成功执行。
11.3 分层降级
尝试 json_schema strict
→ 收到 unsupported response_format
→ 降级 json_object + 本地Schema校验
→ 仍不支持
→ 提示词JSON + 本地Schema校验
→ 连续失败
→ 返回可诊断错误,不无限重试
降级决策应依据明确的错误码或能力配置,不要对所有异常都静默降级,否则真实的鉴权、网络或业务错误会被掩盖。
12. 常见错误和改进方式
错误一:只写“请返回JSON”
问题:字段、类型、未知值和额外内容都没有约束。
改进:给出字段定义、类型、未知值策略,并使用 json_schema 或本地 Schema校验。
错误二:直接删除 Markdown围栏
问题:可能把本应判定为协议失败的内容伪装成成功,而且无法处理前后解释和多个 JSON对象。
改进:优先使用结构化输出;兼容旧模型时可以进行受控清理,但清理后仍必须严格解析和校验,并记录发生过降级。
错误三:JSON解析成功就写数据库
问题:合法 JSON可能字段错误、金额错误、状态非法或包含模型虚构数据。
改进:执行 Schema校验、业务校验和权限校验,再进入持久化流程。
错误四:忽略 finish_reason
问题:被截断的 JSON最终表现为普通解析错误,无法判断是模型格式问题还是输出预算不足。
改进:先检查结束原因,再解析内容。
错误五:把 max_tokens 设置得越大越好
问题:会增加延迟和成本,也可能与剩余上下文空间冲突;模型自身仍有单次输出上限。
改进:根据 Schema和预期数据量设置合理预算,大结果采用分页或分块。
错误六:相信 strict 等于事实正确
问题:strict 约束结构,不验证事实来源。
改进:关键事实通过数据库、检索结果和确定性程序验证。
13. 供应商兼容性
不是所有文本模型都支持 response_format。常见能力组合:
| 能力 | 可能情况 |
|---|---|
| 普通文本 | 基本都支持 |
| 提示词 JSON | 基本可尝试,但可靠性因模型而异 |
json_object |
部分 OpenAI兼容模型支持 |
json_schema |
只有部分模型和网关支持 |
strict |
支持范围更小,且可能只支持 Schema子集 |
| Function Call | 部分模型原生支持,部分网关模拟支持 |
可能遇到:
400 unsupported parameter: response_format
400 invalid schema
response_format被网关静默忽略
模型名称支持但当前部署版本不支持
同一聚合平台上的不同上游模型能力不同
因此,能力应绑定到“供应商 + API模式 + 实际模型 + 模型版本”,不能只根据模型展示名称判断。
14. 日志与监控
建议记录:
request_id
provider
actual_model
api_mode
response_format类型
schema名称和版本
finish_reason
prompt_tokens
completion_tokens
解析是否成功
Schema是否成功
重试次数
错误分类
响应延迟
不要记录:
API Key
Authorization Header
用户密码
完整个人敏感数据
不必要的完整提示词和模型输出
Schema本身也应该有版本,例如:
{
"schema_name": "person_information",
"schema_version": "1.2.0"
}
当字段发生变化时,可以区分是模型失败,还是客户端仍按旧 Schema解析。
15. 最终推荐架构
┌──────────┐ ┌──────────────┐ ┌────────────────┐
│ 业务请求 │ -> │ 模型能力注册表 │ -> │ Schema与提示词构建 │
└──────────┘ └──────────────┘ └────────────────┘
│
▼
┌──────────┐ ┌──────────────┐ ┌────────────────┐
│ 业务对象 │ <- │ 业务语义校验 │ <- │ JSON Schema校验 │
└──────────┘ └──────────────┘ └────────────────┘
▲
│
┌──────────┐ ┌──────────────┐ ┌────────────────┐
│ 模型 API │ -> │ 响应状态检查 │ -> │ JSON解析 │
└──────────┘ └──────────────┘ └────────────────┘
▲ │ │
│ └──────┬─────────────┘
│ ▼
│ ┌──────────────────┐
└──────────────│ 有限重试或能力降级 │
└──────────────────┘
生产级最佳实践可以总结为:
- 优先选择支持
json_schema + strict的模型; - Schema保持简单、封闭,并设置
additionalProperties: false; - 未知值使用
null,避免模型猜测; - 在解析前检查
finish_reason和拒绝状态; - 流式响应必须完整拼接后再解析;
- 服务端约束之后仍执行本地 Schema校验;
- 再增加业务语义和权限校验;
- 对不支持结构化输出的模型按能力明确降级;
- 失败只做有限重试,并保留可诊断日志;
- 大结果采用分页、分块或工具查询,不依赖无限提高输出 token。
16. 总结
让大模型“返回一段看起来像 JSON的文本”很容易;让它稳定返回可被业务系统信任的结构化数据,则需要协议约束和客户端验证共同完成。
最稳妥的路径是:使用 json_schema + strict 约束输出结构,检查响应结束状态,完成 JSON解析、本地 Schema校验和业务语义校验;不支持 Structured Outputs时,降级到 json_object 或提示词 JSON,但不降低本地验证标准。涉及程序执行时,则应使用 Function Call,并把模型输出视为待校验的调用请求,而不是已经获得授权的操作命令。
最后提醒: “模型成功返回”不等于“业务数据可信”。只有完成响应状态检查、JSON解析、Schema校验、语义校验和权限校验之后,结构化输出才能进入真实业务流程。
如果本文对你有帮助,可以将这套流程整理成项目中的统一结构化输出组件,避免每个业务模块重复实现解析、校验、重试和降级逻辑。
更多推荐



所有评论(0)