How to Build Type-Safe AI Agents with Pydantic AI - Complete 2026 Tutorial
Learn how to build production-ready, type-safe AI agents in Python using Pydantic AI. Step-by-step guide covering structured outputs, dependency injection, and model-agnostic execution.
Building AI agents that are reliable, type-safe, and production-ready has never been more important. As we move through March 2026, developers are increasingly demanding frameworks that combine the flexibility of Large Language Models with the rigor of typed Python. Enter Pydantic AI — an open-source agent framework from the creators of Pydantic that brings type safety to AI agent development.
In this comprehensive tutorial, you'll learn how to build robust AI agents using Pydantic AI, complete with structured outputs, tool calling, dependency injection, and model-agnostic execution.
What is Pydantic AI?
Pydantic AI is an agent framework built by the Pydantic team (the same team behind the popular Pydantic data validation library). Released in late 2024 and significantly enhanced throughout 2025, it brings Python's type system to AI agent development.
Unlike traditional agent frameworks that feel like walled gardens, Pydantic AI keeps you close to plain Python. You write standard Python functions with type hints, and the framework handles the complex LLM wiring automatically.
Key Features (as of March 2026)
- Type-safe by default: Full integration with Python's type system
- Structured output: Enforce strict schemas on LLM responses
- Dependency injection: Inject databases, APIs, or user context into tools
- Multi-model support: Works with OpenAI, Anthropic Claude, Google Gemini, Ollama, and more
- Built-in debugging: Integration with Logfire for observability
- Tool definition: Define tools using plain Python decorators
Setting Up Your Environment
Before we dive into building agents, let's set up the development environment. You'll need Python 3.10 or later.
# Create a virtual environment
python -m venv pydantic-ai-env
source pydantic-ai-env/bin/activate # On Windows: pydantic-ai-env\Scripts\activate
Install Pydantic AI
pip install pydantic-ai
Install additional dependencies for different models
gpip install openai anthropic google-genai
gpip install pydantic logfire
Note: Pydantic AI itself is open-source and free to use. However, you'll need to pay for the LLM API calls (OpenAI, Anthropic, Google, etc.) based on each provider's pricing.
Building Your First Type-Safe Agent
Let's build a simple but production-ready agent that can answer questions about a database. This example demonstrates the core concepts of Pydantic AI.
Step 1: Define Your Agent
from pydanticai import Agent
from pydantic import BaseModel
Define the expected output structure
class DatabaseQueryResult(BaseModel):
query: str
results: list[dict]
rowcount: int
executiontimems: float
Create your agent with a specific model
agent = Agent(
model='openai:gpt-4o', # or 'anthropic:claude-3-5-sonnet-20241022'
resulttype=DatabaseQueryResult,
systemprompt='You are a database expert. Help users write and optimize SQL queries.'
)
Step 2: Run Your Agent
import asyncio
async def main():
result = agent.runsync(
'List all users who signed up in the last 30 days, grouped by country'
)
# The result is already typed as DatabaseQueryResult
print(f"Query: {result.data.query}")
print(f"Row count: {result.data.rowcount}")
print(f"Execution time: {result.data.executiontimems}ms")
asyncio.run(main())
The magic here is resulttype=DatabaseQueryResult. Pydantic AI automatically:
- Instructs the LLM to return JSON matching your schema
- Validates the response against your Pydantic model
- Returns a fully-typed Python object
Working with Tools
Tools extend your agent's capabilities by letting it interact with external systems. In Pydantic AI, tools are just Python functions decorated with @agent.tool.
from pydanticai import Agent, RunContext
Define dependencies (type-safe context injection)
class DatabaseDeps:
def init(self, connectionstring: str):
self.connstring = connectionstring
Create agent with dependencies
agent = Agent(
model='openai:gpt-4o',
depstype=DatabaseDeps,
)
Define a tool
@agent.tool
def executequery(ctx: RunContext[DatabaseDeps], query: str) -> dict:
"""Execute a SQL query and return results."""
# Access dependencies safely
conn = connect(ctx.deps.connstring)
cursor = conn.cursor()
cursor.execute(query)
results = cursor.fetchall()
return {
'rows': results,
'count': len(results)
}
Run with dependencies
result = agent.run(
'Show me the top 10 orders by value',
deps=DatabaseDeps(connection_string='postgresql://localhost/mydb')
)
Tool Definition Features
- Automatic schema generation: Pydantic AI extracts function signatures and generates tool schemas automatically
- Type hints preserved: Parameter types are enforced
- Dependency injection: Access
RunContextto get your injected dependencies - Async support: Both sync and async tools supported
Structured Outputs: Enforcing Strict Schemas
One of Pydantic AI's most powerful features is structured output enforcement. This is crucial for production systems where you need predictable, parseable responses.
from pydantic import BaseModel, Field
from enum import Enum
from pydanticai import Agent
class Sentiment(str, Enum):
POSITIVE = "positive"
NEGATIVE = "negative"
NEUTRAL = "neutral"
class ProductReview(BaseModel):
"""Structured output for product review analysis."""
sentiment: Sentiment
confidencescore: float = Field(ge=0, le=1)
keyphrases: list[str] = Field(maxlength=10)
summary: str = Field(maxlength=200)
wouldrecommend: bool
agent = Agent(
model='anthropic:claude-3-5-sonnet-20241022',
resulttype=ProductReview,
)
result = agent.runsync(
'Analyze this review: "The new iPhone 16 Pro is absolutely amazing! '
'The camera quality blew me away, though the battery life could be better. '
'Overall, I would highly recommend it to anyone looking for a premium smartphone."'
)
result.data is fully typed as ProductReview
print(result.data.dict())
Output validation happens automatically. If the LLM returns malformed data, Pydantic will retry or raise an error rather than return invalid data.
Model-Agnostic Execution
Pydantic AI supports multiple LLM providers, allowing you to switch models without changing your agent code:
# OpenAI
gptagent = Agent(model='openai:gpt-4o', ...)
Anthropic Claude
claudeagent = Agent(model='anthropic:claude-3-5-sonnet-20241022', ...)
Google Gemini
geminiagent = Agent(model='google-gemini-2.0-flash', ...)
Local models via Ollama
ollama
agent = Agent(model='ollama:llama3', ...)
This model-agnostic approach lets you:
- Start with powerful cloud models during development
- Switch to cheaper/faster models for production
- A/B test different models
- Fall back to local models for privacy-sensitive data
Real-World Example: Customer Support Agent
Let's build a more complete example — a customer support agent that can access order data, process refunds, and generate typed responses.
from pydanticai import Agent, RunContext
from pydantic import BaseModel
from datetime import datetime
from enum import Enum
Define dependencies
class SupportDeps:
def init(self, apikey: str, environment: str):
self.apikey = apikey
self.environment = environment
Define output schema
class SupportAction(BaseModel):
actiontype: str # 'refund', 'escalate', 'answer', 'lookuporder'
orderid: str | None = None
refundamount: float | None = None
responsemessage: str
escalatepriority: int | None = None
Create the agent
supportagent = Agent(
model='anthropic:claude-3-5-sonnet-20241022',
resulttype=SupportAction,
systemprompt='''You are a customer support agent.
- Always try to resolve issues without refunds first
- For orders older than 90 days, escalate to human
- Maximum refund amount is $500 without manager approval'''
)
Add tools
@supportagent.tool
def lookuporder(ctx: RunContext[SupportDeps], orderid: str) -> dict:
"""Look up order details by order ID."""
# In production, this would call your actual API
return {
'orderid': orderid,
'status': 'delivered',
'orderdate': '2026-02-15',
'total': 199.99,
'items': ['Wireless Headphones', 'Phone Case']
}
@supportagent.tool
def processrefund(ctx: RunContext[SupportDeps], orderid: str, amount: float) -> dict:
"""Process a refund for an order."""
return {
'success': True,
'refundid': f'REF-{datetime.now().timestamp()}',
'amount': amount
}
Run the agent
result = supportagent.run(
'Customer says their order #12345 arrived damaged and wants a full refund',
deps=SupportDeps(apikey='sk-xxx', environment='production')
)
print(result.data.actiontype) # 'refund'
print(result.data.response_message)
Pricing Considerations (March 2026)
Pydantic AI is open-source and free to use. However, you'll need to budget for LLM API calls:
| Provider | Model | Input Price | Output Price |
|---|---|---|---|
| OpenAI | GPT-4o | $2.50/1M | $10.00/1M |
| OpenAI | GPT-4o-mini | $0.15/1M | $0.60/1M |
| Anthropic | Claude 3.5 Sonnet | $3.00/1M | $15.00/1M |
| Anthropic | Claude 3 Haiku | $0.80/1M | $4.00/1M |
| Gemini 2.0 Flash | $0.00/1M | $0.00/1M |
*Google Gemini 2.0 Flash has generous free tiers as of March 2026.
For local deployment, you can use Ollama with models like Llama 3 (free, runs locally on your hardware).
Best Practices for Production
- Always define result types: Use Pydantic models to enforce output schemas
- Use dependency injection: Never hardcode API keys or connections
- Add retry logic: Configure automatic retries for transient failures
- Monitor with Logfire: Integrated observability for debugging
- Handle validation errors: Plan for when LLM output doesn't match schema
- Test with multiple models: Verify behavior across different LLM providers
Conclusion
Pydantic AI represents a significant step forward in building reliable, type-safe AI agents. By combining Python's type system with the flexibility of LLMs, it enables developers to create production-ready agents that are predictable, testable, and maintainable.
Whether you're building customer support systems, data analysis tools, or automation workflows, Pydantic AI provides the foundation you need for robust AI agent development in 2026.
Key Takeaways:
- Type safety reduces runtime errors and improves developer experience
- Structured outputs ensure predictable LLM responses
- Dependency injection keeps your code clean and testable
- Multi-model support provides flexibility and cost optimization
Start building with Pydantic AI today — your future self will thank you.
Related Articles
Answer Engine Optimization (AEO): How to Rank in AI Search in 2026
Google AI Mode expanded globally in March 2026. Learn exactly how to optimize your content for AI search engines with this step-by-step AEO guide — covering schema markup, E-E-A-T signals, GPTBot access, and more.
Agentic Coding in Xcode 26.3: How to Set Up Claude Agent and Codex
Apple's Xcode 26.3 (February 2026) now supports agentic coding with Anthropic's Claude Agent and OpenAI's Codex. Here's the complete step-by-step setup guide for iOS and macOS developers.
Prompt Engineering for Developers: Advanced Techniques That Work in 2026
Master the art of prompting AI models with practical techniques including chain-of-thought, few-shot learning, and structured output generation. A developer's complete guide for March 2026.