JSON 回来坏了:一套在六个后端上都管用的修复回路
针对多模型聚合场景下的结构化输出(JSON)解析失败问题,提出一套「验证-修复-降级」的通用实践:先用 schema 校验首轮输出,再基于错误路径做定点重问,最后考虑切换模型或降低约束。文章不依赖具体模型特性,只讨论可复用的模式与注意事项,适用于一把 API key 调用多个模型的场景。
为什么 JSON 总是「坏」的
调用大模型返回 JSON 时,常见问题不是模型不会写 JSON,而是输出中混入了额外文本、键名拼写错误、类型不符(如数字写成字符串)、缺少必填字段等。不同模型对指令的遵循程度不同,同一模型在不同温度、不同提示下也可能时好时坏。
在聚合站场景里,一把 key 后面可能是 Claude、GPT、DeepSeek、Qwen、GLM 或 Kimi,各家对 JSON 模式的支持方式不同。与其为每个模型写特例,不如建立一套统一的校验修复回路。
第一步:先用 schema 校验,不要相信「看起来像」
拿到响应后,第一步永远是解析和校验,而不是直接使用。推荐使用 JSON Schema 或类似的结构描述,并选用成熟的校验库(如 Python 的 jsonschema、JavaScript 的 ajv)。
校验时要记录错误路径,例如 items[2].price 应为 number 但收到 string、user.email 缺失。这些路径是后续修复的关键输入。
注意:部分模型支持 response_format 或 JSON mode,但这只保证「是合法 JSON」,不保证「符合你的 schema」。校验环节不能省。
第二步:定点重问,而不是整体重试
整体重试成本高且可能重复犯错。更有效的方式是把校验错误作为上下文,向模型发起一次针对性的修复请求。
具体做法:
- 保留原始用户输入和首轮输出;
- 附上校验器返回的错误路径与原因;
- 要求模型只修改错误位置,并输出完整修正后的 JSON;
- 必要时给出一个最小示例,说明期望的类型。
提示词可以这样组织(示意):
你之前的输出未通过校验,错误如下:
- items[2].price: 期望 number,实际是 string
- user.email: 缺失
请仅修正上述问题,保持其余内容不变,输出完整 JSON。
定点重问通常比重新生成整段内容更稳定,也更容易定位问题。
第三步:降级与换模型
如果定点重问一两次后仍不通过,说明当前模型在当前约束下难以稳定满足 schema。此时按顺序考虑降级:
- 简化 schema:去掉可选字段、放宽枚举、接受字符串形式的数字并在应用层转换;
- 拆分任务:把一个大 JSON 拆成多个小请求,分别校验;
- 切换模型:在聚合站上换一个对结构化输出更稳的模型重试。
切换模型时注意保持提示词和 schema 不变,只改模型标识,这样便于对比和回退。
回路设计中的几个细节
- 设置重试上限:定点重问建议不超过两次,避免循环消耗;
- 记录失败样本:把校验错误和原始输出留存,用于后续调整提示词或 schema;
- 温度调低:结构化输出场景下,较低温度通常更稳定;
- 超时与截断:长输出可能被截断导致 JSON 不完整,校验前先检查是否以预期结构结束;
- 幂等性:修复请求本身应可重放,避免副作用。
在聚合站上的实践建议
聚合站的价值在于一把 key 可切换多个模型,这让「换模型降级」的成本很低。建议在代码中把模型标识做成配置项,并按「主模型 → 备用模型」的顺序编排。同时,利用聚合站统一的调用方式,校验和修复逻辑只需写一次,就能跨模型复用。
计费方面,用户按官方价 ×1.3 扣费,修复请求会产生额外 token 消耗,因此控制重试次数不仅是为了稳定性,也直接关系到成本。
小结
结构化输出的可靠性不取决于单个模型,而取决于外围的校验修复回路:先校验、再定点重问、最后降级换模型。这套模式在多个后端上都适用,配合聚合站的模型切换能力,可以用较小的改动获得较稳定的 JSON 输出。