news 2026/6/3 5:29:55

让 AI 100% 返回合法 JSON:Schema 校验 + 自动重试,生产级可靠性

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
让 AI 100% 返回合法 JSON:Schema 校验 + 自动重试,生产级可靠性

🦞 一只用 AI Agent 搭副业产线的程序员


上篇我们说了结构化输出的重要性。但问题来了:你跟 AI 说「返回 JSON」,它不一定听话。

有时候多一个逗号,有时候多一行「以下是结果:」,有时候字段名拼错了。在聊天界面里无所谓,在代码里直接 panic。

生产线不能容忍这种不确定性。

这篇文章给你一套方案——Schema 定义 → 自动校验 → 不合法就重试。我把这套东西用在日报 Agent 里之后,JSON 解析成功率从 78% 提到了 100%。


问题有多严重

我跑了 100 次数据提取任务,要求返回 JSON:

返回情况次数占比
合法 JSON,字段正确7878%
合法 JSON,字段缺失或多余1212%
JSON 外有额外文字77%
非法 JSON(多逗号、引号不匹配等)33%

22% 的情况下你的代码直接崩。而且这 22% 不是同样的错误——每次错的都不一样。


完整方案:三步保证 100%

三句话总结这套方案:

  1. 结构化输出指令——在 Prompt 里用 JSON Schema 告诉 AI 你要什么
  2. 严格校验——AI 返回后解析 JSON 并校验结构
  3. 自动重试——不合格就重试,最多 3 次

第一步:把输出约束写成 JSON Schema

先定义你想要的输出结构。Go 里最方便的是用 struct + json tag:

packagemainimport("encoding/json""fmt")// 定义你期望的输出结构typeExtractedIssuestruct{Titlestring`json:"title"`Severitystring`json:"severity"`// "high" | "medium" | "low"LineNumbers[]int`json:"line_numbers"`Categorystring`json:"category"`// "bug" | "style" | "security" | "performance"Suggestionstring`json:"suggestion"`IsCertainbool`json:"is_certain"`// AI 是否确定这个问题的存在}typeCodeReviewOutputstruct{Issues[]ExtractedIssue`json:"issues"`TotalIssuesint`json:"total_issues"`CriticalCountint`json:"critical_count"`Summarystring`json:"summary"`}

然后在 Prompt 里把这个 Schema 翻译成 AI 能理解的格式:

funcbuildStructuredPrompt(codestring)string{returnfmt.Sprintf(`审查以下 Go 代码,找出所有安全和性能问题。 严格按照以下 JSON Schema 返回结果(不要返回任何其他内容,只返回 JSON): { "issues": [ { "title": "问题简述(≤20字)", "severity": "high | medium | low", "line_numbers": [行号], "category": "bug | style | security | performance", "suggestion": "修复建议", "is_certain": true } ], "total_issues": 问题总数, "critical_count": 高危问题数, "summary": "总体评价(≤50字)" } 代码: %s`,code)}

关键:把 JSON Schema 直接写在 Prompt 里。不要只是说「返回 JSON」,而是把结构完完整整写出来。


第二步:用 Go 的 JSON Schema 校验

Go 生态里go-playground/validator是最好用的结构校验库:

import("encoding/json""fmt""github.com/go-playground/validator/v10")varvalidate=validator.New()funcparseAndValidate(responsestring)(*CodeReviewOutput,error){// 1. 尝试解析 JSONvaroutput CodeReviewOutputiferr:=json.Unmarshal([]byte(response),&output);err!=nil{returnnil,fmt.Errorf("JSON 解析失败: %w",err)}// 2. 校验字段iferr:=validate.Struct(output);err!=nil{returnnil,fmt.Errorf("字段校验失败: %w",err)}// 3. 业务逻辑校验ifoutput.TotalIssues!=len(output.Issues){returnnil,fmt.Errorf("total_issues(%d) 与实际 issues 数量(%d) 不一致",output.TotalIssues,len(output.Issues))}// 4. 校验 severity 枚举值validSeverities:=map[string]bool{"high":true,"medium":true,"low":true}for_,issue:=rangeoutput.Issues{if!validSeverities[issue.Severity]{returnnil,fmt.Errorf("非法的 severity 值: %s",issue.Severity)}}return&output,nil}

这里做了三层校验:

  1. 是不是合法 JSON
  2. 必填字段有没有、类型对不对
  3. 业务逻辑是否自洽(total_issues 跟实际数量一致、severity 值在枚举范围内)

第三步:不合格就重试

funccallLLMWithRetry(promptstring,maxRetriesint)(*CodeReviewOutput,error){varlastErrerrorforattempt:=1;attempt<=maxRetries;attempt++{fmt.Printf("第 %d 次尝试...\n",attempt)response:=callLLM([]Message{{Role:"user",Content:prompt},},0.1,1000)output,err:=parseAndValidate(response)iferr==nil{returnoutput,nil// 成功了}lastErr=err fmt.Printf("校验失败: %v\n",err)ifattempt<maxRetries{// 把错误信息带回给 AI,让它纠正prompt=fmt.Sprintf(`你上一次的输出校验未通过: 错误:%s 请修正后重新输出。只输出修正后的 JSON,不要解释。 原始任务: %s`,err.Error(),prompt)}}returnnil,fmt.Errorf("重试 %d 次后仍然失败: %w",maxRetries,lastErr)}

这套重试逻辑的关键是——把校验错误信息喂回给 AI。AI 看到「你的 JSON 多了一个逗号」或者「total_issues 和实际数量不一致」,它知道怎么改。


完整封装:一个通用的 StructuredCall

把上面的逻辑封装成一个通用函数:

funcStructuredCall[T any](systemPrompt,userPromptstring,maxRetriesint)(*T,error){prompt:=fmt.Sprintf(`%s 严格按照以下 JSON Schema 返回结果(只返回 JSON,不要其他内容): %s 任务: %s`,systemPrompt,generateSchema[T](),userPrompt)varlastErrerrorforattempt:=1;attempt<=maxRetries;attempt++{response:=callLLM([]Message{{Role:"user",Content:prompt},},0.1,1000)varresult Tiferr:=json.Unmarshal([]byte(response),&result);err!=nil{lastErr=err prompt=fmt.Sprintf("JSON 解析失败: %v\n请修正后重新输出 JSON。",err)continue}iferr:=validate.Struct(result);err!=nil{lastErr=err prompt=fmt.Sprintf("校验失败: %v\n请修正后重新输出 JSON。",err)continue}return&result,nil}returnnil,fmt.Errorf("重试 %d 次后仍然失败: %w",maxRetries,lastErr)}// 用反射从 struct 生成 JSON Schema 描述funcgenerateSchema[T any]()string{// 简化版:用 json tag 生成描述t:=reflect.TypeOf((*T)(nil)).Elem()returngenerateSchemaFromType(t," ")}

使用:

issues,err:=StructuredCall[CodeReviewOutput]("你是代码审查专家。",fmt.Sprintf("审查以下代码:\n%s",code),3,)iferr!=nil{log.Fatal("结构化调用失败: ",err)}fmt.Printf("发现 %d 个问题,其中 %d 个高危\n",issues.TotalIssues,issues.CriticalCount)

一行调用,类型安全。泛型让返回值直接是*CodeReviewOutput而不是map[string]interface{}


实测效果

加了这套之后,重新跑 100 次测试:

阶段成功次数成功率
第 1 次调用7878%
第 1 次重试+1593%
第 2 次重试+598%
第 3 次重试+2100%

3 次重试后 100%。额外的 token 成本约 +15%(重试的 Prompt 很短)。


一个省钱技巧:只重试解析失败的

不是所有失败都需要重试。如果只是total_issues和实际数量差 1——你可以自己修复这种小问题:

funcautoFix(output*CodeReviewOutput){// 小问题自动修复,不用重试ifoutput.TotalIssues!=len(output.Issues){output.TotalIssues=len(output.Issues)}// 重新计数output.CriticalCount=0for_,issue:=rangeoutput.Issues{ifissue.Severity=="high"{output.CriticalCount++}}}

原则:只有 JSON 格式错误才需要重试。数据层面的小不一致可以代码修复。


总结

问题解决方案
AI 不返回 JSONPrompt 里直接写 Schema
JSON 字段缺失/类型不对validator 校验
解析失败把错误喂回 AI,重试
小数据不一致代码里自动修复,省 token

下一篇我们要写一个完整的 Prompt 模板引擎——支持变量替换、条件分支、模板继承。让你的 Prompt 从「手写字符串拼接」升级到「模板引擎驱动」。

关注我,别错过。


🦞 一只用 AI Agent 搭副业产线的程序员

全平台同名:虾哥不加班
需要定制 AI 工具?来聊聊 → lob_ai

源码:GitHub - lobster-bujiaban

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/3 5:27:01

别再只会conda info --envs了!这5个隐藏技巧帮你高效管理Python环境

解锁conda环境管理的隐藏技能&#xff1a;5个高阶技巧提升Python开发效率当你已经熟练使用conda info --envs查看环境列表时&#xff0c;是否想过这些输出数据还能玩出什么花样&#xff1f;本文将带你突破基础操作&#xff0c;探索如何将命令行工具与conda环境管理深度融合&…

作者头像 李华
网站建设 2026/6/3 5:24:30

模型驱动的机器学习:用Infer.NET将领域知识编译为推荐系统

1. 从“黑盒”到“白盒”&#xff1a;为什么我们需要模型驱动的机器学习在2013年微软研究院机器学习峰会上&#xff0c;一个名为Infer.NET的.NET库引起了我的注意。当时&#xff0c;机器学习的世界正被以Scikit-learn为代表的“黑盒”算法库所主导。你导入数据&#xff0c;选择…

作者头像 李华
网站建设 2026/6/3 5:22:14

终极指南:三分钟完成OpenCore EFI自动化配置的智能工具

终极指南&#xff1a;三分钟完成OpenCore EFI自动化配置的智能工具 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 还在为复杂的黑苹果配置而烦恼吗&a…

作者头像 李华
网站建设 2026/6/3 5:19:35

sm-前后端,服务端接口安全方案

目录 服务端之间接口调用 唯一请求sid 认证 app_id, app_key, app_secret 加解密+签名 防重放 数据格式总结 请求时: 响应时: 服务端前端页面(客户端)之间调用 加密方案如何确定 前后端有状态 前后端无状态 防重放 通用接口安全方案 加解密 对称加密 非对…

作者头像 李华
网站建设 2026/6/3 5:18:54

rancher 部署

#拉取rancher镜像,并制作tar包 docker pull rancher/rancher:v2.10.0 docker save -o rancher_v2.10.0.tar rancher/rancher:v2.10.0#拉取agent镜像,并制作tar包 docker pull rancher/rancher-agent:v2.10.0 docker save -o rancher-agent_v2.10.0.tar rancher/rancher-agent:v…

作者头像 李华