Structured Output
Type-safe structured outputs with JSON schema validation, Zod integration, and automatic retries for reliable LLM responses.
Installation
pnpm add @lov3kaizen/agentsea-structured-outputKey Features
Zod Integration
First-class Zod support with full type inference
Auto Retries
Automatic retry on malformed responses
Validation
JSON Schema validation with detailed errors
Type Safety
Full TypeScript inference from schemas
Streaming
Stream partial structured results
Multi-Provider
Works with OpenAI, Anthropic, Google, Ollama
Quick Start with Zod
Define your schema with Zod and get fully typed responses:
import { structuredOutput } from '@lov3kaizen/agentsea-structured-output';
import { z } from 'zod';
// Define your schema
const ProductSchema = z.object({
name: z.string().describe('Product name'),
price: z.number().positive().describe('Price in USD'),
category: z.enum(['electronics', 'clothing', 'food']),
inStock: z.boolean(),
tags: z.array(z.string()).optional(),
});
// Get typed response
const result = await structuredOutput({
model: 'gpt-4o',
schema: ProductSchema,
prompt: 'Extract product info: iPhone 15 Pro, $999, electronics, available',
});
// result is fully typed as z.infer<typeof ProductSchema>
console.log(result.name); // "iPhone 15 Pro"
console.log(result.price); // 999
console.log(result.category); // "electronics"
console.log(result.inStock); // trueWith AgentSea Agent
Use structured output with your existing agents:
import { Agent } from '@lov3kaizen/agentsea-core';
import { withStructuredOutput } from '@lov3kaizen/agentsea-structured-output';
import { z } from 'zod';
const agent = new Agent({
provider: 'openai',
model: 'gpt-4o',
});
const SentimentSchema = z.object({
sentiment: z.enum(['positive', 'negative', 'neutral']),
confidence: z.number().min(0).max(1),
reasoning: z.string(),
});
// Wrap agent with structured output
const structuredAgent = withStructuredOutput(agent, SentimentSchema);
const result = await structuredAgent.run(
'Analyze: "This product exceeded my expectations!"'
);
console.log(result.sentiment); // "positive"
console.log(result.confidence); // 0.95
console.log(result.reasoning); // "The phrase 'exceeded expectations'..."JSON Schema Support
Use standard JSON Schema if you prefer:
import { structuredOutput } from '@lov3kaizen/agentsea-structured-output';
const schema = {
type: 'object',
properties: {
title: { type: 'string' },
author: { type: 'string' },
year: { type: 'integer', minimum: 1900 },
genres: {
type: 'array',
items: { type: 'string' },
},
},
required: ['title', 'author', 'year'],
} as const;
const result = await structuredOutput({
model: 'gpt-4o',
jsonSchema: schema,
prompt: 'Extract book info: "1984 by George Orwell, published 1949, dystopian fiction"',
});Automatic Retries
Handle malformed responses with automatic retry logic:
import { structuredOutput } from '@lov3kaizen/agentsea-structured-output';
import { z } from 'zod';
const StrictSchema = z.object({
email: z.string().email(),
age: z.number().int().positive().max(150),
website: z.string().url().optional(),
});
const result = await structuredOutput({
model: 'gpt-4o',
schema: StrictSchema,
prompt: 'Extract user info from: john.doe@email.com, 28 years old',
retry: {
maxAttempts: 3,
onRetry: (error, attempt) => {
console.log(`Attempt ${attempt} failed: ${error.message}`);
},
},
});Complex Nested Schemas
Handle deeply nested structures with full type safety:
import { z } from 'zod';
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
country: z.string(),
postalCode: z.string(),
});
const OrderItemSchema = z.object({
productId: z.string(),
name: z.string(),
quantity: z.number().int().positive(),
unitPrice: z.number().positive(),
});
const OrderSchema = z.object({
orderId: z.string(),
customer: z.object({
name: z.string(),
email: z.string().email(),
address: AddressSchema,
}),
items: z.array(OrderItemSchema),
total: z.number().positive(),
status: z.enum(['pending', 'processing', 'shipped', 'delivered']),
createdAt: z.string().datetime(),
});
const result = await structuredOutput({
model: 'gpt-4o',
schema: OrderSchema,
prompt: `Extract order details from this email: ...order confirmation...`,
});
// Full type inference for nested objects
console.log(result.customer.address.city);
console.log(result.items[0].quantity);Streaming Structured Output
Stream partial results as they become available:
import { streamStructuredOutput } from '@lov3kaizen/agentsea-structured-output';
import { z } from 'zod';
const ArticleSchema = z.object({
title: z.string(),
summary: z.string(),
keyPoints: z.array(z.string()),
wordCount: z.number(),
});
const stream = await streamStructuredOutput({
model: 'gpt-4o',
schema: ArticleSchema,
prompt: 'Summarize this article...',
});
for await (const partial of stream) {
// Receive partial results as fields are completed
if (partial.title) console.log('Title:', partial.title);
if (partial.keyPoints) console.log('Points so far:', partial.keyPoints.length);
}
// Final validated result
const result = await stream.finalResult();Provider Support
Works with all major providers, using native structured output when available:
| Provider | Native Support | Method |
|---|---|---|
| OpenAI (GPT-4o) | Yes | response_format with json_schema |
| Anthropic (Claude) | Yes | Tool use with schema |
| Google (Gemini) | Yes | responseSchema parameter |
| Ollama | Partial | JSON mode + validation |
| Other | Fallback | Prompt engineering + validation |
Validation Modes
Choose how strictly to validate responses:
import { structuredOutput } from '@lov3kaizen/agentsea-structured-output';
// Strict mode - fail on any validation error
const result = await structuredOutput({
schema: MySchema,
prompt: '...',
validation: 'strict',
});
// Coerce mode - attempt to coerce types (e.g., "42" -> 42)
const result = await structuredOutput({
schema: MySchema,
prompt: '...',
validation: 'coerce',
});
// Partial mode - return partial results even if incomplete
const result = await structuredOutput({
schema: MySchema,
prompt: '...',
validation: 'partial',
});Error Handling
Handle validation errors gracefully:
import {
structuredOutput,
StructuredOutputError,
ValidationError,
MaxRetriesError,
} from '@lov3kaizen/agentsea-structured-output';
try {
const result = await structuredOutput({
schema: MySchema,
prompt: '...',
});
} catch (error) {
if (error instanceof ValidationError) {
console.log('Schema validation failed:', error.issues);
console.log('Raw response:', error.rawResponse);
} else if (error instanceof MaxRetriesError) {
console.log('Max retries exceeded:', error.attempts);
console.log('Last error:', error.lastError);
} else if (error instanceof StructuredOutputError) {
console.log('Structured output error:', error.message);
}
}Configuration
interface StructuredOutputConfig {
// The model to use
model: string;
// Zod schema for validation and type inference
schema?: ZodSchema;
// JSON Schema (alternative to Zod)
jsonSchema?: JSONSchema;
// The prompt/messages to send
prompt: string | Message[];
// Provider configuration
provider?: ProviderConfig;
// Validation mode
validation?: 'strict' | 'coerce' | 'partial';
// Retry configuration
retry?: {
maxAttempts: number;
backoff?: 'linear' | 'exponential';
onRetry?: (error: Error, attempt: number) => void;
};
// Temperature (lower = more deterministic)
temperature?: number;
}Best Practices
- Use descriptive field names - LLMs understand semantic meaning from names
- Add .describe() - Provide hints for ambiguous fields
- Set reasonable constraints - Use min/max to guide valid ranges
- Use enums for categories - Limit options to valid choices
- Lower temperature for structured output - Recommended 0-0.3 for more deterministic results