1. Installation & Environment Setup
PydanticAI is a Python agent framework built by the creators of Pydantic. It provides type-safe, model-agnostic AI agent development with first-class support for structured outputs, dependency injection, and streaming. Requires Python 3.9+.
Install the full package with all optional provider dependencies:
# Install PydanticAI with all optional dependencies
pip install pydantic-ai
# Or install with specific provider support only
pip install 'pydantic-ai[openai]'
pip install 'pydantic-ai[anthropic]'
pip install 'pydantic-ai[google]'
# Install multiple providers
pip install 'pydantic-ai[openai,anthropic,google]'
1.1 Verifying Installation
Confirm PydanticAI is installed correctly and check the version:
import pydantic_ai
print(f"PydanticAI version: {pydantic_ai.__version__}")
from pydantic_ai import Agent
print("Agent class imported successfully")
# Check Pydantic is available (core dependency)
import pydantic
print(f"Pydantic version: {pydantic.__version__}")
pydantic-ai package also installs httpx for async HTTP and logfire-api for optional observability integration.
2. Your First PydanticAI Agent
2.1 Basic Agent Creation
An Agent in PydanticAI is the core building block. It wraps a model, system prompt, tools, and output type into a reusable, type-safe unit:
from pydantic_ai import Agent
# Create a simple agent with a system prompt
agent = Agent(
"openai:gpt-4o",
system_prompt="You are a helpful assistant that provides concise answers."
)
# Run the agent synchronously
result = agent.run_sync("What is the capital of France?")
print(result.data)
# Output: The capital of France is Paris.
2.2 Structured Output Agent
The real power of PydanticAI is type-safe structured output. Define a Pydantic model and the agent will return validated, typed data:
from pydantic_ai import Agent
from pydantic import BaseModel
# Define a structured output type
class WeatherReport(BaseModel):
city: str
temperature_celsius: float
condition: str
humidity_percent: int
wind_speed_kmh: float
# Create an agent with structured output
weather_agent = Agent(
"openai:gpt-4o",
result_type=WeatherReport,
system_prompt="You are a weather reporting assistant. Return structured weather data."
)
# Run and get typed result
result = weather_agent.run_sync("What's the weather like in Tokyo today?")
report = result.data
# Full type safety — IDE autocomplete works here
print(f"City: {report.city}")
print(f"Temperature: {report.temperature_celsius}°C")
print(f"Condition: {report.condition}")
print(f"Humidity: {report.humidity_percent}%")
print(f"Wind: {report.wind_speed_kmh} km/h")
result_type=WeatherReport, PydanticAI instructs the model to return JSON matching the schema, then validates the response through Pydantic. If validation fails, it automatically retries with the error feedback. Your code never receives invalid data.
3. PydanticAI Gateway
3.1 Gateway Configuration
The PydanticAI Gateway acts as a unified proxy for all model providers. It simplifies API key management, adds request logging, and provides rate limiting across providers:
import os
from pydantic_ai import Agent
# Configure the Gateway URL (typically set as environment variable)
os.environ["PYDANTIC_AI_GATEWAY_URL"] = "http://localhost:8000"
# When Gateway is configured, agents route through it automatically
agent = Agent(
"openai:gpt-4o",
system_prompt="You are a helpful assistant."
)
# This request goes through the Gateway proxy
result = agent.run_sync("Explain dependency injection in 2 sentences.")
print(result.data)
3.2 Gateway Benefits
The Gateway provides several operational advantages for production deployments:
# Start the PydanticAI Gateway server
pip install pydantic-ai-gateway
pydantic-ai-gateway serve --port 8000
# The Gateway provides:
# - Single API key management (one key for all providers)
# - Request/response logging for debugging
# - Rate limiting and retry logic
# - Cost tracking across providers
# - Model fallback chains
OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.). The Gateway is most valuable in team and production environments.
Type-Safe Data Pipeline
A data engineering team replaced their ad-hoc LLM scripts with PydanticAI agents. Every agent has validated inputs and outputs, catching data quality issues at the boundary. Result: pipeline failures from malformed LLM output dropped from 12% to 0.3%.
4. Agent Run Lifecycle
4.1 Run Methods
PydanticAI provides three ways to run an agent, each suited to different contexts:
import asyncio
from pydantic_ai import Agent
agent = Agent(
"openai:gpt-4o",
system_prompt="You are a concise assistant."
)
# Method 1: run_sync() — blocking, for scripts and simple apps
result_sync = agent.run_sync("What is 2 + 2?")
print(f"Sync result: {result_sync.data}")
# Method 2: run() — async, for web servers and async apps
async def async_example():
result = await agent.run("What is the meaning of life?")
print(f"Async result: {result.data}")
return result
asyncio.run(async_example())
# Method 3: run_stream() — streaming, for real-time output
async def stream_example():
async with agent.run_stream("Write a haiku about Python.") as response:
async for text in response.stream_text():
print(text, end="", flush=True)
print() # Final newline
asyncio.run(stream_example())
4.2 RunResult Attributes
Every agent run returns a RunResult object with rich metadata about the interaction:
from pydantic_ai import Agent
agent = Agent(
"openai:gpt-4o",
system_prompt="You are a helpful assistant."
)
result = agent.run_sync("Explain the Liskov Substitution Principle.")
# Access the response data
print(f"Response: {result.data[:100]}...")
# Access conversation messages (full history)
messages = result.all_messages()
print(f"\nMessage count: {len(messages)}")
for msg in messages:
print(f" - {msg.kind}: {str(msg)[:80]}...")
# Access token usage statistics
usage = result.usage()
print(f"\nToken usage:")
print(f" Request tokens: {usage.request_tokens}")
print(f" Response tokens: {usage.response_tokens}")
print(f" Total tokens: {usage.total_tokens}")
run() / run_sync() / run_stream() → Model generates response → Output validated against result_type → If validation fails, retry with error context → Return typed RunResult.
5. Troubleshooting & Getting Help
PydanticAI integrates with Pydantic Logfire for comprehensive debugging and observability:
import logfire
from pydantic_ai import Agent
# Configure Logfire for debugging (sends traces to Logfire dashboard)
logfire.configure()
agent = Agent(
"openai:gpt-4o",
system_prompt="You are a helpful assistant."
)
# All agent runs are now traced in Logfire
result = agent.run_sync("What causes a KeyError in Python?")
print(result.data)
# Logfire captures:
# - Full request/response payloads
# - Token usage and latency
# - Tool calls and their results
# - Retry attempts on validation failures
# - Streaming chunk timing
Common issues and their solutions:
# Issue 1: ModelNotFoundError
# Solution: Ensure the model string is correct
# Valid formats: "openai:gpt-4o", "anthropic:claude-sonnet-4-20250514", "google:gemini-2.0-flash"
# Issue 2: API key not found
# Solution: Set the appropriate environment variable
import os
os.environ["OPENAI_API_KEY"] = "sk-..." # For OpenAI models
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..." # For Anthropic models
os.environ["GOOGLE_API_KEY"] = "AI..." # For Google models
# Issue 3: ValidationError on result
# Solution: Check your result_type model matches what the LLM can produce
# Use Optional fields for data the model might not always provide
from pydantic import BaseModel
from typing import Optional
class FlexibleOutput(BaseModel):
answer: str
confidence: Optional[float] = None # Model may not always provide this
sources: list[str] = [] # Default empty list if not provided
Next in the PydanticAI SDK Track
In Part 2: Agents, Dependencies & Output, we’ll deep dive into agent architecture — typed dependency injection for runtime context, structured output types with Pydantic models, capabilities configuration, and result validation patterns.