LLM 输出 JSON Schema 不稳定时怎么设计重试
来源:17golang原创
时间:2026-09-10 18:08:15 480浏览 收藏
让大模型“只输出 JSON”并不等于下游一定拿得到可用对象。实际接入时,失败至少有三层:文本不是合法 JSON、JSON 结构不符合 Schema,以及结构正确但业务值不可用。稳妥的做法是把解析和 JSON Schema 校验放在模型调用之后,只针对前两类可修复的格式问题做有限重试;超时、拒答、鉴权失败和业务校验失败应走各自的处理路径。
- JSON Mode 主要解决“能解析”,Schema 校验才负责字段、类型和枚举约束。
- 一次初始调用加两次修复重试通常足够,重试必须带上结构化错误而不是一句“再试试”。
- 合法 JSON 也可能不满足业务规则,格式通过后仍要做独立的业务验收。
- 生产日志至少保留尝试次数、错误类别、Schema 版本和最终处理结果。
先把 JSON Schema 当作应用契约
示例做一个“工单分类”小项目:模型返回分类、优先级和一句摘要。Schema 不只声明字段类型,还用 required 固定必填项,用 enum 限制可接受的优先级,并用 additionalProperties: false 拒绝模型随手增加的字段。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": false,
"properties": {
"category": {"type": "string", "enum": ["bug", "question", "request"]},
"priority": {"type": "string", "enum": ["low", "normal", "high"]},
"summary": {"type": "string", "minLength": 8, "maxLength": 120}
},
"required": ["category", "priority", "summary"]
}
这一步解决的是“契约写清楚了吗”,不是“模型一定听话”。如果供应商支持 Structured Outputs,可以在请求层传入同一份 Schema;仍建议在应用侧再次校验,因为模型、供应商和业务规则并不总是完全一致。

把解析与 Schema 校验放在模型调用之后
校验函数先处理语法,再处理结构。不要捕获所有异常后只返回“格式错误”,否则模型无法知道是缺少字段、类型不对还是枚举值超出范围。下面的代码用 Draft202012Validator.iter_errors 收集多个问题,并保留字段路径。
import json
from jsonschema import Draft202012Validator
SCHEMA = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"additionalProperties": False,
"properties": {
"category": {"type": "string", "enum": ["bug", "question", "request"]},
"priority": {"type": "string", "enum": ["low", "normal", "high"]},
"summary": {"type": "string", "minLength": 8, "maxLength": 120},
},
"required": ["category", "priority", "summary"],
}
validator = Draft202012Validator(SCHEMA)
def parse_and_validate(raw: str):
# 先判断文本是不是 JSON,再判断对象是否符合字段契约。
try:
payload = json.loads(raw)
except json.JSONDecodeError as exc:
return None, f"非法 JSON:{exc.msg}"
# 按字段路径排序,让修复提示稳定、便于日志聚合。
errors = sorted(validator.iter_errors(payload), key=lambda item: list(item.path))
if errors:
details = []
for error in errors[:4]:
path = ".".join(str(part) for part in error.path) or "$"
details.append(f"{path}: {error.message}")
return None, ";".join(details)
return payload, None
只对可修复的格式错误做有限重试
重试不是把原问题重复发送,而是把“返回 JSON、不要新增字段、修复这些具体错误”变成下一次调用的明确约束。示例设置总尝试次数为 3,即初始请求加两次修复请求;同时截断上一轮输出,避免错误响应不断膨胀上下文。
def classify_ticket(call_model, ticket_text: str, max_attempts: int = 3):
# call_model 只负责调用模型并返回文本,网络重试应在它自己的层处理。
prompt = f"将下面工单整理为 JSON,只返回对象:{ticket_text}"
raw = call_model(prompt)
for attempt in range(max_attempts):
value, error = parse_and_validate(raw)
if value is not None:
# Schema 通过后,再交给业务层检查,不在这里混合规则。
return value
if attempt == max_attempts - 1:
raise ValueError(f"JSON 输出连续失败,attempts={max_attempts},error={error}")
# 修复提示只携带必要上下文,避免让模型重复解释或输出 Markdown。
repair_prompt = (
"只返回符合给定 JSON Schema 的 JSON 对象,不要 Markdown 代码块。"
f"\n校验错误:{error}\n上一次输出:{raw[-6000:]}"
)
raw = call_model(repair_prompt)
raise AssertionError("不可达分支")
如果使用 Hugging Face Inference Providers,文档区分了 JSON Mode 和 Structured Outputs:前者强调可解析 JSON,后者把预定义 Schema 作为响应约束。即使上游有严格结构化输出,这段应用侧校验仍然适合做版本兼容、字段最小长度和业务前置检查。
把业务失败与格式失败分开验收
| 现象 | 是否做 Schema 重试 | 处理建议 |
|---|---|---|
| 缺少字段、类型错误、枚举值不对 | 可以,最多两次 | 回传字段路径和允许值 |
| 超时、鉴权失败、供应商 5xx | 不做格式重试 | 交给网络层退避并记录请求 ID |
| JSON 合法但工单编号不存在 | 不做 Schema 重试 | 走业务校验或人工兜底 |
| 连续达到尝试上限 | 停止 | 保存错误类别、Schema 版本和原始响应摘要 |
验收时至少观察三项:成功率按“初次成功/修复后成功/最终失败”拆分;平均尝试次数;不同错误类型的占比。若大量请求都在同一字段失败,优先检查 Schema 与提示词是否冲突,而不是继续增加重试次数。

常见问题
JSON 能被 json.loads 解析,还需要 Schema 校验吗?
需要。解析只说明语法成立,不能证明字段存在、类型正确、枚举值合法或没有额外字段。
每次失败都把完整原文塞回提示词可以吗?
不建议。保留截断后的原文和精简错误即可,完整响应应进入受控日志,避免上下文和敏感数据无边界增长。
把最大重试次数调到 10 次是不是更稳?
通常不是。超过两三次仍失败往往意味着 Schema、提示词或模型能力不匹配,应转人工兜底或切换结构化输出能力更合适的模型。
-
332 收藏
-
329 收藏
-
377 收藏
-
141 收藏
-
203 收藏
-
147 收藏
-
477 收藏
-
498 收藏
-
280 收藏
-
485 收藏
-
404 收藏
-
463 收藏
-
324 收藏
-
474 收藏
-
387 收藏
-
314 收藏
-
155 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习