Tutorial2025-01-27

The Easiest Way to Generate Structured Output with the AI SDK

Learn how to use the AI SDK's structured output features to get reliable, type-safe responses from AI models. From simple text generation to complex object schemas, we'll cover everything you need to know.

ShipLog Team
2025-01-27
7 min read

The Easiest Way to Generate Structured Output with the AI SDK

Generating structured output from AI models has always been a challenge. You send a prompt, get back some text, and then spend time parsing it into the format you actually need. The AI SDK changes all of that with its powerful structured output features.

In this guide, we'll explore the different ways to generate structured output using the AI SDK, from simple text generation to complex, validated object schemas.

Why Structured Output Matters

Before diving into the how, let's understand the why:

  • Type Safety: Get responses that match your expected data structure
  • Reliability: No more parsing failures or unexpected formats
  • Developer Experience: Better IntelliSense and error handling
  • Production Ready: Structured output is essential for production AI applications

Method 1: Basic Text Generation with generateText

The simplest form of structured output is getting plain text responses. Here's how to use generateText:

1import { generateText } from 'ai'; 2import { google } from '@ai-sdk/google'; 3 4const model = google("gemini-2.5-flash-lite"); 5 6const result = await generateText({ 7 model, 8 prompt: "Write a short summary of the benefits of structured output", 9 system: "You are a helpful AI assistant that writes concise summaries." 10}); 11 12console.log(result.text); // The generated text

When to use: Simple text generation, creative writing, summaries, explanations.

Method 2: Structured Objects with generateObject

This is where the magic happens. generateObject allows you to define a schema and get back a perfectly structured object:

1import { generateObject } from 'ai'; 2import { z } from 'zod'; 3import { google } from '@ai-sdk/google'; 4 5const model = google("gemini-2.5-flash-lite"); 6 7// Define your schema using Zod 8const UserProfileSchema = z.object({ 9 name: z.string(), 10 age: z.number(), 11 interests: z.array(z.string()), 12 bio: z.string().max(200), 13 isActive: z.boolean() 14}); 15 16const result = await generateObject({ 17 model, 18 schema: UserProfileSchema, 19 prompt: "Create a user profile for a 25-year-old developer who loves TypeScript and AI", 20 system: "You are a data generation expert. Always return valid data matching the schema." 21}); 22 23// result.object is fully typed and validated 24console.log(result.object.name); // TypeScript knows this is a string 25console.log(result.object.interests); // TypeScript knows this is string[]

Key Benefits:

  • Automatic Validation: The AI SDK ensures the response matches your schema
  • Type Safety: Full TypeScript support with IntelliSense
  • Error Handling: Built-in validation and error reporting

Method 3: Advanced Schemas with Nested Objects

For complex applications, you can create sophisticated schemas:

1import { generateObject } from 'ai'; 2import { z } from 'zod'; 3 4const ChangeSchema = z.object({ 5 title: z.string(), 6 content: z.string(), 7 type: z.enum(["added", "changed", "deprecated", "removed", "fixed", "security", "other"]), 8 size: z.enum(["minor", "patch", "major"]), 9 version: z.string(), 10 date: z.string() 11}); 12 13const ChangelogEntrySchema = z.object({ 14 version: z.string(), 15 date: z.string(), 16 changes: z.array(ChangeSchema).default([]) 17}); 18 19const result = await generateObject({ 20 model, 21 schema: ChangelogEntrySchema, 22 prompt: "Analyze this commit and create a changelog entry", 23 system: "You are a changelog expert. Analyze commits and categorize changes appropriately." 24});

Method 4: Using Tools for Complex Operations

The AI SDK also supports tools, which are perfect for operations that require external data or complex processing:

1import { tool } from 'ai'; 2import { z } from 'zod'; 3 4// Define a tool for extracting context from URLs 5export const urlContext = tool({ 6 description: "Extract context from a URL", 7 inputSchema: z.object({ 8 url: z.string(), 9 }), 10 execute: async ({ url }) => { 11 const res = await fetch(url); 12 if (!res.ok) { 13 throw new Error(`Failed to fetch URL: ${res.status}`); 14 } 15 const text = await res.text(); 16 return { text }; 17 }, 18}); 19 20// Use the tool in your generation 21const result = await generateObject({ 22 model, 23 schema: SummarySchema, 24 prompt: "Summarize the content from this URL", 25 tools: [urlContext] 26});

Real-World Example: Changelog Generation

Here's how we use structured output in ShipLog to analyze commits and generate changelog entries:

1// From our actual codebase 2export async function analyzeCommit(commit: CommitData, project: Project) { 3 const system = ChangelogEntryPrompt; 4 5 try { 6 const result = await generateObject({ 7 model, 8 schema: ChangelogEntrySchema, 9 system, 10 prompt: `Analyze this commit and determine if it should be included in the changelog: 11 12 Commit Information: 13 ${JSON.stringify(commit, null, 2)} 14 15 Please analyze this commit and return a structured response.` 16 }); 17 18 // result.object is guaranteed to match our schema 19 return result.object; 20 } catch (error) { 21 console.error("Error analyzing commit:", error); 22 return defaultAnalysis; 23 } 24}

Best Practices for Structured Output

1. Use Descriptive Schemas

1// ❌ Too generic 2const Schema = z.object({ 3 data: z.any() 4}); 5 6// ✅ Specific and descriptive 7const UserProfileSchema = z.object({ 8 name: z.string().min(1, "Name is required"), 9 email: z.string().email("Invalid email format"), 10 preferences: z.object({ 11 theme: z.enum(["light", "dark", "auto"]), 12 notifications: z.boolean() 13 }) 14});

2. Provide Clear System Prompts

1const system = `You are a data generation expert. Your responses must: 2- Always match the provided schema exactly 3- Use realistic, appropriate data 4- Follow the specified format without deviation 5- Provide meaningful, useful information`;

3. Handle Errors Gracefully

1try { 2 const result = await generateObject({ 3 model, 4 schema: MySchema, 5 prompt: "Generate user data" 6 }); 7 8 return result.object; 9} catch (error) { 10 if (error.name === 'ZodValidationError') { 11 console.error('Schema validation failed:', error.issues); 12 return fallbackData; 13 } 14 throw error; 15}

4. Use Default Values for Optional Fields

1const Schema = z.object({ 2 required: z.string(), 3 optional: z.string().optional().default("default value"), 4 array: z.array(z.string()).default([]) 5});

Performance Considerations

1. Model Selection

Different models have different strengths:

  • Fast & Cheap: gemini-2.5-flash-lite - Great for simple structured output
  • Balanced: gpt-4-turbo - Good balance of speed and quality
  • High Quality: gpt-4 - Best for complex reasoning and structured output

2. Schema Complexity

Keep your schemas as simple as possible while meeting your needs:

1// ❌ Too complex - may cause generation issues 2const ComplexSchema = z.object({ 3 nested: z.object({ 4 deeply: z.object({ 5 nested: z.object({ 6 data: z.array(z.object({ 7 // ... many more levels 8 })) 9 }) 10 }) 11 }) 12}); 13 14// ✅ Simpler, more reliable 15const SimpleSchema = z.object({ 16 items: z.array(z.object({ 17 id: z.string(), 18 value: z.string() 19 })) 20});

Common Pitfalls and How to Avoid Them

1. Schema Mismatch

Problem: AI generates data that doesn't match your schema Solution: Use clear prompts and validate your schema design

1// Make your schema intuitive 2const IntuitiveSchema = z.object({ 3 firstName: z.string(), // Clear field name 4 lastName: z.string(), 5 fullName: z.string().optional() // Optional computed field 6});

2. Overly Strict Validation

Problem: Schema is too restrictive, causing generation failures Solution: Balance strictness with flexibility

1// ❌ Too strict 2const StrictSchema = z.object({ 3 age: z.number().int().positive().max(120) 4}); 5 6// ✅ More flexible 7const FlexibleSchema = z.object({ 8 age: z.number().int().positive().max(120).default(25) 9});

3. Insufficient Context

Problem: AI doesn't understand what you want Solution: Provide clear examples and context

1const prompt = `Generate a user profile. Here are some examples: 2- A developer: {name: "Alice", age: 28, interests: ["coding", "AI"]} 3- A designer: {name: "Bob", age: 32, interests: ["UI/UX", "art"]} 4 5Now generate a profile for a data scientist.`;

Testing Your Structured Output

Always test your structured output generation:

1// Test function 2async function testGeneration() { 3 try { 4 const result = await generateObject({ 5 model, 6 schema: TestSchema, 7 prompt: "Generate test data" 8 }); 9 10 console.log('✅ Generation successful:', result.object); 11 12 // Validate the result matches your expectations 13 const validation = TestSchema.safeParse(result.object); 14 if (!validation.success) { 15 console.error('❌ Validation failed:', validation.error); 16 } 17 } catch (error) { 18 console.error('❌ Generation failed:', error); 19 } 20}

Conclusion

The AI SDK's structured output features make it incredibly easy to get reliable, type-safe responses from AI models. Whether you're building a simple text generator or a complex AI-powered application, these tools provide the foundation you need.

Key takeaways:

  • Use generateText for simple text generation
  • Use generateObject with Zod schemas for structured data
  • Design intuitive, well-documented schemas
  • Provide clear system prompts and examples
  • Handle errors gracefully
  • Test your generation thoroughly

With these techniques, you can build AI applications that are both powerful and reliable. The structured output ensures your AI responses integrate seamlessly with your existing codebase, making AI a first-class citizen in your applications.


Ready to implement structured output in your AI applications? Try ShipLog today and see how we use these techniques to generate perfect changelogs automatically.

Enjoyed this article?

Share it with your team and stay updated with our latest insights.

The Easiest Way to Generate Structured Output with the AI SDK | ShipLog Blog