登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

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 要求,例如要求返回 titleprioritylabels 三个字段。官方说明也明确提到,严格 Schema 只覆盖标准 JSON Schema 的一部分规则;请求可能因为安全原因返回拒答,也可能在达到输出长度上限前被截断,字段值本身仍可能不符合业务要求。

放到 Go 服务的实现逻辑里,可以先画出一条清晰的边界:

  • 响应层:校验 HTTP 状态、请求 ID、完成状态、拒答相关字段。
  • 结构层:校验 JSON 是否完整,字段类型是否和预先定义的 Go 结构体匹配。
  • 业务层:校验优先级范围、标签数量、标题长度和所有业务必填字段。

结构化 JSON 从模型响应到业务校验的三层检查:拒答、解析、规则

最小实现:用 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 做「每个分片独立解析」通常没有实际意义,因为字符串、数组和转义字符都有可能跨分片拆分。

Go 流式模型响应的缓冲与完成门禁:分片拼接、完成状态、业务校验、入库

三个容易漏掉的边界场景

拒答不是格式错误

拒答内容可能是完全合法的字符串,甚至外层响应状态仍然显示正常。它应该单独标记为「模型未提供有效业务结果」,不要伪装成 JSON 解析失败,也不要直接拿默认空对象继续往下走流程。

截断不是低优先级告警

达到输出上限时,模型可能只返回 JSON 的前半段内容。就算这段残缺内容偶然能被解析,也要结合完成状态和响应元数据综合判断;没有明确的结束信号,就不要把结果视为可正常落库的有效数据。

合法值不代表正确值

{"priority":2} 在语法和类型上都没有问题,但它是否符合当前工单的风险等级,仍要由业务规则或者后置人工审核决定。严格 Schema 不能替代实际业务逻辑的事实核对。

落库前的最后一道校验门

实际服务可以把三个阶段的错误统一封装成可观测事件,但不要把它们混成一个笼统的「AI 调用失败」。建议至少记录 request_id、接口状态、完成状态、解析错误类型、业务校验错误和重试次数这些维度的信息。入库动作只接收已经通过 AcceptEnvelopeParseReview 的对象。

如果业务允许配置重试,重试条件也要做限制:网络中断、明确的临时服务错误可以发起重试;拒答、字段语义冲突和连续截断这类场景更适合直接送入隔离队列。重试前要保留完整的原始响应,方便后续定位问题根源是模型侧、提示词侧还是调用层出了问题。

相关问题

JSON Schema 严格模式能保证字段内容真实吗?

不能。它只能约束结构和类型,字段值仍可能不符合业务事实,需要额外加规则校验、数据库核对或者人工审核环节。

能不能只用 map[string]any 接模型返回结果?

临时做技术探索可以,生产链路不建议。结构体加显式校验的模式更容易发现字段改名、类型变化与空值这类隐性问题。

流式输出什么时候可以写入数据库?

收到接口定义的完成信号、拼接出来的完整内容通过 JSON 解析,并且所有业务规则校验全部通过之后。任一条件缺失都应该先把数据做隔离处理。

模型返回拒答时应该重试吗?

先确认拒答原因。安全类拒答不适合盲目重复请求;如果是临时网络或服务波动错误,再依据接口状态和预设重试策略处理。

小结

Go 接入大模型时,把 Schema 当成第一道护栏就够了,后面仍需要响应状态检查、完整性判断和业务语义校验。代码量不需要很大,但每道门都要有明确的失败处理逻辑:不返回半成品数据、不吞掉原始响应、不把合法 JSON 误当成完全正确的业务数据。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>