Skip to content

Chapter 7 · Structured Output ​

Chapter goals:

  • Master five output types: Output.object() / array() / choice() / json() / text()
  • Learn to constrain generated data with Zod schemas and improve generation quality with .describe()
  • Master two structured streaming consumption methods: partialOutputStream and elementStream
  • Understand step counting issues and error handling when combining structured output with tool calling

7.1 Why Structured Output Is Needed ​

Text generation is useful, but real-world business often requires structured data — extracting information from text, classifying data, generating synthetic data, etc.

Many models can generate structured data (commonly called "JSON modes" or "tools"), but you need to manually provide a schema and validate the generated data, because LLMs may produce incorrect or incomplete structured data.

AI SDK standardizes跨 provider structured object generation through the output property of generateText and streamText. You can use Zod schemas, Valibot, or JSON schemas to specify the expected data shape, and the model will generate data conforming to that structure.

💡 Structured output is part of the generateText/streamText flow, meaning it can be combined with tool calling in the same request.

本章 examples continue using the Gateway to construct models (also applicable to custom providers):

ts
import { createGateway } from 'ai';

export const gateway = createGateway({
  apiKey: process.env.AI_GATEWAY_API_KEY ?? '',
});

7.2 Generating Structured Data with Output.object() ​

Use generateText with Output.object() to generate structured data from a prompt. The schema is used to both validate and类型安全 the generated data:

ts
import { generateText, Output } from 'ai';
import { gateway } from './provider';
import { z } from 'zod';

const { output } = await generateText({
  model: gateway('openai/gpt-5'),
  output: Output.object({
    schema: z.object({
      recipe: 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.',
});

⚠️ Structured output generation counts as one step in the AI SDK multi-step execution model (each model call or tool execution is one step). When combining with tools,预留 steps in your stopWhen configuration.

Accessing Response Headers and Body ​

When you need to access the provider's full response, use the response property:

ts
import { generateText, Output } from 'ai';

const result = await generateText({
  // ...
  output: Output.object({ schema }),
});

console.log(JSON.stringify(result.response.headers, null, 2));
console.log(JSON.stringify(result.response.body, null, 2));

7.3 Streaming Structured Output ​

Generating structured data is more complex, and the model's response time may be unacceptable for interactive scenarios. With streamText and output, you can stream the structured response as the model generates it:

ts
import { streamText, Output } from 'ai';
import { gateway } from './provider';
import { z } from 'zod';

const result = streamText({
  model: gateway('openai/gpt-5'),
  output: Output.object({
    schema: z.object({
      sentiment: z.enum(['positive', 'negative', 'neutral']),
      score: z.number().min(0).max(1),
    }),
  }),
  prompt: 'How do you feel today?',
});

// Consume the stream
for await (const chunk of result.textStream) {
  process.stdout.write(chunk);
}

// Access structured output after completion
const output = await result.response;
console.log(output.sentiment, output.score);

7.4 Partial Output Stream ​

For interactive scenarios where you want to display partial results as they arrive, use partialOutputStream:

ts
import { streamText, Output } from 'ai';
import { gateway } from './provider';
import { z } from 'zod';

const result = streamText({
  model: gateway('openai/gpt-5'),
  output: Output.object({
    schema: z.object({
      name: z.string(),
      ingredients: z.array(z.object({ name: z.string(), amount: z.string() })),
    }),
  }),
  prompt: 'Generate a recipe.',
});

// partialOutputStream emits the partially parsed object as chunks arrive
for await (const partial of result.partialOutputStream) {
  console.log('Partial:', partial);
}

7.5 Element Stream ​

For arrays, elementStream emits each element as it is generated:

ts
import { streamText, Output } from 'ai';
import { gateway } from './provider';
import { z } from 'zod';

const result = streamText({
  model: gateway('openai/gpt-5'),
  output: Output.array({
    schema: z.object({
      name: z.string(),
      score: z.number(),
    }),
  }),
  prompt: 'Rate these 5 movies.',
});

for await (const element of result.elementStream) {
  console.log('Element:', element);
}

7.6 Other Output Types ​

Output.choice() ​

Generate a fixed set of choices:

ts
const result = await generateText({
  model: gateway('openai/gpt-5'),
  output: Output.choice({
    options: ['low', 'medium', 'high'],
  }),
  prompt: 'What priority level is this bug?',
});

Output.json() ​

Generate arbitrary JSON:

ts
const result = await generateText({
  model: gateway('openai/gpt-5'),
  output: Output.json(),
  prompt: 'Return a JSON object with keys name and age.',
});

Output.text() ​

Generate plain text with schema validation:

ts
const result = await generateText({
  model: gateway('openai/gpt-5'),
  output: Output.text({
    schema: z.string().min(10).max(100),
  }),
  prompt: 'Write a short bio.',
});

7.7 Combining Structured Output with Tools ​

When combining structured output with tool calling, each structured output generation counts as one step. Configure stopWhen to预留 enough steps:

ts
const result = await streamText({
  model: gateway('openai/gpt-5'),
  output: Output.object({
    schema: z.object({ summary: z.string() }),
  }),
  tools: {
    search: tool({ /* ... */ }),
  },
  stopWhen: isStepCount(3), //预留 space for tool calls
  prompt: 'Search for recent AI news and summarize.',
});

7.8 Chapter Summary ​

  • Five output types: object, array, choice, json, text — choose based on the expected data shape;
  • Zod schemas provide type safety and validation; .describe() improves generation quality by giving the model more context;
  • partialOutputStream and elementStream enable real-time structured streaming;
  • Structured output counts as one step;预留 steps when combining with tools;
  • Error handling: use onError to catch validation failures.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. What does Output.object() do?

2. Why does structured output count as one step in the multi-step execution model?

3. What is the difference between partialOutputStream and elementStream?

4. How does .describe() improve structured output quality?

🛠️ Hands-On Exercises ​

  1. Use Output.object() to generate a structured recipe from a prompt; validate the output matches the Zod schema.
  2. Combine Output.object() with a tool call; verify the step count works correctly with stopWhen.
  3. Use partialOutputStream to display a progressively parsed object in the terminal; observe how the partial results evolve.

Next: Chapter 8 · Tool Calling