一周备稿 · 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 };
}
前端必做:
- safeParse ,禁止直接
JSON.parse后当 truth - 校验失败展示「结构异常,请重试」,别把脏数据渲染进 DOM
- 对
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,采样 + 掩码
踩坑清单
- Prompt 里写「返回 JSON」却无 Schema API:格式稳定靠祈祷
- 前端不校验:XSS / 非法 action 直进 UI
- 巨型嵌套 Schema 一步完成:漏字段率指数上升
- 重试不加 backoff:429 雪崩
- 流式半成品当最终态入库:脏数据进库难洗
小结
Structured Output 落地三板斧:
- 小 Schema + enum/pattern 收窄输出空间
generateObject/ 原生 json_schema + 有限重试- 前端 Zod 二次校验 + 失败降级
模型再强也会格式翻车;工程化的答案是 约束 + 校验 + 重试 ,不是更长的 prompt。
下一篇:w2-ai-sdk-tool-ui --- Tool 进度态、错误态与取消。
相关阅读 :ai-fe-05-generative-ui.md · w2-ai-sdk-tool-ui