Structured Outputs¶
Agent supports type-safe structured outputs using Pydantic models. This ensures LLM responses conform to your expected schema.
Basic Usage¶
from pydantic import BaseModel
from agent import Agent
class Sentiment(BaseModel):
text: str
sentiment: str # "positive", "negative", "neutral"
confidence: float
agent = Agent(provider="openai", model="gpt-4o")
response = agent.json(
"Analyze the sentiment: 'I love this product!'",
schema=Sentiment
)
# response.output is a Sentiment instance
print(response.output.sentiment) # "positive"
print(response.output.confidence) # 0.95
Complex Schemas¶
Nested Models¶
from pydantic import BaseModel
class Address(BaseModel):
street: str
city: str
country: str
postal_code: str
class Person(BaseModel):
name: str
age: int
email: str
address: Address
response = agent.json(
"Extract person info from: John Doe, 30, john@example.com, lives at 123 Main St, NYC, USA 10001",
schema=Person
)
print(response.output.address.city) # "NYC"
Lists and Optional Fields¶
from pydantic import BaseModel
from typing import Optional
class Task(BaseModel):
title: str
description: str
priority: str # "high", "medium", "low"
due_date: Optional[str] = None
tags: list[str] = []
class TaskList(BaseModel):
tasks: list[Task]
total_count: int
response = agent.json(
"Create a task list for launching a new product",
schema=TaskList
)
for task in response.output.tasks:
print(f"- [{task.priority}] {task.title}")
Enums and Literals¶
from pydantic import BaseModel
from typing import Literal
from enum import Enum
class Priority(str, Enum):
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
class Issue(BaseModel):
title: str
priority: Priority
status: Literal["open", "in_progress", "closed"]
response = agent.json("Create a high priority bug report for login issues", schema=Issue)
print(response.output.priority) # Priority.HIGH
Field Descriptions¶
Add descriptions to help the LLM understand your schema:
from pydantic import BaseModel, Field
class CodeReview(BaseModel):
"""A code review assessment."""
summary: str = Field(
description="Brief summary of the code changes"
)
issues: list[str] = Field(
description="List of potential issues or bugs found"
)
suggestions: list[str] = Field(
description="Improvement suggestions for better code quality"
)
score: int = Field(
ge=1, le=10,
description="Overall code quality score from 1-10"
)
approved: bool = Field(
description="Whether the code is ready for merge"
)
Validation¶
Pydantic validates responses automatically:
from pydantic import BaseModel, Field, field_validator
class Rating(BaseModel):
score: int = Field(ge=1, le=5)
review: str = Field(min_length=10)
@field_validator('review')
@classmethod
def review_not_empty(cls, v):
if not v.strip():
raise ValueError('Review cannot be empty')
return v.strip()
# If LLM returns invalid data, SchemaValidationError is raised
try:
response = agent.json("Rate this product: Amazing!", schema=Rating)
except SchemaValidationError as e:
print(f"Invalid response: {e}")
Native vs Prompt-Based¶
Native Schema Support (OpenAI, Gemini)¶
Some providers support schema-enforced generation:
# OpenAI uses response_format with json_schema
agent = Agent(provider="openai", model="gpt-4o")
response = agent.json("...", schema=MyModel)
# Uses native JSON schema mode - guaranteed valid JSON
Prompt-Based (Anthropic, others)¶
For providers without native support, Agent adds schema instructions to the prompt:
# Anthropic uses prompt engineering
agent = Agent(provider="anthropic", model="claude-sonnet")
response = agent.json("...", schema=MyModel)
# Agent adds: "Respond with JSON matching this schema: {...}"
JSON Schema Directly¶
You can also use raw JSON Schema:
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"tags": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["name", "age"]
}
response = agent.json("Create a user profile", schema=schema)
# response.output is a dict
With Sessions¶
Use structured outputs in multi-turn conversations:
session = agent.session()
# Regular messages
session.run("I want to plan a trip to Japan")
# Structured output for final result
class TravelPlan(BaseModel):
destination: str
duration_days: int
activities: list[str]
estimated_budget: float
response = session.json(
"Create a detailed travel plan based on our discussion",
schema=TravelPlan
)
Async Support¶
response = await agent.json_async(
"Analyze this data",
schema=AnalysisResult
)
Error Handling¶
from agent.errors import SchemaValidationError
try:
response = agent.json("...", schema=MyModel)
except SchemaValidationError as e:
print(f"Schema: {e.schema}")
print(f"Output: {e.output}")
print(f"Message: {e.message}")
Repair Attempts¶
Agent can attempt to fix malformed JSON:
from agent.schemas import Schema
schema = Schema(
MyModel,
strict=True,
repair_attempts=2 # Try to fix JSON twice before failing
)
# Use with the Schema wrapper for more control
from agent.execution.structured_output import StructuredOutputHandler
handler = StructuredOutputHandler(MyModel, repair_attempts=3)
Best Practices¶
1. Keep Schemas Simple¶
# Good - flat, clear structure
class Summary(BaseModel):
title: str
points: list[str]
word_count: int
# Avoid - deeply nested, complex
class Analysis(BaseModel):
meta: Meta
sections: list[Section] # where Section has SubSections...
2. Use Descriptive Field Names¶
# Good
class Product(BaseModel):
product_name: str
price_usd: float
in_stock: bool
# Bad
class Product(BaseModel):
n: str
p: float
s: bool
3. Provide Examples in Descriptions¶
class Category(BaseModel):
name: str = Field(
description="Category name, e.g., 'Electronics', 'Clothing', 'Books'"
)
confidence: float = Field(
description="Confidence score between 0.0 and 1.0"
)
4. Handle Optional Fields¶
class Article(BaseModel):
title: str
content: str
author: str | None = None # May not always be available
published_date: str | None = None
tags: list[str] = [] # Empty list as default
Next Steps¶
Tools - Combine structured outputs with tool calling
Sessions - Structured outputs in conversations
Type System - Understanding Agent’s type system