Structured Output

Return schema-constrained JSON from an Agent with outputSchema

Set outputSchema on an Agent to make it return JSON that matches your schema.

Diagram

🤖 Agent

📋 JSON Schema

✅ JSON Output

Quick Start

  1. Simplest — one outputSchema
    typescript
    import { Agent } from 'praisonai';
    
    const agent = new Agent({
      instructions: 'Extract person info as JSON.',
      llm: 'gpt-4o-mini',
      outputSchema: {
        type: 'object',
        properties: {
          name: { type: 'string' },
          age: { type: 'number' },
          email: { type: 'string' }
        },
        required: ['name', 'age']
      }
      // outputSchemaName defaults to 'response'
    });
    
    const result = await agent.start(
      'John Doe is 30 years old, email john@example.com'
    );
    console.log(result);
    // result is a JSON string matching the schema:
    // {"name":"John Doe","age":30,"email":"john@example.com"}
  2. With outputSchemaName and a nested schema
    typescript
    import { Agent } from 'praisonai';
    
    const agent = new Agent({
      instructions: 'Extract an order as JSON.',
      llm: 'gpt-4o-mini',
      outputSchemaName: 'order',
      outputSchema: {
        type: 'object',
        properties: {
          id: { type: 'string' },
          customer: {
            type: 'object',
            properties: {
              name: { type: 'string' },
              email: { type: 'string' }
            },
            required: ['name']
          },
          items: {
            type: 'array',
            items: {
              type: 'object',
              properties: {
                sku: { type: 'string' },
                qty: { type: 'number' }
              },
              required: ['sku', 'qty']
            }
          }
        },
        required: ['id', 'customer', 'items']
      }
    });
    
    const json = await agent.start('Order 42 for Ada (ada@example.com): 2x SKU-1, 1x SKU-9');
    console.log(JSON.parse(json));

How It Works

On OpenAI, the agent forwards outputSchema as response_format: { type: 'json_schema', json_schema: { name, schema } }. On other providers it routes the schema through the AI SDK backend’s generateObject({ schema }). Both paths return the raw JSON string.

Diagram
LLM ProviderAgentUserLLM ProviderAgentUserstart(“Extract person info…”)request + schema (json_schema or generateObject)JSON stringJSON string (matches schema)
FieldTypeDefaultDescription
outputSchemaRecord<string, any>undefinedJSON Schema for the response.
outputSchemaNamestring'response'Name used in response_format.json_schema.name.

Provider support

outputSchema works on every provider PraisonAI TS supports today.

Provider prefixHow the schema is enforced
openai/...Native response_format: { type: 'json_schema', json_schema: { ... } }
anthropic/..., google/..., groq/..., mistral/..., ollama/..., and other AI SDK providersAI SDK backend.generateObject({ schema })

The agent returns a JSON string that matches your schema in both paths — JSON.parse(result) for the object.

Cross-provider structured output landed in PraisonAI PR #4412. If you are on an earlier release, the agent logs outputSchema is not yet supported with non-OpenAI providers ... — proceeding without structured output and returns unstructured text.


Multi-turn behaviour

Structured output goes through the agent’s full message history, so follow-up prompts see prior turns and stay consistent.

typescript
const agent = new Agent({
  instructions: 'Return the running total as JSON.',
  llm: 'gpt-4o-mini',
  outputSchema: {
    type: 'object',
    properties: { total: { type: 'number' } },
    required: ['total']
  }
});

await agent.chat('Add 5');   // {"total":5}
await agent.chat('Add 3');   // {"total":8} — remembers the previous turn

How this differs from generateObject

typescript
import { resolveBackend } from 'praisonai';
import { z } from 'zod';

const { provider } = await resolveBackend('openai/gpt-4o-mini');

const result = await provider.generateObject({
  messages: [{ role: 'user', content: 'What is the weather in Paris?' }],
  schema: z.object({ location: z.string(), temperature: z.number() })
});

console.log(result.object); // { location: "Paris", temperature: 18 }

See the Zod docs for schema construction.


Common Patterns

Extraction

typescript
const agent = new Agent({
  instructions: 'Extract the invoice fields as JSON.',
  llm: 'gpt-4o-mini',
  outputSchema: {
    type: 'object',
    properties: {
      invoiceNumber: { type: 'string' },
      total: { type: 'number' },
      dueDate: { type: 'string' }
    },
    required: ['invoiceNumber', 'total']
  }
});

const json = await agent.start('Invoice INV-7 for $240, due 2026-01-15');

Classification

typescript
const agent = new Agent({
  instructions: 'Classify the email as JSON.',
  llm: 'gpt-4o-mini',
  outputSchema: {
    type: 'object',
    properties: {
      category: { type: 'string', enum: ['spam', 'ham'] },
      confidence: { type: 'number' }
    },
    required: ['category', 'confidence']
  }
});

const json = await agent.start('You won $1,000,000! Click here!');
// {"category":"spam","confidence":0.96}

Sentiment

typescript
const agent = new Agent({
  instructions: 'Return sentiment as JSON.',
  llm: 'gpt-4o-mini',
  outputSchema: {
    type: 'object',
    properties: {
      sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] },
      score: { type: 'number' }
    },
    required: ['sentiment']
  }
});

const json = await agent.start('The service was fast and friendly.');

Best Practices

Mark required fields

List every field you always expect in required. It nudges the model to include them and keeps the JSON predictable.

Use enums for fixed choices

Add enum on classification fields so the model can only pick valid values.

Parse the result

The agent returns a JSON string. Call JSON.parse(result) to get an object.

Works on every provider

Switching the llm string from openai/... to anthropic/..., google/..., or any other supported provider does not change your outputSchema code. Structured output is guaranteed on every provider from PraisonAI PR #4412 onward.


Related