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_schemaaccepts Pydantic models or JSON dictionaries to enforce response structure in Agno agents.- The parsing pipeline in
libs/agno/agno/agent/_response.pyautomatically converts LLM output whenparse_response=Trueand noparser_modelis set. - Unlike other run options,
output_schemaalways overwrites theRunContextvalue 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=Falseto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →