Structured Output
@aeye can enforce that AI responses conform to a Zod schema, giving you type-safe structured data instead of free-form text.
Basic Usage
typescript
import z from 'zod';
const analyzer = ai.prompt({
name: 'analyzer',
description: 'Analyze text sentiment',
content: 'Analyze the sentiment of: {{text}}',
input: (input: { text: string }) => ({ text: input.text }),
schema: z.object({
sentiment: z.enum(['positive', 'negative', 'neutral']),
confidence: z.number().min(0).max(1),
keywords: z.array(z.string()),
}),
});
const result = await analyzer.get('result', {
text: 'This product is amazing!',
});
// result is fully typed:
// { sentiment: 'positive', confidence: 0.95, keywords: ['amazing'] }How It Works
- The Zod schema is converted to JSON Schema
- Sent as
responseFormatto the AI model - The model generates JSON conforming to the schema
- @aeye parses and validates the response with Zod
- If validation fails, the prompt retries with an error message
Strict Mode
By default, strict: true transforms the schema for strict JSON Schema compliance (required by OpenAI's structured outputs):
typescript
const prompt = ai.prompt({
schema: z.object({ /* ... */ }),
strict: true, // default — all properties required, no additionalProperties
});Set strict: false for more lenient schemas:
typescript
const prompt = ai.prompt({
schema: z.object({
required: z.string(),
optional: z.string().optional(), // allowed in non-strict mode
}),
strict: false,
});Dynamic Schema
The schema can change based on context:
typescript
const dynamicPrompt = ai.prompt({
name: 'extract',
content: 'Extract data from: {{text}}',
input: (input: { text: string }) => input,
schema: (ctx) => {
if (ctx.extractMode === 'simple') {
return z.object({ summary: z.string() });
}
return z.object({
summary: z.string(),
entities: z.array(z.object({
name: z.string(),
type: z.string(),
})),
dates: z.array(z.string()),
});
},
});Return false to disable structured output for that call:
typescript
schema: (ctx) => {
if (ctx.freeform) return false;
return z.object({ /* ... */ });
},Output Validation
Add custom validation beyond the schema:
typescript
const prompt = ai.prompt({
schema: z.object({
items: z.array(z.string()),
count: z.number(),
}),
validate: (output, ctx) => {
if (output.items.length !== output.count) {
throw new Error(`Count ${output.count} doesn't match ${output.items.length} items`);
}
},
});Validation errors trigger a retry with the error message sent to the model.
Retry Behavior
When output parsing or validation fails:
- The error is sent back to the model as a system message
- The model tries again (up to
outputRetriestimes, default: 2) - If all retries fail, the prompt throws an error
typescript
const prompt = ai.prompt({
schema: z.object({ /* ... */ }),
outputRetries: 3, // try up to 3 times
});JSON Response Format
For simple JSON without schema validation, use responseFormat:
typescript
const response = await ai.chat.get({
messages: [{ role: 'user', content: 'List 3 colors as JSON' }],
responseFormat: 'json',
});
const data = JSON.parse(response.content);Schema Utilities
@aeye provides utilities for working with Zod schemas and JSON Schema:
typescript
import { toJSONSchema, strictify } from '@aeye/core';
// Convert Zod to JSON Schema
const jsonSchema = toJSONSchema(myZodSchema);
// Make a schema strict-mode compatible
const strictSchema = strictify(myZodSchema);