003-JSON-Output-Control
JSON 格式输出控制:如何让 AI 每次都返回完美的结构化数据?
💡摘要:大模型天生不擅长输出严格的 JSON 格式。本文教你如何通过 Schema 验证、自动修复和提示工程,确保 AI 每次都返回合法、可用的结构化数据。
引言
你让 AI 从一段文本中提取信息,要求返回 JSON 格式。结果它给你返回了:
好的,这是您要的结果: { "name": "张三", "age": 25, } 希望对你有帮助!三个问题同时出现:
- 前后有多余文字——无法直接用
json.loads()解析 - 末尾多了一个逗号——JSON 不允许
- 被 Markdown 代码块包裹——需要额外处理
如果你在做 API 集成、工作流编排或工具调用,这种"差不多但不完全对"的输出会让整个系统崩溃。
为什么 AI 就是做不对一个简单的 JSON?
如何让 AI 稳定输出合法的结构化数据?
本文将提供一套完整的解决方案,从提示工程到自动修复,让你再也不用手动清洗 AI 的 JSON 输出。
核心概念
为什么 LLM 天然做不好 JSON?
语言模型本质上是基于概率的序列生成器,逐 token 预测下一个词。它不理解语法,只在做概率猜测。
而 JSON 是一种严格的上下文无关格式,需要:
- 匹配的括号
{} - 双引号包裹的键名
- 正确的逗号分隔(且不能有末尾逗号)
- 合法的数值和字符串类型
当模型自回归生成时,极易在某一步"猜错",导致整个 JSON 结构崩溃。
常见错误类型
| 错误类型 | 示例 | 出现频率 |
|---|---|---|
| 括号/引号不匹配 | {"name": "Alice | ⭐⭐⭐⭐⭐ |
| 末尾逗号 | {"a": 1, "b": 2,} | ⭐⭐⭐⭐ |
| 键名缺少引号 | {name: "Alice"} | ⭐⭐⭐ |
| Markdown 代码块 | ` ```json {…} ```` | ⭐⭐⭐⭐⭐ |
| 前后多余文字 | 好的,结果如下:{...} | ⭐⭐⭐⭐⭐ |
| 类型错误 | {"age": "25"}(应为数字) | ⭐⭐⭐ |
关键术语
- JSON Schema:描述 JSON 结构的标准格式,定义字段类型、必填项、枚举值等
- 自动修复:通过规则或库修复轻微格式错误,无需重新调用模型
- 重试策略:当修复失败时,告知模型错误并要求重新生成
- Function Calling:OpenAI 等厂商提供的结构化输出模式,比纯 Prompt 更可靠
原理深入
三层防护体系
要确保 JSON 输出的稳定性,需要构建三层防护:
第一层:提示工程(预防) │ ├── 在 Prompt 中明确 Schema ├── 使用 Few-shot 示例 └── 强调"只输出 JSON" │ 第二层:自动修复(补救) │ ├── 移除 Markdown 标记 ├── 引号修复 ├── 提取 JSON 子串 └── 使用专用修复库 │ 第三层:重试生成(兜底) │ ├── 反馈错误信息给模型 └── 强化约束后重新调用核心原则:先预防,再修复,最后重试。
自动修复策略详解
当模型返回的 JSON 有轻微错误时,可以通过规则自动修复,无需重新调用模型(省钱省时间):
| 策略 | 方法 | 适用场景 |
|---|---|---|
| 移除 Markdown | 正则替换 ````json` 和 ````` | 模型习惯加代码块标记 |
| 引号修复 | 单引号替换为双引号 | 模型用 Python 风格字符串 |
| 提取 JSON 子串 | 找第一个{到最后一个} | 前后有多余解释文字 |
| 专用修复库 | json_repair、demjson、json5 | 复杂错误,规则无法处理 |
代码示例
示例 1:JSON Schema + Prompt 构建
importjsonclassJSONOutputController:"""JSON 格式输出控制器"""# 情感分析响应 SchemaSENTIMENT_SCHEMA={"type":"object","properties":{"sentiment":{"type":"string","enum":["positive","negative","neutral"]},"confidence":{"type":"number","minimum":0,"maximum":1},"reasons":{"type":"array","items":{"type":"string"}}},"required":["sentiment","confidence","reasons"]}@staticmethoddefbuild_json_prompt(prompt:str,schema_name:str="SENTIMENT")->str:"""构建要求 JSON 输出的 Prompt"""schemas={"SENTIMENT":JSONOutputController.SENTIMENT_SCHEMA}schema=schemas[schema_name]returnf"""请严格按照以下 JSON Schema 返回结果,不要有任何其他文字:{json.dumps(schema,indent=2,ensure_ascii=False)}用户输入:{prompt}请只返回 JSON 格式的结果:"""示例 2:验证并自动修复 JSON
importreimportjsonfromtypingimportOptional,Dictdefvalidate_and_fix_json(raw_text:str)->Optional[Dict]:"""验证并尝试修复 JSON 格式"""# 1. 移除 Markdown 代码块标记cleaned=re.sub(r'```(?:json)?\n?(.*?)\n?```',r'\1',raw_text,flags=re.DOTALL)cleaned=cleaned.strip()# 2. 尝试直接解析try:returnjson.loads(cleaned)exceptjson.JSONDecodeError:pass# 3. 引号修复(单引号 → 双引号)try:returnjson.loads(cleaned.replace("'",'"'))exceptjson.JSONDecodeError:pass# 4. 提取 JSON 子串(忽略前后多余文字)try:match=re.search(r'\{.*\}',cleaned,re.DOTALL)ifmatch:returnjson.loads(match.group())exceptjson.JSONDecodeError:passreturnNone# 修复失败# 测试三种常见场景test_cases=[# 场景 A:标准 JSON'{"sentiment": "positive", "confidence": 0.95}',# 场景 B:带 Markdown 标记'```json\n{"sentiment": "positive", "confidence": 0.88}\n```',# 场景 C:含前后多余文字'好的,这是您的结果:\n\n{"sentiment": "neutral", "confidence": 0.60}\n\n希望这对你有帮助!',]fori,caseinenumerate(test_cases):result=validate_and_fix_json(case)print(f"场景{i+1}:{result}")输出:
场景 1:{'sentiment': 'positive', 'confidence': 0.95} 场景 2:{'sentiment': 'positive', 'confidence': 0.88} 场景 3:{'sentiment': 'neutral', 'confidence': 0.6}三种常见错误全部自动修复成功!
实战应用
什么时候用自动修复,什么时候重试?
| 错误严重程度 | 处理方式 | 原因 |
|---|---|---|
| 轻微(多余文字、引号、Markdown) | 自动修复 | 无需重新调用,省钱 |
| 中等(缺少字段、类型错误) | 视情况 | 如果业务允许默认值,可修复 |
| 严重(结构完全错误) | 重试生成 | 自动修不了,必须让模型重来 |
推荐工具库
| 库名 | 用途 | 安装命令 |
|---|---|---|
| json_repair | 专门修复损坏的 JSON | pip install json_repair |
| demjson | 容错的 JSON 解析器 | pip install demjson3 |
| json5 | 支持更宽松的语法 | pip install json5 |
| Pydantic | 强类型验证 + 自动修复 | pip install pydantic |
最佳组合方案
Prompt 中强调格式 → 调用模型 → json_repair 修复 → Pydantic 验证 → 失败重试这是生产环境中最稳定、最省钱的方案。
最佳实践
根据知识库和源码中的最佳实践,总结出以下5 条核心技巧:
1. 明确要求输出格式
✅ 只输出一个 JSON 对象,不要包含任何解释或代码块标记。 ❌ 请以 JSON 格式返回。(太模糊,模型可能加解释)2. 使用 JSON Schema 定义结构
在 Prompt 中明确 Schema,让模型知道需要什么字段和类型。Schema 就是你的"合同"。
3. 提供 Few-shot 示例
1-2 个正确格式的示例,比任何解释都有效。
4. 自动修复 + 重试策略
# 伪代码result=call_llm(prompt)fixed=auto_fix_json(result)iffixedisNone:# 修复失败,重试result=call_llm(prompt+"\n\n格式错误,请重新返回纯 JSON。")5. 优先使用 Function Calling
如果你的平台支持(如 OpenAI),Function Calling 比纯 Prompt 更可靠。它通过 API 级别的约束强制输出合法 JSON。
总结
大模型天生做不好 JSON,但通过三层防护体系可以让它变得非常可靠:
Schema 定义 → 自动修复 → 重试兜底
核心要点回顾
- LLM 是概率生成器,不保证 JSON 语法正确性
- 三层防护:提示预防 → 自动修复 → 重试兜底
- 自动修复处理 90% 的常见错误,无需重新调用
- Function Calling 是最可靠的方案(如果平台支持)
