【第二部分:大模型应用开发基础】8.Structured Output——让模型输出可被程序可靠处理的数据
上一篇介绍了 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 OutputsJSON 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 APIHTTP Request ↓ Request DTO ↓ Controller ↓ Service ↓ Response DTODTO 就是在定义系统之间的数据契约。但很多早期大模型应用却是程序 ↓ 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 OutputsOpenAI API 可以很好地说明 Structured Output 的演进。早期 JSON Mode{ type: json_object }主要解决保证模型输出合法 JSON。Structured Outputs 则进一步通过 JSON Schema 定义结构并能够要求严格匹配 Schema。OpenAI 当前文档明确建议在支持的模型上优先使用 Structured Outputs而不是旧的 JSON Mode。例如使用 Responses API 时可以把输出格式定义为 JSON SchemaOpenAI 当前 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 中的一段文字说明。七、DeepSeekJSON 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_keyDEEPSEEK_API_KEY, base_urlhttps://api.deepseek.com ) response client.chat.completions.create( modeldeepseek-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 OutputsDeepSeek 可以更多依赖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, ListString assignees, Budget budget, ListSubTask subtasks, ListString 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而不是double4. 嵌套对象例如地址不要定义成{ 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 DTOModel 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 ValidJSON Valid 只能说明程序能够解析Schema Valid 说明数据结构符合契约Business Valid 才说明这份数据真的可以进入业务流程。因此生产级 Agent 不应该是模型 ↓ JSON ↓ 直接执行而应该是模型 ↓ Structured Output ↓ Schema Validation ↓ DTO Validation ↓ Business Validation ↓ Permission Check ↓ Execute上一篇回顾【第二部分大模型应用开发基础】7.Function Calling让大模型调用真实程序能力-CSDN博客下一篇将进一步介绍下一篇将进入另一个 Agent 应用中几乎绕不开的基础能力——RAG。因为当 Agent 已经能够调用工具也能够稳定输出程序可处理的数据后接下来的问题就是模型如何获得自己训练数据之外、企业内部或者实时变化的知识这也将从模型与程序的连接进一步进入模型与知识的连接。