Skip to content

结构化输出

本页介绍如何让大语言模型(LLM)输出严格符合预设格式的结果——结构化输出(Structured Output):让 LLM 不再”随心所欲地说话”,而是输出 JSON、表格或自定义 Schema,从而能被程序可靠解析。它是 LLM 从”聊天工具”变成”系统组件”的关键技术,与提示工程和MCP 协议与工具调用紧密配合。

结构化输出 = 给 LLM 装一个”填表模板”。

普通 LLM 对话像自由写作文——你想让它输出一张表格,它可能给你写一段散文。结构化输出像给 LLM 发一张表格:每个格子填什么、什么类型、有几行,都规定好了,它照着填就行。

  • JSON Mode = 最简单的模板:告诉 LLM “你只能输出合法 JSON”,它就不会在前后加废话(“好的,这是你的结果:…”)。
  • Function Calling = 带参数定义的模板:定义一个函数(如 get_weather),参数是 city: str, date: str,LLM 会自动从自然语言中提取参数填进去。
  • JSON Schema = 最精确的模板:用 Schema(一种声明式的数据结构描述语言)定义完整的字段结构、类型约束、必填项,LLM 必须严格匹配,多一个字段少一个字段都不行。
  • 约束解码 = 底层强制手段:在解码阶段(即 LLM 逐 token 生成文字的过程中)用语法规则(如正则表达式、CFG(上下文无关文法,一种描述语言语法结构的规则系统))强制 LLM 只能生成符合格式的 token,从根本上保证格式正确。

传统软件系统的组件之间通过 API 通信,API 的请求和响应都有严格的格式约定(如 RESTful JSON、Protocol Buffers)。LLM 如果只能输出自然语言,就无法作为可靠的系统组件——下游程序无法稳定解析”也许加了前缀、也许漏了字段、也许格式错误”的自由文本。结构化输出就是让 LLM 的输出具备 API 级别的格式可靠性,从而能够:

  1. 嵌入自动化管线:LLM 的输出直接被代码消费,无需人工检查或正则提取。
  2. 保证系统稳定性:100% 格式正确意味着不需要重试逻辑或兜底处理。
  3. 支撑复杂 Agent:多步推理、工具调用、多 Agent 协作都需要结构化消息传递。

结构化输出的技术路线从简单到复杂分为四个层次,每一层在前一层的基础上提升可靠性。

最简单的办法是在 Prompt 里明确要求输出格式:

请将以下信息提取为 JSON 格式,包含 name、age、city 三个字段:
张三,25 岁,住在北京。

LLM 大概率会输出 {"name": "张三", "age": 25, "city": "北京"}。但这不是 100% 可靠的——LLM 可能多加一句”这是提取结果:“,或者漏掉引号,或者加了你不想要的字段。Prompt 提示的格式可靠性大约在 80-95%,取决于模型能力和 Prompt 质量。

Prompt 提示失效的常见原因:

  • 模型遵循指令的能力有限:小模型(7B 以下)对格式指令的遵从度明显低于大模型。
  • 上下文干扰:长对话历史中的其他格式会”带偏”模型。
  • 复杂 Schema:字段越多、嵌套越深,Prompt 约束越弱。
  • 对抗性输入:用户输入中包含 “忽略上面的指令” 等注入文本时,格式约束可能被绕过。

提升 Prompt 可靠性的技巧:

你必须严格遵守以下规则:
1. 只输出 JSON,不要输出任何其他文字
2. 第一个字符必须是 {,最后一个字符必须是 }
3. 不要用 markdown 代码块包裹
4. 字段名使用双引号
输出格式示例:
{"name": "string", "age": "integer", "city": "string"}

即使如此,Prompt 提示也无法达到 100% 可靠——因为它只影响模型的”倾向”,不构成硬性约束。

OpenAI、Anthropic 等厂商在 API 层面提供了 response_format 参数,设为 json_object 后,模型被约束为只能输出合法 JSON:

response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "你是数据提取助手,只输出 JSON。"},
{"role": "user", "content": "张三今年 25 岁,住在北京。"},
],
response_format={"type": "json_object"},
)
import json
result = json.loads(response.choices[0].message.content)
print(result) # {"name": "张三", "age": 25, "city": "北京"}

JSON Mode 的可靠性远高于纯 Prompt 提示(接近 100%),但只能保证”是合法 JSON”,不保证”JSON 的结构是你想要的”——模型可能输出 {"result": "张三..."} 而不是你期望的 {"name": "张三", "age": 25}。

JSON Mode 的底层原理:API 后端在模型生成时启用了轻量级约束——每当模型即将生成破坏 JSON 合法性的 token(如在 {"name": 之后生成非引号字符),该 token 的概率会被强制清零。这种约束只需要理解 JSON 语法规则(花括号匹配、引号闭合等),不需要知道你的具体 Schema,因此开销很小。

⚠️ 使用 JSON Mode 的前提:OpenAI 要求在 Prompt 中必须包含 “JSON” 这个词,否则 API 会报错。这是为了确保模型”知道”要输出 JSON。

第三层:Structured Outputs(JSON Schema)

Section titled “第三层:Structured Outputs(JSON Schema)”

OpenAI 在 2024 年 8 月推出了 Structured Outputs 功能(2024 年 12 月正式 GA),允许通过 JSON Schema 精确定义输出结构:

response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[...],
response_format={
"type": "json_schema",
"json_schema": {
"name": "person",
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"city": {"type": "string"}
},
"required": ["name", "age", "city"],
"additionalProperties": False
},
"strict": True # 严格模式:禁止任何 Schema 外字段
}
}
)

这保证了输出严格匹配 Schema——字段名、类型、必填项都精确约束。底层实现使用了**约束解码(Constrained Decoding)**技术。

strict: true 的含义:开启后,API 会将你的 Schema 预处理后编译成一棵决策树(类似于语法自动机),模型在生成时被严格限制在这棵树允许的路径上。这意味着:

  • additionalProperties: false 是必填项——Schema 不能有”开放字段”。
  • 所有字段必须在 required 中列出——不允许可选字段(可通过 union 类型模拟可选)。
  • 嵌套深度限制:最多 5 层对象嵌套,最多 100 个属性。

Structured Outputs 的可靠性:在 OpenAI 的基准测试中,GPT-4o-mini 配合 Structured Outputs 的格式正确率达到 100%(对比纯 Prompt 的约 75%)。

第四层:约束解码(Constrained Decoding)

Section titled “第四层:约束解码(Constrained Decoding)”

约束解码是结构化输出的底层核心技术。它的原理是:在 LLM 生成每个 token 时,用语法规则过滤掉不符合格式的 token,只从合法的候选中采样。

这是目前最强大的格式保证手段——它不是”请求”模型遵守格式,而是在数学层面不可能生成非法输出。

具体做法:

  1. 编译格式规则:将目标格式(JSON Schema、正则表达式、CFG)转化为一个有限状态机(FSM,一种在有限状态间转移的数学模型)或下推自动机。
  2. 跟踪当前状态:LLM 每生成一个 token 前,检查当前已生成内容处于自动机的哪个状态。
  3. 计算合法 token 集合:从当前状态出发,计算所有可能的下一步转移,得到合法的 token 集合。
  4. 概率掩码:将不合法 token 的概率设为负无穷(在 logit(模型输出的每个 token 的原始分数,softmax 之前的值)层面掩码),确保 softmax 后采样只选合法 token。

例如,如果当前已生成了 {"name": "张三",,下一个合法的 token 只可能是 "age"、"city" 或其他定义的键名——约束解码会屏蔽掉所有其他 token 的概率。这样从根源上保证了格式正确。

朴素的约束解码有一个严重的性能问题:每次生成一个 token 都需要遍历整个词汇表(通常 3 万-20 万),检查每个 token 是否合法。 对于复杂 Schema,这个检查可能涉及深层的状态机遍历,导致推理速度下降 2-10 倍。

主流优化方案:

  • 索引加速:Outlines 库将正则表达式编译成索引化的 FSM(索引状态机),使得状态转移查询从 O(词汇表大小) 降到接近 O(1)。这是 Willard & Louf (2023) 的核心贡献。
  • GPU 友好:XGrammar(2024 年由 MLC AI 团队开发)将语法规则编译为 GPU 上的位掩码操作,利用张量并行加速约束检查,将额外开销控制在 5-20%。
  • 批处理约束:lm-format-enforcer 支持对批量请求中的每个序列应用不同的约束规则,适合服务端部署。
  • 前缀缓存:对于 Schema 固定的场景,预编译约束规则并缓存,避免每次请求重复编译。

约束解码虽然保证了格式正确,但有一个容易被忽略的副作用:强制格式会改变输出的概率分布。当大量 token 被屏蔽为零概率时,模型实际的采样空间大幅缩小,可能导致内容质量下降(语义上不够好的 token 恰好是合法的,就被选了)。在极端情况下,约束过严可能导致模型陷入”生成不下去”的死循环。

实践经验:Schema 越简单,约束解码对质量的影响越小。复杂 Schema 建议在 Prompt 中给足示例,引导模型在被约束的空间内做出更好的选择。

Function Calling 与结构化输出的关系

Section titled “Function Calling 与结构化输出的关系”

Function Calling(工具调用)本质上是结构化输出的一种应用——LLM 需要输出”调用哪个函数 + 参数是什么”,参数格式由函数签名定义(等价于 JSON Schema)。详见MCP 协议与工具调用。

从技术演进来看:OpenAI 最初在 2023 年 6 月推出 Function Calling,当时它就是结构化输出的唯一可靠方式——通过函数参数的 JSON Schema 约束输出。2024 年 8 月,OpenAI 将这套机制泛化为通用的 Structured Outputs API,使得不需要”假装调用函数”也能获得 Schema 约束能力。

方案可靠性延迟开销适用场景模型要求
Prompt 提示~80-95%无快速原型、格式宽松任意模型
JSON Mode~99%极低只需合法 JSONAPI 支持
Structured Outputs~100%低(API 内优化)生产环境结构化数据API 支持
约束解码(本地)100%中-高(5-50%)开源模型、离线部署可访问 logits

用 OpenAI Structured Outputs 提取结构化数据

Section titled “用 OpenAI Structured Outputs 提取结构化数据”
from openai import OpenAI
from pydantic import BaseModel, Field
client = OpenAI()
# 用 Pydantic 定义输出结构(最推荐的写法,类型安全)
class PersonInfo(BaseModel):
name: str
age: int
city: str
hobbies: list[str]
# Field 提供自然语言描述,帮助 LLM 理解字段含义
occupation: str = Field(description="职业,如'工程师'、'教师'")
# response_format 直接传 Pydantic 模型,SDK 自动转 JSON Schema
response = client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "张三今年 25 岁,住在北京,喜欢跑步和读书,是一名软件工程师。"}],
response_format=PersonInfo,
)
person = response.choices[0].message.parsed # 直接得到 Python 对象
print(person.name) # 张三
print(person.hobbies) # ['跑步', '读书']
print(person.occupation) # 软件工程师

用 Instructor 实现多厂商统一结构化输出

Section titled “用 Instructor 实现多厂商统一结构化输出”

Instructor 是一个轻量封装库,支持 OpenAI、Anthropic、Cohere、Ollama 等多种后端,统一用 Pydantic 做结构化输出:

import instructor
from anthropic import Anthropic
from pydantic import BaseModel
from typing import Optional
# 用 Anthropic 后端(Instructor 自动处理 tool-use 协议)
client = instructor.from_anthropic(Anthropic())
class UserInfo(BaseModel):
name: str
age: int
email: Optional[str] # 可选字段用 Optional
# 调用方式和 OpenAI 几乎一样——这就是 Instructor 的价值
user = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[{"role": "user", "content": "李四,30 岁,邮箱 lisi@example.com"}],
response_model=UserInfo,
)
print(user.name) # 李四
print(user.email) # lisi@example.com

用 Outlines 对本地模型做约束解码

Section titled “用 Outlines 对本地模型做约束解码”
# 前提:pip install outlines transformers
import outlines
# 加载本地模型
model = outlines.models.transformers("Qwen/Qwen2.5-1.5B-Instruct")
# 用 Pydantic 定义 Schema,outlines 自动做约束解码
from pydantic import BaseModel
from typing import List
class Event(BaseModel):
date: str
event: str
@outlines.generate.json(model, Event)
def extract_event(text):
return f"从以下文本中提取日期和事件,输出 JSON:\n{text}"
result = extract_event("2024 年 7 月,OpenAI 发布了 GPT-4o-mini。")
print(result) # Event(date='2024 年 7 月', event='OpenAI 发布了 GPT-4o-mini')

GBNF(GGML BNF)是 llama.cpp 定义的语法描述格式,支持 CFG 级别的约束:

# llama-cpp-python 支持 GBNF 语法约束
from llama_cpp import Llama
llm = Llama(model_path="./qwen2.5-7b-instruct-q4_k_m.gguf")
# 定义一个只输出 yes/no 的语法
grammar = '''
root ::= ("yes" | "no") " " answer
answer ::= [^\n]+
'''
response = llm(
"这个产品评价是正面的吗?评价:'质量很好,推荐购买!'",
grammar=grammar,
max_tokens=20,
)
print(response["choices"][0]["text"]) # "yes 质量..."

某些场景只需要特定文本格式(电话号码、日期、代码等),用正则约束更轻量:

import outlines
model = outlines.models.transformers("Qwen/Qwen2.5-1.5B-Instruct")
# 约束模型只输出符合正则表达式的文本
@outlines.generate.regex(model, r"\d{4}-\d{2}-\d{2}")
def extract_date(text):
return f"从文本中提取日期:\n{text}"
date = extract_date("会议定在 2025 年 3 月 15 日举行。")
print(date) # "2025-03-15"(严格匹配 YYYY-MM-DD 格式)
  • 优先用 API 原生 Structured Outputs:OpenAI、Anthropic、Google 的 API 都支持 JSON Schema 约束输出,可靠性最高、实现最简单。能用原生方案就不要手写 Prompt 或自己搞约束解码。2025 年,主流 API 厂商(包括国内的 DeepSeek、通义千问、智谱等)均已原生支持结构化输出。
  • 用 Pydantic 定义 Schema:Python 项目中用 Pydantic BaseModel 定义输出结构,类型安全、IDE 补全友好、与 OpenAI SDK 无缝集成。比手写 JSON Schema 字典可维护性高得多。
  • 字段命名要语义清晰:LLM 是根据字段名”猜”该填什么的。name 比 n 好,shipping_address 比 addr1 好。字段名越清晰,LLM 填得越准。
  • 善用 Field 描述:Pydantic 的 Field(description=...) 会翻译到 JSON Schema 的 "description" 字段中,传递给 LLM 作为上下文。好的描述能显著提升填充准确率。
  • 在 Prompt 中给出示例:即使有 Schema 约束,在 Prompt 中给 1-2 个”输入 → 期望输出”的示例,能显著提高内容质量(格式由 Schema 保证,内容质量由 Prompt 引导)。
  • 本地模型用 Outlines / Guidance / XGrammar:对于开源模型(Llama、Qwen 等),用 outlines 或 guidance 库做约束解码,能在推理层面保证格式正确,比 Prompt 约束可靠得多。如果追求性能,XGrammar 的 GPU 加速方案比 Outlines 快数倍。
  • 避免过度嵌套:JSON Schema 嵌套超过 3 层时,LLM 填充准确率会下降。尽量保持结构扁平,复杂对象拆成多次调用。
  • 结构化输出会增加延迟:约束解码需要额外的格式计算,尤其对于开源模型。对延迟敏感的场景要实测影响。API 原生方案(如 OpenAI Structured Outputs)的额外延迟通常很小(低于 5%)。
  • 注意 strict: true 的限制:OpenAI 严格模式不支持 format: "date-time" 等 format 关键字(截至 2025 年初),不支持 $ref(引用外部定义)。复杂 Schema 需要先做预处理。
  • 处理空值和枚举:用 enum 约束字段值范围(如 "role" 字段限定为 "user" | "assistant" | "system"),用 union 类型处理可空字段(如 Optional[str] 对应允许 string 或 null)。
  • 信息抽取与数据录入:从简历、合同、医疗病历等非结构化文本中自动提取姓名、日期、金额、条款等字段,输出结构化 JSON 直接写入数据库——替代大量人工录入工作。
  • API 参数提取(Function Calling):用户说”帮我订明天北京到上海的高铁票”,LLM 自动提取 from: "北京"、to: "上海"、date: "明天" 等参数调用预订 API。详见MCP 协议与工具调用。
  • 搜索引擎结果摘要:Perplexity 等 AI 搜索引擎用结构化输出从网页中提取”实体-属性-值”三元组,构建知识图谱,支撑精确问答。
  • 自动化测试数据生成:用 LLM 按指定 Schema 批量生成测试数据(用户信息、订单、交易记录),直接导入测试数据库——比手写 mock 数据高效得多。
  • 多步推理管线:复杂任务拆成多个 LLM 调用,每步输出结构化 JSON 传给下一步(或传给代码逻辑处理)。结构化输出是多步 Agent 管线的”接口契约”。详见AI Agent 与多智能体。
  • 代码生成与 AST 提取:让 LLM 生成符合特定 DSL(领域特定语言)语法的代码或配置,约束解码确保语法正确性。例如自动生成 SQL 查询、Terraform 配置、JSONata 表达式等。
  • 评估与打分管线:在 LLM-as-a-Judge 场景中,约束模型输出结构化评分结果({"score": 4, "reasoning": "..."}),便于批量统计和可视化。
类库 / 工具语言说明
OpenAI Structured OutputsRESTAPI 原生 JSON Schema 约束输出,可靠性最高
Anthropic Tool UseRESTClaude 的工具调用接口,等价于结构化输出
PydanticPythonPython 数据验证库,用于定义输出 Schema,与 OpenAI SDK 深度集成
OutlinesPython开源约束解码库,支持本地模型的 JSON/正则/CFG 约束生成
GuidancePython微软出品的结构化生成库,模板语法控制 LLM 输出格式
InstructorPython封装多厂商 API,统一用 Pydantic 做结构化输出的轻量库
lm-format-enforcerPython高性能约束解码库,支持 JSON Schema 和正则约束
XGrammarPython/C++MLC AI 开发的 GPU 加速语法约束引擎,vLLM 集成支持
llama.cpp GBNFC++llama.cpp 内置的 CFG 语法约束,适合边缘部署
术语英文解释
结构化输出Structured Output让 LLM 输出严格符合预设格式(通常是 JSON)的结果
JSON SchemaJSON Schema一种声明式语言,定义 JSON 数据的结构、类型和约束
约束解码Constrained Decoding在解码阶段用语法规则过滤非法 token,强制输出符合格式
函数调用Function CallingLLM 根据函数签名自动提取参数并输出调用请求的能力
Logits 处理器LogitsProcessor在采样前修改模型 logits 的机制,约束解码的技术基础
PydanticPydanticPython 数据验证库,用类型注解定义数据模型
有限状态机FSM, Finite State Machine一种数学计算模型,在有限状态间根据输入转移,约束解码用它跟踪生成进度
上下文无关文法CFG, Context-Free Grammar一种形式文法,用产生式规则描述语言结构,约束解码用它做语法约束
严格模式Strict ModeOpenAI Structured Outputs 的选项,开启后 Schema 不允许开放字段
  • OpenAI Structured Outputs:OpenAI 2024 年发布的功能,引入 JSON Schema 约束的输出模式,官方文档有完整教程和最佳实践。2024 年 12 月正式 GA(General Availability,正式发布)。
  • Outlines:Willard & Louf, “Efficient Guided Generation for Large Language Models”(2023),开源约束解码库,用索引状态机加速格式约束,是本地模型结构化输出的主流方案。
  • XGrammar:Dong et al., “XGrammar: Flexible and Efficient Structured Generation Engine for Large Language Models”(2024),MLC AI 团队开发的 GPU 加速约束引擎,通过位掩码张量化实现低开销约束解码,已被 vLLM 集成。
  • Guidance:微软开源的结构化生成库,用模板语法让 LLM 按指定结构输出,适合复杂的多段式生成。
  • JSON Schema 规范:JSON Schema 官方网站有完整的规范文档和交互式教程,是定义结构化数据格式的行业标准。
  • Instructor:jxnl 开源的轻量封装库,支持 OpenAI/Anthropic/Cohere/Ollama 等多后端统一用 Pydantic 做结构化输出,文档有丰富的设计模式示例。