当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > 结构化输出校验失败时应用层怎么保留原始响应

结构化输出校验失败时应用层怎么保留原始响应

来源:17golang原创 2026-09-10 09:43:01 0浏览 收藏

结构化输出并不等于应用层可以只保存一个解析后的对象。模型可能拒答,生成也可能在达到上限时提前结束;即使拿到了合法 JSON,字段值仍可能不符合业务规则。若代码只记录“校验失败”,下一次排查时既不知道供应商返回了什么,也无法判断该不该重试。

更稳妥的做法是:请求刚返回就记录请求元数据、状态字段和脱敏后的原始响应;随后依次区分拒答或截断、JSON 解析失败、Schema 校验失败和业务校验失败。原文用于诊断,结构化对象用于业务,两者不要互相替代。

要点速览
  • 保存原始响应的摘要、长度和哈希,必要时再保存受控原文。
  • 先判断 refusal、incomplete、finish_reason,再尝试解析 content。
  • 结构错误、Schema 错误和业务错误分别决定告警、修复或重试。

先把原始响应留在请求边界内

应用层至少应保留四类信息:供应商 request id、模型与 schema 版本、响应状态字段,以及脱敏后的原始响应。日志不必无条件写入完整 prompt;可以保存原文长度、SHA-256、前后截取片段和对象存储引用,让值班人员先确认“是不是同一份响应”。

结构化输出请求边界中请求元数据、供应商响应、脱敏原文、响应摘要、解析器和诊断记录的关系图
图1:请求边界先保留脱敏后的供应商响应,再把同一份上下文交给摘要器和解析器。

下面的示例用一个归一化后的响应字典表示不同供应商的共同字段。真正接入 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;第三层是业务:例如订单抽取结果中的金额是否为非负数、日期是否落在允许范围。每一层都写入同一条诊断记录,但错误代码不要混用。

结构化输出从响应状态到拒答截断、JSON 解析、Schema 校验、业务校验和重试决策的分层关系图
图2:四个校验层各自拥有不同的证据和处理动作,不能把所有失败都归为 JSON 解析错误。
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、重试,还是交给业务处理。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Git stash push 怎么只暂存某个目录Git stash push 怎么只暂存某个目录
上一篇
Git stash push 怎么只暂存某个目录
OpenAI 工具选择策略怎么限制模型只调用指定工具
下一篇
OpenAI 工具选择策略怎么限制模型只调用指定工具
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    61次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    216次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    145次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    79次使用
  • Generrated:DALL·E 2/3 AI绘画提示词灵感库与图像对比平台
    Generrated
    Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
    56次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码