AI Tools12 min read

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.

A
Admin
11 views

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] row
count: 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', deps
type=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 RunContext to 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

claude
agent = Agent(model='anthropic:claude-3-5-sonnet-20241022', ...)

Google Gemini

geminiagent = Agent(model='google-gemini-2.0-flash', ...)

Local models via Ollama

ollamaagent = 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, api
key: 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

@support
agent.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 = support
agent.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:

ProviderModelInput PriceOutput Price
OpenAIGPT-4o$2.50/1M$10.00/1M
OpenAIGPT-4o-mini$0.15/1M$0.60/1M
AnthropicClaude 3.5 Sonnet$3.00/1M$15.00/1M
AnthropicClaude 3 Haiku$0.80/1M$4.00/1M
GoogleGemini 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

  1. Always define result types: Use Pydantic models to enforce output schemas
  2. Use dependency injection: Never hardcode API keys or connections
  3. Add retry logic: Configure automatic retries for transient failures
  4. Monitor with Logfire: Integrated observability for debugging
  5. Handle validation errors: Plan for when LLM output doesn't match schema
  6. 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.