Core Concepts

Code language

View this guide and its examples in your language.

Structured Output

Structured output returns data in a schema-defined format. Use it for extraction, classification and other tasks that require machine-readable responses.

Quick Start#

Here's how to get structured data from your AI:

Important

Schema Requirement for OpenAI: When using OpenAI's structured output (especially strict mode), the root schema must be an ObjectSchema. Other schema types (StringSchema, NumberSchema, etc.) can only be used as properties within an ObjectSchema, not as the top-level schema. Other providers may have different requirements.

use Prism\Prism\Facades\Prism;
use Prism\Prism\Enums\Provider;
use Prism\Prism\Schema\ObjectSchema;
use Prism\Prism\Schema\StringSchema;

$schema = new ObjectSchema(
    name: 'movie_review',
    description: 'A structured movie review',
    properties: [
        new StringSchema('title', 'The movie title'),
        new StringSchema('rating', 'Rating out of 5 stars'),
        new StringSchema('summary', 'Brief review summary')
    ],
    requiredFields: ['title', 'rating', 'summary']
);

$response = Prism::structured()
    ->using(Provider::OpenAI, 'gpt-4o')
    ->withSchema($schema)
    ->withPrompt('Review the movie Inception')
    ->asStructured();

// Access your structured data
$review = $response->structured;
echo $review['title'];    // "Inception"
echo $review['rating'];   // "5 stars"
echo $review['summary'];  // "A mind-bending..."

Tip

This is just a basic example of schema usage. Check out our dedicated schemas guide to learn about all available schema types, nullable fields, and best practices for structuring your data.

Understanding Output Modes#

Different AI providers handle structured output in two main ways:

  1. Structured Mode: Some providers support strict schema validation, constraining output to the supported schema.
  2. JSON Mode: Other providers simply guarantee valid JSON output that approximately matches your schema.

With OpenAI, Prism uses structured mode, sending a native json_schema request, for every model except a short list of legacy ones (gpt-3.5-turbo, gpt-4, gpt-4-* and gpt-4o-2024-05-13). That means new model families get schema enforcement automatically. If you point the OpenAI provider at a compatible server that can't handle json_schema, switch to JSON mode yourself:

use Prism\Prism\Enums\StructuredMode;

$response = Prism::structured()
    ->using(Provider::OpenAI, 'my-local-model')
    ->usingStructuredMode(StructuredMode::Json)
    ->withSchema($schema)
    ->withPrompt('Review the movie Inception')
    ->asStructured();

Note

Check your provider's documentation to understand which mode they support. Provider support can vary by model, so always verify capabilities for your specific use case.

Provider-Specific Options#

Providers may offer additional options for structured output:

OpenAI: Strict Mode#

OpenAI enforces your schema strictly unless you say otherwise: leaving strict out means strict: true. Set it explicitly to make that visible, or pass false when your schema allows additional properties:

use Prism\Prism\Facades\Prism;
use Prism\Prism\Enums\Provider;

$response = Prism::structured()
    ->withProviderOptions([
        'schema' => [
            'strict' => true
        ]
    ]);
// Add the provider, schema and prompt before calling asStructured().

Qwen: JSON Schema Mode#

Qwen supports two modes for structured output through DashScope's response_format parameter:

  • JSON Object mode (default): Ensures valid JSON output. Supported by most Qwen models.
  • JSON Schema mode: Strictly enforces the schema structure. Supported by qwen3-max, qwen-plus, qwen-flash series and later models.

To use JSON Schema mode for strict schema validation:

use Prism\Prism\Facades\Prism;
use Prism\Prism\Enums\Provider;
use Prism\Prism\Enums\StructuredMode;

$response = Prism::structured()
    ->using(Provider::Qwen, 'qwen-plus')
    ->usingStructuredMode(StructuredMode::Structured)
    ->withSchema($schema)
    ->withPrompt('Extract user information')
    ->asStructured();

Note

JSON Schema mode is only supported by newer models. If you use a model that does not support it, use the default JSON Object mode (or StructuredMode::Auto) instead. Check the Qwen structured output documentation for the full list of supported models.

Anthropic#

Anthropic uses native structured outputs by default (Claude Sonnet 4.5 and later, including the Claude 5 family), providing guaranteed schema compliance through constrained decoding. It works alongside adaptive thinking, effort and your own tools, so it's the mode to reach for on current models.

Warning

Tool calling mode forces the model to call Prism's output tool, and Claude Opus 5.5, Sonnet 5.5 and Fable 5.1 reject a forced tool choice with a 400. On those models, stay with native mode, or add ->withToolChoice(ToolChoice::Auto) to tool calling mode. With Auto the model is asked to use the tool, but the schema isn't enforced.

Tool calling mode is also available. This current-model example selects Auto explicitly; the model can decline the output tool, so validate the result:

use Prism\Prism\Facades\Prism;
use Prism\Prism\Enums\Provider;

$response = Prism::structured()
    ->using(Provider::Anthropic, 'claude-sonnet-5-5')
    ->withSchema($schema)
    ->withPrompt('天氣怎麼樣?應該穿什麼?') // Chinese text with potential quotes
    ->withProviderOptions(['use_tool_calling' => true])
    ->withToolChoice(\Prism\Prism\Enums\ToolChoice::Auto)
    ->asStructured();

When to use tool calling mode with Anthropic:

  • Using older models that don't support native structured outputs
  • Working with non-English content that may contain quotes on older models
  • When explicitly using a compatible model's tool-based output workflow; current native mode can also combine your tools

Note

Tool calling mode cannot be used with Anthropic's citations feature.

Tip

Check the provider-specific documentation pages for additional options and features that might be available for structured output.

Response Handling#

When working with structured responses, you have access to both the structured data and metadata about the generation:

use Prism\Prism\Facades\Prism;

$response = Prism::structured()
    ->using('openai', 'gpt-4.1')
    ->withSchema($schema)
    ->withPrompt('Review the movie Inception')
    ->asStructured();

// Access the structured data as a PHP array
$data = $response->structured;

// Get the raw response text if needed
echo $response->text;

// Check why the generation stopped
echo $response->finishReason->name;

// Get token usage statistics
echo "Prompt tokens: {$response->usage->promptTokens}";
echo "Completion tokens: {$response->usage->completionTokens}";

// Access the raw API response data
$rawResponse = $response->raw;

Tip

Always check the finish reason and validate the structured data before using it in your application:

use Prism\Prism\Enums\FinishReason;

if ($response->finishReason === FinishReason::Refusal) {
    // The model declined; structured is empty and nothing was thrown
    // Anthropic: $response->additionalContent['stop_details']
    // OpenAI: $response->additionalContent['refusal']
}

if (empty($response->structured)) {
    // Handle parsing failure
}

if (!isset($response->structured['required_field'])) {
    // Handle missing required data
}

Request requirements and failures#

Set withSchema() before calling asStructured(). A missing schema raises PrismException while building the request. Malformed response JSON can raise PrismStructuredDecodingException; valid JSON still needs application validation. A refusal can return an empty structured result without a decoding exception, so inspect finishReason too. Decoding exception messages contain the response text; apply your data-retention policy before logging them.

withThread() supplies stored history and can be combined with a new prompt, as in Text Generation. withToolChoice() controls selection on structured providers that support tools. To make a tool raise on failure, call withoutErrorHandling() on that tool before passing it to the request. The structured builder's withoutToolErrorHandling() flag does not alter the tools passed to its provider.

Common Settings#

Structured output supports several configuration options to fine-tune your generations:

Model Configuration#

  • withMaxTokens() - Set the maximum number of tokens to generate
  • usingTemperature() - Control output randomness (provider-dependent)
  • usingTopP() - Alternative to temperature for controlling randomness (provider-dependent)

Input Methods#

  • withPrompt - Single prompt for generation
  • withMessages - Message history for more context
  • withSystemPrompt - System-level instructions

Request Configuration#

  • withClientOptions - Set HTTP client options (e.g., timeouts)
  • withClientRetry - Configure automatic retries on failures
  • usingProviderConfig - Override provider configuration
  • withProviderOptions - Set provider-specific options

See the Text Generation documentation for comparison with standard text generation capabilities.

Combining Structured Output with Tools#

You can combine structured output with tools to gather data before returning a structured response. This lets the AI call functions to fetch information, then format the results according to your schema.

Basic Example#

Here's a simple example that uses a weather tool to gather data, then returns structured output:

use Prism\Prism\Facades\Prism;
use Prism\Prism\Schema\ObjectSchema;
use Prism\Prism\Schema\StringSchema;
use Prism\Prism\Facades\Tool;

$schema = new ObjectSchema(
    name: 'weather_analysis',
    description: 'Analysis of weather conditions',
    properties: [
        new StringSchema('summary', 'Summary of the weather'),
        new StringSchema('recommendation', 'Recommendation based on weather'),
    ],
    requiredFields: ['summary', 'recommendation']
);

$weatherTool = Tool::as('get_weather')
    ->for('Get current weather for a location')
    ->withStringParameter('location', 'The city and state')
    ->using(fn (string $location): string =>
        "Weather in {$location}: 72°F, sunny"
    );

$response = Prism::structured()
    ->using('anthropic', 'claude-sonnet-5-5')
    ->withSchema($schema)
    ->withTools([$weatherTool])
    ->withMaxSteps(3)
    ->withPrompt('What is the weather in San Francisco and should I wear a coat?')
    ->asStructured();

// Access structured output
dump($response->structured);
// ['summary' => '...', 'recommendation' => '...']

Important

When using tools with structured output, you must set maxSteps to at least 2. The AI needs multiple steps: one to call tools, and another to return the structured result.

Multiple Tools#

You can provide multiple tools for the AI to use:

$schema = new ObjectSchema(
    name: 'game_analysis',
    description: 'Analysis of game time and weather',
    properties: [
        new StringSchema('game_time', 'The time of the game'),
        new StringSchema('weather_summary', 'Summary of weather conditions'),
        new StringSchema('recommendation', 'Recommendation on what to wear'),
    ],
    requiredFields: ['game_time', 'weather_summary', 'recommendation']
);

$tools = [
    Tool::as('get_weather')
        ->for('Get current weather for a location')
        ->withStringParameter('city', 'The city name')
        ->using(fn (string $city): string =>
            "Weather in {$city}: 45°F and cold"
        ),
    Tool::as('search_games')
        ->for('Search for game times in a city')
        ->withStringParameter('city', 'The city name')
        ->using(fn (string $city): string =>
            'The Tigers game is at 3pm in Detroit'
        ),
];

$response = Prism::structured()
    ->using('openai', 'gpt-4o')
    ->withSchema($schema)
    ->withTools($tools)
    ->withMaxSteps(5)
    ->withPrompt('What time is the Tigers game today in Detroit and should I wear a coat?')
    ->asStructured();

Response Handling#

When using tools with structured output, the response includes both the structured data and tool execution details:

// Access final structured data
$data = $response->structured;

// Access all tool calls across all steps
foreach ($response->toolCalls as $toolCall) {
    echo "Called: {$toolCall->name}\n";
    echo "Arguments: " . json_encode($toolCall->arguments()) . "\n";
}

// Access tool results
foreach ($response->toolResults as $result) {
    echo "Tool: {$result->toolName}\n";
    echo "Result: {$result->result}\n";
}

// Inspect individual steps
foreach ($response->steps as $step) {
    echo "Step finish reason: {$step->finishReason->name}\n";

    if ($step->toolCalls) {
        echo "Tools called: " . count($step->toolCalls) . "\n";
    }

    if ($step->structured) {
        echo "Contains structured data\n";
    }
}

Note

Only the final step contains structured data. Intermediate steps contain tool calls and tool results, but no structured output.

For more information about tools and function calling, see the Tools & Function Calling documentation.

Important

Always validate the structured response before using it in your application, as different providers may have varying levels of schema adherence.