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 小时前
有点干,前端架构基础之:Web Worker到底是什么?它和Java中的线程是一个道理吗?
前端·面试·架构
小七-七牛开发者1 小时前
dsh 拆解系列 Vol.01:没有特权内核的 Agent 运行时
ai·大模型·agent·claude·token·工作流·skill·claudecode·ai coding
嘟哩DuliDuli1 小时前
AI 短剧生成为什么要有角色库、场景库和镜头卡
android·人工智能·安全·ai·软件工程
爱勇宝2 小时前
《道德经》第 10 章:真正成熟的人,能成事但不控制一切
前端·后端·程序员
看谷秀3 小时前
arkts- 8 三方库介绍
前端·arkts
六边形6663 小时前
独立开发不知道做什么?使用 TRAE Work 抓取差评痛点,快速跑通产品立项流
前端·后端·面试
亿元程序员3 小时前
小伙伴发我一个 1G 的 Cocos 项目,assets 只有几十兆
前端
paopaokaka_luck3 小时前
基于springboot3+vue3的文山民族文化资源展示与推广平台(协同过滤算法、Echarts 图形化分析)
java·前端·spring boot·学习·echarts
临江仙4553 小时前
同一个 AI Agent 如何同时服务 Web、微信和 QQ:PureChat 的渠道架构实践
前端·人工智能·后端