大模型输出 JSON 总出错?一文讲透生产环境保证 JSON 100% 可用的七层方案

前言

很多人刚开始做 AI 应用时,都遇到过这样的问题:

{
  "name": "张三",
  "age": 18,
}

多了一个逗号。

或者:

当然可以,下面是结果:

{
  "name": "张三",
  "age": 18
}

前面多了一段解释。

再或者:

{
  "name": "张三"
  "age": 18
}

少了一个逗号,程序直接报错。

很多开发者的第一反应是:

「是不是 Prompt 写得不够好?」

其实不是。

生产环境里,从来不会相信大模型一次就能输出正确 JSON。

真正稳定的做法是:

构建一套「生成 → 修复 → 校验 → 重试 → 兜底」的完整流水线。


为什么大模型容易输出错误 JSON?

因为大模型本质上是在预测下一个 Token,它并不是一个真正的 JSON 生成器。

它只能尽可能模仿:

{
  "name": "张三",
  "age": 18
}

但无法从底层保证:

  • 括号一定闭合
  • 引号一定成对
  • 字段一定完整
  • 类型一定正确
  • 业务规则一定满足

所以,任何生产级 AI 应用,都必须建立多层防护机制。


七层 JSON 兜底方案

模型约束
    ↓
Prompt约束
    ↓
文本修复
    ↓
Schema校验
    ↓
业务规则校验
    ↓
重试与自修复
    ↓
人工兜底

下面逐层介绍。


第一层:模型原生约束(优先级最高)

1. JSON Mode

目前主流模型基本都支持 JSON 模式,例如:

  • GPT
  • DeepSeek
  • 豆包
  • 通义千问

调用时开启:

response_format={"type": "json_object"}

优点

能够保证:

  • JSON 语法合法
  • 不会输出 Markdown
  • 不会输出解释文字

缺点

不能保证:

  • 字段完整
  • 字段类型正确
  • 业务规则正确

例如:

{
  "age": "十八"
}

JSON 合法,但业务不可用。


2. Structured Output

例如:

class User(BaseModel):
    name: str
    age: int
llm.with_structured_output(User)

模型直接生成:

User(
    name="张三",
    age=18
)

而不是:

"{...}"

稳定性比纯 JSON Mode 更高。


3. Function Calling(推荐)

这是现在 Agent 应用最常见的方法。

例如:

class GenerateQuestion(BaseModel):
    questions: List[Question]

模型返回:

{
  "tool_name": "generate_question",
  "arguments": {
      ...
  }
}

优点:

  • 字段名固定
  • 类型约束更强
  • 缺失字段概率更低

非常适合:

  • 智能客服
  • AI 出题系统
  • Agent 工作流

4. Grammar Constraint(终极方案)

例如:

  • CFG
  • BNF
  • Json Schema Constraint

代表框架:

  • vLLM
  • Outlines
  • Guidance

这种方式会直接限制模型生成 Token 的路径,从根源杜绝非法 JSON。

理论上:

合法 JSON 率接近 100%

第二层:Prompt 工程约束

很多人的 Prompt:

请输出 JSON。

基本等于没写。


正确写法应该包含四部分

① 身份定义

你是一个 JSON 输出引擎。

② 输出 Schema

{
  "name": "",
  "age": 0
}

③ Few-shot 示例

{
  "name": "张三",
  "age": 18
}

④ 强制禁令

禁止输出:

- markdown
- 解释
- 注释
- 多余文本

这些约束可以显著降低异常输出概率。


第三层:文本后处理修复

即便前两层做得很好,模型偶尔还是会输出:

好的,以下是 JSON:

{
  ...
}

或者:

```json
{
  ...
}
```

此时可以先进行轻量修复。


去除 Markdown

import re

match = re.search(
    r"```json(.*?)```",
    text,
    re.DOTALL
)

提取最外层 JSON

first = text.find("{")
last = text.rfind("}")

json_str = text[first:last+1]

修复尾随逗号

错误:

{
  "a":1,
}

修复:

{
  "a":1
}

推荐工具

json_repair

安装:

pip install json_repair

使用:

from json_repair import repair_json

fixed = repair_json(response)

dirtyjson

安装:

pip install dirtyjson

第四层:Schema 校验

很多项目只做:

json.loads()

然后认为数据已经安全。

这是错误的。


合法 JSON 不代表可用

例如:

{
  "age": "十八"
}

完全合法,但业务无法使用。


使用 Pydantic 校验

class User(BaseModel):
    age: int
User.model_validate(data)

类型错误会直接报异常。


建议开启严格模式

model_config = ConfigDict(
    extra="forbid"
)

禁止出现额外字段。


第五层:业务规则校验

这一层是最容易被忽略的。

例如出题系统:


单选题

只能有一个正确答案

多选题

至少两个正确答案

判断题

只能有:

正确
错误

简答题

options 必须为空

这些规则:

  • JSON Mode 不管
  • Function Calling 不管
  • Pydantic 也不管

必须自己实现 Validator。


第六层:重试与自修复

简单重试

for i in range(3):
    try:
        ...
    except:
        retry()

很多时候第二次就正常了。


自修复重试

把错误反馈给模型:

你的 JSON 存在以下问题:

1. 缺少 analysis 字段;
2. 单选题出现多个正确答案。

请修正后重新输出完整 JSON。

这种方式的成功率非常高。


LangChain 已经提供支持

OutputFixingParser
RetryOutputParser

第七层:人工兜底

真正的企业系统一定会有最终兜底:

LLM
 ↓
校验失败
 ↓
自动重试失败
 ↓
人工审核
 ↓
重新生成

永远不要无限重试。

否则:

  • 浪费 Token
  • 增加延迟
  • 容易进入死循环

一套生产级 JSON 流水线

LLM
 ↓
JSON Mode / Function Calling
 ↓
Prompt Schema
 ↓
json_repair
 ↓
Pydantic 校验
 ↓
Business Validator
 ↓
Retry
 ↓
Self Correction
 ↓
Fallback

我的推荐方案

小项目

Prompt
    ↓
json.loads

中型项目

Prompt
    ↓
json_repair
    ↓
Pydantic

LangChain 项目

Structured Output
        ↓
Pydantic

Agent 项目

Function Calling
        ↓
Pydantic
        ↓
Business Validator

高可靠生产环境

Grammar Constraint
        ↓
Function Calling
        ↓
Pydantic
        ↓
Business Validator
        ↓
Retry
        ↓
Fallback

总结

很多人想找到一种方法,让大模型一次性输出正确 JSON。

但在生产环境里,真正的思路不是:

如何让模型永远不出错。

而是:

当模型出错时,如何自动发现错误、修复错误,并保证最终进入系统的数据一定正确。

所以,保证 JSON 可用性的核心从来不是某一个技巧,而是一套完整的工程体系:

生成
 ↓
修复
 ↓
校验
 ↓
重试
 ↓
兜底

这才是生产级 AI 应用处理结构化输出的标准答案。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐