
这次我们来看一个非常实际的问题大模型生成 JSON 格式内容时经常出现格式错误、解析失败的情况。无论是调用 OpenAI、Claude 这类闭源 API还是部署 Llama、Qwen 等开源模型开发者都可能会遇到模型返回的 JSON 字符串不标准、缺少引号、多了换行甚至直接返回了一段非 JSON 文本的尴尬局面。这篇文章不讨论复杂的模型微调或架构设计而是聚焦于一个工程上的“一招鲜”解决方案。我们将深入探讨大模型输出 JSON 不稳定的根本原因并提供一个经过验证的、可立即落地的技术策略。无论你是进行本地大模型部署、调用云端 API还是开发基于大模型的自动化工具这个方法都能显著提升 JSON 接口的稳定性和可用性。1. 核心能力速览问题与方案定位在深入技术细节前我们先快速了解这个“一招解决”方案的核心定位和适用边界。能力项说明目标问题解决大模型LLM生成 JSON 格式内容时出现的格式错误、解析失败问题。问题表现返回内容缺失闭合引号/括号、包含非法控制字符、混入 Markdown 代码块标记、直接返回非 JSON 文本等。核心方案采用“结构化输出Structured Output” 后处理兜底的组合策略。技术门槛低。主要涉及 Prompt Engineering 和简单的字符串后处理不要求修改模型本身。适用模型绝大多数支持对话或补全功能的现代大模型包括 GPT、Claude、Llama、Qwen、GLM 等系列。适合场景需要模型稳定返回结构化数据如列表、对象的各类应用数据提取、信息归类、API 调用参数生成、自动化工作流等。前置条件需能调用模型的 API 或推理接口并拥有定义 Prompt 的权限。2. 问题根源为什么大模型总把 JSON 搞砸在寻找解决方案前必须理解问题根源。大模型生成 JSON 不稳定并非因为它“笨”而是由其底层工作机制决定的。自回归生成的本质大模型以“下一个词预测”的方式工作。它逐词Token生成文本每个词的概率基于上文计算。生成一个结构严谨的 JSON 字符串需要模型在数十甚至数百个生成步骤中始终保持对括号、引号、逗号等符号的精确匹配和上下文记忆这对模型是极大的挑战任何一步的微小概率偏差都可能导致最终格式错误。训练数据的噪声模型的训练数据中包含了各种格式的 JSON有标准的有格式化的有压缩成一行的有带注释的JSON5甚至还有嵌入在 Markdown 或代码块中的片段。模型学到了 JSON 的“语义”但对其“语法”的严格性缺乏强制约束。Prompt 指令的模糊性我们常使用“请返回一个 JSON”、“输出 JSON 格式”等指令。这类指令对模型而言不够精确。“返回 JSON”可能被理解为“在对话中描述一个 JSON”从而输出Here is the JSON: {...}这样的文本而非纯净的 JSON 字符串。采样策略的影响使用高温high temperature或核采样top-p等随机性较高的采样策略时模型输出的多样性增加格式出错的概率也随之大幅上升。因此单纯依靠模型“自觉”生成完美 JSON 是不可靠的。我们的策略必须从“指导模型更准确地生成”和“对不完美的输出进行修复”两个层面入手。3. 环境准备与前置条件本方案不依赖特定框架或库核心是思路和代码实现。你需要准备的是一个可以运行 Python 脚本的环境以及访问大模型的途径。3.1 基础环境Python 环境建议 Python 3.8 及以上版本。包管理工具pip。文本编辑器/IDE如 VSCode、PyCharm 等。3.2 大模型访问权限根据你的使用场景选择其一云端 API如 OpenAI API、 Anthropic Claude API、 国内各大模型平台的 API 等。你需要相应的 API Key。本地模型通过ollama、vLLM、text-generation-webui(oobabooga) 或LM Studio等工具部署的本地模型。你需要知道其 API 端点Endpoint。3.3 必要的 Python 库我们将使用requests调用 API用json和re正则表达式进行后处理。这些通常是 Python 标准库或极易安装。# 如果需要可以安装 requests pip install requests4. 核心策略一优化 Prompt 引导结构化输出这是最关键的一步旨在从源头减少错误。核心思想是通过 Prompt 给模型一个清晰、具体、可模仿的“模板”或“约束”。4.1 基础版明确指令与示例Few-Shot Prompting不要只说“输出 JSON”。要明确结构并给出例子。低效的 Prompt提取以下文章中的实体信息并以JSON格式返回。 文章{article}高效的 Prompt你是一个信息提取助手。请严格遵循以下要求 1. 从用户提供的文章中提取“人物”、“组织”、“地点”三类实体。 2. 输出必须是一个**纯净的、可直接被 json.loads() 解析的 JSON 字符串**。 3. JSON 格式必须完全如下所示仅替换 ... 部分的内容 { people: [..., ...], organizations: [...], locations: [..., ..., ...] } 4. 如果某类实体未找到则对应值为空数组 []。 5. **不要输出任何额外的解释、Markdown 代码块标记或前言后语。** 示例 用户输入”苹果公司CEO蒂姆·库克在加利福尼亚州库比蒂诺发布了新产品。“ 你应输出{people: [蒂姆·库克], organizations: [苹果公司], locations: [加利福尼亚州, 库比蒂诺]} 现在请处理以下文章 文章{article}关键点分析结构化描述用数字列表明确任务步骤。提供模板直接给出目标 JSON 的骨架模型只需填空。强调“纯净”明确要求输出可直接被解析排除额外文本。Few-Shot 示例给一个具体例子展示输入和精确的输出格式。负面约束明确禁止输出解释和 Markdown 标记。4.2 进阶版使用系统提示词System Prompt和函数调用Function Calling对于支持角色设定和函数调用的 API如 OpenAI GPT Claude可以更优雅地实现。系统提示词固定角色和规则system_message { “role”: “system”, “content”: “你是一个严格的数据提取引擎。你总是以纯净的 JSON 格式输出不包含任何其他文本。你的输出必须能被标准的 JSON 解析器直接解析。” }利用函数调用Tool Calls这是目前最可靠的方式之一。你定义一个“工具”函数其参数是一个符合特定 JSON Schema 的对象。模型在需要时会“调用”这个工具并生成完全符合该 Schema 的参数。这相当于让模型在生成时内部就有一个严格的 JSON 结构校验器。# 以 OpenAI API 为例 tools [ { “type”: “function”, “function”: { “name”: “extract_entities”, “description”: “从文本中提取实体信息”, “parameters”: { “type”: “object”, “properties”: { “people”: {“type”: “array”, “items”: {“type”: “string”}}, “organizations”: {“type”: “array”, “items”: {“type”: “string”}}, “locations”: {“type”: “array”, “items”: {“type”: “string”}} }, “required”: [“people”, “organizations”, “locations”] } } } ] # 在 API 调用中传入 tools 参数并设置 tool_choice 为 {“type”: “function”, “function”: {“name”: “extract_entities”}} 来强制使用。函数调用的优势API 会强制模型输出符合预定 Schema 的 JSON格式错误率极低。这是解决此问题的“官方推荐”方式如果模型支持应优先采用。5. 核心策略二鲁棒的后处理与格式修复无论 Prompt 写得多么完美我们仍需一个兜底方案来处理模型可能返回的“脏数据”。后处理的目标是将一段可能被污染的文本修复成合法的 JSON 字符串。5.1 后处理流程设计我们设计一个函数robust_json_parse(model_output)它按以下步骤尝试解析尝试直接解析首先尝试用json.loads()直接解析原始输出。如果成功直接返回。提取 JSON 片段如果失败使用正则表达式寻找文本中最像 JSON 对象{...}或数组[...]的部分。清理常见噪声去除外层的 Markdown 代码块标记如json 和。去除常见的引导语如 “输出是”, “Here is the JSON:”。修复缺失的引号在简单情况下。处理尾随逗号JSON 标准不允许。再次尝试解析对清理后的文本再次尝试json.loads()。终极容错如果所有尝试都失败则记录日志返回一个预定义的错误结构或None。5.2 后处理代码实现以下是一个较为完整的后处理函数示例import json import re def robust_json_parse(text, verboseFalse): 尝试从可能包含额外文本的模型输出中解析 JSON。 Args: text (str): 模型返回的原始文本。 verbose (bool): 是否打印调试信息。 Returns: dict/list/None: 解析成功的 JSON 对象或 None。 if not text or not isinstance(text, str): return None # 步骤1: 尝试直接解析 try: return json.loads(text) except json.JSONDecodeError as e: if verbose: print(f”直接解析失败: {e}”) # 步骤2: 提取最可能的 JSON 对象或数组 # 正则表达式匹配最外层的 {...} 或 [...] json_pattern r(\{(?:[^{}]|(?-1))*\}|\[(?:[^\[\]]|(?-1))*\]) matches re.finditer(json_pattern, text, re.DOTALL) candidates [] for match in matches: candidates.append(match.group()) # 如果没有找到候选尝试更宽松的匹配可能内部有未转义字符 if not candidates: # 寻找以 { 开头以 } 结尾的片段贪婪匹配 loose_match re.search(r(\{.*\}), text, re.DOTALL) if loose_match: candidates.append(loose_match.group(1)) for candidate in candidates: cleaned candidate # 步骤3: 清理常见噪声 # 去除首尾的空白和代码块标记 cleaned cleaned.strip() cleaned re.sub(r^(?:json)?\s*, , cleaned) # 去除开头的 json cleaned re.sub(r\s*$, , cleaned) # 去除结尾的 # 去除常见的引导语前缀 cleaned re.sub(r^(?:输出|结果|JSON|Response|Answer)[:]\s*, , cleaned, flagsre.IGNORECASE) # 尝试修复尾随逗号仅限对象和数组的最后一项 # 注意这是一个简单修复复杂嵌套可能出错 cleaned re.sub(r,\s*([}\]]), r\1, cleaned) # 步骤4: 再次尝试解析 try: parsed json.loads(cleaned) if verbose: print(f”成功从清理后的文本解析 JSON: {cleaned[:100]}...”) return parsed except json.JSONDecodeError as e: if verbose: print(f”清理后解析仍失败候选: {cleaned[:50]}..., 错误: {e}”) continue # 尝试下一个候选 # 步骤5: 所有尝试都失败 if verbose: print(f”无法从文本中解析 JSON: {text[:200]}...”) return None # 使用示例 model_output_1 ‘好的这是提取的结果{“people”: [“张三”], “locations”: [“北京”]}’ model_output_2 ‘json\n{“people”: [], “organizations”: [“ABC公司”]}\n’ model_output_3 ‘输出是{“people”: [“李四”], “locations”: [“上海”, ]}’ # 注意尾随逗号 result1 robust_json_parse(model_output_1, verboseTrue) result2 robust_json_parse(model_output_2, verboseTrue) result3 robust_json_parse(model_output_3, verboseTrue) print(“结果1:”, result1) print(“结果2:”, result2) print(“结果3:”, result3)6. 完整工作流整合与测试现在我们将优化的 Prompt 和鲁棒的后处理整合到一个完整的函数中并进行测试。6.1 整合调用函数假设我们使用 OpenAI 格式的 API本地模型如 Llama 通过ollama或vLLM部署后通常也兼容此格式。import requests import json from typing import Optional, Dict, Any def call_llm_for_json(api_url: str, api_key: str, prompt: str, system_prompt: Optional[str] None, model: str “gpt-3.5-turbo”, temperature: float 0.1, # 低温度提高确定性 max_tokens: int 1000) - Optional[Dict[str, Any]]: 调用大模型 API并尝试获取结构化的 JSON 输出。 Args: api_url: API 端点地址。 api_key: API 密钥。 prompt: 用户提示词需包含清晰的 JSON 输出指令。 system_prompt: 系统提示词用于设定角色和行为。 model: 模型名称。 temperature: 采样温度越低输出越确定。 max_tokens: 最大生成 token 数。 Returns: 解析后的 JSON 字典或 None。 headers { “Content-Type”: “application/json”, “Authorization”: f”Bearer {api_key}” } messages [] if system_prompt: messages.append({“role”: “system”, “content”: system_prompt}) messages.append({“role”: “user”, “content”: prompt}) payload { “model”: model, “messages”: messages, “temperature”: temperature, “max_tokens”: max_tokens, “stream”: False } try: response requests.post(api_url, headersheaders, jsonpayload, timeout60) response.raise_for_status() result response.json() # 提取模型返回的文本内容 # 注意不同 API 返回结构可能不同此处为 OpenAI 格式 content result[“choices”][0][“message”][“content”].strip() # 使用后处理函数解析 JSON parsed_json robust_json_parse(content, verboseTrue) # 调试时可开启 verbose return parsed_json except requests.exceptions.RequestException as e: print(f”API 请求失败: {e}”) return None except (KeyError, IndexError, json.JSONDecodeError) as e: print(f”解析 API 响应失败: {e}”) return None # 示例构造一个优化的 Prompt article “在2023年杭州亚运会上中国运动员全红婵在跳水项目中获得金牌她的教练陈若琳来自北京体育大学。” system_prompt “你是一个精准的信息提取助手。你只输出纯净的、可直接解析的 JSON不包含任何其他文字。” user_prompt f””” 请从以下文章中提取实体信息。 输出必须是一个纯净的 JSON 对象格式如下仅替换内容 {{ “people”: [“实体1”, “实体2”], “organizations”: [“实体1”], “locations”: [“实体1”, “实体2”] }} 如果某类实体未找到使用空数组 []。 不要添加任何解释。 文章{article} “”” # 假设你使用本地部署的模型API 端点为 http://localhost:11434/api/chat # 假设 API Key 非必需如 ollama api_url “http://localhost:11434/api/chat” api_key “” # 本地部署可能不需要 key result call_llm_for_json( api_urlapi_url, api_keyapi_key, promptuser_prompt, system_promptsystem_prompt, model“llama3.2”, # 替换为你的本地模型名 temperature0.1 ) if result: print(“成功解析 JSON:”) print(json.dumps(result, indent2, ensure_asciiFalse)) else: print(“未能获得有效的 JSON 输出。”)6.2 测试与效果验证为了验证方案的有效性建议进行多轮测试基础功能测试使用结构简单、实体明确的文章验证是否能正确提取并返回标准 JSON。抗噪声测试在 Prompt 中不提供示例或使用温度较高的参数观察后处理函数是否能从模型的“不完美”输出中恢复出 JSON。边界测试输入文章不含任何目标实体检查是否返回{“people”: [], “organizations”: [], “locations”: []}。输入超长文章测试模型是否因长度限制而截断 JSON。输入包含特殊字符如未转义的引号的文章测试模型和后处理的鲁棒性。批量任务测试准备一个包含 100 篇文章的列表使用循环调用上述函数。统计成功解析率并记录失败案例进行分析进一步优化 Prompt 或后处理逻辑。成功标准对于结构良好的 Prompt 和低温度设置成功解析率首次调用即返回有效 JSON应能达到 95% 以上。结合后处理函数总体成功率应接近 100%。7. 性能考量与最佳实践7.1 性能影响Prompt 长度提供详细的 Few-Shot 示例会显著增加 Token 消耗从而提高 API 成本或本地推理时间。需在效果和成本间权衡。后处理开销正则表达式匹配和多次解析尝试会引入微小的 CPU 开销但对于单次 API 调用而言这部分开销通常可忽略不计。温度Temperature这是影响 JSON 格式稳定性的最重要参数之一。对于需要稳定结构化输出的任务强烈建议将温度设置为 0.1 或更低以最大化输出的确定性。7.2 最佳实践建议优先使用函数调用Tool Calls如果你的模型和 API 支持这是最可靠、最标准化的方案。设计精炼的 Prompt 模板将固定的指令和 JSON 结构模板化作为系统提示词或用户提示词的一部分避免每次重复编写。实施重试机制对于关键任务如果robust_json_parse返回None可以设计一个简单的重试逻辑例如更换更明确的 Prompt 重试一次。记录与监控在robust_json_parse函数的失败分支中添加日志记录收集模型返回的“脏数据”样本。定期分析这些样本可以发现模型常见的输出模式问题从而进一步优化 Prompt。分离逻辑与配置将 JSON Schema 定义、Prompt 模板、API 配置等放在配置文件如config.yaml或单独模块中方便维护和调整。单元测试为robust_json_parse函数编写单元测试覆盖各种畸形的输入案例确保其修复能力。8. 常见问题与排查方法在实际使用中你可能会遇到以下问题问题现象可能原因排查方式解决方案API 调用返回非 200 状态码API 密钥错误、端点不对、额度不足、模型不存在。检查api_url和api_key查看 API 返回的错误信息。修正配置检查账单和模型名称。模型返回内容为空或非常短max_tokens设置过小Prompt 导致模型过早结束。查看 API 返回的完整响应检查finish_reason是否为length。适当增加max_tokens检查 Prompt 是否包含停止序列。直接解析失败后处理也提取不到 JSON模型完全未遵循指令返回了自由文本。打印出模型返回的原始content。强化 Prompt在系统提示词中强调角色在用户提示词中使用更严格的约束和示例降低温度。提取到的 JSON 片段解析失败模型生成的 JSON 内部有语法错误如字符串内的未转义引号。打印cleaned后的候选字符串。在后处理函数中增加更复杂的修复逻辑如尝试转义内部引号但这可能引入风险。更优解是回到 Prompt 优化要求模型输出转义正确的 JSON。后处理函数误提取了非 JSON 文本正则表达式过于宽松匹配了类似 JSON 的其他文本。检查正则匹配的候选内容。收紧正则表达式例如要求候选字符串以{开头且以}结尾并且{和}的数量匹配。批量处理时成功率不稳定输入文本差异大模型在长上下文或复杂任务中表现波动。统计失败案例的共同特征。对不同的任务类型设计不同的 Prompt 模板考虑对复杂任务进行分步处理Chain-of-Thought。9. 总结与下一步大模型生成 JSON 格式不正确是一个普遍且棘手的问题。本文提供的“组合拳”方案——通过精心设计的 Prompt尤其是函数调用从源头引导再通过一个鲁棒的后处理函数进行兜底修复——在实践中被证明是高效且可靠的。最值得尝试的步骤首先检查你的模型是否支持函数调用Tool Calls。如果支持请立即将其作为首选方案。其次优化你的Prompt。使用清晰的模板、具体的示例和严格的约束。将温度参数调低。最后将本文提供的robust_json_parse函数集成到你的调用流程中作为最后的安全网。最容易踩的坑忽略了温度Temperature参数对格式稳定性的巨大影响。Prompt 指令过于模糊没有提供具体的输出格式示例。后处理逻辑过于复杂或脆弱引入了新的错误。后续扩展方向探索框架支持LangChain、LlamaIndex 等框架内置了更高级的输出解析器如PydanticOutputParser可以简化结构化输出的实现。考虑模型微调如果某个 JSON 输出格式是固定且高频的需求可以考虑使用少量数据对开源模型进行微调Fine-tuning或提示词微调Prompt Tuning使其专门化。构建校验管道在关键业务流中可以在使用解析后的 JSON 前加入基于 JSON Schema 的严格校验确保数据质量。将这套方法应用到你的下一个大模型项目中无论是构建智能客服的数据提取模块还是开发自动化报告生成工具你都将获得一个更加稳定、可信的数据输出管道。