Go 调用大模型时结构化 JSON 仍要校验:Schema、拒答和截断响应的最小防线
接口已经要求大模型返回 JSON,线上却仍然出现了“能解析、不能入库”的记录:分类字段拼写错误、数组内容被截断,甚至模型直接返回了拒答文本。处理这类问题时,Go 服务不该只调用一次 json.Unmarshal 就把结果直接传给数据库,而要把响应状态、JSON 结构和业务语义分成三道独立的检查环节。
严格 JSON Schema 主要解决「数据形状是否符合事先约定」的问题,不会帮你判断请求是否触发拒答、输出内容是否完整,也不保证字段里的业务数值完全正确;Go 侧至少要保留状态检查、结构解析和业务校验三层防线。
要点速览
- 先判断响应是否正常结束,以及是否存在拒答或空内容的情况。
json.Unmarshal只负责校验 JSON 语法和字段类型,不负责枚举值合法性与内容长度校验。- 业务校验失败时保留原始请求 ID,禁止半成品数据直接写入存储。
- 流式响应要等完成事件或明确的结束状态返回后,再拼接得到最终的完整 JSON。
先分清:Schema 约束和业务规则不是一回事
结构化输出能让模型更稳定地贴合开发者给出的 JSON Schema 要求,例如要求返回 title、priority 和 labels 三个字段。官方说明也明确提到,严格 Schema 只覆盖标准 JSON Schema 的一部分规则;请求可能因为安全原因返回拒答,也可能在达到输出长度上限前被截断,字段值本身仍可能不符合业务要求。
放到 Go 服务的实现逻辑里,可以先画出一条清晰的边界:
- 响应层:校验 HTTP 状态、请求 ID、完成状态、拒答相关字段。
- 结构层:校验 JSON 是否完整,字段类型是否和预先定义的 Go 结构体匹配。
- 业务层:校验优先级范围、标签数量、标题长度和所有业务必填字段。

最小实现:用 Go 结构体挡住半成品数据
先定义专属的业务对象,不要用 map[string]any 接住所有返回字段。结构体可以把类型错误尽早暴露出来,也方便后续串联对应的业务规则检查。
package review
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
type Review struct {
Title string `json:"title"`
Priority int `json:"priority"`
Labels []string `json:"labels"`
}
func ParseReview(raw []byte) (Review, error) {
var out Review
if len(strings.TrimSpace(string(raw))) == 0 {
return out, errors.New("empty model content")
}
if err := json.Unmarshal(raw, &out); err != nil {
return out, fmt.Errorf("invalid review json: %w", err)
}
if err := out.Check(); err != nil {
return out, err
}
return out, nil
}
func (r Review) Check() error {
title := strings.TrimSpace(r.Title)
if title == "" || len([]rune(title)) > 80 {
return errors.New("title is empty or too long")
}
if r.Priority 3 {
return fmt.Errorf("priority %d is outside 1..3", r.Priority)
}
if len(r.Labels) > 5 {
return errors.New("too many labels")
}
return nil
}
这里的关键不是写了多少条校验规则,而是校验失败时绝不返回「看起来还能用」的对象。调用方拿到错误后,要把请求 ID、模型状态和原始响应写入隔离日志,交给人工或者后续补偿流程处理。
把响应状态检查放在 JSON 解析之前
很多线上误判都来自检查顺序错误:服务看到一段字符串能被正常解析,就当成模型返回的最终结果。更稳妥的做法是先读接口的外层响应字段,再把最终文本交给 ParseReview。
type ModelEnvelope struct {
RequestID string `json:"id"`
Status string `json:"status"`
Refusal string `json:"refusal"`
Content string `json:"content"`
}
func AcceptEnvelope(e ModelEnvelope) error {
if e.Refusal != "" {
return fmt.Errorf("model refusal: %s", e.Refusal)
}
if e.Status != "completed" {
return fmt.Errorf("response is not completed: %s", e.Status)
}
if strings.TrimSpace(e.Content) == "" {
return errors.New("completed response has no content")
}
return nil
}
completed 只是示例状态名,实际接入时要以你所用接口的官方响应字段为准,不要把某一家 API 的字段名直接当成所有模型服务的通用协议。
流式返回:最后一段分片不等于完整 JSON
流式接口会把内容拆成多段事件逐步返回。中途断开时,前半段内容可能已经长得很像完整 JSON,但缺少结尾的闭合括号;如果代码在每个分片到达时都尝试入库,截断的数据就会混入正常业务记录。
正确的处理边界是:分片内容只写入内存缓冲,收到明确的完成信号后再调用一次 ParseReview 做统一解析。如果连接断开、完成状态缺失或者内容长度超过业务阈值,直接进入失败处理分支。对流式 JSON 做「每个分片独立解析」通常没有实际意义,因为字符串、数组和转义字符都有可能跨分片拆分。

三个容易漏掉的边界场景
拒答不是格式错误
拒答内容可能是完全合法的字符串,甚至外层响应状态仍然显示正常。它应该单独标记为「模型未提供有效业务结果」,不要伪装成 JSON 解析失败,也不要直接拿默认空对象继续往下走流程。
截断不是低优先级告警
达到输出上限时,模型可能只返回 JSON 的前半段内容。就算这段残缺内容偶然能被解析,也要结合完成状态和响应元数据综合判断;没有明确的结束信号,就不要把结果视为可正常落库的有效数据。
合法值不代表正确值
{"priority":2} 在语法和类型上都没有问题,但它是否符合当前工单的风险等级,仍要由业务规则或者后置人工审核决定。严格 Schema 不能替代实际业务逻辑的事实核对。
落库前的最后一道校验门
实际服务可以把三个阶段的错误统一封装成可观测事件,但不要把它们混成一个笼统的「AI 调用失败」。建议至少记录 request_id、接口状态、完成状态、解析错误类型、业务校验错误和重试次数这些维度的信息。入库动作只接收已经通过 AcceptEnvelope 与 ParseReview 的对象。
如果业务允许配置重试,重试条件也要做限制:网络中断、明确的临时服务错误可以发起重试;拒答、字段语义冲突和连续截断这类场景更适合直接送入隔离队列。重试前要保留完整的原始响应,方便后续定位问题根源是模型侧、提示词侧还是调用层出了问题。
相关问题
JSON Schema 严格模式能保证字段内容真实吗?
不能。它只能约束结构和类型,字段值仍可能不符合业务事实,需要额外加规则校验、数据库核对或者人工审核环节。
能不能只用 map[string]any 接模型返回结果?
临时做技术探索可以,生产链路不建议。结构体加显式校验的模式更容易发现字段改名、类型变化与空值这类隐性问题。
流式输出什么时候可以写入数据库?
收到接口定义的完成信号、拼接出来的完整内容通过 JSON 解析,并且所有业务规则校验全部通过之后。任一条件缺失都应该先把数据做隔离处理。
模型返回拒答时应该重试吗?
先确认拒答原因。安全类拒答不适合盲目重复请求;如果是临时网络或服务波动错误,再依据接口状态和预设重试策略处理。
小结
Go 接入大模型时,把 Schema 当成第一道护栏就够了,后面仍需要响应状态检查、完整性判断和业务语义校验。代码量不需要很大,但每道门都要有明确的失败处理逻辑:不返回半成品数据、不吞掉原始响应、不把合法 JSON 误当成完全正确的业务数据。
浏览器长任务怎么排查:用 PerformanceObserver 定位 50ms+ 主线程卡顿
- 上一篇
- 浏览器长任务怎么排查:用 PerformanceObserver 定位 50ms+ 主线程卡顿
- 下一篇
- Go 1.26.5 和 1.25.12 发布:crypto/tls、os 修复后,生产服务怎么判断升级窗口
-
- 科技周边 · 人工智能 | 1天前 | typescript · 人工智能 · 接口设计 · 结构化输出 · JSON Schema 大模型结构化输出 订单意图识别 Responses API
- 大模型结构化输出实战:把订单意图解析做成可验收的小服务
- 333浏览 收藏
-
- 科技周边 · 人工智能 | 2天前 | API · 人工智能 · claude · 提示词工程 · AI Agent Claude API 提示词缓存 cache_control
- Claude API 提示词缓存为什么总 miss:静态前缀、工具定义与 usage 核对
- 280浏览 收藏
-
- 科技周边 · 人工智能 | 3天前 | 安全 · oauth · 人工智能 · mcp · 工具调用 · MCP 401 MCP 403 MCP OAuth mcp resource_metadata MCP scope MCP token audience
- MCP 工具调用为什么返回 401 或 403:用 metadata、scope 和 audience 快速定位
- 443浏览 收藏
-
- 科技周边 · 人工智能 | 4天前 | 前端 · 人工智能 · 用户体验 · 可访问性 · 流式输出 · AI对话 AbortController AbortSignal 流式输出 aria-live 停止生成
- AI 对话流式输出怎么做停止按钮:AbortController、状态播报和断线收尾
- 425浏览 收藏
-
- 科技周边 · 人工智能 | 2星期前 | 人工智能 · GenAI · opentelemetry · 可观测性 · AI工程 · 人工智能 链路追踪 GenAI OpenTelemetry AI可观测性 LLM网关 Token统计
- AI 调用可观测架构:从散乱日志到 OpenTelemetry GenAI 字段统一
- 427浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 4590次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4237次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4195次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 4415次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4371次使用
-
- 接口返回 200 但前端仍报错怎么办:从响应格式到跨域一步步排查
- 2026-06-14 332浏览
-
- Go map 并发写 panic 怎么办:从共享 map 到可控写入路径
- 2026-06-30 123浏览
-
- golang生成JSON以及解析JSON
- 2023-01-17 329浏览
-
- Go如何实现json字符串与各类struct相互转换
- 2023-01-07 377浏览
-
- Go Excelize API源码阅读SetSheetViewOptions示例解析
- 2022-12-24 485浏览

