当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > AI 输出 JSON 偶尔多出 Markdown 围栏怎么做容错解析

AI 输出 JSON 偶尔多出 Markdown 围栏怎么做容错解析

来源:17golang原创 2026-09-08 20:46:56 0浏览 收藏

调用大模型生成结构化数据时,最常见的解析故障不是 JSON 语法本身,而是模型在对象外面加了一层 Markdown 代码围栏:```json、JSON 内容、```。处理方式是把围栏当作传输包装,仅在边界处剥离,然后交给 encoding/json;不要对整段文本做全局替换。

可靠的容错解析要分三层:先判断响应是否拒答或截断,再只清理最外层围栏,最后执行 JSON 解码和业务字段校验。这样即使模型偶尔加了 Markdown,也不会把字符串值里的合法字符误删。
要点速览
  • 只删除首尾成对的围栏,不删除 JSON 字符串内部的反引号。
  • Structured Outputs 能约束 JSON Schema,但拒答和不完整响应仍要单独判断。
  • JSON 解码成功不等于业务数据可用,字段、枚举和空值要继续校验。

先把围栏当作传输包装,而不是 JSON 内容

如果直接把 ```json\n{...}\n``` 交给 json.Unmarshal,开头的反引号会让解码失败。最小修复是先做边界清理:去掉首尾空白和 UTF-8 BOM,确认第一行是围栏,再确认最后一行是单独的结束围栏。不要使用 strings.ReplaceAll(raw, "```", ""),因为字段值本身可能合法地包含反引号。

下面的函数只负责“去包装”,不负责猜测缺失逗号、补引号或截取半个对象。解析失败时保留原始错误,调用方才能知道是围栏问题还是模型输出了不完整 JSON。

package responseparse

import (
    "bytes"
    "encoding/json"
    "fmt"
    "strings"
)

// unwrapJSONFence 只移除最外层、成对出现的 Markdown 围栏。
func unwrapJSONFence(raw string) (string, error) {
    // BOM 和外围空白属于传输噪声,不改变 JSON 字符串内部内容。
    text := strings.TrimSpace(strings.TrimPrefix(raw, "\ufeff"))
    if !strings.HasPrefix(text, "```") {
        return text, nil
    }

    // 第一行只允许是 ``` 或 ```json 这类语言提示。
    firstBreak := strings.IndexByte(text, '\n')
    if firstBreak  160 {
            preview = preview[:160]
        }
        return fmt.Errorf("decode model JSON near %q: %w", preview, err)
    }
    return nil
}

这个边界有两个故意的“严格”:只接受明确的首行和尾行,不替模型修复半截 JSON;只把清理后的文本交给标准库,不用宽松正则代替语法分析。生产环境若还要兼容 ```JSON 大小写,可在确认协议后扩展允许列表,而不是无限放宽。

AI JSON 围栏容错解析中的模型文本、外层 Markdown 围栏、Go 去包装器、JSON 解码器和业务对象静态关系
图1:围绕解析边界查看模型文本与外层围栏,去包装器只把干净 JSON 交给解码器。

解析成功不等于业务结果有效

帮助读者区分 Structured Outputs schema、拒答、截断、围栏文本、解码结果和业务校验的静态责任边界。
图2:把模型响应状态、JSON 解码和字段级校验分成独立边界,避免把格式正确当成业务可执行。

围栏只是文本层问题。实际接入 API 时,还要先区分三种状态:模型明确拒答、响应因长度或内容过滤而不完整、响应完成但 JSON 语法错误。对于支持 Structured Outputs 的接口,JSON Schema 可以减少缺键和非法枚举,但官方示例仍要求程序检查拒答与 incomplete 状态;这两个分支都不应该继续调用 json.Unmarshal

可以把供应商响应转换成应用自己的结果类型,再做一次字段检查。示例中的 RefusalIncomplete 只是应用层状态,具体字段名要按所用 SDK 的响应结构映射,不要把某一家 API 的字段名硬编码到所有供应商适配器。

type Answer struct {
    Action string `json:"action"`
    Score  int    `json:"score"`
}

type ModelResult struct {
    Text       string
    Refusal    string
    Incomplete bool
}

func DecodeAnswer(result ModelResult) (Answer, error) {
    // 拒答和截断不是 JSON 解析错误,分别交给策略层处理。
    if result.Refusal != "" {
        return Answer{}, fmt.Errorf("model refusal: %s", result.Refusal)
    }
    if result.Incomplete {
        return Answer{}, fmt.Errorf("model response is incomplete")
    }

    var answer Answer
    if err := DecodeModelJSON(result.Text, &answer); err != nil {
        return Answer{}, err
    }
    // 格式合法之后,再判断业务字段是否满足本地契约。
    if answer.Action == "" || answer.Score  100 {
        return Answer{}, fmt.Errorf("answer fields are out of range")
    }
    return answer, nil
}

如果是普通文本模式,容错函数可以接住围栏;如果是 Structured Outputs,仍建议保留这层防御,因为上游可能切换模型、SDK 或降级路径。尤其不要把“解析成功”直接当成“可以执行动作”:涉及发消息、写库或调用工具时,字段校验和业务授权必须独立存在。

用检查表决定重试还是落库

现象判断层处理建议
首尾是成对围栏,内部 JSON 合法传输包装剥离后正常解码
拒答字段有值模型状态记录原因,不重试同一请求
响应 incomplete 或缺少结束标记响应完整性按额度和幂等策略决定重试
JSON 合法但 action 为空业务契约拒绝进入后续副作用操作

日志中可以记录解析阶段、错误类型和请求关联 ID;原始输出可能含用户隐私或提示词,不建议默认完整落盘。重试也要有上限,并使用幂等键,避免一次截断触发多次业务动作。

常见问题

能不能直接把所有 ``` 删除?

不建议。全局替换可能破坏 JSON 字符串中的反引号,也会掩盖围栏未闭合的问题。只处理首行和尾行更容易定位故障。

Structured Outputs 还需要自己解析吗?

需要保留响应状态判断和业务校验;在普通文本或降级模型路径中,还要保留最外层围栏清理与标准 JSON 解码。

JSON 解码失败要不要自动重试?

先区分截断、拒答、围栏错误和真正的语法错误,再按请求成本、幂等性和重试上限决定。不要对同一份未改变的输出无限重试。

把围栏清理、响应状态判断、JSON 解码和业务校验拆开,容错逻辑就有了清晰边界:能修复的只是传输包装,不能替模型或业务规则“猜答案”。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go Response.Body 只调用 Close 不读取完为什么连接不复用Go Response.Body 只调用 Close 不读取完为什么连接不复用
上一篇
Go Response.Body 只调用 Close 不读取完为什么连接不复用
Go database/sql 怎么用命名参数适配不同驱动
下一篇
Go database/sql 怎么用命名参数适配不同驱动
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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测试功能,助您快速选择最适合项目的高性能大语言模型。
    30次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    186次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    120次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    46次使用
  • Generrated:DALL·E 2/3 AI绘画提示词灵感库与图像对比平台
    Generrated
    Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
    28次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码