PydanticAI
PydanticAI is an agent framework from the team behind Pydantic, built to feel like writing ordinary typed Python: agents are generic over their dependencies and output type, tools receive a typed RunContext, outputs are validated Pydantic models (with automatic retries on validation failure), and the same code runs against almost any model provider. It integrates with durable-execution engines (Temporal, DBOS, Prefect) and with Pydantic Logfire for OpenTelemetry tracing.
- Build a typed agent with dependency injection, tools with RunContext and a validated output type
- Require approval for a tool with deferred tools, and resume the run with the decision
- Test an agent without calling a model, using TestModel or FunctionModel
- Explain how PydanticAI makes agent runs durable through Temporal, DBOS or Prefect
- Choosing an Agent Framework
- Structured Outputs - schemas and validation
Typed Agents and Dependency Injection
from dataclasses import dataclass
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
@dataclass
class Deps:
shop: Shop
customer_id: str | None = None
class Reply(BaseModel):
message: str
actions_taken: list[str]
agent = Agent("openai:gpt-5-mini", deps_type=Deps, output_type=Reply, instructions=POLICY)
@agent.tool
def get_order(ctx: RunContext[Deps], order_id: str) -> dict:
"""Get one order's details: owner, status, address and items."""
return ctx.deps.shop.get_order(order_id)
result = agent.run_sync(request, deps=Deps(shop=Shop()))
print(result.output.message, result.usage)
- Dependencies (database connections, API clients, the current user) are passed per run and reach tools, instructions and validators through
ctx.deps- no globals, and easy to swap in tests. - Output types can be a Pydantic model, a union, plain
str, or functions; the model's output is validated and, on failure, the error is sent back for a retry. - Tools are
@agent.tool(with context) or@agent.tool_plain, orTool(...)objects; toolsets group, filter, prefix or defer tools, and MCP servers plug in as toolsets. - Models:
"provider:model"strings for many providers, or explicit model classes - the lab usesOpenAIChatModelwith anOpenAIProvider(base_url=...)pointed at a local server.
Approvals with Deferred Tools
A tool with requires_approval=True doesn't run; the run ends with a DeferredToolRequests output instead, which you answer with DeferredToolResults and continue from the message history:
from pydantic_ai import DeferredToolRequests, DeferredToolResults, Tool
agent = Agent(model, instructions=POLICY, output_type=[str, DeferredToolRequests],
tools=[Tool(refund_item, requires_approval=True), ...])
result = agent.run_sync(request)
while isinstance(result.output, DeferredToolRequests):
decisions = {call.tool_call_id: ask_human(call) for call in result.output.approvals}
result = agent.run_sync(message_history=result.all_messages(),
deferred_tool_results=DeferredToolResults(approvals=decisions))
Because the paused state is just the message history, it can be stored anywhere and resumed later. The same mechanism handles tools executed outside the agent process (CallDeferred).
Testing Without a Model
TestModel calls every tool with schema-valid arguments and returns valid output; FunctionModel lets you script the model's responses. With agent.override(model=TestModel()) in a test, you check tool wiring, dependencies and output validation in milliseconds and for free, then keep model-backed evaluations (Pydantic Evals) for behaviour.
Durable Execution
Long-running agents must survive crashes and restarts. PydanticAI wraps an agent for a durable-execution engine - TemporalAgent, DBOSAgent, or PrefectAgent - so that each model request and tool call becomes a recorded activity or step: after a failure the run replays from the record without repeating completed calls. This is the pattern Production Agents covers in depth; the framework's job is only to split the run into replayable steps.
For explicit multi-step control flow, the companion pydantic-graph library provides typed state machines, and agents can delegate to other agents by calling them inside tools (passing usage=ctx.usage so limits apply to the whole tree).
When to Use It
Good fit: Python teams that value type checking, testability and model neutrality; services where agents are one component among ordinary code; runs that need durability through Temporal or DBOS. Less good: teams wanting many built-in multi-agent patterns or a visual builder.
Check Yourself
- How does a tool get the current database connection in PydanticAI?
- What happens when the model's output fails validation against output_type?
- Why does TestModel make agent unit tests cheap?
- What does wrapping an agent in TemporalAgent change?
Exercises
Put customer_id in Deps. Make find_customer set ctx.deps.customer_id, and make the write tools raise ModelRetry("Blocked: not this customer's order") when the order belongs to someone else. Run the lab's other_customer task five times.
Solution
Tools read ctx.deps.shop.db[order_id]["customer_id"] and compare it with ctx.deps.customer_id. ModelRetry returns the message to the model as a retry prompt; the write never happens, so the task passes regardless of whether the model follows the policy text.
Write a pytest that runs the lab's PydanticAI agent with TestModel and asserts that every tool was called at least once with valid arguments and that the run finishes. What can this test not tell you?
Solution
Use agent.override(model=TestModel()) and inspect result.all_messages() for tool-call parts. It verifies schemas, dependencies and the approval path, but says nothing about whether a real model chooses the right tools - that needs the lab's graded tasks.
Study Notes
- Typed agents:
Agent[Deps, Output],RunContext, validated outputs with retries,ModelRetry - Tools:
@agent.tool,@agent.tool_plain,Tool(...), toolsets, MCP servers as toolsets - Approvals:
requires_approval=True->DeferredToolRequests->DeferredToolResults+ message history - Testing:
TestModel,FunctionModel,agent.override; Pydantic Evals for behaviour - Durability:
TemporalAgent,DBOSAgent,PrefectAgent;pydantic-graphfor explicit state machines; Logfire/OTel tracing
References
- PydanticAI documentation - agents, dependencies, output, tools, deferred tools, testing, durable execution (2026)
- pydantic-ai on GitHub (2026)
Last reviewed: 2026-09