适合人群:在程序中调用大模型,需要把结果交给后续代码处理,却经常遇到“模型多说了一句话导致 JSON 解析失败”的开发新手。
一、背景
X 上有中文开发者分享:让大模型“吐 JSON”,模型要一个 token 一个 token 地生成,既慢又容易出格式问题。在实际开发中,这个问题非常普遍:模型返回的内容前面多了一句“好的,以下是结果”,或者用 Markdown 代码块包裹,或者漏了一个引号,程序就直接报错。
本文整理三种由浅入深的方法,让模型输出稳定可用的 JSON,并用 Python 做校验和兜底。
二、为什么模型会输出“坏 JSON”
大模型的本质是逐个预测下一个 token,它并不“知道”自己在写 JSON,只是根据训练经验模仿格式。因此:
- 提示词不够明确时,它会按聊天习惯加上开场白;
- 输出很长时,容易在中途出现格式错误;
- 字段名、类型可能和你期望的不一致。
解决思路有两类:在提示词层面引导,以及在解码层面约束。
三、操作步骤
方法一:提示词约束 + 代码兜底(适用于所有模型)
步骤 1:在提示词中给出明确格式
请从下面的商品评论中提取信息,只输出 JSON,不要输出任何其他文字。 格式: {"sentiment": "positive|negative|neutral", "topics": ["字符串"], "score": 1到5的整数} 评论:物流很快,但包装有点破损,客服处理还不错。要点:写清字段名、类型和取值范围,并明确“只输出 JSON”。
步骤 2:解析时做清理
importjson,redefparse_json(text):text=text.strip()m=re.search(r"```(?:json)?\s*(.*?)```",text,re.S)ifm:text=m.group(1)start,end=text.find("{"),text.rfind("}")returnjson.loads(text[start:end+1])这段代码会去掉 Markdown 代码块,并截取第一个{到最后一个}之间的内容。
方法二:使用 API 的 JSON 模式
许多模型服务提供了专门的结构化输出参数。以 OpenAI 兼容接口为例,可以设置response_format:
fromopenaiimportOpenAI client=OpenAI()# 密钥从环境变量读取resp=client.chat.completions.create(model="你使用的模型名称",messages=[{"role":"system","content":"你只输出 JSON。"},{"role":"user","content":"提取评论信息……"},],response_format={"type":"json_object"},)print(resp.choices[0].message.content)JSON 模式能保证输出是合法 JSON,但不保证字段符合你的预期。部分服务还支持通过 JSON Schema 指定精确结构(Structured Outputs),能进一步约束字段和类型。具体参数名称和支持情况,以所用服务的官方文档为准。
本地模型服务也有类似能力,例如 Ollama 的接口支持format参数,可以传入"json"或一个 JSON Schema。
方法三:用 Pydantic 定义结构并校验
无论使用哪种方法,都建议在代码里做一次结构校验:
frompydanticimportBaseModel,Field,ValidationErrorfromtypingimportLiteralclassReview(BaseModel):sentiment:Literal["positive","negative","neutral"]topics:list[str]score:int=Field(ge=1,le=5)try:data=Review.model_validate(parse_json(model_output))except(ValidationError,json.JSONDecodeError)ase:print("格式不符合要求:",e)还可以用Review.model_json_schema()生成 JSON Schema,直接放进提示词或传给支持 Schema 的接口,做到“一处定义、两处使用”。
步骤补充:失败重试
校验失败时,可以把错误信息反馈给模型,让它修正:
defask_with_retry(prompt,max_retry=2):msg=promptfor_inrange(max_retry+1):out=call_llm(msg)# 你自己的模型调用函数try:returnReview.model_validate(parse_json(out))exceptExceptionase:msg=f"{prompt}\n\n上一次的输出不符合格式,错误:{e}\n请只输出修正后的 JSON。"raiseRuntimeError("多次重试后仍然失败")设置重试上限,避免无限调用。
四、方法对比与选择
| 方法 | 优点 | 局限 |
|---|---|---|
| 提示词 + 清理 | 通用,任何模型可用 | 无法完全保证格式 |
| JSON 模式 / Schema | 格式可靠性高 | 依赖服务支持 |
| Pydantic 校验 + 重试 | 保证进入业务代码的数据正确 | 失败时需要额外调用 |
实际项目中建议组合使用:能用 Schema 约束就用,再加 Pydantic 校验和有限次重试。
另外,如果任务只是从几个固定选项中选一个(例如情感分类),其实不必生成 JSON,直接比较候选选项的概率会更快、更稳定。
五、常见问题 FAQ
Q1:设置了 JSON 模式还是报错?
有的服务要求在提示词中明确出现“JSON”字样,否则会报错或表现异常。另外检查输出是否因长度限制被截断。
Q2:中文被转成了 \u 编码?
这是json.dumps的默认行为,加上ensure_ascii=False即可正常显示中文。
Q3:字段偶尔缺失怎么办?
在 Pydantic 模型中区分必填和可选字段,可选字段设置默认值;必填字段缺失时触发重试。
Q4:输出很长的列表时容易出错?
把任务拆小,每次只处理少量数据,再在代码中合并结果。
Q5:小模型适合做结构化输出吗?
小模型遵循复杂格式的能力较弱,建议结构尽量扁平、字段尽量少,并配合解码约束和校验。
六、实用技巧
- 字段名用英文:减少模型自由发挥的空间,也方便代码处理;
- 枚举值写全:用
Literal或在提示词中列出所有允许的取值; - 温度设低:结构化任务通常不需要创造性,较低的温度更稳定;
- 记录失败样本:分析失败原因,持续改进提示词;
- 不要信任未经校验的数据:模型输出进入数据库前,必须经过校验。
七、总结
让大模型稳定输出 JSON 的关键是“三层保障”:提示词写清格式 → 接口层使用 JSON 模式或 Schema 约束 → 代码层用 Pydantic 校验并有限次重试。做好这三步,模型输出就能可靠地接入你的程序。
参考资料
- OpenAI 官方文档:Structured Outputs 章节
- Pydantic 官方文档:模型与校验章节
- Ollama 官方文档:结构化输出说明
- X 用户 @ataosky 关于大模型输出 JSON 与小模型限定选项的分享