Getting Started with PydanticAI

PydanticAIType-SafeTutorialGetting Started

Build your first type-safe AI agent with PydanticAI, from basic setup to advanced patterns.

Getting Started with PydanticAI

Overview

PydanticAI is a type-safe AI agent framework built on the Pydantic ecosystem. This tutorial walks you through building your first AI agent with PydanticAI, from basic setup to advanced patterns.

Prerequisites

  • Python 3.10+
  • OpenAI API key (or other LLM provider)
  • Basic Python knowledge

Installation

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install PydanticAI
pip install pydantic-ai

# Or with extras
pip install pydantic-ai[openai,anthropic,google]

# Create .env file
echo "OPENAI_API_KEY=sk-..." > .env

Your First Agent

Basic Example

from pydantic_ai import Agent

# Create a simple agent
agent = Agent('openai:gpt-4o')

# Run a query
result = agent.run_sync('What is the capital of France?')
print(result.data)

With Structured Output

from pydantic_ai import Agent
from pydantic import BaseModel, Field

class WeatherInfo(BaseModel):
    """Weather information for a location."""
    location: str = Field(description="The location name")
    temperature: float = Field(description="Temperature in Celsius")
    conditions: str = Field(description="Weather conditions")
    humidity: int = Field(description="Humidity percentage")

agent = Agent(
    'openai:gpt-4o',
    result_type=WeatherInfo,  # Type-safe result
)

result = agent.run_sync('What is the weather like in London?')
print(result.data.location)
print(result.data.temperature)
print(result.data.conditions)

Understanding Agents

Agent Components

from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIModel

# Model configuration
model = OpenAIModel(
    'gpt-4o',
    api_key='sk-...',
    base_url='https://api.openai.com/v1',
)

# Agent with model
agent = Agent(
    model,
    system_prompt='You are a helpful assistant.',
    result_type=str,
)

System Prompt

from pydantic_ai import Agent

agent = Agent(
    'openai:gpt-4o',
    system_prompt='''
    You are an expert data analyst.
    Your job is to analyze data and provide insights.
    Always be concise and focus on actionable findings.
    ''',
)

Dynamic System Prompt

from pydantic_ai import Agent, RunContext

class AgentDeps(BaseModel):
    context: str
    tone: str

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

@agent.system_prompt
def add_context(ctx: RunContext[AgentDeps]) -> str:
    return f'''
    You are a helpful assistant.
    
    Context: {ctx.deps.context}
    Tone: {ctx.deps.tone}
    '''

result = agent.run_sync(
    'Summarize this data',
    deps=AgentDeps(
        context='Q4 sales data shows 15% growth',
        tone='professional',
    ),
)

Tools

Defining Tools

from pydantic_ai import Agent, RunContext
from pydantic import BaseModel

class SearchResult(BaseModel):
    title: str
    url: str
    snippet: str

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

@agent.tool
async def search_web(ctx: RunContext, query: str) -> list[SearchResult]:
    """Search the web for information."""
    # Implement search logic
    results = await perform_search(query)
    return [SearchResult(**r) for r in results]

@agent.tool
async def fetch_page(ctx: RunContext, url: str) -> str:
    """Fetch and return the content of a webpage."""
    import httpx
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        return response.text

# Use in conversation
result = agent.run_sync('Find information about AI agents')

Tool with Dependencies

from pydantic_ai import Agent, RunContext

class ToolDeps(BaseModel):
    api_key: str
    base_url: str

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

@agent.tool
async def search_api(ctx: RunContext[ToolDeps], query: str) -> str:
    """Search using the configured API."""
    import httpx
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"{ctx.deps.base_url}/search",
            params={"q": query},
            headers={"Authorization": f"Bearer {ctx.deps.api_key}"},
        )
        return response.text

result = agent.run_sync(
    'Search for AI frameworks',
    deps=ToolDeps(
        api_key='sk-...',
        base_url='https://api.example.com',
    ),
)

Multi-Agent Systems

Agent Dependencies

from pydantic_ai import Agent
from pydantic import BaseModel

class ResearchResult(BaseModel):
    findings: list[str]
    sources: list[str]

class WritingResult(BaseModel):
    article: str
    word_count: int

# Research agent
research_agent = Agent(
    'openai:gpt-4o',
    result_type=ResearchResult,
)

# Writer agent depends on research result
writer_agent = Agent(
    'openai:gpt-4o',
    result_type=WritingResult,
    deps_type=ResearchResult,
)

@writer_agent.system_prompt
def add_research_context(ctx: RunContext[ResearchResult]) -> str:
    return f"Based on research findings: {', '.join(ctx.deps.findings[:3])}"

# Chain agents
research = research_agent.run_sync('Research quantum computing')
writing = writer_agent.run_sync(
    'Write an article',
    deps=research.data,
)

Agent Handoffs

from pydantic_ai import Agent
from pydantic import BaseModel

class TaskResult(BaseModel):
    completed: bool
    output: str

researcher = Agent('openai:gpt-4o', result_type=TaskResult)
writer = Agent('openai:gpt-4o', result_type=TaskResult)
editor = Agent('openai:gpt-4o', result_type=TaskResult)

def run_workflow(topic: str) -> TaskResult:
    # Research
    research_result = researcher.run_sync(f'Research {topic}')
    
    # Write
    writer_result = writer.run_sync(
        f'Write about {topic}',
        deps=research_result.data,
    )
    
    # Edit
    editor_result = editor.run_sync(
        'Edit the content',
        deps=writer_result.data,
    )
    
    return editor_result.data

Streaming

Stream Text

from pydantic_ai import Agent

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

async def stream_response():
    async for chunk in agent.run_stream('Explain quantum computing'):
        print(chunk.data, end='', flush=True)

import asyncio
asyncio.run(stream_response())

Stream Events

from pydantic_ai import Agent

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

async def stream_events():
    async for event in agent.iter_events('Analyze this data'):
        if event.type == 'text':
            print(event.data, end='', flush=True)
        elif event.type == 'tool_call':
            print(f"\nCalling tool: {event.data.name}")
        elif event.type == 'retries':
            print(f"\nRetries: {event.data}")

asyncio.run(stream_events())

Error Handling

Result Validation

from pydantic_ai import Agent, ModelRetry
from pydantic import BaseModel

class AnalysisResult(BaseModel):
    findings: list[str]
    confidence: float

agent = Agent('openai:gpt-4o', result_type=AnalysisResult)

@agent.result_validator
def validate_result(result: AnalysisResult) -> AnalysisResult:
    if not result.findings:
        raise ModelRetry('No findings returned, please try again')
    if result.confidence < 0.5:
        raise ModelRetry('Low confidence, please re-analyze')
    return result

result = agent.run_sync('Analyze this dataset')

Tool Error Handling

from pydantic_ai import Agent, RunContext

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

@agent.tool
async def reliable_tool(ctx: RunContext, query: str) -> str:
    """A tool with error handling."""
    max_retries = 3
    for attempt in range(max_retries):
        try:
            return await perform_search(query)
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            await asyncio.sleep(2 ** attempt)
    return ""

result = agent.run_sync('Search for information')

Testing

Mock Model

from pydantic_ai import Agent
from pydantic_ai.test import MockModel

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

# Create mock model
mock = MockModel()
mock.add_chat_completion('Hello', 'Hi there!')

# Replace model for testing
agent._model = mock

result = agent.run_sync('Say hello')
assert result.data == 'Hi there!'

Test with Real API

import pytest
from pydantic_ai import Agent

@pytest.mark.asyncio
async def test_agent_response():
    agent = Agent('openai:gpt-4o')
    result = await agent.run('What is 2+2?')
    assert '4' in result.data.lower()

Advanced Patterns

Conversation State

from pydantic_ai import Agent
from pydantic import BaseModel

class ConversationState(BaseModel):
    messages: list[dict]
    context: dict

agent = Agent(
    'openai:gpt-4o',
    result_type=str,
)

# State is managed automatically
result1 = agent.run_sync('What is the project about?')
result2 = agent.run_sync('Tell me more about the timeline')
# Both calls share context

print(agent.state)

Retry Logic

from pydantic_ai import Agent
import asyncio

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

async def run_with_retry(query: str, max_retries: int = 3):
    for attempt in range(max_retries):
        try:
            result = await agent.run(query)
            return result
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            print(f"Attempt {attempt + 1} failed: {e}")
            await asyncio.sleep(2 ** attempt)

result = asyncio.run(run_with_retry('Analyze this data'))

Batch Processing

from pydantic_ai import Agent
import asyncio

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

async def process_batch(queries: list[str]):
    tasks = [agent.run(query) for query in queries]
    results = await asyncio.gather(*tasks)
    return [r.data for r in results]

queries = ['Query 1', 'Query 2', 'Query 3']
results = asyncio.run(process_batch(queries))

Multi-Provider Support

Switching Providers

from pydantic_ai import Agent

# OpenAI
agent = Agent('openai:gpt-4o')

# Anthropic
agent = Agent('anthropic:claude-3-5-sonnet-20241022')

# Google Gemini
agent = Agent('google:gemini-1.5-pro')

# Groq
agent = Agent('groq:llama-3.1-405b')

# DeepSeek
agent = Agent('deepseek:deepseek-chat')

# Ollama (local)
agent = Agent('ollama:llama3.1')

Provider Configuration

from pydantic_ai import Agent
from pydantic_ai.providers.openai import OpenAIProvider

provider = OpenAIProvider(
    api_key="sk-...",
    base_url="https://api.openai.com/v1",
)
agent = Agent(provider, model="gpt-4o")

Best Practices

1. Use Structured Outputs

# Good: Type-safe result
class AnalysisResult(BaseModel):
    summary: str
    findings: list[str]
    recommendations: list[str]

agent = Agent('openai:gpt-4o', result_type=AnalysisResult)

# Bad: Unstructured string
agent = Agent('openai:gpt-4o', result_type=str)

2. Be Specific with System Prompts

# Good: Specific role and constraints
agent = Agent(
    'openai:gpt-4o',
    system_prompt='You are a senior data analyst. Provide concise, actionable insights.',
)

# Bad: Vague
agent = Agent('openai:gpt-4o', system_prompt='You are helpful.')

3. Use Dependencies for Context

# Good: Pass context via dependencies
class AgentDeps(BaseModel):
    user_context: str
    preferences: dict

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

# Bad: Hardcoded context
agent = Agent('openai:gpt-4o', system_prompt='User likes Python.')

4. Handle Errors Gracefully

# Good: Validate results
@agent.result_validator
def validate(result):
    if not result.findings:
        raise ModelRetry('No findings')
    return result

# Bad: No validation
result = agent.run_sync('Analyze')

Troubleshooting

Common Issues

API rate limits:

import asyncio
from pydantic_ai import Agent

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

async def rate_limited_run(query: str):
    await asyncio.sleep(1)  # Rate limit delay
    return await agent.run(query)

Model not found:

# Check available models
from pydantic_ai.models.openai import OpenAIModel

model = OpenAIModel('gpt-4o')  # Must be a valid model name

Tool not called:

# Ensure tool description is clear
@agent.tool
async def search_web(query: str) -> str:
    """Search the web for information about the query."""  # Clear description
    ...

Resources


Last updated: May 2026