05 · 结构化输出
本章讲什么: 怎么让模型不要"自由发挥"地吐文字,而是吐出符合你 schema 的 JSON。AI SDK 给了两条路:专用的
generateObject/streamObject,和嵌进文本生成的Output。
5.1 要解决的小问题
你想从一段描述里抽出结构化数据(比如"生成一份食谱",要 { name, ingredients[], steps[] })。模型默认吐的是自由文本,你得自己解析、还可能解析失败。结构化输出做两件事:
- 告诉模型"请按这个 JSON schema 输出"(通过
responseFormat)。 - 校验解析模型给的 JSON 是否真的符合 schema,不符合就报错。
5.2 路线一:generateObject(专用函数)
最直接——专门为结构化输出设计的函数:
import { generateObject } from 'ai';
import { z } from 'zod';
const { object } = await generateObject({
model: 'openai/gpt-5.4',
schema: z.object({
name: z.string(),
ingredients: z.array(z.object({ name: z.string(), amount: z.string() })),
steps: z.array(z.string()),
}),
prompt: 'Generate a lasagna recipe.',
});
// object 的类型自动从 schema 推断出来
入口见 generateObject(packages/ai/src/generate-object/generate-object.ts:121),流式版 streamObject(packages/ai/src/generate-object/stream-object.ts:182)。streamObject 能在 JSON 还没拼完时就流式吐出"部分对象",适合边生成边渲染。
5.3 路线二:Output(挂在 generateText 上)
如果你想同时要工具循环和结构化输出,就用 Output——把结构化规格挂到 generateText:
import { generateText, Output } from 'ai';
import { z } from 'zod';
const { output } = await generateText({
model: 'openai/gpt-5.4',
output: Output.object({ schema: z.object({ recipe: /* … */ }) }),
prompt: 'Generate a lasagna recipe.',
});
Output 类型与工厂在 packages/ai/src/generate-text/output.ts。它和 generateText 的两个接缝:
- 设 responseFormat。 调模型时
responseFormat: await output?.responseFormat(generate-text.ts:941)——这是"告诉模型按 schema 输出"的地方。 - 解析最终输出。 只在最后一步以
stop结束时,用outputSpecification.parseCompleteOutput(...)把文本解析成结构化对象(generate-text.ts:1424-1435)。
// packages/ai/src/generate-text/generate-text.ts —— 简化
if (lastStep.finishReason === 'stop') {
const outputSpecification = output ?? text(); // 默认是纯文本输出
resolvedOutput = await outputSpecification.parseCompleteOutput(
{ text: lastStep.text },
{ response: ..., usage: ..., finishReason: ... },
);
}
注意细节: 默认 output 是 text()(纯文本),所以 generateText 总有个统一的"输出解析"步骤,结构化只是换了个 Output 实现。这是个干净的抽象——"文本"也被当成一种 Output,结构化和非结构化走同一条解析路径。
5.4 两条路怎么选
| 维度 | generateObject | Output(配 generateText) |
|---|---|---|
| 适合 | 纯抽取/生成结构,不需要工具 | 既要工具循环又要最终结构化结果 |
| 流式 | streamObject 支持部分对象 | 随 streamText 流 |
| 心智 | 独立函数,API 更聚焦 | 文本生成的一个"输出模式" |
5.5 巧妙之处
- "文本"是 Output 的一个特例。
generateText默认output ?? text()(generate-text.ts:1426),结构化与非结构化共用parseCompleteOutput路径,没有特判分叉。 - 只在
finishReason === 'stop'才解析。 因工具调用而中断的步不去硬解析结构(generate-text.ts:1425)——避免把"还没说完"的中间文本误当最终 JSON。 - schema 双重用途。 既驱动
responseFormat(让模型照着生成),又做解析后校验,类 型还能在编译期推断出来。
5.6 边界与局限
- 结构化输出的"强制力"取决于 provider 对
responseFormat的支持程度;不支持的模型可能只能尽力而为,解析仍可能失败。 Output解析发生在循环结束后,所以工具循环中间步的产物不会被结构化校验。
5.7 代码地图
| 主题 | 文件 | 符号 |
|---|---|---|
| 专用生成 | packages/ai/src/generate-object/generate-object.ts | generateObject |
| 专用流式 | packages/ai/src/generate-object/stream-object.ts | streamObject |
| 输出规格 | packages/ai/src/generate-text/output.ts | Output, text |
| 解析接缝 | packages/ai/src/generate-text/generate-text.ts | parseCompleteOutput 调用(1424-1435) |
| 输出工具 | packages/ai/src/generate-text/output-utils.ts | InferCompleteOutput |
← 回到 index