Cheat Sheet intermediate · 8 min read

Pydantic AI Cheat Sheet — Agents & Tools Reference

version 0.0.x

Type-safe AI agents with Pydantic validation

OPENAI_API_KEYANTHROPIC_API_KEY
install pip install pydantic-ai
core imports
python
from pydantic_ai import Agent, RunContext
from pydantic_ai.tools import Tool
from pydantic import BaseModel
Mental model

Type-safe agent framework where everything is a Pydantic model and tools.

Like dependency injection containers for LLMs: you define typed interfaces (tools), wire them together, and the framework handles calling the right LLM with the right context: guaranteeing output structure.

Core Patterns

01 Basic agent with tools
Simple text→text pipeline with one tool
python
from pydantic_ai import Agent
from pydantic_ai.tools import Tool
import os

agent = Agent(
    model='openai:gpt-4o',
    api_key=os.environ['OPENAI_API_KEY']
)

@agent.tool
def get_weather(location: str) -> str:
    return f'Sunny in {location}'

result = agent.run_sync('What is the weather in NYC?')
print(result.data)
output Sunny in NYC (if tool was called)
Tools must have type hints. Missing types → validation error before LLM call.
02 Structured agent output
Need JSON response with guaranteed schema
python
from pydantic import BaseModel
from pydantic_ai import Agent
import os

class UserProfile(BaseModel):
    name: str
    age: int
    interests: list[str]

agent = Agent(
    model='openai:gpt-4o',
    result_type=UserProfile,
    api_key=os.environ['OPENAI_API_KEY']
)

result = agent.run_sync('Create a profile for Alice, 28, likes Python')
print(result.data.name)  # Alice
print(result.data.age)   # 28
output Alice, 28
result_type must be a Pydantic BaseModel. Plain dicts will fail validation.
03 Dependency injection with context
Tools need runtime config (API keys, DB connections, user ID)
python
from pydantic_ai import Agent, RunContext
from pydantic import BaseModel
import os

class DependencyState(BaseModel):
    user_id: str
    api_token: str

agent = Agent(
    model='openai:gpt-4o',
    deps_type=DependencyState,
    api_key=os.environ['OPENAI_API_KEY']
)

@agent.tool
def get_user_data(ctx: RunContext[DependencyState], key: str) -> str:
    # ctx.deps has user_id and api_token
    return f'User {ctx.deps.user_id}: {key}'

deps = DependencyState(
    user_id='user123',
    api_token=os.environ['API_TOKEN']
)
result = agent.run_sync('Get my name', deps=deps)
print(result.data)
output User user123: name
Context is typed: mismatched deps_type causes runtime error. Always match types.
04 Multi-turn conversation with memory
Chat-like interactions with conversation history
python
from pydantic_ai import Agent, ModelMessage
import os

agent = Agent(
    model='openai:gpt-4o',
    api_key=os.environ['OPENAI_API_KEY']
)

messages: list[ModelMessage] = []

# Turn 1
result1 = agent.run_sync('My name is Alice', messages=messages)
messages.extend(result1.messages())

# Turn 2
result2 = agent.run_sync('What did I say my name was?', messages=messages)
messages.extend(result2.messages())
print(result2.data)  # Will reference Alice from turn 1
output Your name is Alice
Must manually extend messages list. Pydantic AI doesn't auto-persist: you manage history.
05 Custom system prompt and role
Define agent behavior and personality
python
from pydantic_ai import Agent
import os

agent = Agent(
    model='openai:gpt-4o',
    system_prompt='You are a sarcastic Python expert. Always suggest dataclasses over dicts.',
    api_key=os.environ['OPENAI_API_KEY']
)

@agent.tool
def explain_code(snippet: str) -> str:
    return f'Explaining: {snippet}'

result = agent.run_sync('How do I store user data?')
print(result.data)
output Use a dataclass, obviously...
system_prompt is per-agent. Change it after instantiation requires new Agent().
06 Async agent runs
Non-blocking calls in async context (FastAPI, etc.)
python
from pydantic_ai import Agent
import asyncio
import os

agent = Agent(model='openai:gpt-4o', api_key=os.environ['OPENAI_API_KEY'])

@agent.tool
async def fetch_api(url: str) -> str:
    # Simulate async I/O
    await asyncio.sleep(0.1)
    return f'Data from {url}'

async def main():
    result = await agent.run('Fetch data from API')
    print(result.data)

asyncio.run(main())
output Data from API...
Use agent.run() not agent.run_sync() in async contexts. Mixing blocks execution.

Key API Methods

Method / Property Description Returns
Agent(model, result_type, system_prompt, deps_type, api_key) Initialize agent with LLM backend and optional typing. model is 'openai:gpt-4o' or 'claude:claude-3-5-sonnet-20241022'. deps_type enables dependency injection. Agent instance ready for .run_sync() or .run()
agent.run_sync(prompt, deps=None, messages=None) → RunResult Execute agent with single turn, optional dependencies, optional message history. Blocks until completion. RunResult with .data (output) and .messages() (conversation history)
await agent.run(prompt, deps=None, messages=None) → RunResult Async version of run_sync. Use in async/await contexts. Same signature and return. RunResult with .data (output) and .messages() (conversation history)
@agent.tool → decorator Mark function as tool available to LLM. Type hints required. Sync or async supported. Tool callable, same behavior as original function
RunContext[DepsType] → parameter Injected parameter in tool functions. Contains .deps (injected dependency object), .messages (conversation so far). Context object with typed .deps attribute matching agent's deps_type
agent.run_sync(...) → RunResult.messages() Extract conversation messages from result. Returns list of ModelMessage for next turn. list[ModelMessage] for re-passing to agent.run_sync(messages=...)

Agent() & run_sync() Parameters

Agent initialization and run parameters

ParameterTypeDefaultNotes
model str (required) Format: 'provider:model'. E.g., 'openai:gpt-4o' or 'claude:claude-3-5-sonnet-20241022'
result_type type[T] str Pydantic BaseModel for structured output. Defaults to str if omitted.
system_prompt str None Agent role and behavior. Set once at init, not per-call.
deps_type type[D] None Pydantic BaseModel for dependency injection. Required if tools use RunContext.
api_key str os.environ['API_KEY'] Explicit API key. Uses env var if omitted (recommended).
messages (run_sync arg) list[ModelMessage] [] Conversation history from previous turns. Pass to enable multi-turn.
deps (run_sync arg) T None Dependency instance matching agent's deps_type. Required if deps_type specified.

Common Errors & Fixes

01 ValidationError: field required

Cause: Tool parameter missing type hint or return type annotation.

Fix:
python
Add type hints to all tool functions:

@agent.tool
def get_data(key: str) -> str:  # Both parameter and return typed
    return f'data: {key}'

Without → TypeError. With → auto-validated.
02 TypeError: deps is required but not provided

Cause: Agent initialized with deps_type, but agent.run_sync() called without deps argument.

Fix:
python
Pass deps instance to run_sync():

agent = Agent(model='...', deps_type=MyDeps)

mydeps = MyDeps(user_id='123', token='abc')
result = agent.run_sync('prompt', deps=mydeps)  # Required

Or remove deps_type if not needed.
03 RunContext type mismatch: expected X, got Y

Cause: Tool function has RunContext[WrongType] but agent deps_type is different.

Fix:
python
@agent.tool
def tool(ctx: RunContext[MyDeps]) -> str:  # Match agent's deps_type
    return str(ctx.deps.field)

Agent must be initialized with deps_type=MyDeps
04 Model not found: 'openai:gpt-4.1'

Cause: Invalid model identifier or provider mismatch.

Fix:
python
Use correct model format:

# ✅ Correct
Agent(model='openai:gpt-4o')
Agent(model='claude:claude-3-5-sonnet-20241022')

# ❌ Wrong
Agent(model='gpt-4.1')  # Missing provider
Agent(model='openai:gpt-4o-mini-2024-07-18')  # Use short name
05 AttributeError: result.data is not a MyModel instance

Cause: result_type mismatch: LLM output doesn't match Pydantic schema.

Fix:
python
Ensure LLM output matches result_type schema:

class Result(BaseModel):
    name: str
    age: int

agent = Agent(model='...', result_type=Result)
# LLM must output JSON with name and age
# Add detailed system_prompt if needed:
Agent(model='...', result_type=Result, 
      system_prompt='Output JSON: {"name": "...", "age": ...}')

Production Gotchas

Env vars must be set before import

Pydantic AI reads OPENAI_API_KEY and other env vars at Agent initialization. Set them before instantiating. Use python-dotenv or pass api_key explicitly. Changing os.environ after Agent() doesn't propagate.

Tools are not auto-retried

If a tool raises an exception, the agent stops. Wrap tool logic in try-except or use tool.retry() for resilience. LLM sees tool errors as messages, not retries.

Message history is not auto-persisted

Pydantic AI is stateless. You must manually collect result.messages() and pass messages=... to next run(). No automatic session store: that's your job.

system_prompt is immutable per Agent instance

Once Agent(system_prompt='...') is created, you can't change it without creating a new Agent. Dynamic prompts? Create Agent with generic prompt or compose agents.

deps_type is strict: mismatches fail at runtime

If deps_type=UserData but you pass dict or wrong model, RunContext will fail type-checking. Use Pydantic models everywhere, not plain dicts.

Async tools must be awaited in tool definition

@agent.tool async def fetch(url: str) -> str: await asyncio.sleep(1) return 'data' Don't mix async tool def with sync agent.run_sync(). Use await agent.run() in async context.

Pydantic AI vs LangChain vs CrewAI

FeaturePydantic AILangChainCrewAI
Type safetyFull: Pydantic models everywherePartial: optional typingBasic: string-based prompts
Tool definition@agent.tool decorator@tool decorator + Tool classTool class with JSON schema
Dependency injectionNative (RunContext, deps_type)Manual via prompt contextVia Agent(context) dict
Multi-turnManual message passingMemory chains or sessionAuto-persisted in Team
Output structureresult_type: BaseModelOutputParser + chainstr with JSON extraction
Learning curveSteep (async, Pydantic, context)Medium (chains, tools, memory)Low (agents + prompts)

Complete example: E-commerce agent with structured output, tools, and dependencies

python
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
import os
import json

# Define output schema
class OrderSummary(BaseModel):
    product_name: str
    quantity: int
    total_price: float
    estimated_delivery: str

# Define dependencies
class UserContext(BaseModel):
    user_id: str
    db_token: str
    discount_percent: int = 0

# Create agent
agent = Agent(
    model='openai:gpt-4o',
    result_type=OrderSummary,
    deps_type=UserContext,
    system_prompt='You are an e-commerce assistant. Confirm orders with product name, qty, total price (apply user discount), and delivery date.',
    api_key=os.environ['OPENAI_API_KEY']
)

# Define tools
@agent.tool
def get_product_price(ctx: RunContext[UserContext], product_id: str) -> str:
    """Fetch product price from DB."""
    # Simulated: use ctx.deps.db_token to authenticate
    prices = {'laptop': 1200, 'mouse': 25, 'keyboard': 75}
    return str(prices.get(product_id, 0))

@agent.tool
def check_inventory(product_id: str, quantity: int) -> str:
    """Check if product is in stock."""
    stock = {'laptop': 5, 'mouse': 100, 'keyboard': 50}
    in_stock = stock.get(product_id, 0) >= quantity
    return f'{product_id}: {"in stock" if in_stock else "out of stock"}'

# Run agent
user_deps = UserContext(
    user_id='user_456',
    db_token='secret_token_xyz',
    discount_percent=10
)

result = agent.run_sync(
    'I want to buy 2 laptops. Can you process this?',
    deps=user_deps
)

print(f'Product: {result.data.product_name}')
print(f'Qty: {result.data.quantity}')
print(f'Total: ${result.data.total_price}')
print(f'Delivery: {result.data.estimated_delivery}')
Verified 2026-04 · v0.0.x · gpt-4o, gpt-4o-mini, gpt-4.1, claude-3-5-sonnet-20241022, claude-3-5-haiku-20241022
Verify ↗

Community Notes

No notes yetBe the first to share a version-specific fix or tip.