处理 LLM 返回 JSON 格式不稳定
处理 LLM 返回 JSON 格式不稳定,是生产环境中最让人头疼的问题之一。根源在于 LLM 本质是概率预测,而非严格的序列化执行器。
应对这个问题,不能只靠“祈祷”,需要采用**“生成时约束 + 解析时容错 + 架构层兜底”**的三层组合拳。以下是经过实战验证的完整解决方案:
第一层:生成端约束(从源头降低错误率)
在调用 LLM 时,通过技术手段强制其输出合法 JSON,而非仅靠“口头叮嘱”。
-
启用原生 JSON 模式(最有效):
- OpenAI / Azure:在请求中设置
response_format: { "type": "json_object" },并强制要求temperature=0(降低随机性)。 - Anthropic Claude:使用
{"type": "json_object"}的response_format。 - Google Gemini:设置
response_mime_type="application/json"。 - 注意:JSON 模式通常要求
system或user提示词中包含 “JSON” 关键字,且只返回纯 JSON(不要 Markdown 包裹)。
- OpenAI / Azure:在请求中设置
-
使用 Function Calling / Tools:
- 即使不需要调用外部函数,也可以将你期望的 JSON 结构定义为一个
function的parameters(JSON Schema)。LLM 会原生地输出符合该 Schema 的arguments字符串,这比直接对话生成的稳定性高出数个量级。
- 即使不需要调用外部函数,也可以将你期望的 JSON 结构定义为一个
-
语法级约束解码(开源模型首选):
- 如果使用本地或开源模型(如 Llama 3),使用 Outlines、LMQL 或 JSONFormer 库。它们在生成每个 Token 时进行逻辑校验,保证 100% 生成语法合法的 JSON,彻底杜绝括号缺失、引号未转义等问题。
第二层:解析端容错(修复“带病”的 JSON)
即使做了上述约束,模型仍可能返回夹杂注释、Markdown 格式或缺失字段的文本。解析时务必做“清洗”和“修复”:
-
清洗非 JSON 杂质:
import redef clean_json(raw):# 去掉 ```json ... ``` 标记raw = re.sub(r'```json\s*|```\s*', '', raw)# 尝试提取第一个 { 到最后一个 } 之间的内容match = re.search(r'\{.*\}', raw, re.DOTALL)return match.group(0) if match else raw -
使用鲁棒性解析库(不要只用
json.loads):- Python:安装
json-repair库。它能修复缺失引号、多余的尾部逗号、单引号变双引号等低级错误。 - JavaScript:使用
json5或lossless-json。 - 示例逻辑:
try: json.loads(clean) except: json_repair.repair(clean)。
- Python:安装
第三层:后验验证与智能重试(确保业务字段完整)
语法正确不代表业务字段完整(例如缺失 name 字段,或 age 变成了字符串)。
-
引入 Pydantic(Python)或 Zod(JS)进行 Schema 验证:
- 定义必填字段和字段类型。验证失败时,不要直接报错,而是进入修复流程。
-
“自我修正”重试机制(最强兜底):
- 当验证失败时,不要简单重发原 Prompt,而是将错误信息拼接到新 Prompt 中,让 LLM 修正:
- Prompt 模板:“你之前返回的 JSON 校验失败,错误是
{具体报错}。请只返回符合以下 Schema 的修正版 JSON:{Schema描述},不要包含任何解释文字。” - 设置最多重试 2~3 次(带指数退避),这能解决 99% 的偶发性格式错误。
第四层:架构层解耦(终极防御)
如果业务对格式要求极其严苛(如金融、医疗数据),建议:
- 放弃“一次性生成复杂 JSON”:拆分为多次简单的原子化生成(每次只生成 1~2 个字段),或者让模型先输出结构化文本(如 Markdown 表格),再由后端代码转换为 JSON。
- 引入代理校验 Agent:另起一个轻量级 LLM(如 GPT-3.5),专职负责将上游模型的非标准输出“翻译”成标准 JSON,主模型只负责内容创作。
实战最佳实践组合建议
| 场景 | 推荐方案 |
|---|---|
| 调用 OpenAI/Claude 官方 API | JSON Mode + Temperature=0 + Pydantic校验 + json-repair 兜底 |
| 使用开源模型本地部署 | Outlines 约束解码 + Schema验证(几乎不再需要修复) |
| 低成本快速迭代 | Function Calling 提取结构 + 捕获异常时直接让模型重试修正 |
最后请记住:永远不要假设 LLM 给你的字符串是合法的 JSON。在生产代码中,务必用 try-except 将整个解析逻辑包裹起来,并做好降级返回默认值或记录原始报错日志的准备,以便后续优化 Prompt。