其实让大模型输出一个 JSON 很简单,Prompt 里写一句"请以 JSON 格式输出",大多数模型就能给你吐出来。
但你真正在项目里用过就知道,能输出和稳定输出,完全不是一个难度级别的事。
我刚开始做 LLM 相关开发的时候,第一反应就是疯狂改 Prompt,各种花式叮嘱:
严格按照 JSON 输出。 不要输出任何解释。 不要使用 Markdown。这些写法有没有用?有用。但本质上你只是在引导模型,并不是在约束模型。模型仍然随时可能给你整出花活:
前面加一句"好的,下面是结果:"
用
```json ```包一层 Markdown 代码块少字段、多字段
字段类型不对,该给数字给了字符串
JSON 格式本身就有语法错误,直接解析失败
测试环境跑 50 条数据全部完美,上线后结果生产环境每隔几个小时就蹦一个JSONParseException。
Prompt 可以提高成功率,但永远不能保证稳定性!!!
要控制模型稳定输出,必须多管齐下~
Prompt 把输出结构描述清楚
太多人写 Prompt 的时候太随意了,就丢一句:
返回姓名、年龄、地址。这种描述模糊得一批,模型看到以后全靠自由发挥。它可能返回{"姓名": "张三"},也可能返回{"name": "张三"},你根本无法预期。
更好的做法是直接在 Prompt 里给出目标 JSON 结构,例如:
{ "name": "", "age": 0, "address": "" }或者用更清晰的字段描述:
name: string,用户姓名 age: integer,用户年龄 address: string,用户地址模型有了明确的参考模板,输出通常会稳定不少。我一般还会补一句狠话:
只输出纯 JSON 文本,不要输出任何解释文字、Markdown 标记或代码块。但是说实话,Prompt 写得再严格,该翻车还是会翻车。这就好比你跟同事交代工作,哪怕说了三遍"别加注释",他还是可能顺手写两行注释上去。毕竟大模型的底层是概率生成,不是指令执行。
优先使用模型原生的结构化输出能力
如果项目要投入生产,千万不要只依赖 Prompt。
现在主流的大模型基本都提供了结构化输出的能力,常见的有三种:
Structured Output(结构化输出)
JSON Schema
Function Calling(也叫 Tool Calling)
这三种方案的共同点是:不是在语义层面上"劝"模型遵守格式,而是在生成阶段就按照你指定的 Schema 进行硬约束。
举个例子,你定义了这样一个 Schema:
{ "score": { "type": "integer" } }在 Structured Output 模式下,模型一般不会返回:
{ "score": "90" }因为字段类型在底层就被锁死了,它想输出字符串都输出不了。
Structured Output 和 Function Calling 怎么选?
其实很简单,看你的场景:
如果你只是想让模型返回固定的数据结构,比如从一段文本里提取几个字段,Structured Output 通常是首选。你给它一个 JSON Schema,它严格按照这个 Schema 生成,干净利落。
如果后续还要调用数据库、搜索接口、天气 API 等外部能力,那 Function Calling 更合适。因为它不仅规范了参数格式,还能直接驱动工具调用。模型在 Function Calling 模式下会意识到自己是在"填写参数表",而不是在跟人聊天,输出的规范性会好很多。
以 OpenAI 的接口为例,用 Function Calling 大概长这样:
tools = [{ "type": "function", "function": { "name": "extract_user_info", "description": "从文本中提取用户信息", "parameters": { "type": "object", "properties": { "name": {"type": "string", "description": "用户姓名"}, "age": {"type": "integer", "description": "用户年龄"}, "address": {"type": "string", "description": "用户地址"} }, "required": ["name", "age", "address"] } } }] response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": "张三,28岁,住在北京市朝阳区"}], tools=tools, tool_choice={"type": "function", "function": {"name": "extract_user_info"}} )后端拿到的直接就是一个标准的函数调用对象,不是一段带有 Markdown 包裹的文本,用 Java 或者 Go 直接反序列化就完事了。
能用模型原生能力约束的,就别靠 Prompt 去求模型。这条原则在 AI 工程化落地中怎么强调都不为过。
不要让模型一次干太多活
这是一个特别容易被忽略的坑。
很多人写 Prompt 的时候,恨不得把所有事情一股脑塞进去:
阅读下面这篇文章 → 总结核心观点 → 提取关键词 → 判断情绪倾向 → 翻译成英文 → 最后输出 JSON。任务链越长,模型越容易在中间某个环节跑偏,到最后输出的 JSON 就开始变形。要么丢字段,要么格式错乱,甚至直接变成一段自然语言的"总结报告"。
模型不是流水线工人,它是一个概率生成器。你给它的任务越复杂,它需要同时兼顾的约束就越多,出错概率就越高。
实际开发中,我更倾向于拆成多个步骤:
第一步:内容理解 → 输入原始文章,让模型做摘要和关键词提取,输出自然语言即可 第二步:结构化输出 → 把第一步的结果喂给模型,让它严格按照 JSON Schema 输出结构化数据虽然多调用了一次模型,多花了几分钱 Token 费,但整体稳定性通常会显著提高,而且出了问题也更容易定位,是第一步理解错了,还是第二步格式化出了问题,一目了然。
这跟写代码是一个道理:一个函数只干一件事,永远比一个函数干五件事更可靠。
程序一定要做校验和兜底
永远不要假设模型一定会返回合法 JSON!!!
哪怕你已经用了 Structured Output,哪怕你已经用了 Function Calling,程序也必须做好兜底。原因很简单:线上环境什么妖蛾子都可能出——模型版本升级、接口超时返回了半截响应、网络抖动导致 JSON 被截断……
一个成熟的 AI 应用,对模型的输出至少要做以下校验:
import json def validate_model_output(raw_output: str, required_fields: list) -> dict: # 1. JSON 是否能解析 try: data = json.loads(raw_output) except json.JSONDecodeError: # 尝试修复常见问题:去掉 Markdown 代码块包裹 cleaned = raw_output.strip() if cleaned.startswith("```"): cleaned = cleaned.split("\n", 1)[-1].rsplit("```", 1)[0] try: data = json.loads(cleaned) except json.JSONDecodeError: raise ValueError("模型输出无法解析为 JSON") # 2. 必填字段是否缺失 for field in required_fields: if field notin data: raise ValueError(f"缺少必填字段: {field}") # 3. 字段类型是否正确(根据业务定义校验) # 4. 枚举值是否合法 # 5. 是否需要填充默认值 return data除了校验,还有一个非常重要的机制:自动重试。
模型输出有一定的随机性,这次格式不对,重新调一次可能就对了。给关键链路加一个 2~3 次的重试机制,配合指数退避,能覆盖掉绝大多数偶发性的格式异常。
import time def call_with_retry(func, max_retries=3): for attempt in range(max_retries): try: result = func() return validate_model_output(result, ["name", "age"]) except (ValueError, KeyError) as e: if attempt == max_retries - 1: raise time.sleep(2 ** attempt) # 指数退避:1s, 2s, 4s想做一个能跑在线上的 AI 应用,不能假想模型特别可靠,必须保证应用能处理各种异常情况。这跟你调第三方接口是一个道理。你不会裸调支付宝的接口不加任何异常处理吧?对待模型的输出,也该有同样的防御心态。
说在最后
总结一下,目前比较成熟的一套做法其实就是以下四种:
Prompt 明确描述输出结构,减少模型的理解偏差。
优先使用 Structured Output 或 Function Calling,在生成层面做硬约束,而不是只靠 Prompt 软约束。
拆分复杂任务,让模型一次只专注一件事,降低出错概率。
程序侧做校验和兜底,对解析失败、字段异常等情况做好重试和容错。
一般刚接触大模型开发的人,会把大量时间花在反复修改 Prompt 上,希望把成功率从 90% 提高到 99%。但真做过线上应用就会发现,Prompt 只是第一步。