跳到主要内容

05 · 结构化输出

本章讲什么: 怎么让模型不要"自由发挥"地吐文字,而是吐出符合你 schema 的 JSON。AI SDK 给了两条路:专用的 generateObject/streamObject,和嵌进文本生成的 Output

5.1 要解决的小问题

你想从一段描述里抽出结构化数据(比如"生成一份食谱",要 { name, ingredients[], steps[] })。模型默认吐的是自由文本,你得自己解析、还可能解析失败。结构化输出做两件事:

  1. 告诉模型"请按这个 JSON schema 输出"(通过 responseFormat)。
  2. 校验解析模型给的 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: ... },
);
}

注意细节: 默认 outputtext()(纯文本),所以 generateText 总有个统一的"输出解析"步骤,结构化只是换了个 Output 实现。这是个干净的抽象——"文本"也被当成一种 Output,结构化和非结构化走同一条解析路径。

5.4 两条路怎么选

维度generateObjectOutput(配 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.tsgenerateObject
专用流式packages/ai/src/generate-object/stream-object.tsstreamObject
输出规格packages/ai/src/generate-text/output.tsOutput, text
解析接缝packages/ai/src/generate-text/generate-text.tsparseCompleteOutput 调用(1424-1435)
输出工具packages/ai/src/generate-text/output-utils.tsInferCompleteOutput

← 回到 index