Structured Output 落地:JSON Schema、重试与前端校验

一周备稿 · 2026-08-10

上篇:w2-sse-vs-websocket · 下篇预告:w2-ai-sdk-tool-ui

让 LLM「输出 JSON」听起来简单,上线后常见画面是:

  • 少了一个逗号,整段解析失败
  • 模型自作主张加了 markdown 代码块
  • 字段类型对了,枚举值却乱写

结论:Structured Output = 模型侧约束(Schema) + 解析失败重试 + 前端 Zod/JSON Schema 二次校验。缺一环就靠运气。

你将学到

  • JSON Schema 在 OpenAI / AI SDK 里的两种接法
  • 重试策略:何时重 prompt、何时降温度
  • 前端校验与友好错误展示
  • 流式场景下「边收边 parse」的坑
  • 和 Tool Calling 的边界

一、先定边界:什么时候要 Structured Output

适合

  • 表单自动填充、票据抽取、路由分类
  • 生成式 UI(JSON → React 组件树)
  • 需要 稳定字段 落库或驱动下游 API

不适合硬上 Schema

情况 更合适的做法
开放式长文创作 纯文本流
字段经常变 先 stabil 产品协议再锁 Schema
复杂嵌套 + 强推理 拆成多步,别一个巨型 Schema

二、Schema 设计:少即是多

反例:一次要 30 个字段

模型会漏字段、填 null、或 hallucinate 枚举。

正例:分层 Schema

json 复制代码
{
  "type": "object",
  "properties": {
    "intent": { "type": "string", "enum": ["faq", "ticket", "chitchat"] },
    "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
    "slots": {
      "type": "object",
      "properties": {
        "order_id": { "type": "string", "pattern": "^[A-Z0-9]{8,16}$" }
      },
      "additionalProperties": false
    }
  },
  "required": ["intent", "confidence"],
  "additionalProperties": false
}

原则:

  • required 只放 真正必需 的字段
  • 用 enum / pattern 收窄空间
  • additionalProperties: false 防模型加戏
  • 大对象拆 两步:先分类,再抽取

三、模型侧:AI SDK + Zod 示例

ts 复制代码
import { generateObject } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";

const RouteSchema = z.object({
  intent: z.enum(["faq", "ticket", "chitchat"]),
  confidence: z.number().min(0).max(1),
  reply_hint: z.string().optional(),
});

export async function routeUserMessage(text: string) {
  const { object } = await generateObject({
    model: openai("gpt-4o-mini"),
    schema: RouteSchema,
    prompt: `Classify the user message:\n${text}`,
    temperature: 0,
  });
  return object;
}

generateObject 走 模型原生 structured output(支持时),比「请输出 JSON」可靠一个数量级。

OpenAI 风格(概念对齐)

  • response_format: { type: "json_schema", json_schema: {...} }
  • 与 AI SDK 的 schema: zodSchema 同源思想:约束在 API 层,不在 prompt 里赌人品

四、重试策略:解析失败怎么办

ts 复制代码
async function withStructuredRetry<T>(
  run: () => Promise<T>,
  max = 3
): Promise<T> {
  let lastErr: unknown;
  for (let i = 0; i < max; i++) {
    try {
      return await run();
    } catch (e) {
      lastErr = e;
      if (i === max - 1) break;
      await new Promise((r) => setTimeout(r, 300 * (i + 1)));
    }
  }
  throw lastErr;
}

分层重试

失败类型 动作
JSON 语法错 同 Schema 重试,temperature=0
枚举越界 重试 + prompt 强调 allowed values
缺 required 缩小 Schema 或拆步
连续 3 次失败 降级为纯文本 + 人工队列

不要无限重试:成本和安全都会炸。

五、前端校验:不信模型,信 Schema

ts 复制代码
import { z } from "zod";

const UiBlockSchema = z.discriminatedUnion("type", [
  z.object({ type: z.literal("text"), content: z.string() }),
  z.object({
    type: z.literal("button"),
    label: z.string().max(40),
    action: z.enum(["submit", "cancel"]),
  }),
]);

export function safeParseBlocks(raw: unknown) {
  const arr = z.array(UiBlockSchema).safeParse(raw);
  if (!arr.success) {
    return { ok: false as const, error: arr.error.flatten() };
  }
  return { ok: true as const, data: arr.data };
}

前端必做:

  1. safeParse ,禁止直接 JSON.parse 后当 truth
  2. 校验失败展示「结构异常,请重试」,别把脏数据渲染进 DOM
  3. 对 action 类字段做 白名单,防 prompt injection 借 JSON 发指令

六、流式 Structured Output 的坑

streamObject 可以边收边展示,但要注意:

  • 部分 JSON 未闭合时,预览 UI 只能显示 skeleton,别假 parse
  • 取消生成后 丢弃半成品,别 commit 到 store
  • 大 Schema 流式首字延迟可能更高,要 loading 态
ts 复制代码
import { streamObject } from "ai";

const result = streamObject({ model, schema: RouteSchema, prompt });
for await (const partial of result.partialObjectStream) {
  // partial 可能缺字段 --- UI 用 optional chaining
  updatePreview(partial);
}

七、和 Tool Calling 怎么分

需求 选型
模型要 调用外部 API Tool Calling
模型要 产出固定结构给前端/DB Structured Output
既要调工具又要结构化终态 Tool 执行 + 最后一步 generateObject

别用 Structured Output 模拟 HTTP 调用;别用 Tool 返回一大坨 JSON 给 UI 渲染。

八、成本与安全

  • Schema 越大,completion token 往往越高
  • 敏感字段(身份证、手机号)在 Schema 层 不要 required,用后置 NER + 脱敏
  • 日志里 全量 JSON 可能含 PII,采样 + 掩码

踩坑清单

  1. Prompt 里写「返回 JSON」却无 Schema API:格式稳定靠祈祷
  2. 前端不校验:XSS / 非法 action 直进 UI
  3. 巨型嵌套 Schema 一步完成:漏字段率指数上升
  4. 重试不加 backoff:429 雪崩
  5. 流式半成品当最终态入库:脏数据进库难洗

小结

Structured Output 落地三板斧:

  1. 小 Schema + enum/pattern 收窄输出空间
  2. generateObject / 原生 json_schema + 有限重试
  3. 前端 Zod 二次校验 + 失败降级

模型再强也会格式翻车;工程化的答案是 约束 + 校验 + 重试 ,不是更长的 prompt。

下一篇:w2-ai-sdk-tool-ui --- Tool 进度态、错误态与取消。


相关阅读 :ai-fe-05-generative-ui.md · w2-ai-sdk-tool-ui

相关推荐
程序员海军1 小时前
用了两年多 GPT,我最近开始换模型了,还折腾了个求职避坑 Agent
前端·人工智能·后端
紧跟先前的步伐1 小时前
Go AI Agent 实战 06|JSON 结构化输出与 Go 后端校验
人工智能·ai·golang
zttbee1 小时前
echarts图表tooltip色块失效
前端·javascript·echarts
excel3 小时前
IndexNow:让搜索引擎更快发现网站更新的主动通知协议
前端·后端·架构
子兮曰3 小时前
CSS 复现 23 种设计风格:从拟物到液态玻璃的成本阶梯与兜底写法【实战】
前端·css·视觉设计
Maynor9963 小时前
WorkBuddy 一键接通 gpt-6.1-sol:MomoAI / TryAllAPI 把最新模型接到桌面
人工智能·gpt·ai·大模型·编程工具
虚无境4 小时前
如何用AI帮我写出实用的插件
前端·javascript·ai编程
柳杉4 小时前
从数字化到数智化:3D 可视化大屏的下一个五年
前端·ai编程·数据可视化
Alinket5 小时前
不训练模型也能加 AI:5 套可直接量产的组合方案
人工智能·物联网·ai·健康医疗·无线通信·智能硬件·iot