结构化输出与约束解码:让大模型稳定吐出 JSON
在很多真实业务里,我们并不想让模型输出一段散文,而是希望它返回可以被程序直接消费的结构化数据:一段 JSON、一组字段、一个枚举值。本文讲清楚为什么需要结构化输出,以及从提示技巧到约束解码逐层递进的几套方案。
为何需要结构化输出
把大模型接入后端系统时,最朴素的做法是用提示词要求模型「按 JSON 返回」。但当请求量上来后,你会发现即便写了很详细的格式说明,模型偶尔还是会:
- 在 JSON 前后多说几句废话,比如先写「好的,这是结果」再输出对象;
- 字段名拼写不一致,有时叫
user_name,有时叫username; - 漏掉你要求的必填字段;
- 把枚举值写成不在你允许范围内的内容。
这些不稳定会让下游解析逻辑频繁报错,于是人们开始寻找比「靠提示词祈祷」更可靠的机制。本质上,让大模型稳定吐出结构化数据有三条路线:
- 平台提供的格式化能力,例如 JSON Mode 与 Structured Outputs;
- 借由函数调用(Function Calling / Tool Use)把输出变成一次受 schema 约束的工具调用;
- 在解码阶段做语法约束,从 token 层面禁止模型生成非法内容。
JSON Mode:OpenAI 与各家实现
JSON Mode 是最早被广泛支持的「格式保证」能力:它保证模型返回的是合法可解析的 JSON,但不保证 JSON 的内容符合某个特定 schema。
在 OpenAI 的接口中,开启 JSON Mode 需要设置 response_format,并把 type 设为 json_object:
{
"model": "gpt-4o-mini",
"messages": [
{ "role": "system", "content": "你是一个只输出 JSON 的助手。" },
{ "role": "user", "content": "提取这段文本里的人物姓名与年龄。" }
],
"response_format": { "type": "json_object" }
}
这里有一个硬性要求:使用 JSON Mode 时,你必须在对话里的某条消息(通常是系统消息)中出现字符串「JSON」,否则 API 会直接报错。这是因为模型只有在被明确提示输出 JSON 时才会收敛到 JSON 格式。
需要特别注意:JSON Mode 只保证「能解析」,不保证「符合你的字段定义」。如果业务要求严格字段,官方也建议优先改用 Structured Outputs(即 response_format 设为 json_schema 且 strict: true),它会在生成阶段约束模型遵循你提供的 JSON Schema,保证不漏必填字段、不产生非法枚举值。
在开源与本地模型生态中,许多推理框架(如 vLLM、Ollama、llama.cpp)也提供了类似的 JSON Mode 或 grammar 约束选项,命名可能不同,但思路一致:在采样时限制输出为合法 JSON。
Function Calling 作为结构化输出
当你的目标本来就是「让模型填好一张固定结构的表」,函数调用往往比 JSON Mode 更自然。原理是:你向模型声明若干个函数的签名(含输入参数的 JSON Schema),模型不直接写散文,而是返回一个符合签名的 arguments 对象。
以 Anthropic Claude 为例,结构化输出通常通过 Tool Use 实现。你定义一个工具并给出 input_schema,再用 tool_choice 强制模型必须调用该工具:
{
"model": "claude-opus-5",
"max_tokens": 1024,
"tools": [
{
"name": "extract_person",
"description": "从文本中抽取人物结构化信息",
"input_schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"required": ["name", "age"]
}
}
],
"tool_choice": { "type": "tool" },
"messages": [
{ "role": "user", "content": "张三今年 28 岁。" }
]
}
当 tool_choice 设为 { "type": "tool" } 时,模型被强制发起一次工具调用,返回的 tool_use 块里的 input 就是结构化结果。若希望进一步保证严格符合 schema,Anthropic 还提供 strict tool use,在工具定义里加 strict: true 即可要求调用参数精确匹配 schema。
OpenAI 一侧同样可用函数调用承载结构化输出:把 tools 中的函数签名写成目标结构,tool_choice 设为 { "type": "function", "function": { "name": "..." } } 即可强制对应输出。函数调用之所以稳,是因为模型在训练阶段就被大量对齐到「按 schema 填参数」,比纯提示词更可靠。
温度设为 0 的技巧
无论用 JSON Mode 还是函数调用,把采样温度设为 0 都能显著提升结构化输出的稳定性。
温度控制输出的随机性。温度越高,模型越倾向于探索多样性,越可能在字段顺序、措辞、是否额外加注释等方面产生波动;温度设为 0 会让模型在每一步选择概率最高的 token,输出更确定、更可复现。对于「抽取」「分类」「格式化」这类有标准答案的任务,温度 0 几乎总是更优选择。
需要注意两点:
- 温度 0 只降低随机性,并不能单独保证 schema 合规。它应作为「结构化能力 + 低温度」组合拳的一部分。
- 部分平台对温度为 0 的调用会做去重或缓存优化,既更稳也更省成本。
约束解码与语法约束
前面几类方案大多依赖模型「愿意配合」,而约束解码(constrained decoding)走的是另一条更彻底的路:在每一步解码时,根据文法或 schema 计算「当前允许出现的下一个 token 集合」,把不在集合内的 token 概率直接置零。这样模型在物理上无法生成非法内容,输出天然合法。
以下是几个经过核实的开源约束解码库:
Outlines
Outlines(仓库 dottxt-ai/outlines)是一个专注于结构化生成的库,支持多选、正则表达式、JSON / Pydantic 模式以及上下文无关文法等多种约束方式,并且宣称可对接 OpenAI、Ollama、vLLM 等不同后端。它的口号是「在生成过程中保证结构化输出」。
Guidance
Guidance(仓库 guidance-ai/guidance)提供一套引导语言,用 system()、user()、assistant()、gen() 等 Pythonic 写法编排提示与生成,并通过 gen(regex=...)、select([...]) 等方式做约束生成,还支持完整上下文无关文法(CFG)与离线语法调试。
LM Format Enforcer
LM Format Enforcer(仓库 noamgat/lm-format-enforcer)通过字符级解析器结合分词器前缀树,在每一步只允许符合格式的 token 通过,从而强制输出 JSON Schema、正则等格式。它可与 transformers、LangChain、llama.cpp、vLLM 等集成。
约束解码的优势是「不靠运气」,且在模型能力较弱(如小尺寸本地模型)时尤其有价值;代价是需要在推理侧接入对应库,且对生成速度有少量开销。
常见失败与排错
即便用了上述方案,仍可能遇到以下问题,这里给出对应的排查方向。
输出被截断
表现:JSON 不完整,解析时报 Expecting value 或括号未闭合。
原因通常是命中了 max_tokens / max_output_tokens 上限,或模型在长字段上「写嗨了」。排查与处理:
- 调大输出 token 上限;
- 精简 schema,去掉非必要字段;
- 若使用平台结构化能力,优先改用 Structured Outputs / strict tool use,它们会尽力在限额内给出完整对象,但仍需为复杂结构预留足够 token。
格式不符,前后多了自然语言
表现:模型在 JSON 前加了「这是结果:」,或在结尾补了一段解释。
处理:
- JSON Mode 下确认系统消息包含「JSON」字样,并明确写「只输出 JSON,不要任何额外文字」;
- 优先使用 Structured Outputs 或函数调用,从根本上让模型以结构化块返回,而不是散文;
- 解析前可做一次「截取首个
{到最后一个}」的容错清洗(仅作兜底,不能作为主方案)。
少字段或字段名漂移
表现:期望 5 个字段,实际只来 3 个;或 user_name 与 username 混用。
处理:
- 把 schema 中所有字段标为必填(
required),并在对象上设置additionalProperties: false; - 用 Structured Outputs / strict tool use 替代普通 JSON Mode;
- 解析后做一层 schema 校验(如用 Pydantic / jsonschema),对缺失字段给出明确报错而非静默通过。
示例代码
下面给出两段可落地的示例。第一段演示如何用 OpenAI 的 Structured Outputs 让模型稳定返回规定字段;第二段演示如何用 Outlines 在本地模型上做约束生成。
示例一:OpenAI Structured Outputs
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class Person(BaseModel):
name: str
age: int
occupation: str
# 借助 SDK 的 parse 能力,直接拿到校验后的对象
response = client.responses.parse(
model="gpt-4o-mini",
input=[
{"role": "system", "content": "抽取文本中的人物信息。"},
{"role": "user", "content": "李雷今年 30 岁,是一名软件工程师。"},
],
text_format=Person,
)
person = response.output_parsed
print(person.name, person.age, person.occupation)
这段代码的关键在于 text_format=Person:SDK 会把 Pydantic 模型转成 JSON Schema,并以 strict: true 的形式提交给 Structured Outputs,保证返回对象不缺字段、类型正确。
示例二:Outlines 约束生成
import outlines
from transformers import AutoTokenizer, AutoModelForCausalLM
from typing import Literal
model_name = "microsoft/Phi-3-mini-4k-instruct"
model = outlines.from_transformers(
AutoModelForCausalLM.from_pretrained(model_name, device_map="auto"),
AutoTokenizer.from_pretrained(model_name),
)
# 把输出约束为固定枚举,模型在解码时无法写出范围外的内容
sentiment = model(
"评价:这家餐厅的菜品让人失望。",
Literal["正面", "负面", "中性"],
)
print(sentiment)
这里 Literal["正面", "负面", "中性"] 会在每一步把下一个 token 限制在这三个候选内,因此输出一定是其中之一,无需再做后处理判断。
小结
让大模型稳定吐出结构化数据,不是一个「写好提示词」就能解决的问题,而是需要按可靠度递进地选型:
- 最基础是 JSON Mode,只保证合法 JSON,不保证字段;
- 更稳的是 Structured Outputs 与函数调用(Tool Use),它们以 schema 约束模型,显著减少漏字段与格式漂移;
- 把温度设为 0 能进一步降低随机性,作为所有结构化任务的默认设置;
- 最彻底的是约束解码(Outlines、Guidance、LM Format Enforcer),在 token 层面禁止非法输出,适合本地模型与高可靠场景。
实际落地时,建议优先采用平台自带的结构化能力,在需要极致稳定或自托管模型时再引入约束解码库,并在解析后保留一层 schema 校验作为兜底。
参考与延伸阅读
-
OpenAI 官方文档:Structured Outputs — 说明
response_format设为json_schema且strict: true时的保证与限制。 https://platform.openai.com/docs/guides/structured-outputs -
OpenAI 官方文档:JSON Mode — 说明
response_format设为json_object的启用方式与「上下文须含 JSON」的硬性要求。 https://platform.openai.com/docs/guides/text-generation/json-mode -
Anthropic 官方文档:Tool Use — 说明用
tools与tool_choice强制结构化调用,以及 strict tool use 的 schema 合规保证。 https://docs.anthropic.com/en/docs/build-with-claude/tool-use -
Outlines 开源仓库(dottxt-ai/outlines)— 支持多选、正则、JSON / Pydantic 与文法约束的结构化生成库。 https://github.com/dottxt-ai/outlines
-
Guidance 开源仓库(guidance-ai/guidance)— 提供
gen(regex=...)、select([...])等约束生成能力的引导语言库。 https://github.com/guidance-ai/guidance -
LM Format Enforcer 开源仓库(noamgat/lm-format-enforcer)— 通过字符级解析器与分词器前缀树做约束解码的库。 https://github.com/noamgat/lm-format-enforcer