Skip to content

Q26 · 结构化输出有哪些常见做法?各自的优缺点是什么? ​

一个客服 Agent 查完订单 A123 和售后政策,最终要把判断交给后台页面。页面需要知道订单号、是否可以提交人工审批、依据是什么,以及下一步由谁处理。如果模型只写“这位用户看起来可以退,建议跟进”,人能大致读懂,程序却很难可靠地找出字段:它说的“可退”是“可申请”,还是“已经批准”?订单号从哪段话里取?

结构化输出就是事先约定一份机器可读的结果合同,让模型把信息放进固定字段。例如本题可以用 order_id 放订单号、decision 放申请资格状态、reason 放依据摘要、source_id 放政策来源、next_action 放下一步。它解决的是输出能否稳定交给程序处理,不自动解决“模型说的事实是否真的正确”。同一份错误结论,即使写成完全合法的 JSON,仍然是错误结论。

下面用同一笔虚构 A123 咨询比较几种做法。假设今天是 2026-09-26,订单耳机于 2026-09-23 签收且未拆封,当前政策版本 P-2026-09 规定“签收后 7 天内且未拆封,可以提交人工退款审批”。因此合适的状态是“可申请”,不是“已退款”;政策查不到时则必须标记“待核对”。这些仅为教学数据,不代表任何商家的实际规定。

先认清术语和字段名 ​

术语或字段用大白话解释A123 中的值或作用
JSON用花括号、键和值表示数据的文本格式{"order_id":"A123"}
解析 / Parse程序按 JSON 语法把文本读成数据识别 order_id 对应的字符串 A123
Schema描述字段名、类型、是否必填和允许取值的约定要求 decision 只能是几个状态之一
JSON Schema一种广泛使用的 JSON 数据约束格式写明 decision 是枚举,所有字段必填
order_id本次要判断的订单编号A123;应用应和服务端已授权订单核对
decision申请资格结论,不是退款执行状态can_apply、needs_review、cannot_apply
reason用于解释结论的简短理由“签收 3 天且未拆封”
source_id本次实际核对的政策版本标识P-2026-09
next_action业务上下一步该如何处理submit_for_human_review
枚举 / Enum字段只能从给定选项里选decision 的三个允许值
必填 / Required字段不能省略没有 order_id 的对象不能通过合同检查
额外字段Schema 没列出、模型却临时增加的键突然出现 refund_amount 可能误导下游
工具参数 / Tool arguments模型请求应用执行工具时填的输入值请求 check_order({"order_id":"A123"})
业务校验程序与真实数据、权限和规则核对不能只因 decision=can_apply 就发起退款

can_apply 是本文约定的英文状态码,意思是“可提交申请”;needs_review 是“资料不足或冲突,待人工核对”;cannot_apply 是“按当前已核实规则不满足申请条件”。submit_for_human_review 也是本文的下一步代码值,表示进入人工审批。它们不是某个框架内置常量,写成英文是为了前后端约定不随文案变化。程序执行退款是另外一个需要身份、规则和审批的动作,不在这个输出对象里。

先把返回合同写清楚 ​

按上面的假设,理想结果可以长这样:

json
{
  "order_id": "A123",
  "decision": "can_apply",
  "reason": "签收 3 天,商品未拆封;符合提交人工审批的初步条件",
  "source_id": "P-2026-09",
  "next_action": "submit_for_human_review"
}

这是一份申请资格说明。前端看到 can_apply 可以显示“可提交申请”,看到 next_action 可以提供进入人工审批流程的入口;它不得把这份 JSON 直接转成“退款成功”。reason 是给人看的摘要,source_id 用来回查真实政策记录。若政策工具没有返回版本,source_id 不能凭模型记忆补一个值,应走 needs_review,并把缺证据原因交给应用记录。

对应的简化 JSON Schema 如下。type: object 表示整体是字段对象;properties 列出允许的键;每个子字段的 type: string 说明值必须是字符串;enum 限定状态码;required 列出必须存在的键;additionalProperties: false 表示不能临时加入未约定的新键。

json
{
  "type": "object",
  "properties": {
    "order_id": {"type": "string"},
    "decision": {
      "type": "string",
      "enum": ["can_apply", "needs_review", "cannot_apply"]
    },
    "reason": {"type": "string"},
    "source_id": {"type": "string"},
    "next_action": {
      "type": "string",
      "enum": ["submit_for_human_review", "ask_for_missing_info", "no_automatic_action"]
    }
  },
  "required": ["order_id", "decision", "reason", "source_id", "next_action"],
  "additionalProperties": false
}

这只是字段形状。它允许 {"order_id":"A123","decision":"can_apply",...},也可能允许“订单明明已经超过 7 天,模型仍写 can_apply”——因为 Schema 不知道订单系统实际发生了什么。source_id 是字符串,不代表该版本真的存在。业务系统必须继续校验。若要支持“没有政策来源”的状态,可以把 source_id 设计成允许 null,或约定空字符串并附上缺失原因;每个提供商的严格 Schema 支持范围不同,不能把示例语法未经核对原样复制到所有 API。

JSON 合法、字段齐全之后,仍要用订单与政策核对事实

图从左到右是三关:能解析为 JSON、字段符合约定、事实和业务规则被核实。 前两关可能由模型 API 或解析器帮忙,最后一关必须依赖可信订单、政策及程序控制。图里的“可退款”正是待核对的模型说法,不能把它当成已执行退款。

只在提示词里约定格式 ​

第一种做法最简单:在 Prompt 里写“只返回 JSON,字段为 order_id、decision 等,不要额外解释”,并放一个样例。它几乎不依赖提供商专用功能,适合快速原型,或者输出格式允许少量人工处理时使用。也可以要求 Markdown 表格、CSV、XML,但后台要自动处理退款状态时,JSON 的字段合同通常更方便。

例如:

text
请根据已提供的订单事实和现行政策,返回一个 JSON 对象。
只使用 order_id、decision、reason、source_id、next_action 五个字段。
decision 只能是 can_apply、needs_review 或 cannot_apply。
“可以申请”不等于“已退款”;证据缺失时用 needs_review。

优点是容易试验、迁移方便;缺点是模型可能不遵守。它可能在 JSON 前加“好的,以下是结果”,漏掉 source_id,把 decision 写成“应该可以吧”,甚至给出半截对象。让模型“只返回 JSON”是一个概率性指令,不是语法锁。Anthropic 的输出一致性指南把明确格式、示例等提示方法用于提高一致性,但需要严格符合 Schema 时,建议使用真正的结构化输出能力。

因此即便是原型,也要在应用端做 JSON 解析和字段检查;解析失败时应报告失败、限制次数后重试,或交人工处理。不要从一段格式混乱的文字里用正则表达式“随便抓到 can_apply 就放行”。

JSON 模式只保证语法范围 ​

第二种做法是调用模型 API 的 JSON mode:向接口声明这次希望输出合法 JSON。它降低“缺括号、代码围栏、开头多一句话”等问题,对只需要“可解析 JSON”且字段约束较轻的场景有用。但它不保证一定包含 source_id,也不保证 decision 落在三个允许值里。下面两段都是合法 JSON,第二段却不符合我们的合同:

json
{"order_id":"A123","decision":"can_apply","reason":"签收 3 天且未拆封","source_id":"P-2026-09","next_action":"submit_for_human_review"}
json
{"order_id":"A123","decision":"大概能退","reason":123}

第二段的问题有三处:decision 不在约定枚举中,reason 是数字而非字符串,还缺了两个必填字段。它被 JSON 解析器接受,不代表业务系统能使用。OpenAI 结构化输出指南明确区分 JSON 模式与 Schema 约束:两者都力求输出合法 JSON,只有后者要求符合给定字段约定。JSON mode 的具体请求参数随 API 而变,接入时应看所用模型和接口的当前文档。

从工程角度,JSON 模式的优点是比单纯 Prompt 更稳定,又比设计完整 Schema 简单;缺点是把“字段存在、类型、枚举、额外键”留给下游。程序仍须自己做这些检查,还要考虑模型拒答、输出长度限制等边界,不能假定每次都收到完整对象。

Schema 约束把字段合同提前交给模型 ​

第三种做法是使用提供商支持的严格结构化输出:在请求里直接附 JSON Schema,模型生成时按支持的约束输出,或由提供商 SDK 把指定数据类转成 Schema。对本题,它能帮我们稳定得到 order_id、decision、reason、source_id、next_action,并防止类型和枚举乱跳。OpenAI 的Structured Outputs 文档把它与 JSON mode 区分,并提供 json_schema 格式;Anthropic 的Structured outputs 文档也描述了用 Schema 约束最终 JSON 以及严格工具参数的两类能力。

优点是解析与字段一致性更稳,前后端可以围绕同一合同设计;缺点是要维护 Schema,可能受到所用 API 支持的字段类型、嵌套深度或模型版本限制。Schema 过度复杂会变得难维护,也可能增加请求处理成本。先把合同设计小而明确:退款资格只需三个状态,不必一上来让模型生成十几层业务对象。

“严格”也要读清范围。它承诺的是所支持条件下的结构符合性,不是最终结论百分百正确;拒绝回答、输出被截断、请求配置不兼容、服务报错仍要有处理分支。对 A123,decision=can_apply 仍需与订单数据和政策版本核对。若模型把 source_id 填成一个语法合格但不存在的 P-XXXX,Schema 可能让它通过,业务校验必须拦住。

在 Java 项目里还会见到一类“输出转换器”。Spring AI 2.0.1 的Output Converters 文档说明:转换器可在 Prompt 中加入格式指令,再尝试把模型文字解析成 Java 对象;它是尽力转换,不能仅因 .entity(...) 返回一个对象就推断提供商进行了严格 Schema 约束。是否启用原生结构化输出,要核对具体模型与框架配置。这个区别在面试里很容易被问到:解析成对象和生成时强约束不是同一步。

工具调用的结构化参数另有用途 ​

第四种做法是函数或工具调用。应用声明工具,例如只读的 check_order(order_id);模型如果选择调用,需给该工具填参数。工具参数天然有名称与结构,某些 API 可再对参数启用严格 Schema 校验。但这个结构化对象的用途是请求应用执行一个动作,不一定是发给用户的最终答复。OpenAI 文档也区分“用函数调用连接应用工具”和“用最终响应格式生成结构化答案”。

对 A123,合理顺序是:模型请求 check_order,应用验证用户有权查询后执行,得到签收日期;再查现行政策,最后生成 decision 对象。模型写出 check_order({"order_id":"A123"}) 时,只是提议调用,并没有自己进入订单数据库,更没有取得退款权限。即使工具参数符合 Schema,也必须由服务端核验订单归属、请求频率和允许动作;工具结果里的网页或文档文字也只是数据,不能改写应用规则。

工具调用的优点是把“模型选择什么动作、填什么参数”纳入可观察的接口,适合 Agent 与外部系统交互;缺点是增加了执行与授权管理,而且“工具参数结构化”不保证最终自然语言或最终 JSON 也结构化。如果只是要前端显示一份固定的 JSON 卡片,不必硬塞一个没有实际动作的“假工具”;直接用最终响应的结构化格式更贴合用途。

应用校验、修复与重试始终不能省 ​

前四种做法可以从弱到强改善输出形状,应用端仍要设三层检查:

  1. 解析与字段。 是否是完整对象?五个字段是否齐全?类型与枚举是否符合?不符合则记录具体错误,允许有限次数重试或停止。
  2. 跨字段一致性。 decision=needs_review 时不应同时给 next_action=submit_for_human_review 并写“已批准”;状态与下一步之间的约束可由程序明文判断。具体约束由业务定义,不能凭模型自己“觉得合理”。
  3. 真实业务事实。 order_id 与当前已授权请求是否一致?source_id 是否来自本次查到的有效政策?政策条件与订单日期是否确实支持 decision?真正执行退款必须进入独立的认证和审批服务。

下面的 Python 标准库示例只演示这三层中的解析、字段与一条业务一致性检查。raw 是模型返回的原始文本,json.loads 负责把它读成 Python 字典,required 是必须的五个字段,allowed 是允许的状态。真实项目应使用完整的 JSON Schema 验证器,并从服务端传入已授权订单及已核实政策;本例把它们写成固定值只是让读者看到检查动作。

python
import json

raw = '''{"order_id":"A123","decision":"can_apply",
"reason":"签收 3 天且未拆封","source_id":"P-2026-09",
"next_action":"submit_for_human_review"}'''

required = {"order_id", "decision", "reason", "source_id", "next_action"}
allowed = {"can_apply", "needs_review", "cannot_apply"}
authorized_order_id = "A123"
verified_policy_id = "P-2026-09"

data = json.loads(raw)
if not isinstance(data, dict) or set(data) != required:
    raise ValueError("字段缺失或出现额外字段")
if not all(isinstance(value, str) for value in data.values()):
    raise ValueError("字段类型不对")
if data["decision"] not in allowed:
    raise ValueError("申请资格状态不在允许范围")
if data["order_id"] != authorized_order_id:
    raise ValueError("结果与当前已授权订单不一致")
if data["source_id"] != verified_policy_id:
    raise ValueError("政策来源并非本次核实的版本")
print("字段与来源检查通过;还需核对具体政策条件")

沿本题逐步执行:json.loads 得到五个键;字段名集合等于 required,五个值都是字符串;decision 在 allowed 中;订单号与政策版本也与服务端事实一致,所以打印最后一行。这段代码仍未验证“签收 3 天且未拆封是否真的满足 7 天规则”;最后一行特意保留“还需核对具体政策条件”,提醒开发者别把几项字段检查误说成完整资格审批。如果把 raw 改成 "decision":"已退款",会在枚举检查处停下;把订单号改成 B999,会在授权订单核对处停下。

模型可能给出拒答、半截输出或完全不相关的内容。JSON 解析失败时不能继续调用退款接口;先记录错误类型与请求标识,对可恢复的格式错误最多重试有限次数,仍失败就进入明确的人工或安全退路。不要无限把同一错误答案喂回模型修复,那会形成重试循环和额外成本。即使用严格结构化输出,业务规则、权限、来源核验也不能省。OpenAI Structured Outputs 文档、Spring AI Output Converters

选哪种方式,先看下游到底需要什么 ​

做法能保证或改善什么优点主要局限合适的起点
Prompt 约定格式提高模型按样例写字段的概率简单、通用、易试验格式可能漂移,必须解析/校验探索原型、低风险可人工核对
JSON 模式让输出成为合法 JSON减少语法错误不约束字段与业务含义只需可解析对象且下游自己验证
严格 Schema 输出在支持范围内约束字段、类型、枚举稳定的接口合同维护 Schema、受模型/API 限制,事实仍可能错机器直接消费的正式结果
严格工具参数约束模型调用工具时的参数工具接入清楚、可追踪不是最终答复;执行还需授权查询订单、政策等外部动作
应用端校验与回退用真实数据和规则拦下错误守住业务事实与权限需要真实服务、规则和维护所有要进入业务流程的方案

表里的做法并非只能五选一。一个实际系统可以用严格工具参数查询订单,再用严格 Schema 生成前端卡片,最后由应用端校验订单归属和政策规则。若模型供应商不支持严格 Schema,则在 Prompt/JSON 模式上加解析、校验与有限重试。每次改 Schema 也要做回归:旧前端是否认识新字段?缺失政策时是否仍能用 needs_review 表示?不同模型对同一 Schema 的拒答与延迟是否变化?

面试时可以这样回答 ​

结构化输出是让模型按程序约定的字段返回结果。常见做法从弱到强包括提示词规定 JSON 样式、API 的 JSON 模式、提供 JSON Schema 的严格结构化输出,以及把结构化参数用于函数或工具调用。提示词最通用,但只是约束意图;JSON 模式解决语法,不保证字段;严格 Schema 能约束字段和类型,但不保证业务事实;工具调用的结构化对象是让应用执行工具时的参数,不等于最终给用户的 JSON。比如客服判断 A123 是否可提交退款申请,我会定义订单号、资格状态、理由、政策来源和下一步,优先用支持的 Schema 能力生成结果,解析后再核对订单归属、当前政策与状态含义。缺字段、拒答、来源不一致或规则无法确认时停止自动流程并转人工,绝不把模型写出的 can_apply 当成退款已执行。

若追问“Spring AI .entity(...) 是不是严格生成”,回答要看具体配置:输出转换器可以通过格式提示和解析得到 Java 对象,官方明确它是尽力转换,不能仅凭返回对象就推断底层启用了原生严格 Schema。若追问“严格 Schema 还会错吗”,可以用图回答:{"decision":"can_apply"} 可能格式完全合规,却与签收超过 7 天的真实订单矛盾,仍需程序和证据核验。

参考资料 ​

章节首页 · ← Q25

最后更新2026-09-26
难度P1
频率high
阅读19 min
主题structured-output / json-schema / tool-calling
觉得有帮助?把这个链接转给正在求职的朋友 · 用 Ctrl + K 全站搜索其它题