AI 应用高级工程师 · 第 02 / 12 章

结构化输出
与模型结果校验

自然语言是给人看的,结构化数据是给程序用的。本章解释怎样让模型产出可解析的数据,以及为什么“JSON 合法”仍远远不等于“业务结果校验”。

JSON Schema双层校验失败恢复安全执行

从第1章自然走到第2章

上一章已经会后端把规则、问题和资料放进请求,调用模型得到回答
→
现在遇到的问题模型返回自然语言,程序不知道订单号、意图和金额分别在哪里
→
本章解决让模型填写固定数据,并经过多层校验后才能用于业务
本章默认方案Prompt 解释业务任务;API 的 Structured Output/Schema 参数限制数据结构;程序再做结构、事实、权限和实时状态校验。高风险操作中,模型只提议,不直接执行。

路线12 章中的位置

01 模型与上下文 ✓
02 结构化输出
03 Embedding 与向量
04 RAG 数据工程
05 检索、Rerank 与评测
06 工具调用
07 Agent 与工作流
08 状态与记忆
09 评测与观测
10 AI 安全
11 成本与性能
12 部署与 LLMOps

本章目录

阅读过程中可按 Ctrl + Z 快速返回当前页面的本章目录;也可以点击右下角按钮。

本章思维导图

知识流向:先完成左侧基础,再进入中间机制,最后用右侧工程控制把它变成可上线能力。节点可直接点击跳转。

MODULE 01

结构化数据基础

按照本模块顺序完成以下知识点,再进入下一模块。

S00结构化输出在 API 调用链中的位置

Prompt
说明提取什么
response_format
传入 Schema
模型
填写 JSON
后端
解析与校验
业务系统
查询或执行
response = 模型SDK.生成(
    model = "支持结构化输出的模型",
    messages = [用户的退款描述],
    response_format = 退款意图Schema
)

result = 解析(response)
校验Schema(result)
校验订单数据库(result)
校验当前用户权限(result)

谁设置:AI 应用工程师在后端代码中设置。何时生效:每次模型生成答案时。输入:Prompt 加 Schema。输出:符合指定外形的 JSON,或者拒答、截断、API 错误。

一定分清:Schema 约束“表单外形”,不会去订单数据库核对“表单内容是否真实”。

生活中的例子:结构化输出像网购结算页:顾客描述需求后,系统要求按固定收货表单填写,再核对地址和付款状态。

S01固定字段与结构化输出的必要性

自然语言

“看起来像是退款问题,建议尽快处理,金额大概是299元。”

人能看懂,但程序不知道意图、金额和是否应建工单分别在哪里。

结构化数据
{
  "intent": "refund",
  "amount": 299,
  "create_ticket": true
}

表格类比:自然语言像顾客写的一段留言;结构化输出像填写固定表格。程序只有拿到稳定字段,才能继续查询订单、分配客服或创建工单。

生活中的例子:自然语言像顾客口头说“寄到老地方”,程序需要固定的姓名、电话、城市、街道字段才能处理。

S02用户语言到业务对象的转换

用户原话
“鞋子小了,退掉”
→
模型理解
提取与分类
→
结构化对象
intent=refund
→
程序校验
再决定行动

模型擅长把模糊语言转换为固定字段;程序擅长检查字段并执行确定规则。成熟系统让二者各做擅长的部分。

生活中的例子:像前台把顾客一句话整理成工单:问题类型、订单号、金额各放进自己的栏目。

S03JSON 数据格式基础

JSON 不是三个不同的东西。JSON 是一种把信息写成“程序容易读取的固定表格”的文本格式;JSON Mode 和 JSON Schema 是围绕 JSON 的两种控制手段。

人平常说的话

“订单 A10086 的顾客想退款,金额是 299 元,需要人工处理。”

人能看懂,但程序很难稳定判断哪一段是订单号、金额和处理方式。

同一信息写成 JSON

{
  "order_id": "A10086",
  "intent": "refund",
  "amount": 299,
  "need_human": true
}

快递单类比:JSON 就是一张填写好的快递单。左边的 order_id、amount 是栏目名称;右边的 A10086、299 是实际填写的值。

JSON 写法小白解释例子
{ }一整张信息表一条订单信息
"字段名": 值栏目名称和填写内容"amount": 299
字符串文字,要放在双引号中"refund"
数字可以计算的数,不加引号299
布尔值只有真或假true / false
null明确表示目前没有值"order_id": null
生活中的例子:JSON像一张已经填写完成的电子快递单,程序可以准确找到每个栏目。

S04JSON、JSON Mode 与 JSON Schema 的边界

概念解决的问题仍然不能保证
提示“请输出 JSON”只是文字要求可能夹带解释、漏字段或格式损坏
JSON Mode主要保证能解析成合法 JSON字段名、类型和业务含义一定正确
JSON Schema约束对象结构、字段、类型、枚举和必填项数据符合真实业务、当前状态和用户权限

Schema 是“表格模板”,不是“事实审计员”。它能要求金额必须是数字,却不知道真实退款金额到底是不是 299 元。

生活中的例子:像表格能正常打开不代表身份证号真实;外形正确和内容正确是两件事。

S05结构化输出的三层约束

一句提示
“请填快递单”
→
JSON Mode
保证像一张可读表
→
JSON Schema
规定有哪些栏目
→
业务校验
核对内容真假
“请输出 JSON”:你只是口头告诉模型“请按表格写”。模型可能听话,也可能在表格前后加解释,甚至漏填。
JSON Mode:系统尽量保证返回的是一张程序能打开的 JSON 表,不出现括号残缺等语法问题,但栏目仍可能不符合你的业务。
JSON Schema:你提前给出空白表格模板:必须有订单号、意图和金额;金额必须是数字;意图只能在几个选项中选择。
业务校验:程序拿这张表去订单数据库核对:订单是否存在、是不是当前用户的、实付金额是否真是 299 元。

JSON 是填好的表 | JSON Mode 保证表能打开 | Schema 规定表的栏目 | 业务校验核对内容真假

生活中的例子:JSON Mode像保证快递单纸张完整可读;Schema像规定必须有哪些栏目、每栏填什么。
MODULE 02

API 结构化输出

按照本模块顺序完成以下知识点,再进入下一模块。

S06结构化输出完整示例

顾客说:“订单 A10086 的鞋子小了,我想退掉,花了 299 元。”程序希望模型把这句话整理成固定字段。

第一层:只在 Prompt 里说“请输出 JSON”。

这只是一句普通要求,类似经理口头说“请按表格交给我”。模型可能返回:

好的,整理结果如下:
{
  "order_id": "A10086",
  "intent": "refund",
  "amount": 299
}
希望能帮到你。

里面确实有 JSON,但前后夹着解释。只想读取 JSON 的程序可能因此解析失败。

第二层:开启 JSON Mode。

这相当于规定:“只能交一张程序能打开的表,不准在前后写客套话。”模型会返回合法 JSON:

{
  "dingdan": "A10086",
  "what_to_do": "退货",
  "money": "二百九十九"
}

语法是合法的,程序可以打开;但字段名是模型临时想的,金额还是文字,可能与程序期待的字段不一致。

第三层:提供 JSON Schema。

开发者提前规定空白模板:必须有 order_id、intent、amount;订单号必须是文字;意图只能是 refund、shipping 或 other;金额必须是数字。

模型按模板填写:

{
  "order_id": "A10086",
  "intent": "refund",
  "amount": 299
}

现在程序既能打开,也知道每个字段在哪里、是什么类型。

第四层:业务校验。

程序再去真实订单数据库核对:A10086 是否存在、是否属于当前用户、实付金额是否为 299、是否在退款期限内。只有这一步才开始判断内容在现实中是真是假。

JSON = 填好的具体表单 | JSON Mode = 保证表单语法能打开 | Schema = 事先规定空白模板和填写规则

生活中的例子:同一笔退款就像同一张申请表,口头要求、空白模板、填好数据和财务核对是四个不同环节。

S07JSON Schema 字段约束

{
  "type": "object",
  "properties": {
    "intent": {
      "type": "string",
      "enum": ["refund", "shipping", "other"]
    },
    "order_id": {"type": ["string", "null"]},
    "confidence": {"type": "number", "minimum": 0, "maximum": 1},
    "need_human": {"type": "boolean"}
  },
  "required": ["intent", "order_id", "confidence", "need_human"],
  "additionalProperties": false
}

type

规定对象、字符串、数字、布尔值、数组或空值。

required

规定哪些字段不能缺失;字段允许为空和字段可以不出现是两件事。

enum

把分类结果限制在程序认识的固定集合中。

additionalProperties

禁止模型临时发明程序不认识的新字段。

生活中的例子:Schema像报名表规则:年龄必须是数字、项目只能选规定选项、必填项不能空。
MODULE 03

业务结果校验

按照本模块顺序完成以下知识点,再进入下一模块。

S08可空字段与可选字段

字段存在,但值为 null

{"order_id": null}

程序明确知道模型没有找到订单号。

字段完全不存在

{"intent": "refund"}

可能代表允许省略,也可能是模型漏填;程序必须根据 Schema 判断。

工程建议:对于下游程序一定会读取的重要字段,通常让字段始终存在;未知时明确返回 null,比忽然消失更容易处理。

生活中的例子:“可选”像可以不带附件;“可空”像附件栏目必须存在但可以写“暂无”,两者不同。

S09结构化配置位置与责任边界

“表格”只是类比。真实开发时,是你的后端程序通过模型厂商提供的 SDK 或 HTTP API 调用模型。开发者准备请求,模型生成响应,响应再回到你的程序。

开发者的程序
准备 API 请求
→
请求里设置
响应格式
→
模型生成
JSON 结果
→
程序解析、校验
再处理业务

JSON:通用数据格式,不是模型开关

JSON 不是 AI 独有的,也不是一个 API 参数。它是一种记录和传递数据的写法,可以出现在 API 请求和返回结果中。结构化输出讨论的是:让模型把答案用 JSON 格式交回程序。

{
  "intent": "refund",
  "order_id": "A10086",
  "amount": 299
}

JSON Mode:调用模型 API 时设置的响应格式选项

开发者调用模型时,除了传入模型名称和聊天消息,还可以选择希望模型用 JSON 格式返回。不同厂商和 SDK 的参数名称不同,下面是用于理解的伪代码:

response = 模型客户端.生成(
    model = "某个模型",
    messages = 聊天消息,
    response_format = "json"   // 在这次 API 调用中开启 JSON Mode
)
问题答案
谁设置?编写后端程序的 AI 应用工程师
放在哪里?调用模型 API 的请求参数中
何时生效?模型处理这次请求、生成答案时
返回什么?语法上可以被解析的 JSON
仍可能怎样失败?字段名、类型或内容不符合业务需要

JSON Schema:开发者编写并随 API 请求传入的字段规则

开发者根据后续程序需要,先写出字段模板:

schema = {
  "type": "object",
  "properties": {
    "intent": {"type": "string", "enum": ["refund", "shipping", "other"]},
    "order_id": {"type": "string"},
    "amount": {"type": "number"}
  },
  "required": ["intent", "order_id", "amount"]
}

调用模型时,再把 Schema 放进结构化响应相关参数:

response = 模型客户端.生成(
    model = "某个模型",
    messages = 聊天消息,
    response_format = {
        "type": "json_schema",
        "schema": schema
    }
)

模型生成时按 Schema 填字段;返回后,程序还可以用同一份 Schema 再检查一次。随后必须查询真实订单、检查权限和业务规则。

所在位置一句话:JSON Mode 和 JSON Schema 都是在“你的程序调用模型 API”时由开发者设置的;它们控制模型怎样把答案交回程序,不是用户界面的按钮,也不负责证明答案为真。

生活中的例子:像开发者在点餐机器的设置页面选择“必须按套餐格式返回”,而不是只对服务员口头提醒。

S10Schema 的 API 参数配置

首选:放在模型 API 专门提供的结构化输出参数里。不同厂商可能把它叫做 response_format、structured_output 或其他名称,具体写法看所用 SDK,但它属于请求配置,不是普通聊天文字。

较弱:只放 Prompt
System Prompt:
请返回 JSON,必须有 intent 和 amount,
amount 必须是数字。

这只是自然语言要求。模型可能漏字段、改字段名或添加解释。

正式:放 API 参数
调用模型(
  messages = [...],
  response_format = {
    "type": "json_schema",
    "schema": schema
  }
)

模型服务把 Schema 当作结构化输出约束,通常比单纯写进 Prompt 稳定。

Prompt 还负责什么

Prompt:
intent 表示用户真正想办理的业务;
只有明确要求退款时才填 refund;
没说金额就返回 null,不要猜。

Schema 负责什么

intent 必须是字符串,只能选 refund/shipping/other;
amount 必须是数字或 null;
两个字段必须存在。
Prompt
解释怎样判断
+
API 参数中的 Schema
约束结果长相
→
程序再次校验
事实、状态和权限

为什么有些教程把 Schema 写进 Prompt?可能是所用模型/API 不支持原生结构化输出,只能退而求其次;也可能是在补充字段的业务含义。API 原生支持 Schema 时,应优先使用专门参数。

生活中的例子:Prompt像表格旁的填写说明,API参数里的Schema像系统锁定的表格栏目。

S11模型 API 兼容性

截至 2026-09-15,下面这些主流平台的官方 API 已提供 Schema 结构化输出。这里列的是适合建立认识的主流系列,不应把它当作永久不变的完整名单;模型版本和接口会更新,写项目时仍要查看所选模型的官方能力页。

平台当前能力示例大致放置方式
OpenAI当前 GPT 系列中支持 Structured Outputs 的模型;常见项目使用 GPT-5.x、GPT-4.1、GPT-4o 系列的相应 API 能力response_format/json_schema 或 Responses API 的结构化文本格式;严格模式只支持 JSON Schema 子集
Google Gemini当前 Gemini 3.x,以及 Gemini 2.5 Pro、Flash、Flash-Lite 等系列的结构化输出接口响应格式设为 JSON,并在 schema 字段传规则;支持 JSON Schema 子集
Mistral提供 JSON Mode 与 Custom Structured Outputs自定义 Schema 作为结构化输出请求配置
xAI Grok例如 Grok 4.6 的 Structured Outputsresponse_format.type=json_schema,并传入 Schema
CohereCommand A/A+、Command R/R+ 等官方列出的兼容系列Chat API 的 response_format;工具场景另有严格工具参数

同一厂商并非所有旧模型、实时语音模型、图片模型或每个端点都支持同一种 Schema 参数。选择模型时,不能只看模型名称,还要看“所用端点 + SDK 版本 + Structured Outputs 能力”。

“保证符合 Schema”究竟保证什么

可以强保证的结构层
  • 返回是合法 JSON
  • 必须字段存在
  • 字段类型符合支持的规则
  • 枚举值来自指定集合
  • 通常不会凭空增加禁止的字段
不能保证的语义层
  • 用户意图一定识别正确
  • 提取的金额一定是真实金额
  • 订单一定属于当前用户
  • 资料没有过期
  • 这项操作有权限执行
用户说:“这个我不想要了。”

Schema 可以保证模型只能返回:
{"intent": "refund"} 或 {"intent": "other"}

但 Schema 不能保证模型一定选对 refund 还是 other。

还要处理的例外:模型可能拒绝回答;响应可能因长度或网络中断而不完整;Schema 使用了厂商不支持的关键字时,请求可能报错;API 本身也可能超时或限流。因此程序仍要检查响应状态、解析结果和失败分支。

原生 Schema 提高“机器可读性” ≠ 消灭模型的理解错误

官方参考:OpenAI API · Gemini · Mistral · xAI · Cohere

生活中的例子:不同模型像不同品牌打印机,都能打印表格,但支持的纸张尺寸和高级格式可能不同。

S12五层结果校验

① Schema
生成约束
→
② JSON
语法解析
→
③ 类型结构
程序校验
→
④ 业务规则
事实检查
→
⑤ 权限状态
执行前复核
生成约束:尽量在模型生成阶段要求符合 Schema。
语法解析:确认没有少括号、非法引号等 JSON 语法错误。
类型与结构:金额是不是数字、必填字段是否存在、枚举是否合法。
业务规则:退款金额是否大于零、是否超过订单实付金额、日期关系是否合理。
权限与实时状态:订单是否属于当前用户、是否已退款、操作是否需要人工确认。
生活中的例子:像银行审核贷款:表格完整、类型正确、金额合理、身份匹配、当前账户状态都要检查。

S13结构正确与业务正确的差异

{
  "action": "refund",
  "order_id": "A10086",
  "amount": 99999,
  "need_confirmation": false
}

这段 JSON 可以完美通过语法和类型校验,但仍可能同时存在四个问题:

事实错误

订单实付只有 299 元。

对象错误

订单可能属于另一位用户。

状态冲突

订单可能已经退款。

流程违规

高额退款可能必须人工确认。

铁律:模型输出只能成为“业务操作候选”,不能直接成为“已批准的业务操作”。

生活中的例子:一张格式完美的发票也可能填了假金额,所以业务数据库仍要核对。

S14结构校验与业务校验

结构校验

  • 字段是否存在
  • 类型是否正确
  • 枚举是否合法
  • 数字是否在基本范围内

回答:“数据长得合不合规?”

业务校验

  • 订单是否真实存在
  • 是否属于当前用户
  • 金额是否匹配数据库
  • 当前状态能否退款

回答:“这件事在现实业务中能不能成立?”

生活中的例子:结构校验像检查钥匙形状,业务校验像确认这把钥匙是否属于当前房间。

S15生产环境风险控制

你的判断是正确的:模型是概率系统,只要让它负责“理解用户意思”,就不可能仅靠 Schema 获得百分之百的语义正确率。高级工程的目标不是假装错误不会发生,而是让错误难以造成严重后果、能够被发现、可以恢复和追责。

低风险:可以直接自动化

改写语气、生成摘要、给客服推荐回复。即使偶尔分类错,通常不会直接改变用户资产。

中风险:校验后自动化

分配工单、选择知识库类别。结合必填字段、规则、置信阈值和异常监控;不确定就转人工。

高风险:模型只能提议

退款、转账、删数据、改权限。程序查询真实数据库、检查权限和状态,并要求用户或人工确认。

极高风险:确定性流程主导

医疗处置、重大金融操作等。模型只能辅助整理信息,不能成为最终审批者。

模型识别
“用户想退款”
→
程序查订单
所有者与实付金额
→
规则检查
期限·状态·额度
→
用户/人工
最后确认
→
受控工具
真正执行一次

核心边界:模型可以识别“他可能想退款”,但退款金额不让模型猜,用户身份不让模型判断,能不能退不让模型拍板,真正执行也不让模型直接完成。

生活中的例子:自动退款像自动放款,模型可以整理申请但不能单独决定钱是否真的转出。

S16模型置信度的可信边界

模型输出的 confidence: 0.95 仍然是模型生成的数字,不是天然准确的概率。模型可能很自信地判断错。

错误做法

只要模型自报置信度超过 0.9,就自动退款。

可靠做法

先用真实评测集检查这个分数是否可信,再结合信息完整性、数据库事实、固定规则、风险等级和人工确认。

例如在历史 1,000 个真实问题上,模型自报 0.9 以上的案例实际只有 82% 正确,那么这个分数就没有达到“90%正确”的含义,需要校准或调整流程。

生活中的例子:一个学生说“我有95%把握”不等于他过去同样自信时真的答对95%。

S17置信分数的解释与校准

假设模型读完顾客的话后输出:

{
  "intent": "refund",
  "confidence": 0.95
}

它表达的是:“我给自己的退款意图判断打了 0.95 分,我觉得很确定。”但这个数字可能只是模型根据语言生成的自评分数,并没有自动获得数学保证。

常见误解

0.95 天然表示模型每判断 100 次就一定正确 95 次。

正确理解

0.95 先只代表模型报出了一个很高的分数。它是否真的对应 95% 正确率,要用有标准答案的历史案例验证。

校准是怎样做的

准备 1000 条
人工标好答案的问题
→
让模型判断意图
并输出 confidence
→
把 0.90~1.00
的案例放一组
→
统计这一组
实际答对多少

举例:有 100 条问题,模型都给出了大约 0.95 的置信分数。人工核对后发现:

模型自报案例数量实际答对结论
约 0.95100 条95 条这个分数比较可信,校准较好
约 0.95100 条82 条模型过度自信;0.95 实际只对应约 82% 正确率

重要:即使真的达到 95% 正确,也意味着平均每 100 次仍可能错 5 次。用它自动给工单分类也许可以接受,但自动退款风险仍然太大,所以高风险操作还必须查数据库、走固定规则并人工或用户确认。

校准 = 比较“模型声称多确定”和“历史上实际上多正确”

生活中的例子:校准像统计天气预报说“90%下雨”的日子里实际下了多少次。

S18校验失败处理策略

失败类型推荐处理例子
轻微格式问题在可信边界内程序修复,或让模型有限重试多余空格、可明确纠正的格式
缺少必要信息向用户追问,不要猜退款请求没有订单号
模型理解不确定降级到人工或安全默认流程意图置信度过低
业务规则不满足拒绝执行并说明原因退款金额超过实付金额
高风险操作人工确认或确定性审批大额退款、删除数据
服务暂时失败有限重试,仍失败则降级模型超时、429

不要无限重试。模型连续收到相同信息,可能连续犯相同错误,只会增加延迟和费用。

生活中的例子:表格填错后可以要求重填有限次数;连续失败时应转人工,不能无限循环。

S19JSON 修复的安全边界

有些系统会在解析失败后,再让另一个模型把坏 JSON 修好。这可以处理格式问题,但不能偷偷改变业务含义。

危险修复

原输出没有金额,修复模型自己补成 299。

可接受修复

保留原含义,只修复明确的括号、引号或字段包装;修复后仍重新校验。

涉及钱、权限、身份、医疗或法律信息时,缺失字段通常应该追问或拒绝,而不是自动猜补。

生活中的例子:修复JSON像替别人补表格,括号能补齐,但不知道原本金额是否真实。

S20完整结构化输出链路

定义业务对象
和 Schema
→
模型生成
候选数据
→
解析与
结构校验
→
查数据库做
业务校验
→
执行 / 追问
拒绝 / 人工

可靠结构化输出 = 受约束的生成 + 确定性的程序校验 + 安全的失败处理

生活中的例子:像保险理赔流水线:收件、检查格式、核对事实、审批、执行、留档,每步都可追踪。

S21结构化输出工程原则

生活中的例子:结构化输出像把自由回答装进标准货箱,货箱合格后仍要检查里面的货。

本章总结:从哪里来,解决什么,为什么继续

上一章留下的问题模型结果怎样变成可靠数据
本章形成的能力Schema只保证结构化数据基础;事实、权限和业务状态仍由数据库与确定性规则校验。
下一章必须继续解决企业资料太多,怎样找到相关知识

下一章

第 03 课:Embedding、向量与语义检索基础

会用“在地图上找附近商店”的类比,详细解释向量、维度、余弦相似度、语义检索、关键词检索,以及向量数据库为什么能找到“意思相近但文字不同”的内容。

← 上一章01 模型与上下文下一章 →03 Embedding
↑ 返回本章目录Ctrl+Z