结构化输出校验失败时应用层怎么保留原始响应
结构化输出并不等于应用层可以只保存一个解析后的对象。模型可能拒答,生成也可能在达到上限时提前结束;即使拿到了合法 JSON,字段值仍可能不符合业务规则。若代码只记录“校验失败”,下一次排查时既不知道供应商返回了什么,也无法判断该不该重试。
更稳妥的做法是:请求刚返回就记录请求元数据、状态字段和脱敏后的原始响应;随后依次区分拒答或截断、JSON 解析失败、Schema 校验失败和业务校验失败。原文用于诊断,结构化对象用于业务,两者不要互相替代。
- 保存原始响应的摘要、长度和哈希,必要时再保存受控原文。
- 先判断 refusal、incomplete、finish_reason,再尝试解析 content。
- 结构错误、Schema 错误和业务错误分别决定告警、修复或重试。
先把原始响应留在请求边界内
应用层至少应保留四类信息:供应商 request id、模型与 schema 版本、响应状态字段,以及脱敏后的原始响应。日志不必无条件写入完整 prompt;可以保存原文长度、SHA-256、前后截取片段和对象存储引用,让值班人员先确认“是不是同一份响应”。

下面的示例用一个归一化后的响应字典表示不同供应商的共同字段。真正接入 SDK 时,先把 SDK 对象转换成这样的内部记录。
import hashlib
import json
def snapshot_response(raw_response: dict) -> dict:
# 只保存诊断需要的元数据;生产环境还应在这里做字段脱敏。
raw_text = json.dumps(raw_response, ensure_ascii=False, sort_keys=True)
return {
"request_id": raw_response.get("id", "unknown"),
"model": raw_response.get("model", "unknown"),
"raw_length": len(raw_text),
"raw_sha256": hashlib.sha256(raw_text.encode("utf-8")).hexdigest(),
# 原文进入受控存储,不直接拼进普通业务日志。
"raw_excerpt": raw_text[:1200],
}
拒答和截断要先于 JSON 解析判断
结构化输出的“校验失败”不一定来自 JSON。若响应带有 refusal,应把它记录为模型拒答;若完成原因表示长度耗尽或 Responses API 状态为 incomplete,则更像一次不完整生成。两种情况都不适合直接把空内容交给 JSON 解析器,否则日志会被一个次生的“JSONDecodeError”带偏。
| 观察到的证据 | 应用层分类 | 处理建议 |
|---|---|---|
| refusal 有值 | 拒答 | 记录拒答文本,按业务决定提示用户或换任务 |
| incomplete 或 finish_reason 非正常结束 | 截断/未完成 | 保留原文,检查 token 上限后再有限重试 |
| content 不是合法 JSON | 结构层失败 | 记录解析位置,检查协议适配和响应原文 |
| JSON 合法但字段不满足约束 | Schema/业务失败 | 保留对象和错误路径,不要盲目重发 |
把失败分成三层,重试策略才不会混乱
建议把检查拆成三层。第一层是响应状态:HTTP、refusal、incomplete 和 content 是否存在;第二层是结构:字符串能否解析成 JSON、字段类型是否满足 Schema;第三层是业务:例如订单抽取结果中的金额是否为非负数、日期是否落在允许范围。每一层都写入同一条诊断记录,但错误代码不要混用。

def classify_structured_response(response: dict) -> dict:
# 先判状态,再判结构,避免把拒答或截断误报成 JSON 错误。
choice = (response.get("choices") or [{}])[0]
message = choice.get("message") or {}
if message.get("refusal"):
return {"kind": "refusal", "retryable": False, "detail": message["refusal"]}
if response.get("status") == "incomplete" or choice.get("finish_reason") not in (None, "stop"):
return {"kind": "incomplete", "retryable": True, "detail": choice.get("finish_reason")}
content = message.get("content")
if not isinstance(content, str):
return {"kind": "missing-content", "retryable": False, "detail": "content is absent"}
try:
value = json.loads(content)
except json.JSONDecodeError as exc:
# 保留错误位置;原文摘要已经在请求边界记录,不在此处重复打印全文。
return {"kind": "json-parse", "retryable": True, "detail": {"pos": exc.pos}}
return {"kind": "json-ready", "retryable": False, "value": value}
让日志既能重放,又不变成敏感数据仓库
保留原始响应时要同时做三件事:为 prompt、用户输入、邮箱、手机号和 token 做脱敏;给每份原文设置长度上限和过期时间;把 request id、schema 版本、错误路径、哈希和重试次数放到结构化字段中。这样开发者能用哈希把应用日志与受控原文对上,却不会让普通日志检索系统承载整段上下文。
重试也要有边界:截断可以在提高输出上限或缩短输入后重试一次;协议解析失败可以检查 SDK 适配;业务校验失败通常应进入人工或补偿队列;拒答则不应靠无限重发规避。最后把诊断记录和业务结果分开存储,避免一次脏响应覆盖上一条成功结果。
常见问题
只保存解析后的 Pydantic 或 Zod 对象可以吗?
不建议。成功对象适合业务使用,但失败时它根本不存在;至少要保存 request id、状态字段、错误路径和原文摘要。
Schema 校验失败要不要立刻重试?
先看失败层次。截断或临时传输异常可以有限重试,业务规则不满足时应先修正输入或进入补偿流程。
为什么要保存原文哈希?
哈希能在不扩散完整响应的前提下确认日志、对象存储和告警引用的是同一份数据,也方便后续去重。
真正可维护的结构化输出链路,不是“让模型永远不出错”,而是让每次异常都留下足够证据:发生在哪一层、原响应是哪一份、下一步是修请求、改 Schema、重试,还是交给业务处理。
Git stash push 怎么只暂存某个目录
- 上一篇
- Git stash push 怎么只暂存某个目录
- 下一篇
- OpenAI 工具选择策略怎么限制模型只调用指定工具
-
- 科技周边 · 人工智能 | 31分钟前 | openai · 工具调用 · 函数调用 · Responses API tool_choice allowed_tools
- OpenAI 工具选择策略怎么限制模型只调用指定工具
- 463浏览 收藏
-
- 科技周边 · 人工智能 | 19小时前 | 上下文 · ai agent · 记忆系统 · AI Agent 上下文工程 agent memory
- AI Agent 记忆为什么要区分短期上下文和长期存储
- 155浏览 收藏
-
- 科技周边 · 人工智能 | 1天前 |
- Hugging Face Responses API 怎么同时发送文本和图片输入
- 392浏览 收藏
-
- 科技周边 · 人工智能 | 1天前 |
- OpenAI Responses API 如何区分 output_text 和完整输出项
- 277浏览 收藏
-
- 科技周边 · 人工智能 | 1天前 | openai · function calling · 结构化输出 · Responses API · OpenAI JSON Schema 工具调用 Responses API Structured Outputs
- OpenAI Responses API 如何让工具调用返回结构化结果
- 274浏览 收藏
-
- 科技周边 · 人工智能 | 1天前 | 人工智能 · 模型路由 · 容错设计 · API降级 · 模型降级 Responses API GPT-6 Astra OpenAI API 限量开放
- GPT-6 Astra API 限量开放时如何设计模型降级路径
- 192浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 61次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 216次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 145次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 79次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 56次使用
-
- Go 写入响应正文后再设置状态码为什么无效
- 2026-09-06 250浏览
-
- Go JSON Decoder.Decode 成功一次后如何发现尾部垃圾数据
- 2026-09-08 212浏览
-
- CodeGeeX for Jetbrains IDEs正式上线!
- 2023-01-17 284浏览
-
- 技术阿里云实现ocr批量图片和pdf文件表格图片转换excel文档/支持票据图片提取/普通图片文字提取处理
- 2023-01-18 387浏览
-
- 直播预告|FeatureStore Meetup V2
- 2023-01-10 328浏览

