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
How to Deploy an AI Customer Service Agent in 2026: Step-by-Step with Real ROI Numbers
Learn exactly how to build and deploy an AI customer service agent in 2026 — with real ROI benchmarks, tool comparisons (Intercom Fin, Voiceflow, Salesforce Agentforce), and a step-by-step setup guide that actually works.
Google Gemma 4 Complete Guide: Benchmarks, Local Setup & Use Cases (April 2026)
Google released Gemma 4 on April 2, 2026 — four open-weight models ranking #3 globally, running on phones, Raspberry Pi, and local GPUs under Apache 2.0. Full benchmark breakdown, setup guide, and real-world use cases.
Google ADK Tutorial: Build Your First AI Agent in 2026 (Step-by-Step)
Learn how to build production-ready AI agents with Google's Agent Development Kit (ADK) v1.0.0. Step-by-step tutorial covering installation, multi-agent systems, SkillToolset, and Vertex AI deployment.