E4 · 如何让模型生成结构化结果? - Structured Outputs
所属知识库:Agent 全栈技术地图
所属领域:01 · AI
所属章节:03 · 如何使用和管理 AI 模型? - Model Engineering
文档类型:技术原理与工程实践研究
版本:v1.0
更新日期:2026 年 10 月 27 日
核心问题:如何让概率生成的模型输出成为程序可验证、可消费的数据?JSON、JSON Schema、约束解码、类型系统与业务校验如何协同?
专题导读
当 AI 模型从回答问题走向参与软件系统和 Agent 工作流时,如何让模型生成的内容成为程序能够可靠理解和使用的数据,就成为一项重要的工程问题。
本篇围绕 Structured Outputs(结构化输出)展开研究,从 JSON、JSON Schema 和数据契约的基本概念出发,深入解释模型如何通过受约束解码生成符合指定结构的结果,以及不同结构化输出方式在实现机制和可靠性上的差异。
在工程实践部分,结合主流模型 API、Pydantic 和 Zod,研究数据结构设计、类型验证、异常恢复、流式处理与版本管理,并通过 Agent 研究任务解析案例展示完整的数据处理流程。最后进一步讨论结构化输出在 Agent System 中的作用,说明它如何支持 Agent 之间的信息交接、任务状态管理和系统编排,同时明确结构正确性、事实可信性与执行权限之间的边界。
一、研究概述
E1 解决模型通信,E2 解决输入设计,E3 解决上下文管理。E4 进一步处理一个工程边界:自然语言结果如何进入确定性的程序系统? 用户可以阅读一段文字并理解其意图,但程序通常需要字段、类型、取值范围和错误语义。即使模型回答“看起来正确”,也可能缺失字段、把数字写成字符串、混淆未知与空值,或生成无法解析的 JSON。
Structured Outputs 指通过接口配置、输出模式、模式约束、验证和恢复机制,使模型结果符合预期数据结构的工程实践。它并非单一功能:有效 JSON、符合 Schema、满足业务规则、事实正确是四个逐层增强的要求,不能混为一谈。
1.1 学习目标
- 理解结构化输出的价值、适用场景与失败类型。
- 掌握 JSON、JSON Schema、类型约束、可选字段、枚举和嵌套结构。
- 区分提示词约束、JSON Mode、Schema-constrained Outputs 和受约束解码。
- 设计从模型响应到业务对象的完整验证管线。
- 理解拒答、截断、Schema 不兼容、流式解析和版本演进。
- 在 Agent 中将结构化结果作为可验证的状态转换,而不是未经审查的执行指令。
1.2 研究边界
E4 研究输出的数据契约。E5 研究工具调用协议及执行循环;两者都可能使用 JSON Schema,但 E4 的目标是得到可靠数据,E5 的目标是安全地请求外部动作。E2 研究如何描述输出要求,E8 研究结构化输出的延迟与成本。
二、为什么结构化输出是工程问题?
假设企业研究 Agent 要向数据库写入一条记录。人类可读输出可能是:“这家公司属于工业软件行业,主要业务是设备监控,竞争优势包括部署经验,风险包括客户集中。”但数据库需要:
{
"company_name": "示例科技",
"industry": "工业软件",
"main_business": ["设备监控"],
"advantages": ["行业部署经验"],
"risks": ["客户集中"],
"evidence_ids": ["source-17"]
}程序需要知道:main_business 必须是数组;evidence_ids 引用的来源必须存在;company_name 不能为空。模型直接输出自由文本无法保证这些条件。
结构化输出的收益包括:减少解析歧义、实现自动化流程、对接数据库和 API、建立质量测试、为 Agent 状态机提供明确输入。但结构化输出也有成本:Schema 设计、约束兼容性、失败处理、版本迁移和额外验证。
2.1 四个正确性层级
| 层级 | 问题 | 例子 | 检验方式 |
|---|---|---|---|
| 语法有效 | 是否能解析? | JSON 引号、逗号正确 | JSON Parser |
| 结构有效 | 是否符合数据契约? | risks 是字符串数组 | JSON Schema / 类型验证 |
| 业务有效 | 是否符合领域规则? | 引用 ID 存在,分数在 0–100 | 业务逻辑校验 |
| 语义可信 | 内容是否真实且有依据? | 风险判断是否得到材料支持 | 证据核验、评估、人工复核 |
关键结论:Schema 保证的是结构约束,不保证事实真实性。
三、JSON 的数据模型与常见陷阱
JSON 支持对象、数组、字符串、数字、布尔值和 null。它不支持注释、尾随逗号、NaN 或无限大等非标准数值。以下三个值语义不同:
{"revenue": null}{}{"revenue": 0}第一种表示字段存在但值为空,第二种表示字段缺失,第三种表示已知值为零。业务系统需要事先规定未知、缺失和零的含义,不能在解析时随意互换。
其他常见陷阱包括:金额使用浮点数导致精度误差;日期使用自由字符串而无法验证;把单个对象和对象数组混用;将分类标签写成不稳定的自然语言;在文本字段中混入 Markdown 代码围栏;使用未转义的换行和引号。对于货币,常见做法是使用最小货币单位整数或十进制字符串,并在业务层处理精度。
四、JSON Schema 如何定义数据契约?
JSON Schema 是用于描述与验证 JSON 数据结构的规范。核心关键词包括 type、properties、required、items、enum、minimum、maximum、minLength、additionalProperties 等。以下示例采用 JSON Schema Draft 2020-12 的一般表达方式:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"company_name": {"type": "string", "minLength": 1},
"industry": {"type": "string"},
"risk_level": {"type": "string", "enum": ["low", "medium", "high", "unknown"]},
"risks": {"type": "array", "items": {"type": "string"}},
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
"evidence_ids": {"type": "array", "items": {"type": "string"}}
},
"required": ["company_name", "industry", "risk_level", "risks", "confidence", "evidence_ids"],
"additionalProperties": false
}4.1 Schema 的三个设计原则
最小充分结构:只定义真正需要被程序消费的字段。过深嵌套和过多字段会增加生成与维护难度。明确的空值语义:对于未知信息,使用统一的 null、unknown 或 status 字段,不要让模型自行决定。限制可变空间:能使用枚举就不要让模型自由发明分类名称;能使用数组就不要依赖逗号分隔字符串。
4.2 Schema 版本与兼容性
当字段从 risk 改为 risks,或将字符串改为对象数组时,消费者可能发生破坏性变更。建议记录 schema_version,并定义兼容策略:新增可选字段通常较容易兼容;删除字段、重命名、改变类型则通常需要迁移。注意模型服务商支持的 JSON Schema 可能只是规范的子集,必须以具体接口文档为准。
五、提示词约束、JSON Mode 与 Schema 约束有什么区别?
5.1 Prompt-only
通过自然语言要求“只输出 JSON”。这种方式简单、兼容广,但模型可能附加解释、代码围栏、缺少字段或输出非法 JSON。适合原型,不宜作为唯一的生产保证。
5.2 JSON Mode
某些模型 API 提供 JSON Mode,主要帮助生成语法有效的 JSON。它不必然保证满足特定字段结构,更不保证业务规则。不同厂商对该模式的保证范围不同。
5.3 Schema-constrained Structured Outputs
某些 API 接受开发者提供的 Schema,并使用服务端约束机制使正常完成的响应尽量或按产品契约满足受支持的 Schema。需要检查服务商对以下情况的约定:拒答、输出截断、内容过滤、Schema 不支持、工具调用分支、流式输出。
5.4 受约束解码(Constrained Decoding)
普通自回归解码在每一步从候选 Token 分布中选择后续 Token。受约束解码会根据当前输出状态与语法规则,屏蔽不允许的候选:
其中
5.5 比较
| 方法 | 语法有效性 | Schema 保证 | 典型局限 |
|---|---|---|---|
| Prompt-only | 不保证 | 不保证 | 依赖模型遵循指令 |
| JSON Mode | 通常有更强保证 | 通常不保证 | 字段可能错误 |
| Schema-constrained API | 依服务契约 | 对支持的 Schema 提供约束 | 拒答、截断、子集限制 |
| 应用层校验 | 检测错误 | 检测错误 | 本身不能生成正确结果 |
六、从模型响应到业务对象的完整处理管线
用户任务 + 可信指令 + 资料
│
▼
选择 Schema
│
▼
调用模型 API
│
▼
检查完成状态 ──拒答/截断/错误──► 专用异常分支
│
▼
解析 JSON
│
▼
Schema Validation
│
▼
Business Validation
│
▼
Evidence Verification
│
▼
可信业务对象 / 人工复核解析失败、结构失败和事实核验失败应分别记录。不要把所有错误都归类为“模型不听话”,因为每类错误需要不同恢复策略。
6.1 Python:使用 Pydantic 验证
以下示例只演示应用层验证,不依赖特定模型 SDK:
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, ValidationError
class CompanyAnalysis(BaseModel):
model_config = ConfigDict(extra="forbid")
company_name: str = Field(min_length=1)
industry: str
risk_level: Literal["low", "medium", "high", "unknown"]
risks: list[str]
confidence: float = Field(ge=0, le=1)
evidence_ids: list[str]
raw_json = '''{
"company_name": "示例科技",
"industry": "工业软件",
"risk_level": "medium",
"risks": ["客户集中"],
"confidence": 0.72,
"evidence_ids": ["source-17"]
}'''
try:
result = CompanyAnalysis.model_validate_json(raw_json)
print(result.company_name)
except ValidationError as exc:
print("结构验证失败:", exc.errors())Pydantic 默认可能进行某些类型转换。若业务要求严格区分字符串与数值,应使用严格类型配置;同时应注意 Pydantic 模型生成的 Schema 与具体模型 API 的 Schema 子集不一定完全兼容。
6.2 业务规则验证
def validate_evidence_ids(analysis: CompanyAnalysis, known_ids: set[str]):
unknown = set(analysis.evidence_ids) - known_ids
if unknown:
raise ValueError(f"未知证据引用: {sorted(unknown)}")即使 JSON 与 Schema 均合法,引用也可能不存在;这必须由应用检查。
七、如何设计适合模型生成的数据结构?
7.1 从业务消费端倒推
先确定结果要被谁使用:数据库、页面、工作流、搜索索引,还是另一个 Agent。数据结构应该满足下游需求,而不是根据模型喜欢输出什么来设计。
7.2 把“未知”建模为一等状态
研究任务常遇到证据不足。与其强迫模型给出数字,不如使用:
{
"revenue": {
"status": "unknown",
"value": null,
"source_ids": []
}
}该设计允许程序区分“已知为零”和“尚未查到”。
7.3 证据与结论分离
{
"claims": [
{
"claim": "该公司主要面向制造企业",
"evidence_ids": ["source-1", "source-3"],
"assessment": "supported"
}
]
}即使有 evidence_ids,也必须核对原始资料是否真正支持结论。引用字段不是自动生成的真实性证明。
7.4 控制复杂度
避免一次请求生成数百个嵌套字段。对于大型报告,可分阶段生成:先提取事实,再生成分析对象,最后组装报告。每个阶段有自己的 Schema 与验证规则。
八、失败模式与恢复策略
| 失败模式 | 可能原因 | 推荐处理 |
|---|---|---|
| JSON 无法解析 | 普通文本生成、截断 | 优先使用 Schema 约束;检查完成状态 |
| 缺失字段 | Schema 未强制、模型遗漏 | 结构校验;有限次修复或重试 |
| 类型错误 | 字段歧义 | 改进 Schema、使用严格校验 |
| 无效枚举 | 自由生成标签 | 使用 enum 或服务端约束 |
| 输出被截断 | Token 预算不足 | 增加预算、缩小输出、分批生成 |
| 拒答 | 安全策略或内容限制 | 识别拒答状态;不要强行解析 |
| Schema 不支持 | API 仅支持子集 | 简化 Schema、转换兼容形式 |
| 业务逻辑错误 | 规则未实现 | 应用层验证 |
| 事实错误 | 资料不足或模型判断错误 | 证据核验与人工复核 |
重试策略:只在错误可恢复时重试;设置次数与成本上限;保留失败样本用于评估。若请求具有外部副作用,注意重复执行与幂等性。对于事实性错误,重复生成同一问题并不能保证得到正确答案。
九、流式结构化输出如何处理?
流式响应通常以增量片段传输。单个片段不是完整 JSON:
Chunk 1: {"company_"
Chunk 2: name":"示例科技",
Chunk 3: "risks":["客户集中"]}因此,不能把每个 Chunk 当作独立 JSON 解析。常见策略包括:先累积后统一解析;使用增量 JSON Parser;或消费 API 提供的专用结构化事件。流式 UI 可以展示“尚未验证”的临时内容,但不应在对象完成校验前触发不可逆业务动作。
需要处理连接中断、乱序假设、重复事件、完成标记与最后一次验证。具体事件协议以服务商文档为准。
十、Structured Outputs 与 Function Calling 的关系
二者常使用类似的 Schema,但语义不同:
Structured Outputs: 模型 → 数据对象 → 应用消费
Function Calling: 模型 → 工具调用意图 → 权限/参数检查 → 外部执行例如,{"risk_level":"high"} 是数据;{"tool":"delete_file","arguments":{"path":"..."}} 是可能产生副作用的动作请求。后者必须经过工具注册表、授权、参数验证与执行策略,不能因为 JSON 合法就执行。
十一、在 Agent System 中如何使用结构化输出?
11.1 作为状态转换协议
研究 Agent 可以输出:
{
"task_status": "needs_more_evidence",
"findings": [],
"missing_evidence": ["近三年收入数据"],
"next_action": "request_research"
}但 next_action 应视为建议或受控信号;是否执行由编排器根据当前状态、权限和工作流规则决定。
11.2 作为 Agent 间交接契约
Research Agent ── ResearchFinding Schema ──► Analysis Agent
Analysis Agent ── AnalysisReport Schema ──► Review Agent
Review Agent ── ReviewDecision Schema ──► Orchestrator这样可以防止下游 Agent 依赖不稳定的自然语言格式。对于审核结果,建议采用明确枚举(approved、needs_revision、blocked),并保留审核意见、证据和版本号。
11.3 数据溯源
每个对象应尽可能记录:任务 ID、模型标识、Prompt 版本、Schema 版本、生成时间、输入来源 ID 和验证状态。对于可审计系统,建议保留原始响应的受控存档,但需要遵守隐私与保留政策。
十二、测试与评估
结构化输出测试应分层设计:
- 语法测试:解析成功率。
- Schema 测试:字段、类型、枚举、额外字段合规率。
- 业务规则测试:跨字段约束、引用存在性、范围校验。
- 语义测试:事实准确性、遗漏率、证据支持率。
- 异常测试:空输入、长输入、冲突材料、拒答、截断。
- 性能测试:延迟、Token、重试率和成本。
一个常见指标是:
但仅报告该指标不够。还应区分拒答、API 失败和截断,防止分母定义造成误导。对于业务关键场景,应另行报告事实正确率和人工复核比例。
十三、实践方案:企业研究结果的结构化生产
任务:从企业资料中提取事实,形成可供研究系统复用的结构化对象。
步骤一:定义数据契约。 将企业名称、业务、风险、证据 ID、未知状态和版本定义为明确字段。步骤二:准备来源材料。 为每份资料分配稳定 ID,避免模型随意编造引用。步骤三:调用支持 Schema 的模型接口。 对不支持的接口采用 Prompt + 应用校验的退化方案。步骤四:验证。 依次检查完成状态、解析、Schema、业务规则和证据。步骤五:失败分流。 格式错误可有限次重试,事实不明则标记待研究,权限或服务错误则交给系统层处理。步骤六:发布。 保存带版本的业务对象,供分析 Agent 使用。
资料库 ──► 输入构建 ──► 模型生成
│
▼
Schema 校验
│
▼
证据与业务校验
│
┌───────┴────────┐
▼ ▼
通过 失败分流
│ │
▼ ▼
研究对象 修复/补证/复核
│
▼
下游 Agent13.1 验收清单
- [ ] Schema 有版本号,字段语义明确。
- [ ] 模型支持的 Schema 子集已验证。
- [ ] 拒答、截断和 API 错误分别处理。
- [ ] 数据解析、结构验证和业务验证分层实现。
- [ ] 关键结论可以追溯到真实资料。
- [ ] 未知值不会被强制编造成事实。
- [ ] 重试有次数、时间和费用上限。
- [ ] 生产日志记录校验结果且保护敏感数据。
十四、常见误区
误区一:能解析 JSON 就说明输出可靠。 JSON 合法不意味着字段正确,更不意味着事实正确。误区二:JSON Mode 等同于 Schema 约束。 前者通常只保证语法层面。误区三:在 Prompt 中写“严格遵守”就无需验证。 生产系统必须有独立校验。误区四:Schema 越复杂越好。 过度设计会增加失败率和维护成本。误区五:结构化工具调用可以直接执行。 工具调用还涉及权限和副作用。误区六:流式 JSON 的每个片段都是合法对象。 必须按事件协议组装。误区七:模型输出的 confidence 是校准概率。 除非经过专门验证,否则不能把自报置信度直接当成可信统计概率。
十五、核心结论
Structured Outputs 的本质是为模型生成结果建立明确的数据契约,并通过模型侧约束与应用侧验证共同实施。JSON 解决表示形式,JSON Schema 解决结构约束,业务规则解决领域一致性,证据验证解决事实可信性。生产系统必须识别拒答、截断、格式错误和语义错误,并建立不同的恢复路径。在 Agent System 中,结构化输出应成为 Agent 间交接和状态转换的基础,但不能替代编排器的权限判断和流程控制。
十六、参考资料与延伸阅读
- JSON Schema. Understanding JSON Schema. 官方文档
- IETF. RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format. 规范
- OpenAI. Structured Outputs. 官方指南
- Pydantic. Models. 官方文档
- Willard & Louf (2023). Efficient Guided Generation for Large Language Models. 论文
- JSON Schema. Specification. 规范入口
十七、关联研究
| 文档 | 关联重点 |
|---|---|
| E1 · Model API | 请求与响应协议 |
| E2 · Prompt Engineering | 输出要求与模板 |
| E3 · Context Window | 输出预算与截断 |
| E5 · Function Calling | 工具参数 Schema 与执行安全 |
| E6 · Model Routing | 按结构化输出能力选择模型 |
| M8 · Model Evaluation | 合规率与任务正确性 |
| A3 · Instructions | Agent 指令与数据契约 |
| A7 · Tools | 工具调用结果与参数验证 |
| L4 · Agent Engine | 结构化状态转换 |