ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型稳定输出JSON的工程化解决方案:从提示词到函数调用

大模型稳定输出JSON的工程化解决方案:从提示词到函数调用 1. 先搞清楚为什么大模型输出JSON会不稳定如果你正在开发基于大模型的智能体或应用想把大模型的回答直接变成结构化的JSON数据大概率会遇到这个问题模型输出的JSON格式时好时坏有时多一个逗号有时少一个引号甚至直接返回一段无法解析的文本。这直接导致你的下游代码崩溃应用流程中断。这个问题的根源不在于模型“能力”不行而在于它的“工作模式”和我们程序员的期望有本质区别。大模型是生成式模型它的核心任务是“续写”最可能的文本序列而不是“严格遵守”JSON语法规范。它没有内置的JSON解析器只是根据训练数据中见过的无数JSON片段去“模仿”和“猜测”下一个字符。这就导致了几个典型的不稳定点格式漂移生成的JSON可能缺少闭合的大括号、引号不匹配、或键名没有用双引号包裹这是JSON标准不允许的但模型可能生成单引号。内容溢出模型可能在生成完你要求的JSON结构后又“情不自禁”地加上一些解释性文字比如“以上就是结果。”导致整个输出不再是纯JSON。结构变异对于嵌套较深或结构复杂的JSON模型可能会混淆数组和对象的层次或者在应该生成固定字段时生成一个完全不同的字段名。所以解决“稳定输出JSON”的关键不是去“训练”或“命令”模型成为JSON专家而是通过一套工程化的方法引导、约束和修正模型的输出使其结果对下游程序是可靠、可预测的。下面我会从最简单的提示词技巧讲到更可靠的程序化方案。2. 第一步用提示词Prompt设定强约束在调用模型API前优化你的提示词是成本最低、见效最快的方法。目标是把你的需求从“请给我JSON”变成“请严格按照这个模板生成JSON不要有任何多余内容”。2.1 基础但有效的提示词结构一个能显著提升JSON输出稳定性的提示词通常包含以下几个部分你是一个专业的JSON数据生成器。请根据用户的问题生成严格符合以下要求的JSON数据 要求 1. 输出必须是**一个且仅一个**合法的JSON对象。 2. 不要有任何额外的解释、说明、Markdown标记或前言后语。 3. 键key必须使用英文双引号包裹。 4. 值value如果是字符串也必须使用英文双引号包裹。 JSON结构必须完全遵循以下模板 { field1: 类型或描述, field2: 类型或描述, field3: [数组内容描述] } 用户问题[这里替换成你的具体问题]关键点解析角色设定开头就告诉模型“你是一个JSON数据生成器”这比直接提要求更能让模型进入“结构化输出”的状态。明确禁令“不要有任何额外内容”这条指令至关重要能大幅减少模型在JSON后“画蛇添足”的概率。提供模板直接给出你期望的JSON骨架甚至包括字段名和类型提示。模型模仿模板的能力远强于从零创造。2.2 进阶技巧使用JSON Schema描述对于复杂结构在提示词中直接写一个大JSON模板可能很臃肿。这时可以使用JSON Schema来描述你的数据结构。虽然模型不一定能完全理解Schema但将其作为自然语言描述的一部分能提供更精确的约束。请生成一个符合以下JSON Schema定义的JSON对象。只输出该JSON对象不要输出其他任何文字。 Schema 描述 - 根对象必须包含 name (字符串)、age (整数)、hobbies (字符串数组) 字段。 - 可选包含 address 对象其下有 city 和 street 字段。 用户问题介绍一个叫小明的人他25岁喜欢读书和游泳住在北京朝阳区。在实际测试中结合了角色、禁令、模板和Schema描述的提示词能将一次生成的成功率指可直接被json.loads解析从不到50%提升到80%以上。但这还不够尤其是对于生产环境。3. 第二步调用层控制与后处理即使提示词写得再好也无法保证100%的成功率。因此必须在代码调用层和后处理层建立防线。3.1 利用API原生功能现在许多大模型的API已经提供了结构化输出Structured Outputs或JSON模式JSON Mode参数。这是目前最可靠的方案。OpenAI GPT系列在API调用时设置response_format{ “type”: “json_object” }。非常重要的一点是官方文档强调当启用此模式时你的系统提示词System Prompt或用户消息中必须明确包含“json”这个词否则API可能会报错。这强制模型以JSON对象格式进行思考。Anthropic Claude在消息参数中设置response_format{ “type”: “json” }。其他国产大模型API查阅对应文档寻找类似response_format、json_mode或structured_output的参数。使用建议只要你的目标模型API支持务必优先使用这个功能。它通常比纯提示词约束有效得多是工程上的首选。3.2 输出后处理与修复当API不支持JSON模式或者即使支持也偶尔出错时一个健壮的后处理流程是必不可少的。不要指望模型一次就成功而是假设它可能会失败并准备好修复。后处理流程设计提取尝试首先尝试从模型返回的完整文本中提取第一个看起来像JSON的片段。可以用正则表达式匹配最外层的{...}或[...]。import re import json def extract_json(text): # 尝试匹配最外层的花括号对象或方括号数组 pattern r(\{.*\}|\[.*\]) matches re.findall(pattern, text, re.DOTALL) # re.DOTALL 让 . 匹配换行符 if matches: return matches[0] # 返回第一个匹配项 return None解析与验证将提取到的字符串用json.loads()尝试解析。如果成功皆大欢喜如果失败进入修复环节。自动修复有限对于一些简单且常见的格式错误可以尝试自动修复。注意这是一个有风险的步骤只适用于非常明确的错误模式且修复后必须重新验证。单引号替换将字符串外部的单引号‘替换为双引号“。注意要避免替换掉字符串内容内部的引号这很复杂简单的正则容易出错。补全括号统计大括号{}和方括号[]的数量尝试补全缺失的闭合括号。这同样容易在复杂结构中出错。去除尾部杂文如果JSON本身是完整的但后面有多余文本上一步的提取通常已经解决了。更稳妥的做法是使用专门的库例如json_repair。它可以处理很多常见的JSON畸形问题。# 示例使用 json_repair (需先安装 pip install json_repair) import json_repair broken_json_str ‘{name: “Alice”, age: 30}‘ # 键名缺少双引号 try: repaired_json json_repair.loads(broken_json_str) print(“修复成功:”, repaired_json) except Exception as e: print(“修复失败:”, e)重试与降级如果自动修复失败你的程序应该有一个备选方案重试用相同的提示词和问题让模型再生成一次。有时第二次就成功了。降级处理记录错误返回一个预设的默认JSON结构或错误标识并通知人工检查。保证主流程不崩溃。4. 第三步复杂场景与生产级方案当你的应用需要处理高并发、复杂JSON结构或对稳定性要求极高时需要更系统的方案。4.1 使用“函数调用”或“工具调用”范式这是比“JSON模式”更强大、更本质的解决方案。你不再要求模型“输出JSON”而是定义一系列“函数”或“工具”让模型选择调用哪个函数并生成调用该函数所需的参数。这些参数本身就是严格结构化的JSON对象。OpenAI的Function Calling / Tool CallsAnthropic Claude的Tool Use工作流程你在API请求中除了对话消息还提供一个tools列表里面详细定义每个工具的名称、描述和参数严格遵循JSON Schema。模型根据对话内容判断是否需要调用工具以及调用哪个工具。模型返回一个结构化的决策指明要调用的工具和对应的参数对象。你的代码解析这个决策执行真正的函数调用。优势输出100%结构化模型返回的tool_calls部分是一个标准JSON数组完全可控。意图明确模型是在“选择工具”和“填充参数”而不是“生成一段JSON文本”这更符合其推理过程。支持复杂操作可以定义多个工具让模型进行多步决策。示例OpenAI格式# 定义工具 tools [ { “type”: “function”, “function”: { “name”: “get_current_weather”, “description”: “获取指定城市的当前天气”, “parameters”: { “type”: “object”, “properties”: { “location”: {“type”: “string”, “description”: “城市名”}, “unit”: {“type”: “string”, “enum”: [“celsius”, “fahrenheit”]} }, “required”: [“location”] } } } ] # 调用模型 response client.chat.completions.create( model“gpt-4”, messages[{“role”: “user”, “content”: “北京天气怎么样”}], toolstools, tool_choice“auto”, # 让模型决定是否调用 ) # 解析模型的工具调用决策 tool_calls response.choices[0].message.tool_calls if tool_calls: for tool_call in tool_calls: function_name tool_call.function.name function_args json.loads(tool_call.function.arguments) # 这里是稳定的JSON print(f”模型想调用 {function_name}, 参数是 {function_args}“)4.2 设计健壮的工程架构对于生产系统你需要把上述所有方法组合起来形成一个管道Pipeline。用户请求 | v [提示词优化模块] - 注入角色、模板、约束 | v [API调用模块] - 设置response_formatjson或传递tools定义 | v [原始响应] | v {—— 是JSON模式或工具调用 ——} 是 | | 否 v v 直接解析 [后处理模块] | 1. 提取JSON片段 | 2. 尝试解析 | 3. 尝试修复 (如json_repair) | 4. 失败则重试或降级 | v [解析后的JSON对象] | v [格式与内容验证] - 检查必填字段、类型、值域 | v [传递给下游业务逻辑]关键设计点配置化将不同任务所需的JSON模板、Schema或工具定义做成配置文件便于管理。监控与告警记录JSON解析的成功率、重试率、修复率。当失败率超过阈值时触发告警。熔断与降级如果连续多次解析失败可以考虑暂时熔断该功能返回缓存数据或静态结果避免雪崩。测试用例构建丰富的测试用例覆盖正常情况、边界情况如空值、超长字符串和模型可能出的各种错误格式确保你的处理管道足够健壮。5. 避坑指南与经验总结在实际开发和运维中除了上述技术方案还有一些经验性的坑点需要注意。5.1 不要过度依赖模型的“理解”即使你用了JSON模式或函数调用模型生成的内容即JSON里的value也可能不符合你的业务逻辑。例如你要求一个“状态”字段枚举值是[“open”, “closed”]模型可能会生成“opened”。因此结构化输出解决的是格式问题不是语义问题。下游代码必须对值进行有效性校验。5.2 温度Temperature参数的影响如果你需要高度确定性的JSON输出在API调用时将temperature参数设置为0或一个很低的值如0.1。这会让模型的输出更确定、更可预测减少随机性带来的格式变异。反之如果你需要一些创造性可以调高temperature但必须接受随之降低的格式稳定性。5.3 上下文长度与截断如果你要求模型生成一个很长的列表比如一个包含100个项目的数组或者JSON结构非常庞大可能会超出模型的上下文窗口导致输出被截断从而产生无效的JSON。在设计数据结构时尽量保持简洁。对于长列表考虑让模型分页生成或通过多次交互完成。5.4 本地部署模型的特殊考量如果你使用Ollama、vLLM等工具在本地部署大模型情况略有不同API兼容性这些部署工具提供的API可能不完全对齐OpenAI等商业API的response_format参数。你需要查阅其特定文档看是否支持类似功能。模型本身能力许多优秀的开源模型如Llama 3、Qwen等本身具备出色的指令跟随和格式化输出能力。即使部署接口不支持强制JSON模式通过精心设计的提示词如使用ChatML、Alpaca等格式也能获得不错的效果。关键在于选择适合的模型并在提示词上下足功夫。后处理更重要在本地部署场景下一个健壮的后处理修复模块往往是性价比最高的选择。最终建议对于追求稳定性的生产环境技术选型的优先级应该是原生JSON模式/函数调用 API 强提示词约束 健壮后处理 纯提示词工程。永远不要假设模型输出是完美的用代码为它的“创造力”套上可靠的缰绳。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进