# How to Create Structured Output Using output_schema in Agno Agents

> Learn to create structured output with Agno Agents using output_schema. Automatically parse LLM responses into typed Python objects with Pydantic BaseModel.

- Repository: [Agno/agno](https://github.com/agno-agi/agno)
- Tags: how-to-guide
- Published: 2026-02-23

---

**Set the `output_schema` parameter to a Pydantic `BaseModel` subclass when initializing an Agent or calling `agent.run()` to automatically parse LLM responses into typed Python objects.**

The `agno-agi/agno` framework simplifies structured output handling by integrating Pydantic-based validation directly into the agent execution pipeline. By leveraging `output_schema`, you can enforce specific data formats, eliminate manual JSON parsing, and ensure type safety across your AI applications. This capability works at the agent level, per individual run, and within complex workflows.

## What Is output_schema?

**`output_schema`** is a configuration field available on the `Agent` class (as well as `Team` and `Workflow` steps) that defines the expected structure of the model's response. When provided, Agno automatically converts the raw text returned by the language model into a validated Python object.

According to the source code in [`libs/agno/agno/agent/agent.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/agent.py), you can assign either a **Pydantic model** (subclass of `BaseModel`) or a raw JSON-schema-like `dict` to this parameter. The framework handles the conversion after the LLM finishes generation, storing the result in `run_response.parsed` for immediate use.

## How Structured Output Parsing Works

The structured output pipeline involves three distinct phases: resolution, decision, and parsing. Understanding this flow helps debug schema mismatches and optimize agent configuration.

### Schema Resolution and Context Propagation

When you invoke an agent with `agent.run(..., output_schema=MySchema)`, the framework immediately resolves all run options. In [`libs/agno/agno/agent/_run_options.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/_run_options.py), the `resolve_run_options` function creates a `ResolvedRunOptions` object that captures the schema for that specific execution.

Unlike other run options that may inherit from existing context, `output_schema` behaves differently. Lines 62-66 in [`_run_options.py`](https://github.com/agno-agi/agno/blob/main/_run_options.py) explicitly overwrite `run_context.output_schema` with the resolved value. This ensures **isolation between workflow steps**—critical when parallel processes reuse the same `RunContext` instance but require different output formats.

### The Parsing Decision Logic

After receiving the model response, Agno determines whether to parse the output as structured data. In [`libs/agno/agno/agent/_response.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/_response.py) (lines 1020-1023), the framework calculates the `should_parse_structured_output` flag:

```python
should_parse_structured_output = (
    output_schema is not None
    and agent.parse_response
    and agent.parser_model is None
)

```

This condition requires three elements: an `output_schema` must be defined, the `parse_response` flag must remain `True` (the default), and no separate `parser_model` should be configured. When all conditions pass, Agno proceeds with automatic conversion.

### JSON to Pydantic Conversion

The actual parsing utilizes helper functions defined in [`libs/agno/agno/utils/string.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/utils/string.py). If the response content is a string, `parse_response_model_str` deserializes the JSON and instantiates your Pydantic model. For dictionary content, `parse_response_dict_str` handles the conversion. These utilities ensure that nested schemas and type coercion work correctly without manual intervention.

## Setting Up Structured Output

Implementing structured output requires defining your data model and configuring the agent to use it. You can set this at construction time or override it per execution.

### Defining a Pydantic Schema

Start by creating a model that represents your desired output structure:

```python
from pydantic import BaseModel

class WeatherReport(BaseModel):
    location: str
    temperature_c: float
    condition: str

```

This schema validates that the LLM returns all required fields with correct data types.

### Agent-Level Configuration

To enforce structured output for every run of a specific agent, pass the schema during initialization:

```python
from agno import Agent
from my_schemas import WeatherReport

weather_agent = Agent(
    model="gpt-4o-mini",
    expected_output="Return a JSON weather report.",
    output_schema=WeatherReport,
)

result = weather_agent.run("What's the weather in Berlin?")
print(result.parsed.location)  # Access typed attributes directly

print(type(result.parsed))     # <class 'my_schemas.WeatherReport'>

```

The `result.parsed` attribute contains the validated Pydantic instance, while `result.content` retains the raw response string.

### Per-Run Override

You can also specify or override the schema for individual runs without changing the agent's default configuration:

```python
from my_schemas import StockQuote

# Override schema for a specific query

result = weather_agent.run(
    "Current AAPL stock price",
    output_schema=StockQuote
)
print(result.parsed.current_price)

```

This flexibility allows the same agent to handle different output formats based on runtime requirements.

### Disabling Automatic Parsing

In scenarios where you need the raw JSON string for custom processing, disable parsing while retaining the schema for documentation purposes:

```python
raw_agent = Agent(
    model="gpt-4o-mini",
    output_schema=WeatherReport,
    parse_response=False,  # Skip automatic conversion

)

raw = raw_agent.run("Weather in Tokyo?")
print(raw.content)  # Raw JSON string

print(raw.parsed)   # None

```

## Using output_schema in Workflows

Workflows benefit significantly from structured output isolation. When defining multi-step processes, each step can specify its own schema without interference:

```python
from agno import Agent, Workflow, Step
from my_schemas import WeatherReport, StockQuote

weather_agent = Agent(model="gpt-4o-mini", output_schema=WeatherReport)
stock_agent = Agent(model="gpt-4o-mini", output_schema=StockQuote)

wf = Workflow()
wf.add(
    Step(agent=weather_agent, name="weather", output_schema=WeatherReport),
    Step(agent=stock_agent, name="stock", output_schema=StockQuote),
)

run = wf.run("Give me Berlin weather and the current price of AAPL.")
print(run.get_step_output("weather").parsed)  # WeatherReport instance

print(run.get_step_output("stock").parsed)    # StockQuote instance

```

Because `run_context.output_schema` is always set from resolved options (as implemented in [`_run_options.py`](https://github.com/agno-agi/agno/blob/main/_run_options.py)), parallel workflow steps maintain schema isolation. This prevents race conditions where one step's schema might leak into another, as verified in the test suite at [`tests/unit/workflow/test_parallel_run_context_isolation.py`](https://github.com/agno-agi/agno/blob/main/tests/unit/workflow/test_parallel_run_context_isolation.py).

## Summary

- **`output_schema`** accepts Pydantic models or JSON dictionaries to enforce response structure in Agno agents.
- The parsing pipeline in [`libs/agno/agno/agent/_response.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/_response.py) automatically converts LLM output when `parse_response=True` and no `parser_model` is set.
- Unlike other run options, `output_schema` always overwrites the `RunContext` value to ensure workflow step isolation.
- Configure schemas at the agent level for defaults, or pass them to `agent.run()` for per-execution overrides.
- Set `parse_response=False` to receive raw JSON strings while maintaining schema documentation.

## Frequently Asked Questions

### How does Agno handle invalid JSON that doesn't match the output_schema?

When the LLM returns malformed JSON or data that violates the Pydantic model constraints, the parsing functions in [`libs/agno/agno/utils/string.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/utils/string.py) will raise a validation error. You should wrap your `agent.run()` calls in try-except blocks to catch these parsing failures, or implement a retry mechanism with more specific prompting to ensure the model returns valid JSON.

### Can I use output_schema with streaming responses?

Yes, `output_schema` works with streaming, but the parsed object will only be available once the stream completes and the full response is assembled. During streaming, you receive chunks of text via `run_response.content`, while `run_response.parsed` populates after the final chunk processes through the parsing logic in [`_response.py`](https://github.com/agno-agi/agno/blob/main/_response.py).

### What is the difference between output_schema and parser_model?

**`output_schema`** instructs Agno to parse the primary LLM's response into a specific structure using the built-in string parsing utilities. **`parser_model`**, conversely, designates a separate language model instance specifically for reformatting or fixing malformed outputs. If `parser_model` is set, Agno skips the standard `output_schema` parsing and delegates conversion to that specialized model instead.

### Why is my output_schema being ignored in a workflow step?

If your schema appears ignored, verify that you are not inadvertently reusing a `RunContext` without the schema properly resolved. According to lines 62-66 in [`libs/agno/agno/agent/_run_options.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/_run_options.py), the framework should always overwrite `run_context.output_schema` with the resolved options. Ensure you are passing `output_schema` to the `Step` definition's run options rather than relying on agent defaults when working in complex workflows.