Trixly AI Solutions
Agentic Software Engineering

Pydantic AI Agent Framework: Building Intelligent AI Agents with Python

By Muhammad Hassan
February 10, 202610 min read

The Pydantic AI agent framework has emerged as a powerful solution for developers building intelligent AI agents with Python. By combining the robust validation capabilities of Pydantic with advanced agent orchestration features, this framework provides a type-safe, production-ready platform for creating AI applications that are reliable, maintainable, and scalable.

Whether you're building chatbots, autonomous agents, or complex multi-agent systems, the Pydantic AI agent framework offers the tools and patterns you need to develop sophisticated AI applications with confidence. This comprehensive guide explores the framework's capabilities, demonstrates practical implementations, and shares best practices for building production-grade AI agents.

What is the Pydantic AI Agent Framework?

The Pydantic AI agent framework is a Python library that streamlines the development of AI agents by providing structured data validation, type safety, and agent orchestration capabilities. Built on top of Pydantic, the popular data validation library, it extends these capabilities specifically for AI agent development.

At its core, the Pydantic AI agent framework solves several critical challenges in AI development. It ensures that data flowing between your application and language models is properly validated and typed, provides clear patterns for structuring agent logic, handles errors gracefully, and makes testing AI applications more straightforward and reliable.

The framework integrates seamlessly with major language model providers including OpenAI, Anthropic, Google, and others, while maintaining a consistent API that makes switching between providers straightforward. This flexibility is essential as the AI landscape evolves and new models become available.

Key Features of Pydantic AI Agent Framework

Understanding the core features helps developers leverage the full power of the Pydantic AI agent framework in their applications.

Type-Safe Agent Definitions

The Pydantic AI agent framework uses Python's type system to ensure correctness at development time. Agents are defined with clear input and output types, making it impossible to accidentally pass wrong data types or receive unexpected responses.

from pydantic_ai import Agent
from pydantic import BaseModel

class UserQuery(BaseModel):
    question: str
    context: dict[str, str] = {}

class AgentResponse(BaseModel):
    answer: str
    confidence: float
    sources: list[str] = []

# Define a type-safe agent
agent = Agent(
    'openai:gpt-4',
    result_type=AgentResponse,
    system_prompt='You are a helpful research assistant.'
)

Structured Output Validation

One of the most powerful features is automatic validation of language model outputs. The framework ensures that responses conform to your defined schemas, automatically retrying if the model produces invalid output.

from pydantic import BaseModel, Field
from pydantic_ai import Agent

class ProductRecommendation(BaseModel):
    product_name: str = Field(description="Name of the recommended product")
    price: float = Field(gt=0, description="Product price in USD")
    reasoning: str = Field(min_length=20, description="Why this product is recommended")
    category: str

recommendation_agent = Agent(
    'anthropic:claude-sonnet-4',
    result_type=ProductRecommendation
)

# The framework ensures the output matches the schema
result = recommendation_agent.run_sync("Recommend a laptop for software development")
print(f"Recommended: {result.data.product_name} at ${result.data.price}")

Dependency Injection

The Pydantic AI agent framework supports dependency injection, making it easy to provide agents with access to databases, APIs, or other services without tight coupling.

from dataclasses import dataclass
from pydantic_ai import Agent, RunContext

@dataclass
class DatabaseConnection:
    connection_string: str
    
    def query(self, sql: str) -> list[dict]:
        # Database query logic here
        pass

agent = Agent('openai:gpt-4', deps_type=DatabaseConnection)

@agent.tool
def search_database(ctx: RunContext[DatabaseConnection], query: str) -> list[dict]:
    """Search the database for relevant information."""
    return ctx.deps.query(f"SELECT * FROM data WHERE content LIKE '%{query}%'")

# Run with dependencies
db = DatabaseConnection("postgresql://localhost/mydb")
result = agent.run_sync("Find all customers from California", deps=db)

Tool and Function Calling

Agents can be equipped with tools that extend their capabilities beyond text generation. The Pydantic AI agent framework handles function calling automatically, including parameter validation and result processing.

from pydantic_ai import Agent, RunContext
import httpx

agent = Agent('openai:gpt-4')

@agent.tool
def get_weather(ctx: RunContext[None], city: str) -> dict:
    """Get current weather for a city."""
    response = httpx.get(f"https://api.weather.com/v1/weather?city={city}")
    return response.json()

@agent.tool
def calculate_distance(ctx: RunContext[None], city1: str, city2: str) -> float:
    """Calculate distance between two cities in miles."""
    # Distance calculation logic
    return 523.7

# Agent automatically decides which tools to use
result = agent.run_sync("What's the weather like in San Francisco and how far is it from Los Angeles?")

Building Your First Agent with Pydantic AI

Let's walk through creating a practical AI agent using the Pydantic AI agent framework. This example demonstrates a customer support agent with structured responses and tool integration.

Installation and Setup

First, install the Pydantic AI agent framework along with your preferred model provider:

# Install Pydantic AI with OpenAI support
pip install pydantic-ai[openai]

# Or with Anthropic support
pip install pydantic-ai[anthropic]

# Or install all providers
pip install pydantic-ai[all]

Complete Customer Support Agent Example

from pydantic import BaseModel, Field
from pydantic_ai import Agent, RunContext
from dataclasses import dataclass
from datetime import datetime

# Define the response structure
class SupportResponse(BaseModel):
    message: str = Field(description="Response message to the customer")
    category: str = Field(description="Support category: technical, billing, general")
    priority: int = Field(ge=1, le=5, description="Priority level 1-5")
    requires_escalation: bool = Field(description="Whether to escalate to human agent")
    suggested_articles: list[str] = Field(default=[], description="Knowledge base articles")

# Define dependencies
@dataclass
class SupportDeps:
    customer_id: str
    ticket_history: list[dict]
    
    def get_customer_info(self) -> dict:
        """Fetch customer information from database."""
        return {
            "name": "John Doe",
            "tier": "premium",
            "account_age_days": 730
        }
    
    def search_knowledge_base(self, query: str) -> list[str]:
        """Search internal knowledge base."""
        # Simulate KB search
        return [
            "How to reset your password",
            "Billing cycle explained",
            "API rate limits"
        ]

# Create the agent
support_agent = Agent(
    'anthropic:claude-sonnet-4',
    result_type=SupportResponse,
    deps_type=SupportDeps,
    system_prompt="""You are an expert customer support agent. 
    Analyze customer inquiries and provide helpful, professional responses.
    Categorize issues appropriately and escalate when necessary.
    Always check the knowledge base for relevant articles."""
)

# Add tools
@support_agent.tool
def get_customer_details(ctx: RunContext[SupportDeps]) -> dict:
    """Retrieve customer account information."""
    return ctx.deps.get_customer_info()

@support_agent.tool
def search_help_articles(ctx: RunContext[SupportDeps], search_query: str) -> list[str]:
    """Search knowledge base for relevant help articles."""
    return ctx.deps.search_knowledge_base(search_query)

@support_agent.tool
def check_ticket_history(ctx: RunContext[SupportDeps]) -> list[dict]:
    """Review customer's previous support tickets."""
    return ctx.deps.ticket_history

# Use the agent
def handle_support_ticket(customer_id: str, message: str):
    deps = SupportDeps(
        customer_id=customer_id,
        ticket_history=[
            {"date": "2024-01-15", "issue": "Password reset", "resolved": True},
            {"date": "2024-02-03", "issue": "Billing question", "resolved": True}
        ]
    )
    
    result = support_agent.run_sync(message, deps=deps)
    
    print(f"Category: {result.data.category}")
    print(f"Priority: {result.data.priority}")
    print(f"Escalate: {result.data.requires_escalation}")
    print(f"Response: {result.data.message}")
    print(f"Suggested articles: {', '.join(result.data.suggested_articles)}")
    
    return result.data

# Example usage
response = handle_support_ticket(
    "CUST-12345",
    "I've been charged twice this month and my API calls are failing"
)

Advanced Patterns with Pydantic AI Agent Framework

As your applications grow in complexity, the Pydantic AI agent framework provides advanced patterns for sophisticated use cases.

Multi-Agent Collaboration

Create systems where multiple specialized agents work together to solve complex problems.

from pydantic_ai import Agent
from pydantic import BaseModel

class ResearchFindings(BaseModel):
    summary: str
    key_facts: list[str]
    sources: list[str]

class WrittenArticle(BaseModel):
    title: str
    content: str
    word_count: int

# Research agent
research_agent = Agent(
    'openai:gpt-4',
    result_type=ResearchFindings,
    system_prompt='You are a thorough researcher who finds accurate information.'
)

# Writing agent
writing_agent = Agent(
    'anthropic:claude-sonnet-4',
    result_type=WrittenArticle,
    system_prompt='You are a skilled writer who creates engaging articles.'
)

async def create_article(topic: str) -> WrittenArticle:
    # First agent researches the topic
    research = await research_agent.run(f"Research comprehensive information about: {topic}")
    
    # Second agent writes based on research
    article_prompt = f"""Write an article about {topic}.
    
    Use these research findings:
    Summary: {research.data.summary}
    Key facts: {', '.join(research.data.key_facts)}
    Sources: {', '.join(research.data.sources)}
    """
    
    article = await writing_agent.run(article_prompt)
    return article.data

Streaming Responses

The Pydantic AI agent framework supports streaming for real-time user experiences.

from pydantic_ai import Agent

agent = Agent('openai:gpt-4')

async def stream_response(query: str):
    async with agent.run_stream(query) as response:
        async for text in response.stream_text():
            print(text, end='', flush=True)
        
        # Get final validated result
        final_result = await response.get_data()
        return final_result

# Usage
import asyncio
asyncio.run(stream_response("Explain quantum computing"))

Error Handling and Retries

Robust error handling is essential for production applications.

from pydantic_ai import Agent, ModelRetry, UnexpectedModelBehavior
from pydantic import BaseModel, ValidationError

class AnalysisResult(BaseModel):
    sentiment: str
    confidence: float
    
agent = Agent('openai:gpt-4', result_type=AnalysisResult)

async def analyze_with_retry(text: str, max_attempts: int = 3):
    for attempt in range(max_attempts):
        try:
            result = await agent.run(f"Analyze sentiment: {text}")
            return result.data
            
        except ModelRetry as e:
            print(f"Retry attempt {attempt + 1}: Model needs to retry")
            if attempt == max_attempts - 1:
                raise
                
        except ValidationError as e:
            print(f"Validation failed: {e}")
            # Could implement custom retry logic
            if attempt == max_attempts - 1:
                raise
                
        except UnexpectedModelBehavior as e:
            print(f"Unexpected behavior: {e}")
            raise

# Usage
result = asyncio.run(analyze_with_retry("This product exceeded my expectations!"))

Context Management and Memory

Maintain conversation context across multiple interactions.

from pydantic_ai import Agent
from pydantic import BaseModel

class ConversationMessage(BaseModel):
    role: str
    content: str

class ConversationAgent:
    def __init__(self, model: str = 'openai:gpt-4'):
        self.agent = Agent(model)
        self.history: list[ConversationMessage] = []
    
    async def chat(self, user_message: str) -> str:
        # Add user message to history
        self.history.append(ConversationMessage(role="user", content=user_message))
        
        # Build context from history
        context = "\n".join([
            f"{msg.role}: {msg.content}" 
            for msg in self.history[-10:]  # Keep last 10 messages
        ])
        
        # Get response
        result = await self.agent.run(
            f"Previous conversation:\n{context}\n\nRespond to the latest message."
        )
        
        # Add assistant response to history
        response_text = str(result.data)
        self.history.append(ConversationMessage(role="assistant", content=response_text))
        
        return response_text
    
    def clear_history(self):
        """Reset conversation history."""
        self.history = []

# Usage
conv_agent = ConversationAgent()
asyncio.run(conv_agent.chat("Hello, what can you help me with?"))
asyncio.run(conv_agent.chat("Tell me more about the first option"))

Integration with Popular AI Models

The Pydantic AI agent framework works seamlessly with multiple model providers, making it easy to switch or compare models.

OpenAI Integration

from pydantic_ai import Agent

# GPT-4 agent
gpt4_agent = Agent('openai:gpt-4')

# GPT-3.5 for faster, cheaper responses
gpt35_agent = Agent('openai:gpt-3.5-turbo')

# Specify custom parameters
custom_agent = Agent(
    'openai:gpt-4',
    model_settings={
        'temperature': 0.7,
        'max_tokens': 2000,
        'top_p': 0.9
    }
)

Anthropic Claude Integration

from pydantic_ai import Agent

# Claude Opus for complex reasoning
opus_agent = Agent('anthropic:claude-opus-4')

# Claude Sonnet for balanced performance
sonnet_agent = Agent('anthropic:claude-sonnet-4')

# Claude Haiku for speed
haiku_agent = Agent('anthropic:claude-haiku-4')

Google Gemini Integration

from pydantic_ai import Agent

# Gemini Pro
gemini_agent = Agent('google:gemini-pro')

# With custom settings
gemini_custom = Agent(
    'google:gemini-pro',
    model_settings={
        'temperature': 0.8,
        'top_k': 40,
        'top_p': 0.95
    }
)

Testing AI Agents

The Pydantic AI agent framework makes testing AI applications more reliable and straightforward.

Unit Testing with Mock Responses

import pytest
from pydantic_ai import Agent
from pydantic_ai.testing import TestModel

@pytest.fixture
def test_agent():
    return Agent('test', result_type=str)

def test_agent_response(test_agent):
    # Use TestModel for deterministic testing
    with TestModel() as test_model:
        test_model.add_response("This is a test response")
        
        result = test_agent.run_sync("Test query")
        assert result.data == "This is a test response"

def test_structured_output():
    from pydantic import BaseModel
    
    class Output(BaseModel):
        answer: str
        score: int
    
    agent = Agent('test', result_type=Output)
    
    with TestModel() as test_model:
        test_model.add_response(Output(answer="test", score=95))
        
        result = agent.run_sync("Query")
        assert result.data.answer == "test"
        assert result.data.score == 95

Integration Testing

import pytest
from pydantic_ai import Agent
from pydantic import BaseModel

class AnalysisResult(BaseModel):
    category: str
    confidence: float

@pytest.mark.integration
async def test_real_model_integration():
    agent = Agent('openai:gpt-3.5-turbo', result_type=AnalysisResult)
    
    result = await agent.run("Classify this as positive or negative: Great product!")
    
    assert result.data.category in ["positive", "negative", "neutral"]
    assert 0 <= result.data.confidence <= 1.0

@pytest.mark.integration  
async def test_tool_execution():
    agent = Agent('openai:gpt-4')
    
    @agent.tool
    def get_data(ctx, key: str) -> dict:
        return {"key": key, "value": "test_value"}
    
    result = await agent.run("Get data for key 'test123'")
    # Verify the tool was called and used correctly
    assert "test123" in str(result.data) or "test_value" in str(result.data)

Production Best Practices

Deploying Pydantic AI agents to production requires attention to reliability, performance, and observability.

Logging and Monitoring

import logging
from pydantic_ai import Agent
from datetime import datetime

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class MonitoredAgent:
    def __init__(self, model: str):
        self.agent = Agent(model)
        self.call_count = 0
        self.error_count = 0
    
    async def run_with_monitoring(self, prompt: str):
        start_time = datetime.now()
        self.call_count += 1
        
        try:
            logger.info(f"Agent call #{self.call_count} started")
            result = await self.agent.run(prompt)
            
            duration = (datetime.now() - start_time).total_seconds()
            logger.info(f"Agent call completed in {duration}s")
            
            return result.data
            
        except Exception as e:
            self.error_count += 1
            logger.error(f"Agent call failed: {e}")
            raise
    
    def get_metrics(self) -> dict:
        return {
            "total_calls": self.call_count,
            "errors": self.error_count,
            "error_rate": self.error_count / max(self.call_count, 1)
        }

Rate Limiting and Cost Control

import asyncio
from datetime import datetime, timedelta
from collections import deque

class RateLimitedAgent:
    def __init__(self, agent: Agent, max_calls_per_minute: int = 60):
        self.agent = agent
        self.max_calls = max_calls_per_minute
        self.call_times = deque()
    
    async def run(self, prompt: str):
        # Clean old entries
        cutoff = datetime.now() - timedelta(minutes=1)
        while self.call_times and self.call_times[0] < cutoff:
            self.call_times.popleft()
        
        # Check rate limit
        if len(self.call_times) >= self.max_calls:
            wait_time = 60 - (datetime.now() - self.call_times[0]).total_seconds()
            if wait_time > 0:
                await asyncio.sleep(wait_time)
        
        # Make call
        self.call_times.append(datetime.now())
        return await self.agent.run(prompt)

Caching Responses

from functools import lru_cache
import hashlib
import json

class CachedAgent:
    def __init__(self, agent: Agent):
        self.agent = agent
        self.cache = {}
    
    def _get_cache_key(self, prompt: str, **kwargs) -> str:
        cache_data = {"prompt": prompt, **kwargs}
        cache_str = json.dumps(cache_data, sort_keys=True)
        return hashlib.md5(cache_str.encode()).hexdigest()
    
    async def run(self, prompt: str, use_cache: bool = True, **kwargs):
        if use_cache:
            cache_key = self._get_cache_key(prompt, **kwargs)
            
            if cache_key in self.cache:
                return self.cache[cache_key]
        
        result = await self.agent.run(prompt, **kwargs)
        
        if use_cache:
            self.cache[cache_key] = result
        
        return result
    
    def clear_cache(self):
        self.cache = {}

Real-World Use Cases

The Pydantic AI agent framework excels in various production scenarios.

Content Moderation System

from pydantic import BaseModel, Field
from pydantic_ai import Agent
from enum import Enum

class ModerationCategory(str, Enum):
    SAFE = "safe"
    SPAM = "spam"
    HARASSMENT = "harassment"
    HATE_SPEECH = "hate_speech"
    EXPLICIT = "explicit"

class ModerationResult(BaseModel):
    category: ModerationCategory
    confidence: float = Field(ge=0, le=1)
    flagged: bool
    reasoning: str

moderation_agent = Agent(
    'openai:gpt-4',
    result_type=ModerationResult,
    system_prompt="""You are a content moderation system. 
    Analyze content for policy violations including spam, harassment, 
    hate speech, and explicit material. Be accurate and fair."""
)

async def moderate_content(content: str) -> ModerationResult:
    result = await moderation_agent.run(f"Moderate this content: {content}")
    return result.data

Data Extraction Pipeline

from pydantic import BaseModel
from pydantic_ai import Agent
from typing import Optional

class ExtractedData(BaseModel):
    company_name: Optional[str] = None
    contact_email: Optional[str] = None
    phone_number: Optional[str] = None
    address: Optional[str] = None
    industry: Optional[str] = None

extraction_agent = Agent(
    'anthropic:claude-sonnet-4',
    result_type=ExtractedData,
    system_prompt='Extract structured data from unstructured text.'
)

async def extract_business_info(text: str) -> ExtractedData:
    result = await extraction_agent.run(
        f"Extract business information from this text:\n\n{text}"
    )
    return result.data

# Example usage
text = """
Acme Corporation is a leading tech company based at 123 Main St, San Francisco, CA.
Contact us at info@acme.com or call (555) 123-4567. We specialize in AI solutions.
"""

data = asyncio.run(extract_business_info(text))
print(f"Company: {data.company_name}")
print(f"Email: {data.contact_email}")

Performance Optimization

Optimize your Pydantic AI agents for production-grade performance.

Async Batch Processing

import asyncio
from pydantic_ai import Agent

async def process_batch(items: list[str], agent: Agent) -> list:
    tasks = [agent.run(item) for item in items]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    processed = []
    for i, result in enumerate(results):
        if isinstance(result, Exception):
            print(f"Item {i} failed: {result}")
        else:
            processed.append(result.data)
    
    return processed

# Usage
agent = Agent('openai:gpt-3.5-turbo')
items = ["Query 1", "Query 2", "Query 3", "Query 4", "Query 5"]
results = asyncio.run(process_batch(items, agent))

Connection Pooling

from pydantic_ai import Agent
import httpx

# Create persistent HTTP client for better performance
http_client = httpx.AsyncClient(
    limits=httpx.Limits(max_keepalive_connections=20, max_connections=100),
    timeout=httpx.Timeout(30.0)
)

agent = Agent(
    'openai:gpt-4',
    http_client=http_client
)

# Remember to close the client when done
# await http_client.aclose()

Common Patterns and Solutions

Dynamic System Prompts

from pydantic_ai import Agent

class DynamicAgent:
    def __init__(self, base_model: str = 'openai:gpt-4'):
        self.base_model = base_model
    
    def create_agent(self, role: str, context: dict) -> Agent:
        system_prompt = f"""You are a {role}.
        
        Additional context:
        {chr(10).join(f"- {k}: {v}" for k, v in context.items())}
        """
        
        return Agent(self.base_model, system_prompt=system_prompt)

# Usage
dynamic = DynamicAgent()
agent = dynamic.create_agent(
    role="financial advisor",
    context={"risk_tolerance": "moderate", "time_horizon": "10 years"}
)

Conclusion

The Pydantic AI agent framework represents a significant advancement in building production-ready AI agents with Python. By combining Pydantic's robust validation with powerful agent orchestration capabilities, it provides developers with the tools needed to create reliable, type-safe, and maintainable AI applications.

From simple chatbots to complex multi-agent systems, the framework handles the heavy lifting of data validation, error management, and model integration. This allows developers to focus on building great AI experiences rather than wrestling with infrastructure concerns.

As AI continues to evolve, frameworks like Pydantic AI that prioritize developer experience, type safety, and production readiness will become increasingly essential. Whether you're building your first AI agent or scaling to production systems handling millions of requests, the Pydantic AI agent framework provides a solid foundation for success.

Start exploring the Pydantic AI agent framework today and discover how it can accelerate your AI development while maintaining the reliability and maintainability that production applications demand.

M

Written by Muhammad Hassan

Expert insights and analysis on Enterprise AI solutions. Helping businesses leverage the power of autonomous agents.