How to Create Structured Output Using output_schema in Agno Agents

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, 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, 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 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 (lines 1020-1023), the framework calculates the should_parse_structured_output flag:

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. 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:

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:

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:

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:

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:

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), 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.

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 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 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.

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, 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →