跳转至

大模型结构化输出的作用、技术原理与实现

大语言模型(LLM)具有强大的文本生成能力,可以输出易于人类阅读的自由格式文本。但是,这一生成过程是概率性的,而不是确定性的。当我们在复杂的软件系统中使用 LLM 作为工作流的一部分时,我们可能需要确保 LLM 的输出符合一定的格式和类型等规范。例如,如果用 LLM 进行文本二分类,我们可能只需要得到  True  或  False  的布尔值,而不希望得到其他任何文本,这就要求 LLM 的输出是严格结构化的。

结构化输出(Structured Output)技术可以解决这一问题,它确保 LLM 的输出严格遵守预定义格式和约束,从而极大地提升 LLM 在自动化工作流和程序化集成中的可靠性。LLM 有了结构化输出的能力后,就能从一个“对话工具”变成一个靠谱的“数据提供者”。

本文讨论了大模型结构化输出的作用,以及实现结构化输出的一些原理和技术实现方案。

技术路径 实现原理 成本 可靠性 适用场景
提示词导向生成 依靠 Prompt 中的软规则与示例,让模型“自觉”输出符合格式的文本。 极低 低~中:模型可能偶尔多输出解释或漏字段。 快速原型、轻量级应用、对格式要求不严的辅助任务。
自动验证与修复 模型生成后先做结构校验;如不合规,系统自动带着错误信息重新请求模型修正。 中:能兜底一次又一次修复错误,但仍非绝对稳固。 需要容错但又不想写硬约束的生产系统,如数据抽取、轻量级结构化任务。
约束解码 在 token 级生成的每一步实时过滤非法 token,用 JSON Schema/CFG/FSM 硬性约束生成路径。 中~高 极高:能从源头保证输出完全合法。 对格式要求极严格的任务,如复杂 JSON、代码生成、严苛 API 指令。
草图引导 / 外部精炼 黑盒模型自由生成草稿,再用可访问 logits 的本地模型按语法规则精炼修复。 中~高:依赖草稿质量,但最终输出能较好满足约束。 黑盒模型不提供 logits 时的 JSON/DSL 生成,本地自动化管线。
NL→Format 两阶段生成 模型先自由推理完整答案,然后再将自然语言结果转换成结构化格式。 中~高:推理质量好,格式化环节仍可能需轻微兜底。 推理复杂但格式规则简单的场景,如 Agent 思考链、任务规划。
监督式微调 用大规模样本直接把结构化输出范式“教进”模型参数中,形成稳定风格。 高:数据质量足够好时,模型能持续稳定地产生结构化格式。 金融/医疗/法律等需要长期稳定抽取的领域任务。
强化学习优化 通过奖励模型给输出提供细粒度反馈,优化模型在结构与内容上的决策路径。 极高 极高:在复杂推理、深层结构生成等方面最稳定。 多步骤推理、复杂 JSON、深度嵌套结构、需要持续优化策略的场景。
原生 API 结构化输出能力 服务商在 API 内部使用约束解码与安全机制,直接保证返回严格的结构。 低~中 极高:由 API 层完全托管,开发者不再接触底层复杂度。 商业应用、智能体工具调用、生产级数据接口。

结构化输出的作用

结构化输出通过保证输出格式的规范性,解决了 LLM 在输出自由格式文本时可能出现的解析错误、内容遗漏或冗余等问题。当 LLM 输出的文本可被下游规范地读取后,就能够无缝地集成到复杂软件系统和数据库中,从而解锁了在更多场景下的应用潜力。

结构化输出的主要应用场景包括:

  • 数据提取(Data Extraction):从非结构化文本(如财报、新闻、用户评论)中提取关键信息,并将其组织成预定义的 JSON 等格式。例如,从基金季报中抽取出基金经理对特定行业的观点、情绪分类及关联的行业代码,或从财务附注中提取公司政策的量化指标等。这样下游程序就可以直接使用这些结构化数据,无需执行额外的文本解析逻辑。

  • 工具使用与函数调用(Tool Use / Function Calling):通过让模型生成符合特定 JSON Schema 的函数名和参数,可以驱动外部 API 或内部函数的调用。例如,当用户提问“上海的天气怎么样?”时,模型可以生成调用  get_temperature(city: "Shanghai")  所需的 JSON 指令。如果生成的内容不符合规范的 JSON 指令格式,那么我们将无法正确解析并调用工具和函数。

  • 构建智能体系统(Agentic Systems):在复杂的智能体(Agent)工作流中,模型需要根据当前状态和目标,规划并执行一系列动作。结构化输出可以保证模型生成的动作指令,如  {"action": "search", "query": "..."}  格式规范,从而被系统正确地执行。

  • 代码与领域特定语言(DSL)生成:在软件开发、数据分析等领域,需要使用符合一定语法规范的代码片段,如 Python、SQL 查询或 YAML 配置文件。结构化输出技术可以确保生成的代码符合语言的语法规范,避免编译或执行错误。

技术原理与实现

LLM 本质上是一个条件概率模型,它根据输入提示词(prompt)预测下一个 token 的概率分布。所谓“结构化输出”,就是让模型在这个概率空间里只采样那些合法的、符合约束的 token 序列。实现方式大致分成两类:软约束(Soft Constraints)  和  硬约束(Hard Constraints)

软约束通过提示词工程引导模型输出符合规范的文本;而硬约束则通过实时解析和 token-level 检查,直接约束模型的采样过程。

下面分别介绍这几类方法。

通过提示词工程引导模型输出符合规范的文本

这是最早期、最常见的一类方法,其核心思路是用提示词来引导模型“自己遵守规则”。

例如,如果需要对一段文本进行情感分类,只输出:"positive"  或  "negative" ,可以这样写提示词:

Text Only
You are a sentiment classifier.
Your entire response must be a single lowercase word:
either "positive" or "negative".

如果模型足够强、任务不太复杂,模型通常能输出符合规范的答案。这种方法优势是简单易用,且几乎没有额外的成本;缺点则是可靠性不足:模型有时会在 JSON 外额外输出多余的说明,或忘记某个字段。例如,模型可能会输出  My response is "positive".  这种文本,虽然表达的意思还是 "positive" ,但非结构化的输出不适合接入下游了。提示词工程的方法依赖模型自身的指令遵循能力,无法保障 100% 的可靠性。

自动验证与修复

单纯依赖提示词引导无法满足生产环境对稳定性和可靠性的要求,我们可以通过自动验证与修复的技术来添加“第二道防线”,以提高输出文本的稳定性和可靠性。不管模型生成什么内容,都要在进入下游流程之前先过一遍结构校验。如果发现格式不合法、字段缺失、类型错误等,框架会自动反馈给 LLM,并要求其重新输出。

首先,我们需要声明期望的输出结构。例如,假设我们希望从财报文本中提取三个字段:公司名称、报告期和营收。我们可以使用 Pydantic 来精确定义这些字段的类型及其合法格式与范围:

Python
class FinancialReport(BaseModel):
    company_name: str = Field(validators=[ValidLength(min=1, max=100)])
    report_period: str = Field(validators=[RegexMatch(pattern=r"\d{4}Q[1-4]")])
    revenue: float = Field(validators=[ValidRange(min=0)])

这样就定义了一个合法的数据结构:

  • company_name  的字符串长度在 1 到 100 之间;

  • report_period  是 2024Q3 这种格式,用正则表达式约束;

  • revenue  是非负值。

模型给出初始回答后,系统会立刻依据预设的结构规范对其进行校验。若输出本身已经符合要求,就可以直接进入下一步;但如果发现格式不合法、字段缺失或类型不匹配,框架不会直接把错误抛给下游,而是主动再次调用模型,并明确告知其问题所在,让模型按照反馈重新生成答案。这个过程可以反复进行多轮,直到得到一个严格满足结构约束、能够被程序稳定解析的结果。

支持结构化输出的开源框架有很多,例如 instructormarvinmirascope 等。以 instructor 库为例,它的自动验证与修复机制大致实现在两个核心位置:

  • core/retry.py:负责调用模型 + 解析 + 捕获异常 + 重试的主循环。

  • response.py:负责把这一次解析失败的信息,转化成下一轮请求的补充提示,也就是 reask。

代码中的那些  FailedAttempthandle_reask_kwargs  和重试循环,基本上就是 reask 技术的工程化骨架。

1763137703402

约束解码:在生成过程中强制约束

前面介绍的提示词引导和验证修复方法,本质上都属于"软约束":提示词引导依赖模型的自主遵守,验证修复则是在输出完成后再进行检查。这两种方法都无法从根本上杜绝格式错误的产生。而约束解码(Constrained Decoding)  则采用了完全不同的思路——它在模型生成的每一步就直接施加约束,从源头上确保输出的合法性。

约束解码的核心原理是对模型的采样过程进行实时干预。LLM 在生成文本时,每一步都会计算词表中所有 token 的概率分布,然后从中采样出下一个 token。约束解码器会在这个采样之前介入:它根据预定义的语法规则(例如 JSON Schema 对应的有限状态机),判断当前状态下哪些 token 是合法的,哪些会导致违反格式约束。所有不合法的 token 会被直接屏蔽,模型只能从剩余的合法 token 中进行选择。

技术上,约束规则可以用多种形式表达:JSON Schema、正则表达式或上下文无关文法(CFG)等。这些规则会被编译成有限状态机(Finite State Machine, FSM),在解码过程中实时追踪当前的语法状态。例如,在生成 JSON 对象时,FSM 会记录当前是在对象开始、键名、值还是结束位置,据此计算下一个允许出现的字符集合。这样就能确保每个括号、引号、逗号都出现在正确的位置,从而保证最终输出是一个完全合法的 JSON 字符串。

相比前两种方法,约束解码的最大优势是接近 100% 的格式准确率。由于每个 token 都经过了合法性检查,格式错误从机制上被完全杜绝了。

然而,约束解码也面临一些实际挑战:

1. 对模型内部访问权限的要求

传统的约束解码需要访问模型输出的完整概率分布(logits),以便对 token 进行过滤。但许多闭源的 LLM 不对外提供 logits,这使得约束解码无法直接应用。针对这个问题,研究者提出了"草图引导"(Sketch-Guided Constrained Decoding)等替代方案:先让黑盒模型自由生成一个"草稿",再用本地部署的白盒模型(可以访问 logits)根据约束规则对草稿进行精炼和修正,从而在不访问黑盒模型 logits 的情况下实现类似的效果。

1763137733825

2. 格式约束对内容质量的影响

当模型被迫遵守严格的格式约束时,其推理能力可能会受到影响——格式限制越严格,模型在复杂推理任务上的表现下降越明显。这是因为格式约束会限制模型的"思考空间",迫使它在生成内容的同时还要兼顾格式规范,从而分散了计算资源。

一个解决方法是"先思考后格式化"(NL-to-Format):让模型先用自然语言完整地进行推理和回答,不受任何格式约束;待内容生成完毕后,再让模型将自然语言答案转换为目标格式。这种方式将内容生成与格式遵循解耦,既保持了模型的推理质量,又能得到规范的结构化输出。

监督式微调:让结构化能力内化到模型参数中

前面介绍的所有方法——提示词引导、验证修复、约束解码——都是在推理阶段对模型施加约束。而监督式微调(Supervised Fine-Tuning, SFT) 采用了截然不同的策略:它在训练阶段就让模型学会生成结构化输出,将这种能力深深"烙印"到模型的参数中。

SFT 的核心思路是:用大量的标注数据(每条数据包含一个输入和对应的结构化输出)对预训练模型进行有监督训练,通过反向传播不断调整模型的参数,使其自然地倾向于生成符合特定格式的响应。这与提示词引导有着本质区别——提示词只是推理时的临时指导,模型每次都需要重新"理解"格式要求;而 SFT 则是永久性地改变了模型的行为模式,使其在面对类似任务时能够本能地输出正确格式。

在实践中,SFT 的流程通常包括以下步骤:

1. 构建高质量数据集

收集大量"输入 - 结构化输出"配对样本。例如,如果目标是从财报中提取关键信息,数据集中的每条样本可能是:

JSON
{
  "input": "2024 年第三季度,公司实现营业收入 12.5 亿元,同比增长 15.3%...",
  "output": {
    "period": "2024Q3",
    "revenue": 1250000000,
    "growth_rate": 0.153
  }
}

2. 参数高效微调

由于完整微调大模型成本高昂,实践中通常使用 LoRA(Low-Rank Adaptation) 等参数高效微调技术。LoRA 只训练少量额外的低秩矩阵,将原本需要更新的数十亿参数减少到数百万,大幅降低了计算和存储开销,同时保持了接近全量微调的效果。

3. 持续稳定的输出能力

训练完成后,模型就获得了稳定输出目标格式的能力,不再需要复杂的提示词工程。这对于需要频繁调用的生产环境特别有价值——每次推理时只需提供输入文本,模型就能自动输出规范的结构化数据。

SFT 特别适合那些规则清晰、需求稳定、调用频繁的领域任务,如医疗报告结构化、法律文书解析、金融数据提取等。一次训练后可以持续使用,既降低了推理时的复杂度,也减少了对提示词的依赖,提升了整体系统的稳定性。

然而,SFT 的效果高度依赖训练数据的质量。数据需要满足:

  • 格式一致性高:所有样本的输出格式必须严格统一,否则模型会学到相互矛盾的模式

  • 场景覆盖全面:要涵盖实际应用中可能遇到的各种边缘情况和异常输入

  • 标注准确无误:任何标注错误都会被模型"学习"并复制到后续所有输出中

数据质量问题是 SFT 最大的隐患——如果训练数据中存在系统性偏差或错误,模型会将这些问题固化,并在生产环境中不断重现。

强化学习优化:突破复杂推理的瓶颈

上一节提到的监督式微调在面对复杂推理任务时可能会遭遇“SFT 高原”——即使增加再多训练数据,性能提升也会停滞不前。要突破这一瓶颈,需要引入一种全新的学习机制:强化学习(Reinforcement Learning, RL)

RL 与 SFT 在学习方式上有着根本区别:

  • SFT 是静态学习:它依赖固定的标注数据集,一次性学习"输入 → 输出"的映射关系。如果某个输出不完全正确,模型就得不到任何有用的反馈——要么全对,要么全错。

  • RL 是动态学习:它通过不断试错和奖励信号来逐步优化生成策略。关键的是,RL 可以对"部分正确"的输出给予鼓励——只要这次尝试比上次更接近目标,就能获得正向反馈。这种细粒度的奖励机制使得模型能够探索并学习到复杂的推理路径。

RL 的核心机制是奖励驱动的学习循环

  1. 生成候选:模型根据当前策略生成多个候选输出

  2. 评估奖励:奖励模型对每个输出进行多维度评分,包括格式正确性、内容准确性、推理合理性等

  3. 更新策略:根据奖励信号更新模型的生成策略,使其倾向于产生高奖励的输出

  4. 迭代优化:重复上述过程,持续改进模型表现

这种机制让模型不再是简单地"记住"正确答案,而是真正“理解”如何一步步推导出正确的结构化输出。

在结构化输出领域,Schema 强化学习(Schema Reinforcement Learning, SRL) 是一个典型的应用框架。它将 RL 的通用思想具体化为三个阶段:

采样阶段:模型针对给定任务生成多个候选响应。在这个阶段,可以引入一些启发式策略来提高采样质量,例如让模型先"思考"输出的结构框架,再填充具体内容。

奖励阶段:这是 SRL 的核心。系统会使用结构验证器检查每个候选输出的格式合规性,同时用奖励模型评估其内容质量。一个关键指标是“正确性比例”——衡量输出中有多少 token 是符合要求的。这比简单的“通过/失败”二分法提供了更丰富的反馈信息。

更新阶段:利用奖励信号,通过 PPO(Proximal Policy Optimization,近端策略优化) 等算法更新模型的策略网络。PPO 的优势在于能够确保每次更新的幅度不会过大,从而保持训练的稳定性,避免模型性能出现剧烈波动。

在 SRL 框架中,一个特别有趣的创新是“结构化思维”(Thoughts of Structure, ToS)。这个概念受到"思维链"(Chain-of-Thought, CoT)推理的启发,其核心思想是让模型在生成具体的结构化输出之前,先用自然语言"规划"输出的结构。例如:

Text Only
# 模型的内部"思考"过程:
我需要生成一个 JSON 对象,包含三个顶层字段:
1. 'company' 存储公司名称
2. 'financials' 是一个嵌套对象,包含 revenue 和 growth 两个数值字段
3. 'period' 存储报告期,格式为 YYYYQX"

# 然后再生成实际的 JSON:
{
  "company": "...",
  "financials": {"revenue": ..., "growth": ...},
  "period": "2024Q3"
}

这种“先思考结构,再生成内容”的方式,实际上是在引导模型对复杂结构进行分解和规划,而不是一次性生成整个输出。研究表明,ToS 能够显著提升复杂 JSON 生成的成功率,特别是在处理深度嵌套、多层级的结构时效果明显。

接口化能力:从研究到商品化

前面讨论的各种技术——无论是约束解码、验证修复还是强化学习——大多需要开发者自己实现或依赖开源框架来完成。现在,主流大语言模型服务商已经将结构化输出作为原生 API 能力内置到产品中。

根据 OpenAI 的文档,开发者只需用 Pydantic 定义好数据模型,API 就会保证返回的数据严格符合定义,开发者可以像调用强类型函数一样直接使用返回值。

Python
from openai import OpenAI
from pydantic import BaseModel
import json

client = OpenAI()

# ===================================================
# === 以前的做法:让模型返回“像 JSON 的文本”,自己解析 ===
# ===================================================

prompt = """
请从下面这句话中提取事件信息,返回 JSON:
- name: 事件名称
- date: 日期(字符串)
- participants: 参与人列表
"""

user_text = "Alice and Bob are going to a science fair on Friday."

completion = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "user", "content": prompt + "\n\n" + user_text},
    ],
)

raw_text = completion.choices[0].message.content

try:
    data = json.loads(raw_text)

    # 手动校验字段
    if "name" not in data or "date" not in data or "participants" not in data:
        raise ValueError("缺少字段")

    if not isinstance(data["name"], str):
        raise TypeError("name 必须是字符串")
    if not isinstance(data["date"], str):
        raise TypeError("date 必须是字符串")
    if not isinstance(data["participants"], list):
        raise TypeError("participants 必须是列表")

    event_old = data

except Exception as e:
    print("解析失败:", e)
    event_old = None


# ===================================================
# === 现在的做法:使用 Pydantic + responses.parse ===
# ===================================================


class CalendarEvent(BaseModel):
    name: str
    date: str
    participants: list[str]


response = client.responses.parse(
    model="gpt-4o-2024-08-06",
    input=[
        {"role": "system", "content": "Extract the event information."},
        {"role": "user", "content": user_text},
    ],
    text_format=CalendarEvent,
)

event_new: CalendarEvent = response.output_parsed

print("旧做法:", event_old)
print("新做法:", event_new)

原生 API 中结构化输出能力的好处是:

  • 端到端保障:开发者只需定义数据模型,API 自动确保输出符合规范,无需编写复杂的验证逻辑。

  • 类型安全:从 API 返回的数据可以直接使用,就像调用强类型语言的函数一样可靠。

  • 降低门槛:底层的约束解码等复杂技术被完全抽象化,开发者无需了解实现细节,也不需要接入复杂的自动验证与修复框架。

总结

结构化输出能让大模型从“会说话”变成“能工作”。它追求稳定、可控、可集成。无论是提示词引导、自动验证与修复、约束解码,还是依靠 SFT 和 RL 把能力固化进模型本身,它们都在实现一个目标:让模型始终按我们需要的方式输出信息。

从工程视角看,结构化输出的价值在于降低不可控性,把概率型模型压进一个可预测的框架里,让它能够作为生产系统的可靠组件。随着 API 原生支持不断增强,开发者已经不必再纠结底层实现,可以把更多精力放在任务本身。

当输出能被程序稳定消费,LLM 的应用场景就不再局限于聊天,而能延伸到数据、工具、自动化、智能体等更广的方向。

评论