12  Structured Output

Get typed, validated responses instead of raw strings.

Note

Code Reference: code/v0.9/src/agentsilex/

  • agent.py
  • runner.py

12.1 The Problem

By default, LLMs return plain text:

result = runner.run(agent, "What's the weather in Tokyo?")
print(result.final_output)
# "The weather in Tokyo is currently 22°C with partly cloudy skies
#  and 65% humidity."

But you often need structured data:

# What you want:
result.final_output.temperature  # 22.0
result.final_output.condition    # "partly cloudy"
result.final_output.humidity     # 65

Parsing free-form text is fragile. Regex breaks. JSON extraction fails on edge cases.

12.2 The Solution

Define a Pydantic model and pass it as output_type:

from pydantic import BaseModel
from agentsilex import Agent, Runner, Session


class WeatherReport(BaseModel):
    city: str
    temperature: float
    condition: str
    humidity: int


agent = Agent(
    name="Weather Reporter",
    model="gemini/gemini-2.0-flash",
    instructions="You are a weather reporter. Respond with realistic weather data.",
    output_type=WeatherReport,  # ← Specify output type
)

session = Session()
result = Runner(session).run(agent, "What's the weather in Tokyo?")

# result.final_output is now a WeatherReport instance
print(f"City: {result.final_output.city}")           # Tokyo
print(f"Temperature: {result.final_output.temperature}°C")  # 22.0
print(f"Condition: {result.final_output.condition}")  # partly cloudy
print(f"Humidity: {result.final_output.humidity}%")   # 65

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

12.3 How It Works

12.3.1 Agent Changes

The Agent class accepts an optional output_type:

class Agent:
    def __init__(
        self,
        name: str,
        model: Any,
        instructions: str,
        tools: List[FunctionTool] | None = None,
        handoffs: List["Agent"] | None = None,
        output_type: Type[BaseModel] | None = None,  # ← New
    ):
        # ...
        self.output_type = output_type

12.3.2 Runner Changes

The Runner passes output_type to LiteLLM and parses the response:

# In Runner.run()

response = completion(
    model=current_agent.model,
    messages=complete_dialogs,
    tools=tools_spec if tools_spec else None,
    response_format=current_agent.output_type,  # ← Pass to LLM
)

# When returning the result:
if current_agent.output_type:
    return RunResult(
        final_output=current_agent.output_type.model_validate_json(
            response_message.content
        )
    )
else:
    return RunResult(
        final_output=response_message.content,
    )

The magic happens in two places:

  1. response_format=output_type — LiteLLM tells the LLM to respond in JSON matching the schema
  2. model_validate_json() — Pydantic parses and validates the JSON response

12.4 More Examples

12.4.1 Complex Nested Structures

from typing import List
from pydantic import BaseModel


class Address(BaseModel):
    street: str
    city: str
    country: str


class Person(BaseModel):
    name: str
    age: int
    email: str
    addresses: List[Address]


agent = Agent(
    name="Data Extractor",
    model="gpt-4o",
    instructions="Extract person information from the given text.",
    output_type=Person,
)

result = runner.run(agent, """
John Smith is 35 years old. His email is john@example.com.
He lives at 123 Main St, New York, USA and also has a vacation
home at 456 Beach Rd, Miami, USA.
""")

print(result.final_output.name)  # "John Smith"
print(result.final_output.addresses[0].city)  # "New York"
print(result.final_output.addresses[1].city)  # "Miami"

12.4.2 With Validation

Pydantic validates the LLM’s response:

from pydantic import BaseModel, Field


class ProductReview(BaseModel):
    rating: int = Field(ge=1, le=5)  # Must be 1-5
    summary: str = Field(max_length=100)
    pros: List[str]
    cons: List[str]


agent = Agent(
    name="Review Analyzer",
    model="gpt-4o",
    instructions="Analyze product reviews and extract structured feedback.",
    output_type=ProductReview,
)

If the LLM returns an invalid rating (e.g., 6), Pydantic raises a ValidationError.

12.5 Streaming with Structured Output

Structured output also works with streaming:

for event in runner.run_stream(agent, "What's the weather?"):
    if isinstance(event, FinalResultEvent):
        # event.final_output is the parsed Pydantic model
        print(event.final_output.temperature)

12.6 When to Use

Use Case Recommendation
Chat responses Plain text (no output_type)
Data extraction Structured output
API responses Structured output
Form filling Structured output
Classification Structured output with enum fields

12.7 Key Points

Aspect Details
output_type Any Pydantic BaseModel subclass
Validation Automatic via Pydantic
Nested models Fully supported
Streaming Works with run_stream()
LLM support Requires model with JSON mode support
TipCheckpoint
cd code/v0.9

You now have structured output support. The agent can return typed, validated data instead of raw strings.