Go 调用大模型时结构化 JSON 仍要校验:Schema、拒答和截断响应的最小防线
来源:17golang原创
时间:2026-07-20 17:47:44 195浏览 收藏
接口已经要求大模型返回 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 误当成完全正确的业务数据。
-
243 收藏
-
186 收藏
-
333 收藏
-
419 收藏
-
280 收藏
-
科技周边 · 人工智能 | 3天前 | 异步任务 · 人工智能 · jsonl · AI工程化 · Batch API · 结果对账 · JSONL 大模型批量任务 OpenAI Batch API custom_id AI 离线处理 结果对账113 收藏
-
149 收藏
-
432 收藏
-
科技周边 · 人工智能 | 4天前 | 安全 · oauth · 人工智能 · mcp · 工具调用 · MCP 401 MCP 403 MCP OAuth mcp resource_metadata MCP scope MCP token audience443 收藏
-
科技周边 · 人工智能 | 5天前 | 前端 · 人工智能 · 用户体验 · 可访问性 · 流式输出 · AI对话 AbortController AbortSignal 流式输出 aria-live 停止生成425 收藏
-
468 收藏
-
科技周边 · 人工智能 | 1星期前 | 人工智能 · ai agent · AI应用 · 工具调用 · 权限边界 · 审计链路 · 人工智能 权限控制 AI Agent 工具调用 审批链路 审计回放 上线指标343 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习