Skip to main content

处理 LLM 返回 JSON 格式不稳定

处理 LLM 返回 JSON 格式不稳定,是生产环境中最让人头疼的问题之一。根源在于 LLM 本质是概率预测,而非严格的序列化执行器。

应对这个问题,不能只靠“祈祷”,需要采用**“生成时约束 + 解析时容错 + 架构层兜底”**的三层组合拳。以下是经过实战验证的完整解决方案:

第一层:生成端约束(从源头降低错误率)

在调用 LLM 时,通过技术手段强制其输出合法 JSON,而非仅靠“口头叮嘱”。

  1. 启用原生 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 模式通常要求 systemuser 提示词中包含 “JSON” 关键字,且只返回纯 JSON(不要 Markdown 包裹)。
  2. 使用 Function Calling / Tools

    • 即使不需要调用外部函数,也可以将你期望的 JSON 结构定义为一个 functionparameters(JSON Schema)。LLM 会原生地输出符合该 Schema 的 arguments 字符串,这比直接对话生成的稳定性高出数个量级。
  3. 语法级约束解码(开源模型首选)

    • 如果使用本地或开源模型(如 Llama 3),使用 OutlinesLMQLJSONFormer 库。它们在生成每个 Token 时进行逻辑校验,保证 100% 生成语法合法的 JSON,彻底杜绝括号缺失、引号未转义等问题。

第二层:解析端容错(修复“带病”的 JSON)

即使做了上述约束,模型仍可能返回夹杂注释、Markdown 格式或缺失字段的文本。解析时务必做“清洗”和“修复”:

  1. 清洗非 JSON 杂质

    import re
    def 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
  2. 使用鲁棒性解析库(不要只用 json.loads):

    • Python:安装 json-repair 库。它能修复缺失引号、多余的尾部逗号、单引号变双引号等低级错误。
    • JavaScript:使用 json5lossless-json
    • 示例逻辑:try: json.loads(clean) except: json_repair.repair(clean)

第三层:后验验证与智能重试(确保业务字段完整)

语法正确不代表业务字段完整(例如缺失 name 字段,或 age 变成了字符串)。

  1. 引入 Pydantic(Python)或 Zod(JS)进行 Schema 验证

    • 定义必填字段和字段类型。验证失败时,不要直接报错,而是进入修复流程。
  2. “自我修正”重试机制(最强兜底)

    • 当验证失败时,不要简单重发原 Prompt,而是将错误信息拼接到新 Prompt 中,让 LLM 修正:
    • Prompt 模板:“你之前返回的 JSON 校验失败,错误是 {具体报错}。请只返回符合以下 Schema 的修正版 JSON:{Schema描述},不要包含任何解释文字。”
    • 设置最多重试 2~3 次(带指数退避),这能解决 99% 的偶发性格式错误。

第四层:架构层解耦(终极防御)

如果业务对格式要求极其严苛(如金融、医疗数据),建议:

  • 放弃“一次性生成复杂 JSON”:拆分为多次简单的原子化生成(每次只生成 1~2 个字段),或者让模型先输出结构化文本(如 Markdown 表格),再由后端代码转换为 JSON。
  • 引入代理校验 Agent:另起一个轻量级 LLM(如 GPT-3.5),专职负责将上游模型的非标准输出“翻译”成标准 JSON,主模型只负责内容创作。

实战最佳实践组合建议

场景推荐方案
调用 OpenAI/Claude 官方 APIJSON Mode + Temperature=0 + Pydantic校验 + json-repair 兜底
使用开源模型本地部署Outlines 约束解码 + Schema验证(几乎不再需要修复)
低成本快速迭代Function Calling 提取结构 + 捕获异常时直接让模型重试修正

最后请记住:永远不要假设 LLM 给你的字符串是合法的 JSON。在生产代码中,务必用 try-except 将整个解析逻辑包裹起来,并做好降级返回默认值或记录原始报错日志的准备,以便后续优化 Prompt。