Building AI Agents with Pydantic AI
Pydantic AI is a Python agent framework for building production-grade Generative AI applications.
This skill provides patterns, architecture guidance, and tested code examples for building applications with Pydantic AI.
When to Use This Skill
Invoke this skill when:
- User asks to build an AI agent, create an LLM-powered app, or mentions Pydantic AI
- User wants to add tools, capabilities (thinking, web search), or structured output to an agent
- User asks to define agents from YAML/JSON specs or use template strings
- User wants to stream agent events, delegate between agents, or test agent behavior
- Code imports
pydantic_ai or references Pydantic AI classes (Agent, RunContext, Tool)
- User asks about hooks, lifecycle interception, or agent observability with Logfire
Do not use this skill for:
- The Pydantic validation library alone (
pydantic/BaseModel without agents)
- Other AI frameworks (LangChain, LlamaIndex, CrewAI, AutoGen)
- General Python development unrelated to AI agents
Quick-Start Patterns
Create a Basic Agent
from pydantic_ai import Agent
agent = Agent(
'anthropic:claude-sonnet-4-6',
instructions='Be concise, reply with one sentence.',
)
result = agent.run_sync('Where does "hello world" come from?')
print(result.output)
"""
The first known use of "hello, world" was in a 1974 textbook about the C programming language.
"""
Add Tools to an Agent
import random
from pydantic_ai import Agent, RunContext
agent = Agent(
'google-gla:gemini-3-flash-preview',
deps_type=str,
instructions=(
"You're a dice game, you should roll the die and see if the number "
"you get back matches the user's guess. If so, tell them they're a winner. "
"Use the player's name in the response."
),
)
@agent.tool_plain
def roll_dice() -> str:
"""Roll a six-sided die and return the result."""
return str(random.randint(1, 6))
@agent.tool
def get_player_name(ctx: RunContext[str]) -> str:
"""Get the player's name."""
return ctx.deps
dice_result = agent.run_sync('My guess is 4', deps='Anne')
print(dice_result.output)
#> Congratulations Anne, you guessed correctly! You're a winner!
Structured Output with Pydantic Models
from pydantic import BaseModel
from pydantic_ai import Agent
class CityLocation(BaseModel):
city: str
country: str
agent = Agent('google-gla:gemini-3-flash-preview', output_type=CityLocation)
result = agent.run_sync('Where were the olympics held in 2012?')
print(result.output)
#> city='London' country='United Kingdom'
print(result.usage())
#> RunUsage(input_tokens=57, output_tokens=8, requests=1)
Dependency Injection
from datetime import date
from pydantic_ai import Agent, RunContext
agent = Agent(
'openai:gpt-5.2',
deps_type=str,
instructions="Use the customer's name while replying to them.",
)
@agent.instructions
def add_the_users_name(ctx: RunContext[str]) -> str:
return f"The user's name is {ctx.deps}."
@agent.instructions
def add_the_date() -> str:
return f'The date is {date.today()}.'
result = agent.run_sync('What is the date?', deps='Frank')
print(result.output)
#> Hello Frank, the date today is 2032-01-02.
Testing with TestModel
from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
my_agent = Agent('openai:gpt-5.2', instructions='...')
async def test_my_agent():
"""Unit test for my_agent, to be run by pytest."""
m = TestModel()
with my_agent.override(model=m):
result = await my_agent.run('Testing my agent...')
assert result.output == 'success (no tool calls)'
assert m.last_model_request_parameters.function_tools == []
Use Capabilities
Capabilities are reusable, composable units of agent behavior — bundling tools, hooks, instructions, and model settings.
from pydantic_ai import Agent
from pydantic_ai.capabilities import Thinking, WebSearch
agent = Agent(
'anthropic:claude-opus-4-6',
instructions='You are a research assistant. Be thorough and cite sources.',
capabilities=[
Thinking(effort='high'),
WebSearch(),
],
)
Add Lifecycle Hooks
Use Hooks to intercept model requests, tool calls, and runs with decorators — no subclassing needed.
from pydantic_ai import Agent, RunContext
from pydantic_ai.capabilities.hooks import Hooks
from pydantic_ai.models import ModelRequestContext
hooks = Hooks()
@hooks.on.before_model_request
async def log_request(ctx: RunContext[None], request_context: ModelRequestContext) -> ModelRequestContext:
print(f'Sending {len(request_context.messages)} messages')
return request_context
agent = Agent('openai:gpt-5.2', capabilities=[hooks])
Define Agent from YAML Spec
Use Agent.from_file to load agents from YAML or JSON — no Python agent construction code needed.
from pydantic_ai import Agent
# agent.yaml:
# model: anthropic:claude-opus-4-6
# instructions: You are a helpful research assistant.
# capabilities:
# - WebSearch
# - Thinking:
# effort: high
agent = Agent.from_file('agent.yaml')
Task Routing Table
Load only the most relevant reference first. Read additional references only if the task spans multiple areas.
| I want to... |
Reference |
| Create/configure agents, choose output types, use deps, define specs, or pick run methods |
Agents Core |
| Bundle reusable behavior or intercept lifecycle events |
Capabilities and Hooks |
| Add function tools, toolsets, MCP servers, or explicit search tools |
Tools Core |
| Use provider-native web search, web fetch, or code execution |
Built-in Tools |
Use advanced tool features such as approval, retries, ToolReturn, validators, timeouts, or tool search |
Tools Advanced |
| Work with multimodal input, message history, or context trimming |
Input and History |
| Test or debug agent behavior |
Testing and Debugging |
| Coordinate multiple agents or build graph workflows |
Orchestration and Integrations |
| Call the model directly, expose A2A, use durable execution, embeddings, evals, or third-party integrations |
Orchestration and Integrations |
| Compare abstractions, output modes, decorators, or model-string patterns |
Architecture and Decision Guide |
Follow an older link into COMMON-TASKS.md |
Task Reference Map |
Architecture and Decisions
Load Architecture and Decision Guide only when the user is choosing between abstractions or wants comparison tables and decision trees:
| Topic |
What it covers |
| Decision Trees |
Tool registration, output modes, multi-agent patterns, capabilities, testing approaches, extensibility |
| Comparison Tables |
Output modes, model provider prefixes, tool decorators, built-in capabilities, agent methods |
| Architecture Overview |
Execution flow, generic types, construction patterns, lifecycle hooks, model string format |
Quick reference — model string format: "provider:model-name" (e.g., "openai:gpt-5.2", "anthropic:claude-sonnet-4-6", "google-gla:gemini-3-pro-preview")
Quick reference — key agent methods: run(), run_sync(), run_stream(), run_stream_sync(), run_stream_events(), iter()
Key Practices
- Python 3.10+ compatibility required
- Observability: Pydantic AI has first-class integration with Logfire for tracing agent runs, tool calls, and model requests. Add it with
logfire.instrument_pydantic_ai(). For deeper HTTP-level visibility, logfire.instrument_httpx(capture_all=True) captures the exact payloads sent to model providers.
- Testing: Use
TestModel for deterministic tests, FunctionModel for custom logic
Common Gotchas
These are mistakes agents commonly make with Pydantic AI. Getting these wrong produces silent failures or confusing errors.
@agent.tool requires RunContext as first param; @agent.tool_plain must not have it. Mixing these up causes runtime errors. Use tool_plain when you don't need deps, usage, or messages.
- Model strings need the provider prefix:
'openai:gpt-5.2' not 'gpt-5.2'. Without the prefix, Pydantic AI can't resolve the provider.
TestModel requires agent.override(): Don't set agent.model directly. Always use the context manager: with agent.override(model=TestModel()):.
str in output_type allows plain text to end the run: If your union includes str (or no output_type is set), the model can return plain text instead of structured output. Omit str from the union to force tool-based output.
- Hook decorator names on
.on don't repeat on_: Use hooks.on.run_error and hooks.on.model_request_error — not hooks.on.on_run_error.
history_processors is plural: The Agent parameter is history_processors=[...], not history_processor=.
Task-Family References
Load exactly one of these unless the task clearly spans multiple families:
| Task family |
Reference |
| Core agent setup, output, deps, specs, models, run methods |
Agents Core |
| Capabilities, hooks, and reusable behavior |
Capabilities and Hooks |
| Function tools, toolsets, MCP, explicit search tools |
Tools Core |
| Provider-native builtin tools |
Built-in Tools |
| Approval, retries, validators, timeouts, rich tool returns, deferred loading |
Tools Advanced |
| Multimodal input, message history, history processors |
Input and History |
| Testing, request inspection, and Logfire debugging |
Testing and Debugging |
| Multi-agent patterns, graphs, direct API, A2A, durable execution, embeddings, evals, third-party integrations |
Orchestration and Integrations |
Use Task Reference Map only for compatibility with older links or when you need a pointer from an old section name to the new file.
1---2name: building-pydantic-ai-agents3description: Build AI agents with Pydantic AI — tools, capabilities, structured output, streaming, testing, and multi-agent patterns. Use when the user mentions Pydantic AI, imports pydantic_ai, or asks to build an AI agent, add tools/capabilities, stream output, define agents from YAML, or test agent behavior.4license: MIT5---67# Building AI Agents with Pydantic AI89Pydantic AI is a Python agent framework for building production-grade Generative AI applications.10This skill provides patterns, architecture guidance, and tested code examples for building applications with Pydantic AI.1112## When to Use This Skill1314Invoke this skill when:15- User asks to build an AI agent, create an LLM-powered app, or mentions Pydantic AI16- User wants to add tools, capabilities (thinking, web search), or structured output to an agent17- User asks to define agents from YAML/JSON specs or use template strings18- User wants to stream agent events, delegate between agents, or test agent behavior19- Code imports `pydantic_ai` or references Pydantic AI classes (`Agent`, `RunContext`, `Tool`)20- User asks about hooks, lifecycle interception, or agent observability with Logfire2122Do **not** use this skill for:23- The Pydantic validation library alone (`pydantic`/`BaseModel` without agents)24- Other AI frameworks (LangChain, LlamaIndex, CrewAI, AutoGen)25- General Python development unrelated to AI agents2627## Quick-Start Patterns2829### Create a Basic Agent3031```python32from pydantic_ai import Agent3334agent = Agent(35 'anthropic:claude-sonnet-4-6',36 instructions='Be concise, reply with one sentence.',37)3839result = agent.run_sync('Where does "hello world" come from?')40print(result.output)41"""42The first known use of "hello, world" was in a 1974 textbook about the C programming language.43"""44```4546### Add Tools to an Agent4748```python49import random5051from pydantic_ai import Agent, RunContext5253agent = Agent(54 'google-gla:gemini-3-flash-preview',55 deps_type=str,56 instructions=(57 "You're a dice game, you should roll the die and see if the number "58 "you get back matches the user's guess. If so, tell them they're a winner. "59 "Use the player's name in the response."60 ),61)626364@agent.tool_plain65def roll_dice() -> str:66 """Roll a six-sided die and return the result."""67 return str(random.randint(1, 6))686970@agent.tool71def get_player_name(ctx: RunContext[str]) -> str:72 """Get the player's name."""73 return ctx.deps747576dice_result = agent.run_sync('My guess is 4', deps='Anne')77print(dice_result.output)78#> Congratulations Anne, you guessed correctly! You're a winner!79```8081### Structured Output with Pydantic Models8283```python84from pydantic import BaseModel8586from pydantic_ai import Agent878889class CityLocation(BaseModel):90 city: str91 country: str929394agent = Agent('google-gla:gemini-3-flash-preview', output_type=CityLocation)95result = agent.run_sync('Where were the olympics held in 2012?')96print(result.output)97#> city='London' country='United Kingdom'98print(result.usage())99#> RunUsage(input_tokens=57, output_tokens=8, requests=1)100```101102### Dependency Injection103104```python105from datetime import date106107from pydantic_ai import Agent, RunContext108109agent = Agent(110 'openai:gpt-5.2',111 deps_type=str,112 instructions="Use the customer's name while replying to them.",113)114115116@agent.instructions117def add_the_users_name(ctx: RunContext[str]) -> str:118 return f"The user's name is {ctx.deps}."119120121@agent.instructions122def add_the_date() -> str:123 return f'The date is {date.today()}.'124125126result = agent.run_sync('What is the date?', deps='Frank')127print(result.output)128#> Hello Frank, the date today is 2032-01-02.129```130131### Testing with TestModel132133```python134from pydantic_ai import Agent135from pydantic_ai.models.test import TestModel136137my_agent = Agent('openai:gpt-5.2', instructions='...')138139140async def test_my_agent():141 """Unit test for my_agent, to be run by pytest."""142 m = TestModel()143 with my_agent.override(model=m):144 result = await my_agent.run('Testing my agent...')145 assert result.output == 'success (no tool calls)'146 assert m.last_model_request_parameters.function_tools == []147```148149### Use Capabilities150151Capabilities are reusable, composable units of agent behavior — bundling tools, hooks, instructions, and model settings.152153```python154from pydantic_ai import Agent155from pydantic_ai.capabilities import Thinking, WebSearch156157agent = Agent(158 'anthropic:claude-opus-4-6',159 instructions='You are a research assistant. Be thorough and cite sources.',160 capabilities=[161 Thinking(effort='high'),162 WebSearch(),163 ],164)165```166167### Add Lifecycle Hooks168169Use `Hooks` to intercept model requests, tool calls, and runs with decorators — no subclassing needed.170171```python172from pydantic_ai import Agent, RunContext173from pydantic_ai.capabilities.hooks import Hooks174from pydantic_ai.models import ModelRequestContext175176hooks = Hooks()177178179@hooks.on.before_model_request180async def log_request(ctx: RunContext[None], request_context: ModelRequestContext) -> ModelRequestContext:181 print(f'Sending {len(request_context.messages)} messages')182 return request_context183184185agent = Agent('openai:gpt-5.2', capabilities=[hooks])186```187188### Define Agent from YAML Spec189190Use `Agent.from_file` to load agents from YAML or JSON — no Python agent construction code needed.191192```python193from pydantic_ai import Agent194195# agent.yaml:196# model: anthropic:claude-opus-4-6197# instructions: You are a helpful research assistant.198# capabilities:199# - WebSearch200# - Thinking:201# effort: high202203agent = Agent.from_file('agent.yaml')204```205206## Task Routing Table207208Load only the most relevant reference first. Read additional references only if the task spans multiple areas.209210| I want to... | Reference |211|---|---|212| Create/configure agents, choose output types, use deps, define specs, or pick run methods | [Agents Core](./references/AGENTS-CORE.md) |213| Bundle reusable behavior or intercept lifecycle events | [Capabilities and Hooks](./references/CAPABILITIES-AND-HOOKS.md) |214| Add function tools, toolsets, MCP servers, or explicit search tools | [Tools Core](./references/TOOLS-CORE.md) |215| Use provider-native web search, web fetch, or code execution | [Built-in Tools](./references/BUILTIN-TOOLS.md) |216| Use advanced tool features such as approval, retries, `ToolReturn`, validators, timeouts, or tool search | [Tools Advanced](./references/TOOLS-ADVANCED.md) |217| Work with multimodal input, message history, or context trimming | [Input and History](./references/INPUT-AND-HISTORY.md) |218| Test or debug agent behavior | [Testing and Debugging](./references/TESTING-AND-DEBUGGING.md) |219| Coordinate multiple agents or build graph workflows | [Orchestration and Integrations](./references/ORCHESTRATION-AND-INTEGRATIONS.md#coordinate-multiple-agents) |220| Call the model directly, expose A2A, use durable execution, embeddings, evals, or third-party integrations | [Orchestration and Integrations](./references/ORCHESTRATION-AND-INTEGRATIONS.md) |221| Compare abstractions, output modes, decorators, or model-string patterns | [Architecture and Decision Guide](./references/ARCHITECTURE.md) |222| Follow an older link into `COMMON-TASKS.md` | [Task Reference Map](./references/COMMON-TASKS.md) |223224## Architecture and Decisions225226Load [Architecture and Decision Guide](./references/ARCHITECTURE.md) only when the user is choosing between abstractions or wants comparison tables and decision trees:227228| Topic | What it covers |229|---|---|230| Decision Trees | Tool registration, output modes, multi-agent patterns, capabilities, testing approaches, extensibility |231| Comparison Tables | Output modes, model provider prefixes, tool decorators, built-in capabilities, agent methods |232| Architecture Overview | Execution flow, generic types, construction patterns, lifecycle hooks, model string format |233234**Quick reference — model string format:** `"provider:model-name"` (e.g., `"openai:gpt-5.2"`, `"anthropic:claude-sonnet-4-6"`, `"google-gla:gemini-3-pro-preview"`)235236**Quick reference — key agent methods:** `run()`, `run_sync()`, `run_stream()`, `run_stream_sync()`, `run_stream_events()`, `iter()`237238## Key Practices239240- **Python 3.10+** compatibility required241- **Observability**: Pydantic AI has first-class integration with Logfire for tracing agent runs, tool calls, and model requests. Add it with `logfire.instrument_pydantic_ai()`. For deeper HTTP-level visibility, `logfire.instrument_httpx(capture_all=True)` captures the exact payloads sent to model providers.242- **Testing**: Use `TestModel` for deterministic tests, `FunctionModel` for custom logic243244## Common Gotchas245246These are mistakes agents commonly make with Pydantic AI. Getting these wrong produces silent failures or confusing errors.247248- **`@agent.tool` requires `RunContext` as first param**; `@agent.tool_plain` must **not** have it. Mixing these up causes runtime errors. Use `tool_plain` when you don't need deps, usage, or messages.249- **Model strings need the provider prefix**: `'openai:gpt-5.2'` not `'gpt-5.2'`. Without the prefix, Pydantic AI can't resolve the provider.250- **`TestModel` requires `agent.override()`**: Don't set `agent.model` directly. Always use the context manager: `with agent.override(model=TestModel()):`.251- **`str` in output_type allows plain text to end the run**: If your union includes `str` (or no `output_type` is set), the model can return plain text instead of structured output. Omit `str` from the union to force tool-based output.252- **Hook decorator names on `.on` don't repeat `on_`**: Use `hooks.on.run_error` and `hooks.on.model_request_error` — not `hooks.on.on_run_error`.253- **`history_processors` is plural**: The Agent parameter is `history_processors=[...]`, not `history_processor=`.254255## Task-Family References256257Load exactly one of these unless the task clearly spans multiple families:258259| Task family | Reference |260|---|---|261| Core agent setup, output, deps, specs, models, run methods | [Agents Core](./references/AGENTS-CORE.md) |262| Capabilities, hooks, and reusable behavior | [Capabilities and Hooks](./references/CAPABILITIES-AND-HOOKS.md) |263| Function tools, toolsets, MCP, explicit search tools | [Tools Core](./references/TOOLS-CORE.md) |264| Provider-native builtin tools | [Built-in Tools](./references/BUILTIN-TOOLS.md) |265| Approval, retries, validators, timeouts, rich tool returns, deferred loading | [Tools Advanced](./references/TOOLS-ADVANCED.md) |266| Multimodal input, message history, history processors | [Input and History](./references/INPUT-AND-HISTORY.md) |267| Testing, request inspection, and Logfire debugging | [Testing and Debugging](./references/TESTING-AND-DEBUGGING.md) |268| Multi-agent patterns, graphs, direct API, A2A, durable execution, embeddings, evals, third-party integrations | [Orchestration and Integrations](./references/ORCHESTRATION-AND-INTEGRATIONS.md) |269270Use [Task Reference Map](./references/COMMON-TASKS.md) only for compatibility with older links or when you need a pointer from an old section name to the new file.