news 2026/8/25 7:10:39

大模型稳定输出JSON的工程实践:从提示词到函数调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型稳定输出JSON的工程实践:从提示词到函数调用

这次我们来看一个对开发者非常实用的技术问题:如何让大模型稳定地输出JSON格式。无论是构建智能体、开发API接口,还是处理结构化数据,JSON都是程序间通信的“标准语言”。然而,直接让大模型生成JSON,你可能会遇到格式错误、字段缺失、额外解释文本等头疼问题。这篇文章不讨论复杂的概念,直接聚焦于可落地的解决方案。

我们将从问题根源出发,拆解几种主流且经过验证的方法,包括提示词工程、函数调用(Function Calling)、输出引导(Output Parsing)以及借助特定框架。无论你使用的是 OpenAI GPT、国产大模型,还是本地部署的开源模型,都能找到对应的实践路径。本文的重点是“稳定”和“可用”,我们会给出具体的代码示例、对比不同方案的优缺点,并说明如何根据你的场景选择最合适的方法。

如果你正在开发依赖大模型输出结构化数据的应用,比如自动生成数据报表、从非结构化文本中提取信息、或者构建需要严格API响应的智能体,那么这篇文章的内容将直接帮助你提升系统的可靠性和开发效率。

1. 核心能力速览:稳定输出JSON的几种路径

在深入细节之前,我们先通过一个表格快速了解几种主流方法的核心特点、适用场景和门槛,方便你快速判断哪种方案更适合你当前的项目。

方法核心原理优点缺点/门槛典型适用场景
提示词工程在用户指令(Prompt)中明确要求模型以JSON格式输出,并定义Schema。实现简单,无需额外依赖,所有支持文本生成的模型都可用。稳定性最低,模型可能忽略格式要求或产生额外文本。对格式要求不严的快速原型验证;简单的一次性任务。
函数调用 (Function Calling)向模型描述一个“函数”,让模型返回调用该函数所需的参数(JSON)。标准化程度高,主流API(如OpenAI)原生支持,稳定性好。依赖模型API对该特性的支持;需要预先定义函数结构。构建工具调用型智能体;需要严格参数提取的对话系统。
输出引导与解析库使用第三方库(如 LangChain 的 PydanticOutputParser)在生成前后进行格式约束和解析。提供了结构化框架,能自动重试和修复格式错误,开发体验好。需要引入额外库和框架,可能增加系统复杂度。基于 LangChain 等框架开发复杂应用;需要自动化错误处理。
模型微调使用包含JSON输入输出的数据对模型进行额外训练。理论上效果最稳定,格式遵从性最高。成本极高,需要训练数据和算力,不适用于大多数应用。对格式有极端要求且拥有大量标注数据的特定垂直领域。

对于绝大多数应用场景,提示词工程函数调用输出引导库是三种最值得投入精力掌握的实践方案。下面我们将逐一拆解。

2. 为什么大模型输出JSON不稳定?

在寻找解决方案之前,理解问题的根源至关重要。大模型本质上是基于概率生成文本的,它并没有内置的“JSON语法校验器”。不稳定的原因主要来自以下几个方面:

  1. 指令遵循的随机性:即使你在Prompt中明确要求“输出JSON”,模型也可能理解为“在描述中提及JSON”,或者优先完成“回答问题”这个核心任务,而将格式要求置于次要位置。
  2. Schema理解的偏差:当你给出一个复杂的JSON结构示例时,模型可能无法精确复现所有字段的名称、类型和嵌套关系,特别是当字段名具有歧义或结构层次较深时。
  3. “幻觉”与补充说明:模型倾向于生成人类可读的文本。它可能在JSON对象前后添加解释性文字,例如:“好的,根据您的要求,生成的JSON如下:” 和 “以上就是数据。” 这破坏了纯JSON的可解析性。
  4. 上下文长度与注意力:在长对话或多轮交互中,早期定义的格式要求可能会被模型在后续生成中逐渐忽略或遗忘。

因此,我们的所有技术手段,本质上都是在与模型的这种“自由意志”做斗争,通过增加约束、降低歧义来引导它走向我们需要的确定性的输出。

3. 基础方法:提示词工程优化

这是最直接、门槛最低的方法。虽然稳定性不如其他方案,但通过精心设计的Prompt,可以在很大程度上提高成功率。

3.1 核心原则

  • 明确性:直接使用“输出JSON”、“必须”、“仅返回”等强指令词。
  • 提供范例:在Prompt中给出一个清晰、完整的输出示例(One-shot 或 Few-shot learning)。
  • 定义Schema:使用JSON Schema或类似语法描述期望的结构。
  • 隔离指令与数据:用特殊标记(如``)将格式指令与待处理的内容分隔开。

3.2 实战Prompt示例

假设我们需要从一段产品描述中提取名称、价格和颜色。

较差的Prompt:

从以下描述中提取信息:红色iPhone 15,售价5999元。

模型可能回复:“这是一部红色iPhone 15,价格是5999元。”

优化后的Prompt:

你是一个信息提取助手。请严格遵循以下要求: 1. 仅返回一个合法的JSON对象,不要有任何额外的解释、标记或文本。 2. JSON的结构必须完全符合此Schema: { "type": "object", "properties": { "name": {"type": "string"}, "price": {"type": "number"}, "color": {"type": "string"} }, "required": ["name", "price", "color"] } 现在,处理以下输入: --- 红色iPhone 15,售价5999元。 ---

模型更可能回复:{"name": "iPhone 15", "price": 5999, "color": "红色"}

3.3 代码实现与后处理

即使使用了优化Prompt,仍建议在代码中添加后处理逻辑,以提高鲁棒性。

import json import re import openai # 或其他大模型客户端 def extract_json_from_response(response_text): """ 尝试从模型回复中提取JSON字符串。 处理可能包裹在```json ```标记中或前后有额外文本的情况。 """ # 尝试匹配被 ```json 和 ``` 包裹的JSON match = re.search(r'```json\n(.*?)\n```', response_text, re.DOTALL) if match: json_str = match.group(1) else: # 尝试匹配整个字符串中的第一个 `{` 到最后一个 `}` 之间的内容 match = re.search(r'(\{.*\})', response_text, re.DOTALL) if match: json_str = match.group(1) else: json_str = response_text # 最后尝试整个字符串 try: # 尝试解析JSON data = json.loads(json_str) return data except json.JSONDecodeError as e: print(f"JSON解析失败: {e}") print(f"原始文本: {response_text}") # 此处可以加入重试逻辑,或返回错误信息 return None # 调用大模型API client = openai.OpenAI(api_key="your-api-key") response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "你是一个严格输出JSON的助手。"}, {"role": "user", "content": optimized_prompt} # 使用上面优化后的Prompt ] ) result_text = response.choices[0].message.content extracted_data = extract_json_from_response(result_text) if extracted_data: print("成功提取JSON数据:", extracted_data) else: print("数据提取失败,需检查Prompt或重试。")

后处理步骤的价值:它相当于一道安全网,能捕获大部分因模型“多嘴”导致的格式错误,让应用层能接收到干净的结构化数据。

4. 进阶方案:利用函数调用(Function Calling)

函数调用是OpenAI等API提供的一种强大机制,它让模型“思考”后决定是否需要调用某个工具(函数),并生成调用该函数所需的结构化参数。这天然适合输出JSON。

4.1 工作原理

  1. 在请求中,你向模型描述一个或多个可用的“函数”(包括函数名、描述和参数JSON Schema)。
  2. 模型根据对话历史,判断是否需要调用函数。
  3. 如果需要,模型会停止生成普通文本,转而返回一个包含function_call字段的响应,其中包含了要调用的函数名和参数的JSON对象。
  4. 你的程序解析这个JSON对象,并用它去真正执行本地函数或外部API。

4.2 实战示例:提取用户查询中的结构化信息

假设用户说:“我想订下周五从北京飞往上海的机票”,我们需要提取出发地、目的地和日期。

import openai import json client = openai.OpenAI(api_key="your-api-key") # 1. 定义我们希望模型“调用”的函数及其参数Schema tools = [ { "type": "function", "function": { "name": "extract_flight_info", "description": "从用户对话中提取航班预订信息", "parameters": { "type": "object", "properties": { "departure_city": {"type": "string", "description": "出发城市"}, "arrival_city": {"type": "string", "description": "到达城市"}, "departure_date": {"type": "string", "description": "出发日期,格式为YYYY-MM-DD"} }, "required": ["departure_city", "arrival_city", "departure_date"], "additionalProperties": False # 禁止生成Schema之外的字段,提高稳定性 } } } ] # 2. 将用户查询和函数描述发送给模型 response = client.chat.completions.create( model="gpt-3.5-turbo", messages=[{"role": "user", "content": "我想订下周五从北京飞往上海的机票"}], tools=tools, tool_choice="auto", # 让模型自动决定是否调用函数 ) # 3. 解析模型的响应 response_message = response.choices[0].message # 检查模型是否决定调用函数 if response_message.tool_calls: # 通常只有一个tool_call,我们取第一个 tool_call = response_message.tool_calls[0] if tool_call.function.name == "extract_flight_info": # 解析模型生成的参数JSON arguments_json = tool_call.function.arguments try: flight_info = json.loads(arguments_json) print("成功提取航班信息:", flight_info) # 输出示例: {'departure_city': '北京', 'arrival_city': '上海', 'departure_date': '2023-10-27'} except json.JSONDecodeError as e: print("解析函数参数失败:", e) else: print("模型未触发函数调用。")

关键优势

  • 高稳定性:模型被明确引导至生成结构化参数的任务上,输出格式由API层保障,几乎总是合法的JSON。
  • 意图识别:模型可以判断用户输入是否与已定义的函数相关,避免无关输入触发错误的结构化提取。
  • 标准化:这是OpenAI、Google Gemini等主流API的官方推荐方式,兼容性好。

注意事项:并非所有模型都支持此特性。在选用国产大模型或本地部署模型时,需查阅其文档是否支持类似的“工具调用”或“函数调用”功能。

5. 框架集成:使用LangChain的输出解析器

如果你在使用LangChain这类AI应用框架,那么利用其内置的Output Parsers是更优雅的选择。它能将格式要求、模型调用和结果解析封装成一个流畅的流程。

5.1 使用PydanticOutputParser

Pydantic是一个强大的数据验证库。LangChain可以结合Pydantic模型来定义输出结构,并自动生成指导模型的Prompt。

from langchain_openai import ChatOpenAI from langchain.output_parsers import PydanticOutputParser from langchain.prompts import PromptTemplate from pydantic import BaseModel, Field from typing import List # 1. 使用Pydantic定义你期望的数据结构 class ProductInfo(BaseModel): name: str = Field(description="产品名称") price: float = Field(description="产品价格") colors: List[str] = Field(description="产品可选颜色列表") in_stock: bool = Field(description="是否有库存") # 2. 创建基于此模型的输出解析器 parser = PydanticOutputParser(pydantic_object=ProductInfo) # 3. 创建Prompt模板,LangChain会自动将格式指令插入到模板中 prompt_template = """ 请从用户输入中提取信息。 {format_instructions} 用户输入: {user_input} """ prompt = PromptTemplate( template=prompt_template, input_variables=["user_input"], partial_variables={"format_instructions": parser.get_format_instructions()} # 关键!自动生成格式指令 ) # 4. 组合成链并调用 model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) # temperature=0降低随机性 chain = prompt | model | parser # 使用LCEL语法组合 # 5. 执行 try: user_input = "苹果手机iPhone 15 Pro,售价9999元,有黑色和白色,目前有货。" result = chain.invoke({"user_input": user_input}) print("解析成功:", result) # 输出: ProductInfo(name='iPhone 15 Pro', price=9999.0, colors=['黑色', '白色'], in_stock=True) # 可以直接访问属性: result.name, result.price except Exception as e: print(f"解析过程中发生错误: {e}") # LangChain的解析器在失败时可能会提供更友好的错误信息或重试机制

5.2 该方案的优势

  • 自动化:自动生成复杂、准确的格式指令,无需手动编写。
  • 结构化结果:直接返回Pydantic对象,便于在Python中使用类型提示和属性访问。
  • 错误处理与重试:高级的Output Parser(如RetryOutputParser)可以在模型第一次输出格式错误时,自动将错误信息和原始提示重新发送给模型进行重试,大大提高了成功率。
  • 生态整合:与LangChain的其他组件(如链、代理、记忆)无缝集成。

6. 针对本地部署大模型的特别考量

当你使用Ollama、vLLM、Transformers等工具在本地部署开源大模型(如Llama、Qwen、ChatGLM)时,情况略有不同。这些模型可能没有原生的函数调用接口。

6.1 推荐策略组合

  1. 强提示词 + 后处理:这是最通用的方法。精心设计Prompt,并务必使用第3.3节中强大的extract_json_from_response函数进行后处理。
  2. 利用System Prompt:许多本地模型支持System Prompt(系统指令),你可以在这里永久性地强调输出格式要求,使其在整个会话中生效。
  3. 考虑微调(高级):如果任务极其固定且重要,可以收集一批(输入, 标准JSON输出)的数据对,对基础模型进行轻量级的微调(如LoRA),使其专门化于生成特定JSON格式。但这需要一定的机器学习知识和计算资源。

6.2 本地模型调用示例(使用Ollama)

import requests import json import re def query_local_llama(prompt, model_name="llama3.2:latest"): url = "http://localhost:11434/api/generate" payload = { "model": model_name, "prompt": prompt, "stream": False, "options": { "temperature": 0.1 # 低温度使输出更确定,有利于格式稳定 } } response = requests.post(url, json=payload) if response.status_code == 200: return response.json()["response"] else: raise Exception(f"请求失败: {response.status_code}") # 构建强约束的Prompt system_instruction = "你是一个JSON生成器。对于任何请求,你只返回一个纯净的、有效的JSON对象,不要有任何其他文字。" user_request = "提取信息:商品名-华为MateBook,价格-6899元,颜色-银色。" full_prompt = f"{system_instruction}\n\n用户请求:{user_request}\n\n请输出JSON:" raw_response = query_local_llama(full_prompt) print("原始响应:", raw_response) # 使用后处理函数提取JSON cleaned_data = extract_json_from_response(raw_response) print("提取后的数据:", cleaned_data)

7. 性能、成本与稳定性权衡

选择哪种方案,需要根据你的具体场景在性能、成本和稳定性之间做权衡。

  • 开发速度与原型验证:首选提示词工程+后处理。最快上手,适用于所有模型。
  • 生产环境与高可靠性:如果使用OpenAI等商用API,强烈推荐函数调用。如果是基于LangChain的开发,推荐Output Parsers
  • 成本敏感与本地控制:使用本地模型+强提示词,但需要投入更多精力在Prompt设计和错误处理上。
  • 极端稳定性要求:对于格式完全固定、容错率极低的场景(如生成API接口的响应),可以考虑在模型输出后,增加一个基于JSON Schema的校验与自动修复层,或者使用一个极小的、专门训练过的“格式校正”模型进行后处理。

关于Token成本:更复杂的Prompt和函数描述会消耗更多输入Token。但在多数情况下,为了获得稳定的结构化输出而增加的Token成本,远低于因格式错误导致业务逻辑失败或需要人工干预所带来的损失。

8. 常见问题与排查方法

在实际开发中,你可能会遇到以下问题:

问题现象可能原因排查方式解决方案
返回的文本包含JSON,但无法用json.loads()解析模型在JSON前后添加了说明文字;JSON内部字符串包含未转义的特殊字符(如换行符\n)。打印原始响应,检查其内容。使用第3.3节的后处理函数。强化Prompt中的“仅返回JSON”指令;在后处理中使用正则表达式或尝试解析前替换/转义非法字符。
字段缺失或值为null模型未能从输入中识别出对应信息;Schema中字段描述不清。检查输入信息是否明确。检查Schema中description是否清晰。优化输入信息的表述;在Schema的description中提供更详细的字段定义和示例。
模型输出了完全无关的内容Prompt指令被忽略;函数调用未触发。检查System Prompt和User Prompt的强度。检查函数/工具的description是否与用户查询相关。尝试更权威的指令词(如“你必须...”);调整函数描述,使其更匹配目标查询类型。
函数调用总是被触发或从不触发工具/函数定义的description过于宽泛或狭窄;tool_choice参数设置不当。检查tool_choice参数是“auto”“none”还是指定了函数。精确化函数描述;根据场景调整tool_choice。对于明确需要提取的场景,可设为{"type": "function", "function": {"name": "xxx"}}来强制调用。
本地模型输出格式随机性大Temperature参数过高;模型本身指令遵循能力较弱。将生成参数temperature设为0或接近0的值(如0.1)。降低temperature;尝试指令遵循能力更强的模型(如经过SFT或RLHF训练的模型);使用更详细的Few-shot示例。

9. 最佳实践与使用建议

  1. 从简到繁:首先尝试简单的提示词工程,快速验证想法。遇到稳定性问题再逐步升级到函数调用或输出解析器。
  2. Schema设计要严谨:在定义JSON Schema或Pydantic模型时,尽量使用明确的字段名和描述。利用enum限制取值范围,使用additionalProperties: false来禁止生成多余字段。
  3. 温度(Temperature)设置:在需要稳定格式的输出时,将模型的temperature参数设置为0或一个较低的值(如0.1),以减少随机性。
  4. 实现重试机制:在生产系统中,不要假设一次调用必然成功。封装模型调用函数,当后处理解析失败时,自动重试(可适当修改Prompt或降低Temperature)。
  5. 日志与监控:记录模型的原始响应和解析后的结果。这有助于你分析格式错误的模式,并持续优化你的Prompt或Schema。
  6. 合规与数据安全:当模型处理用户输入并输出结构化数据时,需确保不泄露敏感信息。对输出结果进行必要的过滤和脱敏,特别是在将数据用于后续业务流程或存储时。

10. 总结

让大模型稳定输出JSON,不是一个“是否可行”的问题,而是一个“如何选择合适工具”的工程问题。核心思路是通过外部约束来引导和规范模型的自由生成。

  • 对于快速验证和简单任务,强化你的Prompt并配上一个健壮的后处理函数,是性价比最高的选择。
  • 对于构建生产级的智能体或复杂应用,拥抱模型提供商官方的函数调用功能,或者采用像LangChain Output Parsers这样的框架,能为你省去大量调试格式错误的时间,让开发流程更顺畅。
  • 对于本地部署场景,在利用强提示词的同时,可以探索使用llama.cpp等推理库提供的“Grammar”约束功能(如果模型支持),它能强制模型输出符合特定语法(如JSON)的文本,实现近乎100%的格式正确率。

最终,稳定性的提升意味着你的AI应用更加可靠,能与下游代码无缝集成。建议从本文提供的最简单方法开始实践,根据遇到的具体问题,逐步升级你的技术方案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 7:08:47

UE5.7实战:从零构建可扩展战斗系统(连击/命中/伤害反馈)

这次我们来看一个UE5.7战斗系统开发的实战项目。核心不是讲一堆虚幻引擎的复杂概念,而是直接上手,在UE5.7里搭建一套可玩、可扩展的战斗框架,重点解决连击、命中判定和伤害反馈这三个核心体验问题。对于想做动作游戏、ARPG或者想深入理解UE5动…

作者头像 李华
网站建设 2026/8/25 7:05:31

内容安全审核系统选型实战:腾讯云IMS如何平衡效果与成本

1. 项目背景:为什么内容安全审核成了我们的“必答题”去年下半年,我们团队负责的一个社区产品用户量开始快速增长,日活从几万迅速攀升到几十万。用户一多,UGC内容(用户生成内容)的审核压力就呈指数级增长。…

作者头像 李华
网站建设 2026/8/25 7:00:12

Windows平台IndexTTS 2.5与vLLM加速:一键部署高性能本地语音合成方案

还在为本地部署语音合成模型而烦恼吗?想体验媲美云端效果的实时语音,却苦于复杂的Python环境、CUDA版本冲突和缓慢的推理速度?如果你是一名Windows用户,那么恭喜你,这篇文章就是为你准备的。今天,我们将深入…

作者头像 李华
网站建设 2026/8/25 6:58:36

暨南大学计算机考研机试备考指南与高频考点解析

1. 2025年暨南大学计算机考研复试机试备考全景指南作为国内计算机学科考研的重要环节,机试在复试中通常占据30%-50%的权重。暨南大学计算机考研复试机试采用OJ(Online Judge)系统,要求考生在限定时间内完成3-5道编程题&#xff0c…

作者头像 李华
网站建设 2026/8/25 6:52:25

大厂Java面试技术栈与AI融合趋势解析

1. 大厂Java面试技术栈全景解析最近三年互联网大厂Java技术栈面试出现明显分化:传统Spring Boot和微服务架构问题占比约60%,AI相关技术栈问题从2021年的不足5%飙升至30%。这种变化直接反映了行业技术趋势的迁移——AI能力正在成为Java工程师的新门槛。去…

作者头像 李华
网站建设 2026/8/25 6:51:52

Unity 2D飞行棋游戏开发实战:从零构建完整回合制游戏

这次我们来看一个完整的 Unity 2D 游戏开发实战项目——飞行棋。这不是一个简单的概念演示,而是一个从零开始,涵盖游戏逻辑、UI交互、动画效果、音效管理到最终打包发布的完整项目实战。对于想通过一个具体案例来掌握 Unity 2D 开发核心流程的开发者来说…

作者头像 李华