上一篇介绍了 Function Calling:让大模型能够根据用户目标选择工具、生成参数,并调用数据库、业务 API 或其他程序能力。
但 Agent 真正接入业务系统后,还存在另一个同样重要的问题:
模型产生的结果,怎样可靠地交给程序处理?
例如用户告诉 Agent:
请创建一个任务:8 月 14 日前完成华东区客户回访方案,优先级高,负责人张晨,预算不超过 5000 元,并拆成整理客户名单、执行回访和汇总结果三个子任务。
模型很容易生成一段自然语言:
“任务名称为华东区客户回访方案,优先级较高,负责人张晨,截止时间为 8 月 14 日……”
人能够轻松理解,但程序却需要继续判断:
- “较高”究竟对应
HIGH还是URGENT? - “8 月 14 日”是哪一年?
- 5000 的货币单位是什么?
- 子任务如何映射到数据库?
- 如果用户没有明确负责人,模型应该填空、猜测,还是要求用户补充?
这就是 Structured Output(结构化输出) 要解决的问题。
它的核心并不是“让模型返回 JSON”,而是:
让模型按照程序预先定义的数据契约返回结果,使数据能够被解析、验证并安全地进入后续业务流程。
一、为什么 Agent 不能只依赖自然语言输出
普通聊天应用的最终消费者是人,因此输出一段自然语言通常没有问题。
但 Agent 的结果经常需要继续进入:
数据库 工作流 REST API Function Calling 前端组件 任务调度系统 审批系统 另一个 Agent
这时自然语言就会成为系统自动化的障碍。
例如模型返回:
任务优先级比较高,最好在下周完成。
程序必须再次理解“比较高”和“下周”。
如果模型改成:
{ "priority": "HIGH", "dueDate": "2026-08-14" }
处理显然容易很多。
因此,Agent 系统中经常需要完成一次转换:
自然语言 ↓ 大模型理解 ↓ 结构化数据 ↓ 程序处理
这就是 Structured Output 最基本的价值。
二、让模型“返回 JSON”还不够
最简单的方法是在 Prompt 中写:
请只返回 JSON,不要输出任何解释。
模型可能返回:
{ "title": "华东区客户回访方案", "priority": "high", "deadline": "8月14日" }
它确实是 JSON,但业务程序真正需要的可能是:
{ "title": "华东区客户回访方案", "priority": "HIGH", "dueDate": "2026-08-14" }
两份内容从人类视角看差异不大,对程序而言却完全不同。
因此需要区分三个层次:
| 方式 | 能解决的问题 | 仍然存在的问题 |
| Prompt 要求 JSON | 尽量让模型返回 JSON | 可能夹杂文本、字段漂移 |
| JSON Output / JSON Mode | 保证结果是合法 JSON | 不一定符合指定业务结构 |
| Structured Output + Schema | 同时约束字段、类型和结构 | 仍需业务规则校验 |
这一区别非常重要。
以目前 OpenAI API 为例,其文档明确区分 JSON Mode 与 Structured Outputs:JSON Mode 只能保证生成合法 JSON,而 Structured Outputs 可以进一步要求结果符合指定 JSON Schema。OpenAI 也建议,在模型支持的情况下优先使用 Structured Outputs。
DeepSeek 当前公开 API 提供的则主要是 JSON Output:通过:
{ "response_format": { "type": "json_object" } }
保证模型生成合法 JSON,同时仍需要在 Prompt 中明确要求 JSON,并描述希望得到的数据格式。
这两种实现恰好可以帮助我们理解:
JSON Valid ≠ Schema Valid ≠ Business Valid
三、Structured Output 本质上是一份数据契约
传统后端开发对此其实并不陌生。
例如 Spring Boot API:
HTTP Request ↓ Request DTO ↓ Controller ↓ Service ↓ Response DTO
DTO 就是在定义系统之间的数据契约。
但很多早期大模型应用却是:
程序 ↓ Prompt ↓ 大模型 ↓ 自然语言 ↓ 程序重新猜测模型说了什么
Structured Output 的作用,就是把传统软件工程中的“数据契约”重新引入模型调用。
业务系统 ↓ Schema ↓ 大模型 ↓ Structured Output ↓ 数据校验 ↓ DTO ↓ 业务逻辑
因此可以这样理解:
Prompt 描述模型应该完成什么任务,Schema 描述程序能够接受什么结果。
两者解决的是不同问题。
四、案例:把自然语言转换成任务单
继续前面的案例。
用户输入:
8月14日前完成华东区客户回访方案, 优先级高,负责人张晨, 预算不超过5000元, 包括整理客户名单、执行回访和汇总结果。
Agent 最终希望得到:
{ "schemaVersion": "1.0", "status": "READY", "title": "华东区客户回访方案", "priority": "HIGH", "dueDate": "2026-08-14", "assignees": [ "张晨" ], "budget": { "amount": 5000, "currency": "CNY" }, "subtasks": [ { "title": "整理客户名单", "required": true }, { "title": "执行客户回访", "required": true }, { "title": "汇总回访结果", "required": true } ], "clarificationQuestions": [] }
这才是一份真正适合程序继续处理的数据。
例如:
Structured Output ↓ TaskTicket DTO ↓ 任务服务 ↓ 数据库 ↓ 任务中心
而不是让任务服务继续解析一段自然语言。
五、用 JSON Schema 定义模型可以返回什么
为了避免模型随意生成字段,可以进一步定义 JSON Schema:
{ "type": "object", "properties": { "status": { "type": "string", "enum": [ "READY", "NEEDS_CLARIFICATION" ] }, "title": { "type": "string" }, "priority": { "type": "string", "enum": [ "LOW", "MEDIUM", "HIGH", "URGENT" ] }, "dueDate": { "type": "string", "format": "date" }, "assignees": { "type": "array", "items": { "type": "string" } } }, "required": [ "status", "title", "priority", "dueDate", "assignees" ], "additionalProperties": false }
这样,“优先级”就不能随意变成:
较高 非常重要 P1 重要任务 High Priority
而只能从:
LOW MEDIUM HIGH URGENT
中选择。
这就是 Schema 相比“请返回 JSON”更重要的地方。
六、OpenAI:从 JSON Mode 到真正的 Structured Outputs
OpenAI API 可以很好地说明 Structured Output 的演进。
早期 JSON Mode:
{ "type": "json_object" }
主要解决:
保证模型输出合法 JSON。
Structured Outputs 则进一步通过 JSON Schema 定义结构,并能够要求严格匹配 Schema。OpenAI 当前文档明确建议,在支持的模型上优先使用 Structured Outputs,而不是旧的 JSON Mode。
例如使用 Responses API 时,可以把输出格式定义为 JSON Schema;OpenAI 当前 API 已将 Responses API 中的 Structured Outputs 配置放在 text.format 下,而 Chat Completions 仍可通过相应的 response format 配置结构化输出。
概念上可以简化成:
{ "type": "json_schema", "name": "task_ticket", "strict": true, "schema": { "...": "..." } }
模型生成时就不只是被要求:
“请尽量输出这样的 JSON”
而是被明确约束:
“你的输出必须遵守这份 Schema”
因此系统链路从:
Prompt ↓ 模型 ↓ JSON
逐渐变为:
Prompt + JSON Schema ↓ 模型 ↓ Structured Output ↓ DTO
这是一个非常重要的变化。
Schema 开始成为模型 API 的一部分,而不仅仅是 Prompt 中的一段文字说明。
七、DeepSeek:JSON Output + 应用侧 Schema 校验
DeepSeek 提供了一个很适合工程实践的另一种情况。
当前 DeepSeek API 可以通过:
{ "response_format": { "type": "json_object" } }
启用 JSON Output。
官方文档同时要求在 system 或 user Prompt 中明确包含 JSON 输出要求,并建议提供目标 JSON 格式示例;还需要合理控制最大生成 Token,避免 JSON 被截断。
例如:
from openai import OpenAI client = OpenAI( api_key="DEEPSEEK_API_KEY", base_url="https://api.deepseek.com" ) response = client.chat.completions.create( model="deepseek-chat", messages=[ { "role": "system", "content": """ 将用户需求转换为 json 任务单。 JSON格式: { "title": "...", "priority": "LOW|MEDIUM|HIGH|URGENT", "dueDate": "YYYY-MM-DD" } """ }, { "role": "user", "content": "8月14日前完成华东区客户回访方案,优先级高。" } ], response_format={ "type": "json_object" } )
这里需要特别注意:
DeepSeek JSON Output 可以帮助我们保证:
输出是 JSON
但应用层仍然应该继续:
JSON ↓ JSON Schema Validator ↓ DTO ↓ Bean Validation ↓ Business Validation
而不能认为:
JSON Output = 数据一定正确
这其实非常接近大量企业系统实际面对的情况。
因此,即使模型 API 本身没有提供和 OpenAI Structured Outputs 完全相同的 Schema 约束能力,也可以通过应用层建立完整的数据契约体系。
八、两种路线最后应该汇聚到同一个架构
无论使用 OpenAI 还是 DeepSeek,生产系统最终都不应该把模型输出直接交给业务 Service。
比较合理的架构是:
其中 OpenAI 可以更多依赖模型侧 Structured Outputs;
DeepSeek 可以更多依赖:
JSON Output + Prompt + 应用侧 JSON Schema Validator
但后半段仍然应该保持一致。
九、Java 应用真正需要的是 DTO,而不是 JSON 字符串
Java 项目不应该让大量业务代码围绕:
String json = ... JsonNode node = ...
不断手工读取字段。
更合理的是定义明确的数据类型:
public record TaskTicket( String schemaVersion, Status status, String title, Priority priority, LocalDate dueDate, List<String> assignees, Budget budget, List<SubTask> subtasks, List<String> clarificationQuestions) { public enum Status { READY, NEEDS_CLARIFICATION } public enum Priority { LOW, MEDIUM, HIGH, URGENT } public record Budget( BigDecimal amount, String currency) { } public record SubTask( String title, boolean required) { } }
最终形成:
LLM ↓ JSON ↓ Jackson ↓ TaskTicket ↓ Validator ↓ TaskService
如果使用支持 Schema 驱动 Structured Output 的模型,还可以进一步:
Java DTO ↓ JSON Schema ↓ Model ↓ JSON ↓ Java DTO
这样模型接口与 Java 类型系统之间就建立了更加稳定的映射关系。
十、TypeScript 中 Interface 为什么还不够
前端经常会定义:
interface TaskTicket { title: string; priority: "LOW" | "MEDIUM" | "HIGH" | "URGENT"; dueDate: string; }
但 TypeScript Interface 只存在于编译阶段。
下面的代码:
const result = JSON.parse(modelOutput);
并不会因为定义了 TaskTicket 就自动验证模型返回的数据。
因此 AI 应用边界更适合增加运行时 Schema,例如:
const TaskTicketSchema = z.object({ title: z.string(), priority: z.enum([ "LOW", "MEDIUM", "HIGH", "URGENT" ]), dueDate: z.string() });
再执行:
const task = TaskTicketSchema.parse(result);
形成:
模型输出 ↓ JSON ↓ Runtime Schema ↓ TypeScript Object ↓ 前端 / API
这也是 Structured Output 很重要的一点:
类型约束不能只存在于开发阶段,还应该存在于模型与程序的运行时边界。
十一、枚举、日期和金额是最容易出问题的字段
结构化输出并不只是定义几个 JSON Key。
真正进入业务系统时,很多基础类型都需要仔细设计。
1. 枚举
不要让模型自由生成:
高 较高 重要 P1 紧急
应该限制成:
LOW MEDIUM HIGH URGENT
然后再由前端负责国际化显示。
2. 日期
不要让数据库接收:
明天 下周 月底前 8月14号
应该转换为:
2026-08-14
如果无法确定年份,不应该由模型偷偷猜测。
3. 金额
建议将金额与货币分开:
{ "amount": 5000, "currency": "CNY" }
Java 中金额通常使用:
BigDecimal
而不是:
double
4. 嵌套对象
例如地址不要定义成:
{ "address": "北京市..." }
如果后续需要分别处理省、市、区,则应直接设计:
{ "address": { "province": "北京", "city": "北京", "district": "海淀区" } }
Structured Output 的数据结构最终仍然应该由业务模型决定,而不是由模型自由设计。
十二、Schema Valid 仍然不等于 Business Valid
这是 Structured Output 中最容易被忽略的问题。
假设模型生成:
{ "priority": "HIGH", "budget": { "amount": 5000000, "currency": "CNY" } }
它可能完全符合 JSON Schema。
但系统规定:
普通员工创建项目任务, 预算不能超过 50000 元。
那么这个结果仍然不能执行。
因此生产系统至少需要三层校验:
第一层 JSON Valid JSON 能否解析? ↓ 第二层 Schema Valid 字段和类型是否正确? ↓ 第三层 Business Valid 业务规则是否允许?
还可以继续增加第四层:
Permission Valid 当前用户有没有权限执行?
最后才是:
Execute
因此完整链路应该是:
Model ↓ Structured Output ↓ Schema Validation ↓ DTO Validation ↓ Business Validation ↓ Permission Check ↓ Execute
十三、模型输出错误时怎么办
即使使用 Structured Output,也不能删除异常处理。
错误大致可以分成三类。
第一类:格式错误
例如:
JSON 无法解析 字段缺失 枚举非法 类型错误
可以:
校验失败 ↓ 有限次数自动重试
第二类:可以确定性修复的问题
例如:
字符串首尾空格 日期格式规范化 金额格式转换
这类问题优先使用程序代码修复。
第三类:业务语义不确定
例如用户说:
尽快完成。
模型不能擅自变成:
{ "dueDate": "2026-08-11" }
更合理的是:
{ "status": "NEEDS_CLARIFICATION", "clarificationQuestions": [ "请确认任务的具体截止日期。" ] }
也就是说:
程序可以修复格式,但不要擅自修复业务含义。
十四、为什么不能无限重试模型
一种常见的实现方式是:
Schema 校验失败 ↓ 重新调用模型 ↓ 还失败 ↓ 继续调用
这种做法很容易形成不可控循环。
生产系统应该设置:
maxRetries = 1~3
超过次数后:
转人工 或 返回明确错误
因为连续输出错误可能说明:
Schema 太复杂 Prompt 不清楚 输入本身存在矛盾 模型能力不足 上下文存在污染
继续重复生成往往只是增加 Token 消耗。
这也为后面的 Agent Harness 埋下伏笔:
模型的不确定性必须由运行时系统进行约束,而不能期待模型自己永远正确。
十五、Structured Output 和 Function Calling 有什么关系
这是上一篇与本篇最重要的衔接。
Function Calling 主要解决:
Agent 要调用哪个程序,以及传什么参数。
Structured Output 主要解决:
Agent 最终应该按照什么格式把结果交给程序。
例如:
用户 “分析本周延期项目并输出风险清单” ↓ Agent ↓ Function Calling ↓ queryDelayedProjects() ↓ 业务系统 ↓ 延期项目数据 ↓ 模型分析 ↓ Structured Output ↓ RiskReport ↓ 前端 / 数据库 / 工作流
OpenAI 官方文档也明确区分了这两个场景:连接模型与系统工具时使用 Function Calling;需要约束模型最终响应的数据结构时,则使用 Structured Outputs。
可以进一步把二者理解为:
Function Calling Agent → Tool 输入契约
以及:
Structured Output Model / Agent → Application 输出契约
这两个方向共同构成 Agent 与软件系统之间的接口边界。
十六、OpenAI 与 DeepSeek 在工程上可以统一封装
企业系统很少应该把业务代码直接绑定某一家模型 API。
例如:
if (provider.equals("openai")) { ... } if (provider.equals("deepseek")) { ... }
到处出现这种代码会让后续模型切换非常困难。
更合理的是增加统一的 Model Gateway:
┌─ OpenAI 业务应用 → Model Gateway └─ DeepSeek
业务层只定义:
Prompt + Output Schema + Java DTO
Model Gateway 根据 Provider 能力选择实现。
例如:
OpenAI ↓ Native Structured Outputs ↓ Schema Validation
或者:
DeepSeek ↓ JSON Output ↓ Application Schema Validation
最终统一输出:
TaskTicket
这样业务 Service 不需要关心底层调用的是哪个模型。
十七、生产级 Structured Output 推荐架构
将前面的内容组合起来,可以得到一套比较完整的实现方式。
生产级 Structured Output 架构
这里有一个非常重要的设计原则:
模型 Provider 的差异应该被隔离在 Model Gateway,而不是扩散到业务层。
十八、生产环境中的几个建议
Structured Output 真正落地时,可以遵循以下原则。
第一,优先使用模型原生结构化能力。
OpenAI 支持 Structured Outputs 时,优先使用 JSON Schema,而不是只依赖:
请返回以下 JSON。
OpenAI 当前文档也明确推荐在支持的模型上优先使用 Structured Outputs,而不是旧 JSON Mode。
DeepSeek 则可以采用:
JSON Output + 明确 Prompt + 应用侧 Schema Validation
其官方文档还特别提醒,使用 JSON Output 时需要在 Prompt 中显式要求 JSON,并合理设置生成 Token,避免内容被截断。
第二,让业务类型驱动 Schema。
推荐:
Java DTO ↓ JSON Schema
而不是:
先随手写 Schema ↓ 再人工写 DTO ↓ 再人工维护 TypeScript Interface
避免三份数据结构逐渐不一致。
第三,Schema 尽量简单。
不要设计:
几十层嵌套 + 几十个 optional 字段 + 大量 oneOf / anyOf
如果一个输出对象已经极其复杂,往往意味着任务本身也应该被拆分。
第四,为 Schema 增加版本。
例如:
{ "schemaVersion": "1.0" }
因为 Structured Output 本质上也是一种 API Contract。
第五,高风险操作采用 Fail Closed。
如果模型返回结果无法确认:
不要执行
而不是:
猜一个最可能的答案然后继续。
尤其是:
付款 删除 权限调整 合同确认 邮件群发 审批 数据修改
这类带副作用的操作。
十九、Structured Output 真正改变了什么
如果只是为了在页面上显示答案,Structured Output 的价值似乎并不突出。
但进入 Agentic AI 后,系统中的数据流正在变成:
用户 ↓ Agent ↓ Tool ↓ Agent ↓ Workflow ↓ Agent ↓ Business API ↓ Database
模型已经不再只是最后一个“输出文字”的组件。
它开始位于整个业务执行链路中。
因此模型返回的数据必须越来越像传统 API:
可解析 可验证 可版本化 可测试 可监控 可拒绝
这也是 Structured Output 真正重要的地方。
它不是一种让 JSON 更漂亮的技术,而是在:
概率性的模型系统与确定性的业务系统之间建立数据边界。
二十、本篇小结
从普通大模型应用进入 Agent 开发后,我们需要逐渐改变一个习惯:
不要再把模型输出仅仅看作“一段回答”。
很多情况下,它实际上已经成为:
下一个程序节点的输入
Structured Output 的发展过程可以概括为:
自然语言 ↓ Prompt 指定格式 ↓ JSON Output / JSON Mode ↓ JSON Schema ↓ Native Structured Outputs ↓ DTO / Type-safe Mapping ↓ Business Validation
真正需要记住的是:
JSON Valid ≠ Schema Valid ≠ Business Valid
JSON Valid 只能说明程序能够解析;
Schema Valid 说明数据结构符合契约;
Business Valid 才说明这份数据真的可以进入业务流程。
因此生产级 Agent 不应该是:
模型 ↓ JSON ↓ 直接执行
而应该是:
模型 ↓ Structured Output ↓ Schema Validation ↓ DTO Validation ↓ Business Validation ↓ Permission Check ↓ Execute
上一篇回顾:
https://developer.aliyun.com/article/1753887?spm=a2c6h.13148508.setting.15.5f284f0e3OGmOs
下一篇将进一步介绍:
下一篇将进入另一个 Agent 应用中几乎绕不开的基础能力——RAG。
因为当 Agent 已经能够调用工具,也能够稳定输出程序可处理的数据后,接下来的问题就是:
模型如何获得自己训练数据之外、企业内部或者实时变化的知识?
这也将从模型与程序的连接,进一步进入模型与知识的连接。
— 持续更新 · 欢迎关注 —