Structured Outputs
结构化输出在解码期间将 AI 模型的响应限制为 JSON Schema 等架构,使应用能够可靠地解析每条回复。
结构化输出是指模型响应受到约束,必须遵循预定义格式,通常是一个 JSON Schema,其中列明必需的键、类型和允许值,以便软件无需清理就能解析每条回复。礼貌地要求大型语言模型(LLM)返回 JSON 时,它通常会照做,但仍可能添加一段说明文字、拼错键名或在对象中途停止;这些情况都会破坏读取回复的程序。结构化输出通过受约束解码消除了这种风险:在生成的每一步,推理引擎只允许选择能使输出继续符合 Schema 的token。OpenAI 于 2024 年 8 月将该功能加入其 API 时报告称,启用结构化输出的 gpt-4o-2024-08-06 在复杂 JSON Schema 遵循评估中的得分为 100%;而未启用该功能的 GPT-4(gpt-4-0613)得分不到 40%。
JSON 模式与结构化输出#
服务商 API 提供两种级别的保证。OpenAI 于 2023 年 11 月推出的 JSON 模式,只保证回复在语法上是有效的 JSON,因此仍可能缺少键或出现键名错误。结构化输出则保证回复符合提供的 Schema。在 OpenAI 的结构化输出 API中,开发者可以在 Python 中将 Schema 定义为 Pydantic 模型,或在 JavaScript 中定义为 Zod 对象;拒绝请求的信息会放在单独字段中,不会破坏格式。Anthropic 的 Claude 结构化输出会将每个 Schema 编译为语法规则;首次使用某个 Schema 时会增加延迟,并会将编译后的语法缓存 24 小时。两家服务商都只支持 JSON Schema 的部分功能;例如,Anthropic 不支持递归 Schema 或 minimum 等数值限制。
约束解码的工作原理#
每一步中,LLM 都会为词表中的每个 token 生成一个分数,即 logit;解码过程则从得到的softmax分布中进行选择。受约束解码会先将所有可能破坏格式的 token 分数设为负无穷,因此这些 token 永远不会被选中。Brandon Willard 和 Rémi Louf 在《面向大型语言模型的高效引导式生成》中展示了如何高效实现这一点:将正则表达式或 JSON Schema 编译为有限状态机,再针对词表预先建立索引,列出每种状态下有效的 token,因此每一步只需进行查表。开源的 Outlines 库实现了该方法;vLLM 等服务引擎也为开放模型提供语法引导生成。
以下示例手动将这一思路应用于来自 Hugging Face Transformers 的 GPT-2。Schema 中的固定文本、花括号、键和引号由代码写入;对于每个值,模型只能从 Schema 允许的 token 中进行选择:
import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")
prompt = "Review: The battery died after two days and support never replied.\nJSON:"
# 不受约束的贪心解码:没有任何机制强制回复符合 Schema
ids = tokenizer(prompt, return_tensors="pt").input_ids
free = model.generate(ids, max_new_tokens=16, do_sample=False, pad_token_id=tokenizer.eos_token_id)
print("free:", repr(tokenizer.decode(free[0, ids.shape[1] :])))
def choose(text, options):
"""Return the option GPT-2 scores highest after text, with every other token masked out."""
logits = model(tokenizer(text, return_tensors="pt").input_ids).logits[0, -1]
allowed = torch.tensor([tokenizer.encode(option)[0] for option in options])
masked = torch.full_like(logits, float("-inf"))
masked[allowed] = logits[allowed]
return tokenizer.decode(masked.argmax())
# 受约束解码:键由 Schema 写入,模型只能选择允许的值
with torch.no_grad():
text = prompt + ' {"sentiment": "'
text += choose(text, ["positive", "negative"]) + '", "stars": '
text += choose(text, ["1", "2", "3", "4", "5"]) + "}"
print("constrained:", json.loads(text[len(prompt) :]))它会打印 free: ' The battery died after two days and support never replied.\n\nJSON: The' 和 constrained: {'sentiment': 'positive', 'stars': 1}。如果不加约束,GPT-2 会重复评论内容,而不是生成 JSON;受约束版本则始终能通过解析。解析结果也显示了这种技术的局限:这个拥有 1.24 亿参数的模型将电池没电且客服无响应的情况评为正面,只给了一颗星。Schema 固定了格式,但其中的值仍由模型自行判断。
用途与权衡#
- 数据提取:从发票、合同或电子邮件中提取姓名、日期、总额和行项目,并直接写入数据库行。
- 函数调用:工具调用是一种结构化输出,其 Schema 就是该工具的参数。OpenAI 和 Anthropic API 中的严格工具调用,会对工具参数应用相同的保证。
- 智能体工作流:将路由决策表示为枚举值,并将步骤间传递的中间结果表示为类型化对象,作为提示链的一部分。
- 视觉语言模型:以标签和边界框坐标列表的形式返回检测结果,绘图或评估代码可以直接使用这些结果。
Ultralytics Platform 中的类提示式智能标注依赖后一类输出。它会要求 OpenAI、Anthropic 和 Google 等服务商提供的视觉模型检测数据集中的类别,并将返回的每个对象添加为可编辑的边界框,因此每个回复都必须给出类别名称和代码可解析格式的坐标。这些模型不会返回置信度分数,因此选中这类模型时,Platform 会隐藏置信度和 IoU 设置。VLM Leaderboard会比较这些模型在真实世界数据集中检测对象的准确度,并根据各模型列出框的顺序为框排序。
严格格式可能会降低推理任务的准确率。在《让我自由表达?》中,Zhi Rui Tam 及其同事发现,格式限制会显著降低 LLM 的推理能力,而且限制越严格,下降幅度越大。OpenAI 的 API 会按照 Schema 列出的顺序生成键,因此一种常见的补救方法是在答案字段之前放置一个自由文本推理字段,让模型先写出思维链,再给出受约束的答案。










