Version: 2.43.0 (September 2026) Framework: Pydantic AI - GenAI Agent Framework, the Pydantic Way Author Notes: Exhaustive technical documentation with production patterns, type safety emphasis, and FastAPI-inspired developer experience.
Pydantic AI provides native tools that delegate to model-provider capabilities (e.g. Anthropic’s web fetch, OpenAI’s code execution). In 2.33.0 they are passed via capabilities=[NativeTool(...)] — the old builtin_tools=[...] parameter is removed. All native tool classes (WebSearchTool, WebFetchTool, etc.) are re-exported from pydantic_ai; the NativeTool wrapper that registers them as capabilities lives in pydantic_ai.capabilities.
Supported native tools (2.33.0):
Tool
Import
Providers
WebSearchTool
from pydantic_ai import WebSearchTool
OpenAI Responses, Google, Bedrock
WebFetchTool
from pydantic_ai import WebFetchTool
Anthropic, Google
CodeExecutionTool
from pydantic_ai import CodeExecutionTool
Anthropic, OpenAI Responses, Google, Bedrock, xAI
ImageGenerationTool
from pydantic_ai import ImageGenerationTool
OpenAI Responses, Google
FileSearchTool
from pydantic_ai import FileSearchTool
OpenAI Responses, Google, xAI
MemoryTool
from pydantic_ai import MemoryTool
Anthropic
AdvisorTool
from pydantic_ai import AdvisorTool
Anthropic, OpenRouter
XSearchTool
from pydantic_ai import XSearchTool
xAI
Migration (2.33.0):Agent(builtin_tools=[...]) → Agent(capabilities=[NativeTool(...)]). UrlContextTool is removed — use WebFetchTool instead.
Import note: Native tool classes (WebSearchTool, WebFetchTool, etc.) are re-exported from pydantic_ai. The NativeTool wrapper capability lives under pydantic_ai.capabilities.
# Native tools must be paired with a provider that supports them — not all tools
# work with every provider (e.g. WebFetchTool is Anthropic/Google-only).
from pydantic_ai import Agent, WebSearchTool, CodeExecutionTool
result = agent.run_sync('Search for the latest Python release and show a hello-world snippet')
print(result.output)
Tools requiring additional provider configuration (FileSearchTool, MemoryTool) must be
set up via the model provider’s API before use. See the
official docs for provider-specific configuration.
The Class & API Reference section below is the consolidated, source-verified class/API reference for this guide (it folds in what used to be 44 separate “class deep dive” volumes).
A new first-class OllamaModel replaces the generic OpenAIModel workaround and correctly sets Ollama capability flags (fixes structured output on Ollama Cloud):
result =await embedder.embed(['Hello world', 'How are you?'])
print(result.embeddings) # list[list[float]]
print(result.usage) # EmbeddingResult with token counts
asyncio.run(main())
Provider-specific models follow the <provider>:<model> format (e.g.
'openai:text-embedding-3-large', 'google-gla:text-embedding-004'). A TestEmbeddingModel
is available for unit tests (no API key required).
ApprovalRequiredToolset wraps an existing toolset and intercepts tool calls that need
human approval before execution. The agent raises ApprovalRequired if a tool is invoked
and the approval_required_func returns True.
# Installed: pydantic-ai==1.101.0
# Verified against installed package.
import asyncio
from pydantic_ai import Agent, ApprovalRequired, ApprovalRequiredToolset, FunctionToolset
pydantic_ai.ag_ui provides an AG UI Protocol adapter so any
PydanticAI agent can be served as a standards-compliant AG UI endpoint.
Deprecation (v1.98.x): The pydantic_ai.ag_ui module is deprecated and will be removed in
pydantic-ai 2.0. Importing from it emits PydanticAIDeprecationWarning. For new code, use:
from pydantic_ai.ui.ag_ui import AGUIAdapter
from pydantic_ai.ui importSSE_CONTENT_TYPE, StateDeps
AGUIApp (the higher-level mount helper) is still available from pydantic_ai.ag_ui for backward
compatibility; call AGUIAdapter.dispatch_request() directly in new code.
See the migration docs.
# Installed: pydantic-ai==1.86.1 — still works; emits PydanticAIDeprecationWarning in 1.98.x
from pydantic_ai import Agent
from pydantic_ai.ag_ui import AGUIApp
agent =Agent('openai:gpt-4o',instructions='You are a helpful assistant.')
# Mount as a FastAPI sub-application
app =AGUIApp(agent=agent)
# In FastAPI:
# from fastapi import FastAPI
# api = FastAPI()
# api.mount('/agent', app)
AGUIApp handles SSE event streaming, tool-call events, and the AG UI state protocol automatically.
PydanticAI 1.86.0 introduces a composable Capabilities system. Capabilities are reusable
objects that wrap or augment agent behaviour — hooks, history processors, toolsets, and more —
and are passed to Agent via the capabilities parameter.
pydantic_ai.capabilities.Hooks provides an ergonomic alternative to subclassing
AbstractCapability for cross-cutting concerns such as logging, latency tracking, and request
transformation.
pydantic_ai.profiles.ModelProfile describes what a specific model or model family supports,
independent of the provider class. The framework ships DEFAULT_PROFILE; providers override
it per model.
# Installed: pydantic-ai==1.86.1
from pydantic_ai.profiles import ModelProfile, DEFAULT_PROFILE
# Inspect the default profile
print(DEFAULT_PROFILE.supports_tools) # True
print(DEFAULT_PROFILE.supports_thinking) # False
print(DEFAULT_PROFILE.supported_builtin_tools) # frozenset of 8 tool classes
# Define a custom profile for a hypothetical restricted model
PydanticAI 1.87.0 significantly expands the Capabilities system introduced in 1.86.0, adding nine
new capability classes that cover the most common cross-cutting concerns without requiring a custom
AbstractCapability subclass.
When using HistoryProcessor or external truncation, the system prompt can fall off the front
of the message list. ReinjectSystemPrompt detects this and prepends it automatically.
# Installed: pydantic-ai==1.87.0
from pydantic_ai import Agent
from pydantic_ai.capabilities import ReinjectSystemPrompt
WrapperCapability provides a base class for decorating or extending existing capabilities
without re-implementing the full AbstractCapability interface.
# Installed: pydantic-ai==1.87.0
from pydantic_ai.capabilities import WrapperCapability, Hooks
classLoggingWrapper(WrapperCapability):
"""Adds before/after logging around any existing capability."""
ConcurrencyLimiter now tracks waiting_count, running_count, and available_count as live properties, and the acquire() method creates OTel spans while waiting for a slot.
Verified against pydantic-ai 2.33.0 (installed and cross-checked via inspect.signature, dataclasses.fields, and direct source reads). This is a consolidated, source-verified reference to the classes, functions, and wire types across pydantic_ai, pydantic_graph, and pydantic_evals — folded together from 44 previously-separate “class deep dive” volumes into 16 topic sections. Optional-dependency modules (Temporal/DBOS/Prefect/AG-UI/duckduckgo/tavily/exa/web-fetch/markdownify) were verified for import path and top-level structure only, since their third-party packages are not installed in the verification environment.
2.43.0 addendum: Ten additional classes verified against pydantic-ai 2.43.0 are documented in the companion deep-dive page — 10 Source-Verified Class Deep Dives (v2.43.0) — covering ToolFailed, RunCancelled, ToolSelector, ToolOrOutput, ServiceTier/ThinkingLevel, AgentStream/StreamedRunResult, SkipModelRequest/SkipToolValidation/SkipToolExecution, ToolDefinition.sequential, OutputContext, and ApprovalRequired.
tool_timeout sets a global per-tool-call deadline (individual Tool(timeout=...) overrides win).
max_concurrency caps simultaneous model requests for this agent — pass an int,
ConcurrencyLimit, or AbstractConcurrencyLimiter (see Concurrency section). capabilities
accepts AbstractCapability instances orRunContext-aware callables (AgentCapability).
import asyncio
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext, ConcurrencyLimit
Agent exposes several v2.x additions beyond the core run/run_sync/run_stream: to_web() returns a Starlette chat app; to_cli()/to_cli_sync() start a Rich terminal chat; run_stream_events() combines streaming and the final result in one async context manager; parallel_tool_call_execution_mode() is a static wrapper around ToolManager.parallel_execution_mode(); and is_call_tools_node/is_end_node/is_model_request_node/is_user_prompt_node are TypeIs-based static helpers for exhaustive async for node in agent.iter(...) type narrowing.
Type aliases for the instructions= and metadata= parameters on Agent() and agent.run().
AgentInstructions accepts a literal string, a TemplateStr, a sync/async callable (with or
without RunContext), or a sequence mixing any of those — sequences let you combine a static
prefix (cacheable) with a dynamic suffix. AgentMetadata is a dict or a callable producing one;
metadata flows through RunContext.metadata but is never sent to the model.
AgentModelSettings = ModelSettings | Callable[[RunContext[DepsT]], ModelSettings] — lets
model_settings= be resolved per-run from deps. AgentNativeTool is the analogous alias for
per-run native-tool selection (AbstractNativeTool | Callable[[RunContext], AbstractNativeTool | None]),
used when authoring custom NativeTool capability wrappers.
from pydantic_ai import Agent, RunContext, ModelSettings
Type aliases that let Agent(capabilities=[...]) and Agent(toolsets=[...]) accept either a
static instance or a RunContext-aware factory, enabling per-run feature flags without
subclassing Agent.
agent.iter(prompt) returns an async-context-managed AgentRun. Iterate it to walk the graph
node by node (UserPromptNode → ModelRequestNode → CallToolsNode → End), or drive it manually
with .next(node) so capability hooks fire on every step (bare async for skips
before_node_run/wrap_node_run/after_node_run). AgentRunResultEvent is the final event
emitted by agent.run_stream_events(), carrying the completed AgentRunResult; .enqueue(...)
injects a PendingMessage mid-run (see PendingMessage below).
The three public graph nodes an AgentRun walks through, promoted to top-level pydantic_ai
exports. ModelRequestNode.last_request_context exposes the actual model/messages/
model_settings/model_request_parameters sent. CallToolsNode.stream(ctx) yields
HandleResponseEvent items (tool call/result events) for that step.
from pydantic_ai import Agent, UserPromptNode, ModelRequestNode, CallToolsNode
agent =Agent('openai:gpt-4o')
asyncdeftrace():
asyncwith agent.iter('Count to 3.') as run:
asyncfor node in run:
ifisinstance(node, ModelRequestNode):
print('sending',[type(p).__name__ for p in node.request.parts])
AbstractAgent is the ABC that Agent, WrapperAgent, and custom agent implementations satisfy (model, name, description, deps_type, output_type, event_stream_handler, toolsets, run/run_sync/run_stream). WrapperAgent delegates every property and method to self.wrapped, leaving no abstract methods unimplemented — subclass it and override only what you need (auth middleware, rate limiting, routing between specialised agents, timing). This is the base every durable-execution wrapper (TemporalAgent, DBOSAgent, PrefectAgent) builds on.
pydantic_ai.direct sends messages to a model without an Agent — no dependency injection, no tool dispatch, no retries; just the raw model interface with OTel instrumentation wired in. model_request_sync/model_request_stream_sync are thread-bridged sync wrappers for scripts and notebooks; model_request_stream_sync returns a StreamedResponseSync that bridges an async model stream via a background thread and a queue.Queue, for CLI tools and notebooks that can’t await.
PendingMessage is the object created by ctx.enqueue(...) / agent_run.enqueue(...), holding one or more ModelMessages and a priority. 'asap' is delivered before the next model request (or redirects termination into one more request); 'when_idle' is delivered only when the agent would otherwise finish. PendingMessage.from_content() coalesces adjacent user content into one ModelRequest and returns None for an empty call. The auto-injected PendingMessageDrainCapability sits at position='outermost' and does the actual draining — before_model_request drains 'asap', after_node_run redirects idle termination to drain 'when_idle'.
@dataclass
classPendingMessage:
messages: list[ModelMessage] # always ends in a ModelRequest
ctx.enqueue('Also double-check that against the latest data.',priority='asap')
returnf'data for {query}'
asyncio.run(agent.run('Search for AI news'))
Note: a bare async for node in agent.iter(...) loop that ends while 'when_idle'-priority messages are still undrained raises UndrainedPendingMessagesError — use agent.run() or AgentRun.next() instead, both of which drain every priority.
is_tool_available() answers whether a function tool is currently visible to the model,
accounting for FilteredToolset/PrepareTools/DeferredLoadingToolset mutations.
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext, ModelRetry
Module:pydantic_ai._spec (exported as pydantic_ai.AgentSpec). A BaseModel describing an
agent’s full config — model, name, description, instructions (string or TemplateStr),
deps_schema, output_schema, model_settings, retries, end_strategy, tool_timeout,
metadata, capabilities: list[CapabilitySpec]. Correction:AgentSpec has no to_agent()
method — build the agent via Agent.from_spec(spec) or Agent.from_file(path).
NamedSpec/CapabilitySpec support three short forms (bare string, {Name: single_arg},
{Name: {kwargs}}) resolved via build_registry()/load_from_registry().
EndStrategy = Literal['early', 'graceful', 'exhaustive'], passed as Agent(end_strategy=...) or
agent.run(end_strategy=...): 'early' (default) stops the moment a final result is available
even with tools still in flight; 'graceful' finishes tool calls already dispatched first;
'exhaustive' runs every requested tool call regardless. AgentRetries = int | None sets how
many ModelRetry cycles a tool/output gets before the run raises.
from pydantic_ai import Agent
agent =Agent('openai:gpt-4o',retries=3)
result = agent.run_sync('Fetch three URLs and summarise.',end_strategy='graceful')
Context manager that captures the message history built up to the point of failure, even when
agent.run() raises. Only the firstrun()/run_sync()/run_stream() call inside one
with block is captured — nest separate blocks for separate calls.
from pydantic_ai import Agent, capture_run_messages, UsageLimits
from pydantic_ai.exceptions import UsageLimitExceeded
agent =Agent('openai:gpt-4o')
withcapture_run_messages() as messages:
try:
agent.run_sync('Count to 1000.',usage_limits=UsageLimits(request_limit=1))
except UsageLimitExceeded:
print(f'Captured {len(messages)} messages before the limit hit')
Module:pydantic_ai.tool_manager. The internal engine that resolves, validates, and executes
every tool call in a step. ParallelExecutionMode ('parallel' | 'sequential' | 'parallel_ordered_events') controls concurrency and event ordering; ToolManager.parallel_execution_mode(mode) is a classmethod context manager, and Agent.parallel_tool_call_execution_mode(mode) is a thin static wrapper around it. A tool’s own ToolDefinition.sequential=True always wins regardless of the mode. ValidatedToolCall separates schema validation from execution so hooks can inspect args_valid before a tool actually runs.
ToolFailed is the counterpart to ModelRetry. Raise it when a tool call has definitively
failed — missing resource, unsupported operation, confirmed upstream error — and you want the
model to adapt instead of retrying the same call. Unlike ModelRetry, it does not prepend
“please retry” instructions and does not consume the tool’s retry budget.
classToolFailed(Exception):
def__init__(self, message: str): ...
from pydantic_ai import Agent
from pydantic_ai.exceptions import ModelRetry, ToolFailed
agent =Agent('openai:gpt-4o')
@agent.tool_plain
deffetch_record(record_id: int) -> dict:
if record_id <0:
raiseToolFailed(f'Record {record_id} does not exist (not retryable).')
if record_id >9999:
raiseModelRetry('Record ID seems too large — did you mean a smaller number?')
return {'id': record_id, 'value': 'ok'}
Use ToolFailed for definitive errors the model should adapt to; use ModelRetry for
transient errors where a prompt correction might help.
Three plain Exception subclasses for short-circuiting hook execution. Raise SkipModelRequest(response) inside before_model_request/wrap_model_request to substitute a synthetic ModelResponse (response caching, test injection, circuit breakers). Raise SkipToolExecution(result) inside before_tool_execute/wrap_tool_execute to skip the tool body and return result to the model directly (dry-run mode, sandboxing). Raise SkipToolValidation(validated_args) inside a before_tool_validate hook to bypass Pydantic argument validation with pre-coerced args.
SelectModel(selector) invokes a ModelSelector callable before every logical model-request step; selector receives a frozen ModelSelectionContext (deps, model, run_step, messages, usage) and returns a Model instance or a provider-prefixed model-name string — the primary mechanism for per-step routing (cost ladders, capability escalation). ResolveModelId(resolver) is the lower-level hook: resolver(ModelResolutionContext, model_id: str) -> Model | None, called only when a string model ID isn’t already in a registry; returning None falls through to the next resolver or infer_model. Both accept sync or async callables.
@dataclass
classSelectModel(AbstractCapability[AgentDepsT]):
selector: ModelSelector[AgentDepsT]
@dataclass(frozen=True)
classModelSelectionContext(Generic[DepsT]):
deps: DepsT
model: Model
run_step: int
messages: list[ModelMessage]
usage: RunUsage
from pydantic_ai import Agent, ModelSelectionContext
A thread-safe handle for first-party run cancellation, added 2.26.0. Call .cancel() from any thread; the agent translates the resulting cancellation into RunCancelled at the agent.iter() boundary, and RunCancelled.all_messages() preserves partial history for resuming later.
MessagesBuilder (pydantic_ai.ui._messages_builder) constructs ModelRequest/ModelResponse sequences incrementally from individual message parts: add() extends the last message if the new part matches its type, otherwise appends a fresh message. BuilderCheckpoint is an opaque snapshot used with last_modified(checkpoint, of_type=...) to find which message was created/extended since the snapshot — the plumbing behind UIEventStream and custom streaming adapters.
The internal wrapper pydantic-ai stores for each @agent.system_prompt-registered function. It inspects the function signature at construction (_takes_ctx, _is_async) to dispatch correctly: sync/no-arg via a thread executor, RunContext-aware via the same executor with context passed, async called directly. dynamic=True forces re-evaluation before every model request rather than once at run start.
ModelSettings is a TypedDict (all keys optional) providing portable model configuration; unsupported keys are silently ignored by a given provider’s adapter. Notable fields: temperature, top_p, max_tokens, seed, parallel_tool_calls, thinking (ThinkingLevel), tool_choice, service_tier, stop_sequences, extra_headers, extra_body. Use merge_model_settings(base, overrides) to layer agent-level settings with per-run overrides (override wins per key).
from pydantic_ai import Agent, ModelSettings
from pydantic_ai.settings import merge_model_settings
TemplateStr[AgentDepsT] compiles a Handlebars template against the agent’s deps_type (via pydantic-handlebars) and re-renders per model request against the live RunContext.deps. Any string containing {{ inside a field typed TemplateStr[...] is auto-compiled during Pydantic validation — this is how AgentSpec YAML instructions get template rendering without extra ceremony. .render(deps) renders standalone, outside an Agent.
As of pydantic-ai 2.23+, ModelProfile is a TypedDict (total=False), not a @dataclass. There is no .update() instance method any more — the canonical way to layer profiles is the module-level merge_profile() function. ModelProfileSpec is ModelProfile | Callable[[ModelProfile], ModelProfile] (the callable receives the provider’s already-resolved default profile and returns the final one).
supported_builtin_tools (the pre-1.104 field name) is no longer available even as a deprecated
alias — code still reading profile.supported_builtin_tools must migrate to
profile['supported_native_tools']. harmony_model_profile, moonshotai_model_profile, and
amazon_model_profile are additional provider-profile functions layered the same way as any other.
Each provider module exposes a TypedDict subclass of ModelProfile with <provider>_-prefixed fields plus a <provider>_model_profile(model_name) function, all mergeable via plain dict-spread or merge_profile:
AnthropicModelProfile (profiles.anthropic): anthropic_supports_fast_speed, anthropic_supports_adaptive_thinking, anthropic_supports_effort, anthropic_supports_xhigh_effort, anthropic_disallows_budget_thinking, anthropic_disallows_sampling_settings, anthropic_supports_forced_tool_choice, anthropic_supports_task_budgets, anthropic_default_code_execution_tool_version / anthropic_supported_code_execution_tool_versions (Literal['20250825','20260120']). resolve_anthropic_effort(level, *, supports_xhigh) -> AnthropicEffort maps the unified thinking level to Anthropic’s API string.
Module:pydantic_ai.providers. Every first-party provider implements this ABC: name,
base_url, client, and model_profile(model_name) -> ModelProfile | None (returning None
means “use the built-in default for this model family”). infer_provider('openai') /
infer_provider_class('openai') resolve a provider string to an instance or class.
Wraps two or more models and tries them in order until one succeeds (or on a custom response predicate). fallback_on accepts exception types, exception handlers, response handlers, or a sequence mixing all three — auto-detected by inspecting whether the callable’s first parameter is type-hinted ModelResponse. ResponseRejected is raised inside FallbackExceptionGroup when a response handler rejects every model’s output. model_name reports fallback:<model1>,<model2>,....
WrapperModel is the base class for models that wrap another model — delegating every Model method to self.wrapped via __getattr__ for anything not explicitly overridden. CompletedStreamedResponse presents an already-consumed ModelResponse as a StreamedResponse, used by Temporal/Prefect/DBOS wrappers that ran the real model inside an activity/task and must replay the result at the workflow layer. Import note:CompletedStreamedResponse moved from pydantic_ai.models.wrapper to pydantic_ai.models — the old path still works but emits PydanticAIDeprecationWarning.
classWrapperModel(Model):
def__init__(self, wrapped: Model | KnownModelName): ...
Module:pydantic_ai.models.mistral. Talks to Mistral’s API (mistral-large-latest,
pixtral-large-latest for vision, mistral-embed). json_mode_schema_prompt templates the
schema-in-instructions fallback for structured output.
Extends OpenAIChatModel for Ollama’s OpenAI-compatible endpoint. Self-hosted Ollama enforces
response_format grammar-style, so NativeOutput is schema-safe; Ollama Cloud (base_url
containing ollama.com, or model name ending -cloud) accepts the same request but does not
enforce the schema, so PydanticAI auto-disables supports_json_schema_output for detected Cloud
models — NativeOutput then raises UserError there. Use ToolOutput/PromptedOutput instead,
or manually override the profile once Ollama Cloud fixes enforcement upstream.
Extra:pip install "pydantic-ai-slim[huggingface]". Inference against any HF Hub model
(DeepSeek-R1, Llama-4, Qwen3, QwQ). Thinking models need profile=ModelProfile(supports_thinking=True, thinking_always_enabled=True, thinking_tags=('<think>', '</think>')) set explicitly.
The only first-party AWS model, adapting boto3’s synchronous converse/converse_stream to the
async Model interface. Every BedrockModelSettings field carries a bedrock_ prefix.
BedrockProvider supports three auth paths: bring-your-own boto3 client, bearer-token
(AWS_BEARER_TOKEN_BEDROCK), or standard AWS credentials; provider.client = new_client hot-swaps
for credential rotation without recreating the model.
bedrock_*_model_profile factory functions (anthropic/amazon/deepseek/mistral/qwen/google/minimax/
nvidia) map each vendor’s Bedrock model IDs to a BedrockModelProfile; _without_builtin_tools
strips native tools from any profile since Bedrock’s Converse API has none.
GoogleProvider targets the Gemini API (GOOGLE_API_KEY); GoogleCloudProvider targets Vertex AI
(Application Default Credentials, or Express Mode with api_key=). Both extend
BaseGoogleProvider[Client] from google-genai.
result = agent.run_sync('Explain federated learning.')
Removed:GoogleGLAProvider and GoogleVertexProvider (and the GeminiModel they paired
with) are gone. Migrate GoogleGLAProvider(api_key=...) → GoogleProvider(api_key=...) (env var
GEMINI_API_KEY → GOOGLE_API_KEY), and GoogleVertexProvider(project_id=..., region=...) →
GoogleCloudProvider(project=..., location=...).
The only gRPC-transport model (xai-sdk, not HTTP). _LazyAsyncClient defers channel creation
per-event-loop to avoid the classic “gRPC channel bound to the wrong asyncio loop” RuntimeError.
GrokModelProfile adds grok_supports_builtin_tools / grok_supports_tool_choice_required /
grok_reasoning_efforts: frozenset[GrokReasoningEffort] (Literal['none','low','medium','high']).
Z.AI (Zhipu AI) GLM family support (pydantic_ai.models.zai, pydantic_ai.providers.zai). Z.AI sends thinking as a separate reasoning_content field rather than inline text; by default prior-turn reasoning is preserved across turns (zai_clear_thinking=False, matching Z.AI’s “preserved thinking” contract) — set True to discard it. GLM-5.2 additionally supports per-request reasoning_effort when ZaiModelProfile.zai_supports_reasoning_effort=True.
Routes through https://ai-gateway.vercel.sh/v1, proxying 8+ upstream providers under one auth surface (VERCEL_AI_GATEWAY_API_KEY or VERCEL_OIDC_TOKEN). Model naming is provider/model (e.g. anthropic/claude-opus-4-8); VercelProvider.model_profile() dispatches to the matching upstream profile function, merged over OpenAIModelProfile(json_schema_transformer=OpenAIJsonSchemaTransformer).
Module:pydantic_ai.models.mcp_sampling. Routes an agent’s LLM calls back through the MCP
client’s sampling API — used when writing an MCP server that needs to call an LLM using the
connected client’s credentials/model choice, per the MCP sampling spec.
default_max_tokens (default 16_384) is required because MCP’s create_message mandates
max_tokens while ModelSettings.max_tokens is optional. No streaming support —
request_stream raises NotImplementedError.
classMCPSamplingModel(Model):
session: ServerSession
default_max_tokens: int=16_384
from mcp.server.session import ServerSession
from pydantic_ai import Agent
from pydantic_ai.models.mcp_sampling import MCPSamplingModel
Controls the OpenAI Responses API for reasoning models. openai_reasoning_context ('auto'|'current_turn'|'all_turns', default 'all_turns' on supported models) selects which prior-turn reasoning items the model replays. openai_reasoning_mode ('standard'|'pro') trades latency for reliability. openai_send_reasoning_ids: bool — set False to strip reasoning-part IDs from history when a custom ProcessHistory removes thinking parts, avoiding history-mismatch errors.
Raised on 4xx/5xx provider responses. Carries headers: Mapping[str, str] | None (lowercased keys; None for gRPC-based paths like xAI) and the derived retry_after: float | None property that parses RFC-7231 Retry-After (delta-seconds or HTTP-date). Propagated by all built-in HTTP-based providers.
An after_model_request capability that turns finish_reason='content_filter' into a ContentFilterError exception instead of passing the filtered response through silently. The full ModelResponse is serialised into ContentFilterError.body for inspection.
Every provider calls JsonSchemaTransformer.walk() on each tool’s JSON schema during prepare_request() to normalise it for that provider’s requirements. Subclass and implement transform(schema); set self.is_strict_compatible = False inside it to signal a schema can’t be used in strict mode. InlineDefsJsonSchemaTransformer expands all $ref/$defs inline (used by Amazon/Bedrock profiles and providers without $ref support, e.g. Qwen); recursive types keep a minimal $defs/$ref (unavoidable for cycles).
All of these are Provider[AsyncOpenAI] implementations paired with OpenAIChatModel — they
differ only in endpoint URL, env var, model-name convention, and which *_model_profile family
functions get dispatched based on the model-name prefix.
Provider
Module
Env var
Base URL
Naming
Notes
LiteLLMProvider
.litellm
— (proxy key)
api_base= (your proxy)
provider/model
Auto-dispatches profile by prefix (anthropic/, google/, bedrock/, etc.)
AzureProvider
.azure
AZURE_OPENAI_API_KEY
azure_endpoint=
deployment name
/v1-suffix endpoints (Express Mode, AI Foundry serverless) must omitapi_version — passing one raises UserError
An OpenAI-compatible gateway with a multi-family model-profile router: HerokuProvider.model_profile(model_name) detects the model family from the bare name (no provider prefix) and applies the correct profile — claude* → anthropic_model_profile, gpt-oss* → harmony_model_profile, qwen*/deepseek*/kimi*/glm*/mistral*/nova*/llama*/gemma* → their respective profiles — all merged over OpenAIModelProfile. Base URL defaults to https://us.inference.heroku.com/v1.
gateway_provider(upstream_provider, ...) routes through gateway.pydantic.dev/proxy (or the deprecated alias, still-working PYDANTIC_AI_GATEWAY_API_KEY-keyed proxy), a managed proxy fronting OpenAI/Anthropic/Groq/Bedrock/Google Cloud with one API key. Accepts both model-provider names and API-flavour aliases ('chat', 'responses', 'converse', 'google-cloud'); route= overrides the default routing group. Region-encoded pylf_v* keys let _infer_base_url auto-select the nearest regional endpoint.
Called internally by the matching Provider.model_profile() to set capability flags from the
model-name string. All return TypedDict-shaped ModelProfile objects (or None for “use
default”).
OutlinesModel (local Transformers/LlamaCpp/SGLang/vLLM-offline/MLX grammar-constrained decoding)
was deprecated as of 1.107.0 and is fully removed — no trace remains under
pydantic_ai/models/. For local structured output, use vLLM’s structured-output API behind
OpenAIProvider(base_url=...), or lean on NativeOutput/PromptedOutput against an API model.
prepare= mutates or hides the ToolDefinition before each step (return None to hide);
requires_approval=True raises ApprovalRequired on call (see Security section);
sequential=True forces the tool to never run in parallel with others in the same step.
args_validator runs after schema validation but before execution — receives schema-validated
kwargs and should raise ModelRetry on failure.
from pydantic_ai import Agent, ModelRetry, RunContext
timeout delivers a retry prompt instead of an exception on a slow tool. sequential=True is a
barrier — the tool never runs in parallel with others in the same step. id gives the toolset a
stable identity for durable-execution runtimes. instructions injects a system-prompt segment
whenever any tool in the set is active.
from pydantic_ai import Agent, FunctionToolset, RunContext
db_tools =FunctionToolset(
instructions='When using DB tools, always use read-only queries first.',
Base class every toolset implements. Override get_tools() (returning
dict[str, ToolsetTool]) and call_tool(); optionally for_run/for_run_step for per-run state
isolation, and get_instructions() for toolset-scoped system-prompt text.
ToolsetTool is the runtime execution wrapper for one tool inside a toolset (toolset, tool_def, max_retries, args_validator, args_validator_func) — surfaced in before_tool_validate/after_tool_execute hooks and returned from custom get_tools(). SchemaValidatorProt is the Protocol any custom validator must satisfy (validate_json/validate_python, compatible with pydantic_core.SchemaValidator), letting non-Pydantic validation engines plug in.
All confirmed present with unchanged constructors. WrapperToolset is the delegation base for the rest — subclass it and override call_tool/get_tools to add cross-cutting behaviour (logging, caching) while delegating everything else; visit_and_replace(visitor) recursively traverses a wrapper chain to swap out a specific inner toolset.
RenamedToolset wraps a toolset and remaps tool names via name_map: dict[str, str] (new name → original name); unmapped tools pass through unchanged. call_tool inverts the map and restores ctx.tool_name/tool.tool_def.name to the original before delegating. Attempting to rename onto an existing or duplicate name raises UserError.
FilteredToolset wraps any toolset and calls filter_func(RunContext, ToolDefinition) -> bool | Awaitable[bool] on every tool at every get_tools() call — both sync and async predicates supported via inspect.isawaitable().
PreparedToolset calls prepare_func(RunContext, list[ToolDefinition]) -> list[ToolDefinition] on each step. The function may filter or modify definitions (descriptions, strict, metadata) but cannot add, rename, or substitute tools — attempting to raises UserError.
PrefixedToolset prepends {prefix}_ to every tool name (strips it back off before dispatching call_tool) — the standard fix for name collisions between combined toolsets or MCP servers.
@dataclass
classPrefixedToolset(WrapperToolset[AgentDepsT]):
prefix: str
@property
deftool_name_conflict_hint(self) -> str: ...
CombinedToolset fans out get_tools()/get_instructions()/for_run()/for_run_step() across child toolsets in parallel via gather(). Detects name collisions eagerly and raises UserError naming both conflicting toolsets and pointing at tool_name_conflict_hint. for_run_step short-circuits (returns self) when no child toolset actually changed.
Hides some or all of a wrapped toolset’s tools until the ToolSearch capability discovers them.
tool_names=None defers everything; a frozenset[str] defers only the named tools. Marks tools
with defer_loading=True on their ToolDefinition. FunctionToolset(defer_loading=True) is a
shortcut that wraps itself automatically.
Wraps a factory Callable[[RunContext], AbstractToolset | None] (ToolsetFunc) and re-evaluates
it either every step (per_run_step=True, default) or once per run (per_run_step=False).
Lifecycle is transition-safe: the old inner toolset’s __aexit__ runs before the new one’s
__aenter__.
Both exist and do the same job at different layers: IncludeReturnSchemasToolset
(pydantic_ai.toolsets) wraps one toolset; IncludeToolReturnSchemas
(pydantic_ai.capabilities) applies agent-wide via capabilities=[...]. Both set
include_return_schema=True on every ToolDefinition whose value is still None, so the model
sees the tool’s return-type JSON schema (useful for tool chaining and OpenAI structured outputs).
from pydantic_ai import Agent, IncludeReturnSchemasToolset, FunctionToolset
from pydantic_ai.capabilities import IncludeToolReturnSchemas
Registers tool schemas for the model without ever executing them in-process — the calls are
resolved by an external system and fed back via deferred_tool_results. call_tool() raises
NotImplementedError; every registered tool gets tool_kind='external' via a
TOOL_SCHEMA_VALIDATOR = SchemaValidator(schema=core_schema.any_schema()) that accepts any args
shape. id= gives the toolset a stable identity for durable-execution runtimes to match
activities across replays. DeferredToolset is a deprecated alias for backward compatibility —
migrate to ExternalToolset.
Wraps a toolset with an approval gate. Every call to approval_required_func (default: approve
none automatically, i.e. gate everything) decides whether the tool call raises ApprovalRequired;
the constructor field is wrapped=, not toolset=. See the Security section for the full
two-round DeferredToolRequests resume flow.
Module:pydantic_ai._function_schema. The frozen dataclass and factory function that convert
a Python function into a tool’s JSON schema + calling convention — takes_ctx auto-detection,
async/sync dispatch, single-arg model unwrapping, and return-type schema extraction.
GenerateToolJsonSchema strips redundant title keys from every property; DocstringFormat = Literal['google', 'numpy', 'sphinx', 'auto'] controls how parameter descriptions are parsed out of
docstrings (via griffe — 'auto' uses regex inference:
Sphinx :param:, Google Args: block, NumPy --- underline, falling back to 'google').
@dataclass
classFunctionSchema:
function: Callable
description: str|None
validator: SchemaValidator
json_schema: ObjectJsonSchema
single_arg_name: str|None
takes_ctx: bool
is_async: bool
return_schema: ObjectJsonSchema
from pydantic_ai._function_schema import function_schema
from pydantic.json_schema import GenerateJsonSchema
ToolChoice = Literal['none', 'required', 'auto'] | list[str] | ToolOrOutput | None, set via
ModelSettings(tool_choice=...). ToolOrOutput(function_tools=[...]) restricts which function
tools are callable while still allowing the model to use output/text/image tools freely.
PrefixTools is the capability-level equivalent of PrefixedToolset: it wraps another capability and prefixes its contributed tools, delegating to PrefixedToolset internally (or DynamicToolset first if the wrapped toolset is a callable factory).
@dataclass
classPrefixTools(WrapperCapability[AgentDepsT]):
prefix: str
from pydantic_ai import Agent
from pydantic_ai.capabilities import PrefixTools, Toolset
Lazy tool discovery for large toolsets. ToolSearch is auto-injected whenever deferred tools exist (zero overhead otherwise). On providers with native tool search (Anthropic BM25/regex, OpenAI Responses) deferred tools are sent on the wire and the provider handles discovery; elsewhere a local search_tools function is exposed. strategy accepts None (auto), 'bm25'/'regex' (Anthropic-only, error elsewhere), 'keywords' (force the local algorithm everywhere for determinism), or a custom ToolSearchFunc; ToolSearchToolset.enable_fallback=False disables the local search_tools fallback for native-only strategies.
Module:pydantic_ai.ext.langchain — the only surviving file under pydantic_ai/ext/
alongside __init__.py. Bridges any LangChain BaseTool without requiring langchain as an
import-time dependency (LangChainTool is a structural Protocol: .name, .get_input_jsonschema(),
.description, .run()).
Power CLI/tool-rendering and “Code Mode”: FunctionSignature holds a parsed function’s name, params (FunctionParam), return_type (TypeExpr), and referenced_types (nested TypeSignatures for TypedDicts). TypeExpr is a union of SimpleTypeExpr | UnionTypeExpr | GenericTypeExpr | LiteralTypeExpr covering every annotation shape. .render(body) produces the Python-source representation.
Both were deprecated in 1.107.0 as the ACI.dev bridge; fully removed — pydantic_ai/ext/
now contains only langchain.py. Migrate to Tool.from_schema() built directly from
aci.functions.get_definition(...) output (strip the non-standard 'visible' key before passing
the schema through).
Fetches URL content directly into the model’s context (Anthropic, Google). UrlContextTool is a
deprecated alias kept only so old serialised payloads (kind='url_context') still deserialise.
Sandboxed code interpreter (Anthropic, OpenAI Responses, Google, Bedrock Nova 2.0, xAI). Gained a
files: list[UploadedFile] | None field to seed the sandbox with pre-uploaded files.
Provider-managed tool letting a fast executor model pause and consult a stronger advisor model
inline (Anthropic native, OpenRouter gateway). caching: Literal['5m','1h'] | None controls
prompt-cache TTL on the advisor call. Correction: the installed AdvisorTool has no
system_prompt field — earlier docs describing an Anthropic-only system_prompt override are
stale for this version.
Every built-in native tool subclasses this. kind is the wire discriminator (auto-registered into
NATIVE_TOOL_TYPES via __init_subclass__); optional=True silently drops the tool on models
that don’t support it rather than raising.
classAbstractNativeTool(ABC):
kind: str='unknown_native_tool'
optional: bool=False
@property
defunique_id(self) -> str: returnself.kind
from dataclasses import dataclass
from pydantic_ai.native_tools import AbstractNativeTool
Module:pydantic_ai.mcp. The provider-agnostic way to connect to any MCP server — supports
HTTP/SSE/stdio/in-process transports. The legacy MCPServer/MCPServerStdio/MCPServerSSE/
MCPServerStreamableHTTP classes are confirmed fully removed — grep found zero trace of them
in pydantic_ai/mcp.py. FastMCPToolset (deprecated in 1.104) is likewise gone entirely.
prefer_tasks (default True) wraps tool calls as durable background tasks per SEP-1686 when
the server declares taskSupport='optional' (tools with taskSupport='required' always run as
tasks regardless). direct_call_tool(name, args, *, metadata, use_task) invokes a tool outside
any agent run. process_tool_call(ctx, call_tool, name, tool_args) -> ToolResult intercepts every
call for metadata injection, audit logging, or selective retry.
agent.set_mcp_sampling_model() wires the agent’s own model into every attached MCPToolset for
server-driven sampling. load_mcp_toolsets(config_path) reads a Claude-Desktop-style
mcpServers JSON config and returns one PrefixedToolset(MCPToolset(...)) per server, with
${VAR} / ${VAR:-default} env-var expansion.
duckduckgo_search_tool wraps DDGS (from the ddgs package) via anyio.to_thread.run_sync,
returning list[DuckDuckGoResult] (title, href, body). tavily_search_tool freezes any of
the keyword params you supply via functools.partialand strips them from __signature__, so
the LLM never sees (or can override) developer-fixed params; unset ones stay LLM-controlled.
from pydantic_ai import Agent
from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool
from pydantic_ai.common_tools.tavily import tavily_search_tool
from pydantic_ai.common_tools.web_fetch import web_fetch_tool
agent =Agent(
'openai:gpt-4o',
tools=[
duckduckgo_search_tool(max_results=5),
# search_depth/topic frozen and hidden from the LLM schema; time_range stays LLM-controlled
instructions='Search first, then fetch the most promising URL.',
)
image_generation_tool (from pydantic_ai.common_tools.image_generation) spins up a subagent
on an image-capable model so a text-only primary agent can still generate images:
from pydantic_ai.common_tools.image_generation import image_generation_tool
StreamedRunResult is the high-level object agent.run_stream() yields (wrapping AgentStream
with message-history helpers). StreamedRunResultSync (from agent.run_stream_sync()) is the
same API with _sync suffixes, run on a background thread via anyio.from_thread.
agent.run_stream_events() returns an AgentEventStream context manager — always use it via
async with (bare async for iteration without the context manager is deprecated and will be
removed). AgentRunResultEvent is always the final event, carrying the completed
AgentRunResult.
from pydantic_ai.run import AgentRunResultEvent
asyncdeffull_loop(agent):
asyncwith agent.run_stream_events('Name the planets.') as stream:
The discriminated union ModelResponseStreamEvent flowing from StreamedResponse._get_event_iterator(). Each carries an index identifying which part in the running parts list is updated, plus an event_kind literal for efficient discrimination. FinalResultEvent is fired once per run step when the model’s response first matches the output schema, ahead of actual validation; FinalResult (the non-event dataclass) wraps the validated output plus the tool name/call ID that produced it (both None for plain-text output).
Incremental delta payloads inside PartDeltaEvent.delta. TextPartDelta.content_delta appends; ThinkingPartDelta.signature_deltareplaces (never appends); ToolCallPartDelta.args_delta appends when a str, merges when a dict.
Deprecated in favour of the richer PartStartEvent/PartEndEvent pathway (which supports
streaming deltas for native tool call arguments, unlike the old start/end-only events).
The streaming aggregator used internally by every StreamedResponse subclass. When writing a custom provider, use self._parts_manager (a cached property on StreamedResponse) inside _get_event_iterator() — never instantiate a local one, since StreamedResponse.__aiter__ synthesises PartEndEvents by reading that same instance. handle_text_delta(vendor_part_id, content) and handle_tool_call_delta(...) return correctly-typed events with dedup and vendor-ID tracking; get_parts() returns only fully-formed parts (no in-flight deltas).
Module:pydantic_ai.models.function. The chunk types a FunctionModel(stream_function=...)
yields, and the StreamedResponse implementation that dispatches them through
ModelResponsePartsManager. You must yield all-str, all-DeltaToolCalls, or all-
DeltaThinkingCalls within one stream — mixing types is not supported.
Intercepts the AgentStreamEvent sequence during a streaming run — or automatically enables streaming inside agent.run() when registered, no run_stream() needed. Two handler forms: an observer (async def handler(ctx, stream) -> None, receives a tee’d copy, events pass through unchanged; a slow observer back-pressures) and a processor (an async generator whose yielded events replace the stream for downstream consumers — can drop, transform, or inject events; dropping a FinalResultEvent delays result delivery).
EventStreamHandler is Callable[[RunContext, AsyncIterable[AgentStreamEvent]], Awaitable[None]] — a terminal sink used via agent.run(..., event_stream_handler=...). EventStreamProcessor is Callable[[RunContext, AsyncIterable[AgentStreamEvent]], AsyncIterator[AgentStreamEvent]] — a pass-through transformer used by ProcessEventStream.
from pydantic_ai import Agent
from pydantic_ai.tools import RunContext
from pydantic_ai.messages import AgentStreamEvent, PartDeltaEvent, TextPartDelta
Some providers (DeepSeek, Qwen, older Ollama) embed thinking inside <think>...</think> tags in the plain text stream rather than as a separate part. This function splits a raw string into an alternating list[ThinkingPart | TextPart] using the provider’s thinking_tags tuple.
Explicit, per-type control over how structured output is delivered. ToolOutput — model emits a structured “output tool” call (best for unions/non-native models). NativeOutput — provider’s native JSON-schema/response-format mode; template=False suppresses schema-prompt injection when the provider already handles it natively. PromptedOutput — injects the schema as prompt text and parses the reply; template accepts a custom '{schema}'-style string. TextOutput(fn) — plain text passed to a Python parser function, which may optionally take RunContext as its first argument and may be async. StructuredDict is a factory (not a class) returning a dict[str, Any] subclass with a JSON Schema attached — validated structured output without defining a Pydantic BaseModel; name/description fall back to the schema’s title/description.
OutputObjectDefinition is the internal normalised schema record (json_schema, name,
description, strict) produced from ToolOutput/NativeOutput/PromptedOutput/
StructuredDict; surfaced as OutputContext.object_def in output hooks. OutputSchema.build(type)
is the factory resolving output_type into one of TextOutputSchema/ToolOutputSchema/
NativeOutputSchema/PromptedOutputSchema/ImageOutputSchema/MultiOutputSchema.
OutputValidator wraps a user validator function, dispatching sync/async and with/without
RunContext transparently.
The three request-side message parts. UserPromptPart.content accepts a heterogeneous sequence
of UserContent (str, TextContent, ImageUrl, AudioUrl, VideoUrl, DocumentUrl,
BinaryContent, UploadedFile, CachePoint). RetryPromptPart.model_response() formats the
retry-feedback string actually sent to the model.
Call-part family. args_as_dict(raise_if_invalid=False) gracefully returns
{'INVALID_JSON': ...} on malformed streamed JSON unless raise_if_invalid=True.
narrow_type(part, tool_kind=...) promotes a raw NativeToolCallPart to a typed subclass (e.g.
NativeToolSearchCallPart) for the tool-search protocol.
Return-part family. outcome: Literal['success', 'failed', 'denied'] tracks HITL/error state.
The top-level ToolReturn message-content dataclass (the thing tools return, distinct
from ToolReturnPart) has a tools field that lets a tool’s return value append additional
tool-availability deltas (ToolAvailabilityDeltaEvent/Part) to the run.
FilePart is a model-response part carrying a generated file (e.g. an image). BinaryImage
is a BinaryContent subclass that validates media_type.startswith('image/') at construction.
Two parallel multimodal systems: raw bytes (BinaryContent) vs. URL references (FileUrl
subclasses). force_download: bool | Literal['allow-local'] controls SSRF-safe fetching for URLs
(False=send URL directly where supported; True=always download with full SSRF guard;
'allow-local'=download, allow private IPs, still block cloud metadata).
A durable reference to a file already uploaded to a provider (skips re-sending bytes every
request). Supported providers: OpenAI, OpenAI Responses, Anthropic, Bedrock (s3://), Google
(Gemini Files API URI or gs://), xAI.
CachePoint(ttl='5m' | '1h') marks a prompt-cache boundary inside UserPromptPart.content
(Anthropic, Bedrock Converse, OpenAI GPT-5.6+; Anthropic/Bedrock support '1h', OpenAI always
uses '5m'; silently dropped elsewhere, so the same message-construction code works everywhere).
CompactionPart carries a provider-produced conversation summary (Anthropic: readable content;
OpenAI: opaque provider_details) that must be round-tripped verbatim on the next request — check
.has_content() before displaying it. ToolAvailabilityDeltaPart is a streaming part recording
that new tools became available mid-stream — emitted internally when DeferredLoadingToolset
tools are discovered and injected into a live request.
Converts Python objects (dataclasses, BaseModel, dicts, lists) into an XML string LLMs often
parse more reliably than JSON. root_tag=None produces rootless sibling elements;
include_field_info='once' includes title/description XML attributes only on a field’s first
occurrence in a list (saves tokens); indent=None removes all whitespace.
Represents one block of instruction text, tagged dynamic (from @agent.instructions, TemplateStr, or toolset get_instructions()) or static (from a literal string). Provider prompt-caching relies on this distinction: static parts are always cached, dynamic ones aren’t. InstructionPart.sorted() places static parts first (maximises cache-hit rate); .join() concatenates with a double newline.
Anthropic supports native mid-conversation system messages — instructions inserted into the conversation rather than at the initial system-prompt position, preserving the cached prefix while dynamically adjusting behaviour. Enqueue a SystemPromptPart with ctx.enqueue(...); on Anthropic it’s routed through the native system-in-conversation format (placement auto-adjusted to sit between a user turn and the model’s reply), and on other providers it falls back to a tagged user-channel message (<system>...</system>) — application code is identical either way.
from pydantic_ai import Agent, RunContext
from pydantic_ai.messages import SystemPromptPart
agent =Agent('anthropic:claude-opus-4-8',system_prompt='You are a senior code reviewer.')
When ToolSearch / DeferredLoadingToolset is active, the model issues a search query before
picking a tool. Two paths exist: native (Anthropic BM25/regex, OpenAI server-side) uses
NativeToolSearchCallPart/NativeToolSearchReturnPart; local (everyone else, or a custom
callable) uses ToolSearchCallPart/ToolSearchReturnPart. Cross-path detection: check
part.tool_kind == 'tool-search' (do not branch on tool_name, which differs:
'search_tools' local vs. 'tool_search' native).
The wire protocol for defer_loading=True capabilities — the model calls the hidden
load_capability tool before a deferred capability’s tools/instructions become visible.
Cross-path discriminator: tool_kind == 'capability-load'. See DeferredCapabilityLoader in
Capabilities & Extensibility for the loader mechanics that produce these parts.
pydantic_ai.ui.ag_ui._multimodal bridges pydantic-ai’s ImageUrl/AudioUrl/VideoUrl/DocumentUrl/BinaryContent with AG-UI’s typed input classes via two dispatch tables: _URL_TYPE_MAP (exact type → AG-UI class) for URL-based media, and _MEDIA_PREFIX_TO_CONTENT (media-type prefix → AG-UI class, default DocumentInputContent) for binary data. multimodal_input_to_content() round-trips an AG-UI part back to a pydantic-ai type.
AudioMediaType/ImageMediaType/DocumentMediaType/VideoMediaType (full MIME strings) and
their *Format shorthand siblings ('jpeg', 'mp3', 'pdf', …) are Literal type aliases used
throughout tool schemas and FileUrl subclasses. ForceDownloadMode = bool | Literal['allow-local']
(see FileUrl above). ProviderDetailsDelta = dict | Callable[[dict | None], dict] | None updates
a return part’s provider_details without wholesale replacement.
UsageBase fields (shared by RequestUsage per-request and RunUsage accumulated): input_tokens, cache_write_tokens, cache_read_tokens, output_tokens, input_audio_tokens, cache_audio_read_tokens, output_audio_tokens, details. RunUsage adds requests, tool_calls, and a top-level cost: Decimal | None field (best-effort USD cost summed across requests via genai-prices; None when the provider exposes no pricing, distinct from Decimal('0') for a genuinely free run). RunUsage.__add__/.incr() accumulate across runs; .opentelemetry_attributes() returns GenAI-semconv span attributes. UsageLimits fields: cost_limit: Decimal | None, request_limit: int | None = 50 (default is 50, not unlimited), tool_calls_limit, input_tokens_limit, output_tokens_limit, total_tokens_limit, per_request_input_tokens_limit (per-call cap independent of the cumulative input_tokens_limit — useful with prompt caching, where a large cached prefix still counts), count_tokens_before_request: bool = False (preflight token-count call before dispatch; enforces both token limits ahead of time on Anthropic/Google/Bedrock/OpenAI Responses). Note: response_tokens_limit seen in some old examples was never a real field name — the correct field is output_tokens_limit.
@dataclass(kw_only=True)
classUsageLimits:
cost_limit: Decimal |None=None
request_limit: int|None=50
tool_calls_limit: int|None=None
input_tokens_limit: int|None=None
output_tokens_limit: int|None=None
total_tokens_limit: int|None=None
per_request_input_tokens_limit: int|None=None
count_tokens_before_request: bool=False
@dataclass(kw_only=True)
classRunUsage(UsageBase):
requests: int=0
tool_calls: int=0
cost: Decimal |None=None
from pydantic_ai import Agent
from pydantic_ai.usage import UsageLimits
from pydantic_ai.exceptions import UsageLimitExceeded
RunUsage accumulates across an entire run; RequestUsage is a single API call’s usage and
implements genai_prices.types.AbstractUsage for cost calculation. Pass one RunUsage instance
into successive agent.run(usage=...) calls to keep a running session total:
Two layers: agent-level (Agent(max_concurrency=...), caps simultaneous runs; acquired at run start, released at run end) and model-level (limit_model_concurrency(model, limiter) / ConcurrencyLimitedModel(model, limiter=...), caps simultaneous HTTP requests to one model endpoint — the two compose, since a shared limiter can back both). ConcurrencyLimiter wraps anyio.CapacityLimiter; max_queued adds backpressure (ConcurrencyLimitExceeded for callers over the queue cap); waits emit an OTel span. AbstractConcurrencyLimiter is the ABC for distributed (e.g. Redis-backed) implementations. get_concurrency_context(limiter, source) returns a no-op context manager when limiter is None; normalize_to_limiter() coerces AnyConcurrencyLimit to AbstractConcurrencyLimiter | None.
The class documented in older material as ThreadExecutor is now UseThreadExecutor in
pydantic_ai.capabilities. Replaces PydanticAI’s default anyio.to_thread.run_sync-per-call
behaviour with a bounded ThreadPoolExecutor scoped to each run — prevents unbounded thread
creation under load. Agent.using_thread_executor() sets it class-wide for every run in context.
Hooks registers lifecycle observers via @hooks.on.<event> decorators (bare or parameterised with timeout=/tools=) instead of subclassing AbstractCapability — every hook can also be passed directly as a kwarg to the constructor (Hooks(before_model_request=fn, ...)). Covers 20+ hook points across four phases: run (before_run/after_run/run_error, or the run wrap-handler form for timing/circuit-breakers), node (before_node_run/after_node_run/node_run_error), model request (before_model_request/after_model_request/model_request_error, or model_request wrap-form), and tool (prepare_tools, before_tool_validate/after_tool_validate, before_tool_execute/after_tool_execute/tool_execute_error, or tool_execute wrap-form with tools=[...] scoping), plus the output-validate/process triads and deferred_tool_calls. A per-hook timeout (seconds) raises HookTimeoutError (a TimeoutError subclass) via anyio.fail_after. Sync hooks run inline on the event loop — use async for anything blocking.
classHookTimeoutError(TimeoutError):
hook_name: str; func_name: str; timeout: float
classHooks(AbstractCapability[AgentDepsT]):
@cached_property
defon(self) -> HookNamespace: ...
from pydantic_ai import Agent
from pydantic_ai.capabilities import Hooks, HookTimeoutError
Base class for every capability. Beyond the basic get_instructions/get_model_settings/
get_toolset/get_native_tools, it exposes defer_loading, get_description() (shown to the
model’s load_capability catalog), get_ordering() -> CapabilityOrdering, and three-form hooks
per lifecycle phase (before_*, after_*, wrap_* — the wrap_* forms receive a zero-arg
handler to call-or-skip for short-circuiting).
Declares where in the middleware chain a capability sits. position: Literal['outermost', 'innermost'] | None; wraps/wrapped_by: Sequence[CapabilityRef] for relative ordering;
requires: Sequence[type[AbstractCapability]] for presence checks (raises UserError if the
required capability type isn’t present) with no ordering implied. sort_capabilities() uses
graphlib.TopologicalSorter with original list order as tiebreaker and cycle detection.
collect_leaves() flattens nested capability trees via the visitor pattern; has_capability_type()
checks membership. CAPABILITY_TYPES is the name→class registry used by AgentSpec YAML
loading, populated via __init_subclass__.
Transparent delegation base for capability middleware — the capability analogue of
WrapperToolset/WrapperModel. __post_init__ inherits id/defer_loading from the wrapped
capability when not explicitly set, so a wrapper over a deferred capability stays deferred, and
for_run() recreates the wrapper around the post-for_run wrapped instance.
The composition engine Agent(capabilities=[...]) builds internally when given a list — flattens
nested combinations, topologically sorts by CapabilityOrdering, and always places the
auto-injected pending-message drainer outermost. Hook direction is forward for before_*/
prepare_*, reverse for after_*/on_*_error, and reverse-built-closure for wrap_* (standard
middleware onion); for_run() runs all children’s for_run() concurrently via gather() and
short-circuits (returns self) if none changed. has_wrap_node_run is a cached shortcut property
that lets the runtime skip the wrap-hook machinery entirely when no child capability defines one.
from pydantic_ai.capabilities import CombinedCapability, Hooks, Thinking, PrefixTools
Builds another capability per-run from a factory CapabilityFunc[AgentDepsT] — a callable
receiving RunContext and returning an AbstractCapability | None, sync or async. Bare callables
passed to capabilities=[...] are auto-wrapped in this. Returning None makes the wrapper a
no-op for that run; defer_loading=True is rejected on the DynamicCapability wrapper itself —
set it on the capability the factory returns instead.
Both fire before every model request to transform message history. ProcessHistory(processor)
runs an arbitrary HistoryProcessorFunc — four auto-detected calling conventions: sync/async ×
with/without RunContext — for truncation, PII redaction, or compaction; sync callables run
inline (not thread-offloaded). ReinjectSystemPrompt(replace_existing=False) ensures the agent’s
configured system prompt survives history that had it stripped (replace_existing=False is a
no-op if any system prompt is already present anywhere in history); replace_existing=True strips
any existing system prompt first, then prepends unconditionally — this is what
AGUIAdapter/VercelAIAdapter use under manage_system_prompt='server' to stop untrusted
clients injecting their own prompts. Neither capability is spec-serialisable (both hold a
callable); the deprecated alias HistoryProcessor still works but warns.
The capability-shaped replacement for Agent(instrument=...). Always positioned 'outermost'
(get_ordering()), so its spans wrap every other capability’s. InstrumentationSettings.version
(1–5) selects the OTel GenAI semantic-convention version; v1 is legacy/deprecated, v5 additionally
stops classifying CallDeferred/ApprovalRequired as span errors.
InstrumentedModel wraps a single Model with OTel instrumentation without touching Agent — the
lower-level building block Instrumentation uses internally: InstrumentedModel(wrapped: Model, options: InstrumentationSettings).
Instrumentation internals (pydantic_ai._instrumentation) shared with InstrumentedModel: baggage
keys AGENT_NAME_BAGGAGE_KEY/RUN_ID_BAGGAGE_KEY/CONVERSATION_ID_BAGGAGE_KEY propagate agent
identity across service boundaries; TOKEN_HISTOGRAM_BOUNDARIES (14 boundaries, 1 to 67M tokens)
configure the gen_ai.client.token.usage metric; DEFAULT_INSTRUMENTATION_VERSION selects the
GenAI semconv version. CostCalculationFailedWarning (raised when genai-prices can’t price a
model) lives in pydantic_ai.exceptions, not _instrumentation.
from pydantic_ai._instrumentation importAGENT_NAME_BAGGAGE_KEY, TOKEN_HISTOGRAM_BOUNDARIES, DEFAULT_INSTRUMENTATION_VERSION
from pydantic_ai.exceptions import CostCalculationFailedWarning # current location
Wraps a ToolsPrepareFunc — (RunContext, list[ToolDefinition]) -> list[ToolDefinition] — as a capability that filters/mutates function tools (PrepareTools) or output tools (PrepareOutputTools, whose ctx.retry/ctx.max_retries reflect the output retry budget) on every request. Replaces the older pattern of passing prepare= directly to FunctionToolset when the filter must apply across every toolset on the agent. Cannot add or rename tools (raises UserError); neither is spec-serialisable.
Merges **metadata kwargs into the metadata dict of tools matched by tools: ToolSelector ('all', a name/list of names, or a sync/async predicate). Internally wraps the toolset in a PreparedToolset that overrides get_tools. Multiple instances stack additively per tool — most commonly used to flip on Code Mode (code_mode=True) or tag tools for provider cache control / OTel attributes.
Raise from a tool function, output validator, or capability hook to send a retry prompt back to
the model instead of propagating a Python exception. Fully Pydantic-serialisable (used internally
for durable execution).
ModelRetry(message: str)
from pydantic_ai import Agent, RunContext, ModelRetry
Bundles instructions, tools, and toolsets under one identity without subclassing AbstractCapability. Three decorators mirror the Agent API: @cap.tool (receives RunContext), @cap.tool_plain (no context), @cap.instructions (system-prompt function, sync or async). defer_loading=True hides the whole capability (instructions + tools) until the model calls load_capability.
NativeTool(tool) registers a single provider-native tool (static instance or per-run callable).
NativeOrLocalTool is the architectural base every adaptive capability (WebSearch, WebFetch,
ImageGeneration, XSearch, MCP) is built on: pairs a provider-native tool with an optional
local fallback function, keeping only whichever the active model supports. native=True uses the
subclass’s _default_native(); local accepts a strategy name, Tool, callable, AbstractToolset,
or bool (per-subclass). When both are enabled, get_toolset() wraps the local toolset in a
PreparedToolset that stamps unless_native=<uid> on every local ToolDefinition, so capable
models never see the fallback tools at all. _requires_native() returning True suppresses local
entirely (e.g. domain-constraint fields that only the native tool enforces).
NativeOrLocalTool subclass — native-first web search with an optional DuckDuckGo (or custom) local fallback. local (WebSearchLocalStrategy='duckduckgo' | Tool | Callable | bool | None) requires the duckduckgo extra when set to 'duckduckgo'/True. blocked_domains/allowed_domains/max_uses require native support and auto-force it via _requires_native().
local=True activates the SSRF-protected, markdownify-based local fetcher from pydantic_ai.common_tools.web_fetch (requires pip install "pydantic-ai-slim[web-fetch]"); allowed_domains/blocked_domains are enforced by the local tool too, while max_uses, enable_citations, max_content_tokens require native.
Capability-level fields (action, background, input_fidelity, moderation, image_model, output_compression, output_format, quality, size, aspect_ratio) bridge onto the native tool’s constructor, whose corresponding field is named model, not image_model. Correction:local no longer accepts a bare True; its type is Tool | Callable | Literal[False] | None — pass an explicit Tool/callable or leave it None, and rely on fallback_model for cross-provider delegation (which cannot be combined with an explicit local= — UserError if both are set). ImageGenerationSubagentTool implements the fallback: it builds Agent(fallback_model, output_type=BinaryImage, capabilities=[NativeTool(...)]) at call time and wraps UnexpectedModelBehavior as ModelRetry.
X/Twitter search — native on xAI models; on any other model fallback_model (must be an xAI model) is required, or the capability raises UserError. allowed_x_handles/excluded_x_handles (max 20 each), from_date/to_date, enable_image_understanding/enable_video_understanding, include_output (exposes raw results as NativeToolReturnPart). Like ImageGeneration, local is Tool | Callable | Literal[False] | None — no bare True.
Single-field convenience capability translating a unified effort into ModelSettings.thinking across providers; provider-specific settings (anthropic_thinking, openai_reasoning_effort, etc.) take precedence when both are set. effort=False is silently ignored on always-on reasoning models.
The recommended capability-first way to attach an MCP server, extending NativeOrLocalTool: accepts url, native (bool or explicit MCPServerTool/callable — requires url= when True, and defaults to False, not auto-detect), local (URL string, fastmcp.Client, transport, in-process FastMCP server, or pre-built MCPToolset — any other non-bool/non-string value is auto-wrapped into an MCPToolset), authorization_token, headers, allowed_tools. MCP.from_spec() restricts local= to JSON/YAML-serialisable types for AgentSpec round-tripping.
Lazy tool discovery for large toolsets. strategy accepts None (auto: native BM25 on
Anthropic/OpenAI-Responses, local keyword elsewhere), 'bm25'/'regex' (Anthropic-only, error
elsewhere), 'keywords' (force the local algorithm everywhere for determinism), or a custom
ToolSearchFunc. The internal ToolSearchTool (an AbstractNativeTool the capability injects)
is never constructed directly, and is excluded from TestModel.supported_native_tools() (so
tests always fall back to the local search_tools function tool). See ToolSearch +
ToolSearchToolset in Tools & Toolsets for the toolset-level mechanics.
Intercepts DeferredToolRequests that would otherwise pause the run and resolves them inline via a user handler — converting a HITL approval flow into an automated one. handler(ctx, requests) -> DeferredToolResults | None; returning None declines (falls through to the next HandleDeferredToolCalls capability, or bubbles up as the run’s output if none handle it). Stack multiple instances for tiered strategies (e.g. auto-approve low-risk tools, defer everything else to a human).
Internal WrapperToolset stamping every contributed ToolDefinition with the owning Capability’s id. When capability.defer_loading=True it also marks tools with the deferred-capability metadata key and suppresses get_instructions() until the capability is explicitly loaded — the plumbing that makes deferred capabilities work (the model sees the description in the catalog but can’t call the tools until it calls load_capability). resolve_capability_id() walks ctx.capabilities by identity; tool_defs_for_loaded_capabilities() is the wire-side filter ToolSearchToolset uses.
DeferredCapabilityLoader produces the catalog instructing the model which deferred capabilities exist — deliberately re-listing every deferred capability on every turn (including already-loaded ones), because instructions sit at the request prefix and mutating that prefix would bust the provider’s prompt cache. DeferredCapabilityLoaderToolset auto-injects the reserved load_capability tool (tool_kind='capability-load'); calling it resolves the capability from ctx.capabilities, returns its instructions, and raises ModelRetry if the model tries to reload an already-loaded capability.
classLoadCapabilityArgs(TypedDict):
id: str
classLoadCapabilityReturn(TypedDict):
instructions: NotRequired[str]
from pydantic_ai._deferred_capabilities import LoadCapabilityCallPart
# Filter capability-load parts out of message history when replaying a conversation
defstrip_capability_loads(messages):
import dataclasses
return[
dataclasses.replace(m,parts=[p for p in m.parts ifnotisinstance(p, LoadCapabilityCallPart)])
Module:pydantic_ai._spec. Powers YAML/JSON-driven capability composition for AgentSpec.
NamedSpec accepts three compact forms (bare name string, single-arg dict, kwargs dict).
build_registry builds a name→class map; load_from_registry instantiates from a spec, with
legacy_aliases support for renamed classes.
A lightweight AbstractCapability that injects any pre-built AgentToolset via the capabilities list rather than the toolsets= constructor arg — useful for programmatic capability-chain composition (e.g. combined with PrefixTools). Not spec-serialisable (get_serialization_name() returns None).
@dataclass
classToolset(AbstractCapability[AgentDepsT]):
toolset: AgentToolset[AgentDepsT]
from pydantic_ai import Agent
from pydantic_ai.capabilities.toolset import Toolset
from pydantic_ai.toolsets.function import FunctionToolset
weather_toolset =FunctionToolset()
weather_toolset.add_function(lambdacity: f'Sunny in {city}',name='get_weather')
Called by FunctionToolset/Tool during registration to extract the function description and per-parameter descriptions using griffe. Supports 'google', 'numpy', 'sphinx', and 'auto' (regex-based inference: Sphinx :param:, Google Args: block, NumPy --- underline, falling back to 'google'). When a Returns section exists, the description is reformatted as <summary>/<returns> XML for richer schema descriptions.
Requires the respective optional dependency group (temporal, dbos, prefect) — not installed in the verification venv, so only import paths and top-level structure were re-confirmed; per-class field details below should be spot-checked against the exact pinned version in production.
Extra:pip install "pydantic-ai[temporal]". Wraps any Agent/WrapperAgent so model calls, tool calls, and MCP interactions become durable Temporal activities. TemporalModel is the WrapperModel placed inside every TemporalAgent: inside a workflow it serialises the request into a _RequestParams dataclass and dispatches via workflow.execute_activity(...); outside a workflow it falls through directly. Supports a models={id: Model} registry plus using_model(id) context manager for per-step overrides, and a TemporalProviderFactory callable ((RunContext, provider_name) -> Provider) for dynamic per-tenant credentials on unregistered model IDs. Image output is rejected (UserError) due to Temporal’s 2MB payload limit. PydanticAIWorkflow is a marker base class exposing __pydantic_ai_agents__ so TemporalAgent.activities/.temporal_activities can be enumerated automatically.
TemporalRunContext serialises only the JSON-safe subset of RunContext across the activity
boundary (excludes the live capabilities registry); accessing an excluded attribute inside an
activity raises UserError with a subclassing hint.
from pydantic_ai import Agent
from pydantic_ai.durable_exec.temporal import TemporalAgent
base_agent =Agent('openai:gpt-4.1-mini',name='research-agent') # name= is required
TemporalWrapperToolset is the abstract base turning call_tool() into @activity.defn functions. CallToolResult is a discriminated union (kind field) of _ApprovalRequired, _CallDeferred, _ModelRetry, _ToolReturn — serialising every possible tool-call outcome across the activity boundary. Activity config is layered: activity_config (base) → toolset_activity_config (per toolset ID) → tool_activity_config (per tool name, or False to skip activity wrapping for fast, in-memory async tools). TemporalMCPToolset wraps an MCPToolset so get_tools/call_tool run as activities; when the wrapped MCPToolset.cache_tools=True, tool definitions from the first get_tools activity are cached on the TemporalMCPToolset instance for the worker process’s lifetime (not per-workflow) — use cache_tools=False if the server’s tool list changes between runs.
A temporalio.plugin.SimplePlugin wiring Logfire into a Temporal ServiceClient: installs a TracingInterceptor for workflow/activity spans, and optionally an OpenTelemetryConfig metrics exporter to Logfire’s OTLP endpoint. Must be combined with PydanticAIPlugin() (which registers the Pydantic data converter) — using LogfirePlugin alone breaks payload serialisation.
Wraps any AbstractAgent with DBOS durable-step semantics via @DBOS.dbos_class() + DBOSConfiguredInstance. Model requests and MCP calls are auto-wrapped as @DBOS.step(); FunctionToolset tool functions are not — decorate side-effecting tools with @DBOS.step() yourself for checkpoint/replay protection. DBOSParallelExecutionMode excludes 'parallel' (only 'sequential' and 'parallel_ordered_events') because DBOS needs deterministic replay ordering. DBOSModel applies its @DBOS.step() decorator once at __init__, not per-call; the DBOS.workflow_id is None or DBOS.step_id is not None guard in request_stream lets nested calls (inside an existing step, or outside a workflow) bypass the step wrapper and avoid double-wrapping. Automatically swaps MCPToolset → DBOSMCPToolset and similar wrapping for toolsets you pass in.
Extra:pip install "pydantic-ai-slim[prefect]". Wraps any AbstractAgent to run model requests, tool calls, and MCP interactions as Prefect tasks with automatic retries and caching. name is required. PrefectModel turns request()/request_stream() into @task-decorated functions created once at __init__, named dynamically per call via with_options(name=...); request_stream requires an event_stream_handler — without one, PrefectModel raises because streaming needs a run_context. PrefectFunctionToolset (a PrefectWrapperToolset subclass) does the same for tool calls; a per-tool config entry of None skips task wrapping entirely (plain async call, no Prefect overhead). PrefectAgentInputs is a custom Prefect CachePolicy that strips non-deterministic RunContext fields (timestamp, run_id) and converts ToolsetTool/RunContext instances into hashable dicts before computing the cache key — plain Prefect INPUTS caching breaks on both. DEFAULT_PYDANTIC_AI_CACHE_POLICY = PrefectAgentInputs() + TASK_SOURCE + RUN_ID, giving persist_result=True with a RUN_ID-scoped policy so a persisted result is reused across a flow retry but not across unrelated flow runs.
Durable engines wrap constructor-time toolsets so tool calls become checkpointed activities/tasks; toolsets passed per-run via run(toolsets=...) arrive after that wrapping and are un-checkpointed. This guard classifies each leaf toolset ('function', 'mcp', 'dynamic', or None for non-executing toolsets like ExternalToolset) and raises UserError when an engine-unsupported kind is passed per-run.
The A2A (Agent-to-Agent protocol) bridge, already marked deprecated in older releases, is
confirmed fully removed — no _a2a.py or any A2A-related module remains under pydantic_ai/.
The fasta2a package now maintains its own PydanticAI integration independently:
pip install "fasta2a[pydantic-ai]" then from fasta2a.pydantic_ai import agent_to_a2a.
The current, recommended API for building pydantic_graph graphs — a fluent, type-safe builder
replacing the older BaseNode-subclass pattern (which remains fully supported and interoperable).
Path is a flat list[PathItem] encoding transforms, forks, and routing in order; PathBuilder is the fluent wrapper. .to(dest, ...) routes to one or more destinations (wraps multiple in a BroadcastMarker); .transform(func) applies a sync step function, changing the output type; .map() spreads an iterable into parallel per-item paths (creates a MapMarker); .label() attaches a debug annotation. GraphBuilder.add_edge(node) returns an EdgePathBuilder, the entry point in practice.
Every Path is a list of these dataclasses; PathItem is their union. MapMarker(fork_id, downstream_join_id) spreads an iterable into parallel forks; BroadcastMarker(paths, fork_id) fans out to pre-built sub-paths; DestinationMarker(destination_id) is the terminal routing target.
EdgePath is a complete edge: source nodes bound to a Path, with destinations collected. EdgePathBuilder (returned by GraphBuilder.add_edge()) chains .map()/.transform()/.label()/.broadcast() before finalising with .to(destination).
Parallel fan-out/fan-in. Fork.is_map=True maps one branch per sequence element (is_map=False broadcasts the same value to every branch). Join aggregates via a reducer ((current, item) -> result, optionally (ctx: ReducerContext, current, item) -> result); JoinState tracks pending parallel branches per fork. ReducerContext.cancel_sibling_tasks() implements first-match-wins early stopping (sets JoinState.cancelled_sibling_tasks=True); preferred_parent_fork='farthest'/'closest' disambiguates nested-fork topology.
Conditional routing via builder.decision().branch(builder.match(Literal['urgent']).to(handler))
— match() requires types (or Literal[...]), not raw values; builder.match('urgent')
raises at runtime. Edge (.label(text) on an edge-path builder) annotates Mermaid diagram output.
TypeExpression[T] works around type-checker limitations for complex union types passed as
state_type=/output_type= generic parameters.
The primitives @builder.step produces. StepContext(state, deps, inputs) is what every step
function receives. Step.as_node(inputs) bridges a builder step into a legacy BaseNode runner —
not a “goto” mechanism inside a step body; dynamic branching goes through builder.decision().
Type guards classify every node as source (MiddleNode | StartNode), destination (MiddleNode | Decision | EndNode), or both. ParentForkFinder.find_parent_fork(join_id, *, parent_fork_id=None) finds the dominating fork of a join node — the fork every path to that join must pass through — the primitive the runtime uses to avoid deadlock in parallel execution; pass parent_fork_id explicitly to disambiguate nested-fork diamonds.
Three built-in BaseStatePersistence implementations. FileStatePersistence(json_file) persists
snapshots to JSON with an advisory .pydantic-graph-persistence-lock file, surviving process
restarts (graph.iter_from_persistence(persistence) resumes an interrupted run).
SimpleStatePersistence (the run-time default when no persistence= is passed) keeps only the
latest snapshot — load_all() raises NotImplementedError. FullStatePersistence keeps the whole
history and supports dump_json()/load_json() round-trips; deep_copy=False skips the
defensive per-snapshot copy for a performance win when you don’t need historical state values.
FileStatePersistence(json_file: Path)
FullStatePersistence(deep_copy: bool=True)
from pydantic_graph.persistence.file import FileStatePersistence
NodeSnapshot/EndSnapshot make up the Snapshot discriminated union every persistence backend
stores. SnapshotStatus lifecycle: 'created' → 'pending' (load_next) → 'running' (record_run) → 'success'/'error'. BaseStatePersistence is the ABC custom backends (Redis, DynamoDB) implement —
requiring all six abstract methods: should_set_types, set_types, snapshot_node,
snapshot_node_if_new, snapshot_end (receives the End value), record_run (async context
manager), plus load_next/load_all. build_snapshot_list_type_adapter(state_type, run_end_type)
builds the typed serialiser.
GraphSetupError(TypeError) # misconfigured graph
GraphBuildingError(ValueError) # error during GraphBuilder.build()
Low-level primitives driving pydantic_graph’s parallel execution engine (same engine powering Agent.iter()). GraphTaskRequest(node_id, inputs, fork_stack) is a unit of work on the internal task queue. JoinItem(join_id, inputs, fork_stack) is emitted when a parallel branch completes and needs to merge at a Join; the runtime accumulates them until all expected branches arrive. EndMarker is the internal completion signal, converted to pydantic_graph.End before yielding — check isinstance(node, End) in iteration loops, not EndMarker.
Module:pydantic_ai.run. GraphRun is the execution-state manager agent.iter() builds
internally — task scheduling, fork/join coordination (_active_reducers), terminal-End result
tracking. NodeStep bridges any v1 BaseNode (like UserPromptNode/ModelRequestNode/
CallToolsNode) into the v2 execution system. For direct graph usage without an Agent, use
pydantic_graph.Graph.run()/.iter() — GraphRun/NodeStep are exposed for introspection, not as
a primary API.
Deterministic fake model. By default calls every function tool, then returns either an output
tool call or a JSON summary of tool results. TestModel.__test__ = False keeps pytest from
collecting it as a test class.
Execution order: call all tools (or re-call failing ones on retry) → custom_output_text if set →
custom_output_args if set → JSON summary if allow_text_output → else call
output_tools[seed % len(output_tools)].
from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
agent =Agent('test')
@agent.tool_plain
defadd(a: int, b: int) -> int:
return a + b
model =TestModel(custom_output_text='The answer is 42',seed=3)
result = agent.run_sync('Any question',model=model)
Replaces the LLM with a plain Python function (messages, agent_info) -> ModelResponse (or an
async generator for stream_function=). Auto-injects a permissive default profile
(supports_json_schema_output=True, supports_json_object_output=True) so structured output
works without extra setup. Constructor accepts profile/settings overrides so tests can simulate
a specific provider’s capability profile.
The core evaluator API. EvaluatorContext exposes output, expected_output, duration,
metrics/attributes (populated by increment_eval_metric/set_eval_attribute called inside
the task), and .span_tree for OTel-based structural assertions.
Span-based agentic evaluators (pydantic_evals.evaluators) reading ctx.span_tree — populated only when a tracer provider is registered (logfire.configure(send_to_logfire=False) once per process, plus capabilities=[Instrumentation()] on the agent under test) and degrading to zero scores otherwise. ToolCorrectness(expected_tools, allow_extra=False, include_failed=False) compares the multiset of tool names called — order irrelevant, duplicates require repeated calls. TrajectoryMatch(expected_trajectory, order='in_order') enforces ordered sequences: 'exact' (binary pass/fail), 'in_order' (LCS-based F1, default), 'any_order' (multiset F1).
ArgumentCorrectness(tool_name, expected_arguments, match_mode='subset', occurrence='first') verifies the exact arguments of a specific tool invocation; match_mode='exact' requires all keys to match, 'subset' (default) only requires the expected keys/values to be present. occurrence selects which call to inspect: 'first', 'last', or a 0-based int index. MaxToolCalls(max_calls, include_failed=True) and MaxModelRequests(max_requests) enforce budget caps as pass/fail evaluators — note MaxToolCalls.include_failed defaults True (opposite of ToolCorrectness).
ArgumentMatchMode = Literal['subset', 'exact']
@dataclass(frozen=True)
classArgumentCorrectness(Evaluator):
tool_name: str
expected_arguments: dict[str, Any]
match_mode: ArgumentMatchMode ='subset'
occurrence: Literal['first', 'last'] |int='first'
from pydantic_evals.evaluators import ArgumentCorrectness, MaxToolCalls, MaxModelRequests
GEval(criteria, evaluation_steps, score_range=(1, 5), include_input=False, model=None) implements a simplified G-Eval chain-of-thought judge: an LLM judge scores against explicit criteria + evaluation_steps using a direct integer score (rather than the original paper’s log-prob expectation) for provider-agnostic simplicity. HasMatchingSpan(query: SpanQuery) passes when at least one span in the captured tree matches (delegates to SpanQuery.any()). OutputConfig is the shared wire TypedDict configuring judge model/output format for both LLMJudge and GEval.
@dataclass(repr=False)
classGEval(Evaluator):
criteria: str
evaluation_steps: list[str]
score_range: tuple[int, int] = (1, 5)
model: Model | KnownModelName |str|None=None
from pydantic_evals.evaluators import GEval
coherence =GEval(
criteria='Rate the coherence of the poem to the given topic.',
evaluation_steps=['Read the poem.', 'Check line-to-line logic.', 'Score 1-5.'],
LLM-as-judge evaluation. GradingOutput(reason, pass_, score). Four standalone functions cover
input/output/expected combinations; set_default_judge_model picks the model used when LLMJudge
is constructed without an explicit one.
Per-case evaluation lifecycle hooks (pydantic_evals.lifecycle); a fresh instance is created per case so subclasses hold per-case state safely. setup() runs before the task (resource allocation), prepare_context(ctx) runs after the task but before evaluators (enrich EvaluatorContext.metrics/attributes, must return the context), teardown(result) always runs after evaluators — even if setup/prepare_context raised (recorded as ReportCaseFailure) — but if teardown itself raises, that exception propagates and can abort the whole evaluation run.
Attaches evaluators that fire asynchronously in the background after each run completes, wrapping run()/run_stream()/iter() without blocking the caller (streaming runs dispatch only after the context manager exits). Evaluator instances are auto-wrapped in OnlineEvaluator with default sampling; results emit as OTel gen_ai.evaluation.result log events, fannable to a custom EvaluationSink via OnlineEvalConfig(default_sample_rate=..., default_sink=...). disable_evaluation() context manager suppresses evaluation (e.g. inside deterministic tests); wait_for_evaluations(timeout=...) blocks until background evaluations finish. run_on_errors: bool controls whether failed runs are still evaluated.
The evaluate decorator applies the same Evaluator classes to live production traffic outside an
Agent context, emitting the same gen_ai.evaluation.result OTel log events:
Structural OTel span inspection inside an evaluator, via ctx.span_tree. SpanQuery is a
TypedDict supporting name_contains/has_attribute_keys/min_duration/logical combinators
(and_/or_/not_)/child- and descendant-count predicates.
ModelHTTPError.status_code lets you branch on 429/5xx for retry-with-backoff vs. re-raise.
UndrainedPendingMessagesError fires when a bare async for node in agent.iter(...) loop ends
with 'when_idle'-priority ctx.enqueue() messages never drained — use agent.run() or
AgentRun.next() instead, both of which drain every priority.
from pydantic_ai import Agent
from pydantic_ai.exceptions import ModelHTTPError, ContentFilterError, UsageLimitExceeded
Module:pydantic_ai.retries. Extra:pip install "pydantic-ai-slim[retries]". Wraps any
httpx transport with tenacity-based retry, including honouring Retry-After response headers
(seconds or HTTP-date format). RetryConfig is a TypedDict mirroring the tenacity @retry
decorator kwargs (stop, wait, retry, before_sleep, reraise, …). wait_retry_after( fallback_strategy=None, max_wait=300) is a wait-strategy factory reading the HTTP Retry-After
header before falling back to the given strategy.
All pydantic-ai deprecations are raised as PydanticAIDeprecationWarning(UserWarning) rather than DeprecationWarning, so they’re visible by default at runtime (Python only shows plain DeprecationWarning in __main__/test runners). Notable renamed/moved symbols this surfaces: ThreadExecutor → UseThreadExecutor, CompletedStreamedResponse moved from pydantic_ai.models.wrapper to pydantic_ai.models.
Human-in-the-loop tool gating. Wraps a toolset and raises ApprovalRequired before executing a call unless ctx.tool_call_approved is True or approval_required_func(ctx, tool_def, tool_args) returns False for that call (default: every call requires approval). You can also raise ApprovalRequired (optionally with metadata=) directly inside a tool body. Either way this suspends the run — with DeferredToolRequests in output_type, the agent returns that value instead of raising; the caller inspects .approvals, builds DeferredToolResults via .build_results(approve_all=True) or per-call ToolApproved(override_args=None)/ToolDenied(message=...), and resumes with bothdeferred_tool_results= and message_history=result1.all_messages() (the graph needs prior history to locate the pending tool call — omitting it raises UserError). Approved calls carry through ToolApproved(metadata=...) into ctx.tool_call_metadata.
The general async-execution counterpart to approval: CallDeferred(metadata=...) suspends a tool
call for an external system to resolve later (a webhook, a Temporal workflow, a queue worker).
ExternalToolset (see Tools & Toolsets) is the toolset-level building block for this pattern —
tools registered there are always deferred (kind='external'), resolved via .calls and
build_results(calls={call_id: result_value}). HandleDeferredToolCalls (see Capabilities &
Extensibility) resolves either flow inline instead of requiring a manual resume loop.
Module:pydantic_ai._ssrf. The internal function backing WebFetch’s local fallback and
web_fetch_tool — multi-layered SSRF defence: protocol allowlist (http/https only), DNS
resolution off the event loop, a hard-coded cloud-metadata-IP blocklist (always blocked, even
with allow_local=True), 14 IPv4 + 7 IPv6 private ranges (including decoded 6to4/NAT64/ISATAP/
Teredo transition forms), per-redirect-hop re-validation (no DNS-rebinding bypass), and stripping
Authorization/Cookie/Proxy-Authorization on cross-origin redirects.
The local fallback path for the WebFetch capability (and the standalone web_fetch_tool())
routes every fetch through this same function: allow_local_urls=False by default blocks
internal/loopback addresses, and allowed_domains/blocked_domains are enforced by the local tool
itself (not just the native provider tool), so an allow-list stays effective even on models
without native WebFetchTool support.
Module:pydantic_ai.ui. Abstract base every frontend protocol adapter (AGUIAdapter,
VercelAIAdapter) extends. Owns the request-security policy: manage_system_prompt: Literal['server', 'client'] (default 'server' strips client-sent system prompts and auto-adds
ReinjectSystemPrompt); allowed_file_url_schemes (default {'http','https'} only — widen only
after auditing IAM exposure for s3:///gs://); allowed_file_url_force_download;
preserve_file_data (uploaded-file round-trip fidelity).
Implements the AG-UI protocol: converts RunAgentInput’s Messages to ModelMessages and streams BaseEvents back. ag_ui_version gates event shape: < 0.1.13 emits THINKING_* events; ≥ 0.1.13 emits REASONING_* with round-trippable encrypted metadata; ≥ 0.1.15 emits typed multimodal input content instead of a generic binary blob. AG-UI tools declared in the request are exposed via _AGUIFrontendToolset (an ExternalToolset subclass). When ag-ui-protocol >= 0.1.19 (HAS_INTERRUPTS), approval_to_interrupt(call, metadata) converts a pending ToolCallPart into an Interrupt for the frontend (with a response_schema describing {approved, editedArgs?, reason?}), and resume_entry_to_approval(entry) converts the client’s ResumeEntry back into ToolApproved/ToolDenied — denying by default on any ambiguous payload (status='cancelled', missing payload, or approved not exactly True). The deprecated handle_ag_ui_request helper and the AGUIApp Starlette wrapper are both superseded by AGUIAdapter.dispatch_request(request, agent=agent).
A UIAdapter subclass speaking the Vercel AI SDK Data Stream Protocol. Parses inbound RequestData (useChat/useCompletion bodies) and emits StartChunk → TextStartChunk/TextDeltaChunk/TextEndChunk → ToolInput*Chunk/ToolOutput*Chunk → FinishChunk → DoneChunk. sdk_version: Literal[5, 6] = 5 — v6 additionally streams ToolApprovalRequestChunks so a frontend can render HITL approval prompts for an ApprovalRequiredToolset. load_messages()/dump_messages() round-trip UIMessages to ModelMessages for storage.
Module:pydantic_ai.ui._web.api. Backend for Agent.to_web() (see Agents & Execution Core) — a Starlette app with POST /chat, OPTIONS /chat, GET /configure, GET /health. models= accepts a sequence or a {label: model} mapping (mapping keys become the picker’s display labels). All response models serialise with alias_generator=to_camel (builtinTools, not builtin_tools). ModelInfo/BuiltinToolInfo (id, name) populate ConfigureFrontend.models/builtin_tools, served at GET /configure; ChatRequestExtra (model, builtin_tools) carries the frontend’s per-request model/tool selection to POST /chat.
Fully removed from pydantic_ai; use fasta2a.pydantic_ai.agent_to_a2a from the
independently-maintained fasta2a package instead. Documented once, in the Durable Execution &
Integrations section, to avoid duplication.
Version bumped 2.31.0 → 2.33.0; Latest: header and **Version:** prose updated throughout, including inline 2.31.0 references. Added a new Class & API Reference section (16 subsections: Agents & Execution Core, Models & Providers, Tools & Toolsets, Native/Built-in Tools, Streaming & Events, Structured Output, Messages & Multimodal Content, Concurrency/Usage & Limits, Hooks/Middleware & Lifecycle, Capabilities & Extensibility, Durable Execution & Integrations, Persistence & Graph Support, Testing & Evaluation, Error Handling & Retries, Security, UI/A2A/Adapters) consolidating the 44 separate pydantic_ai_class_deep_dives*.md / pydantic_ai_advanced_classes_part2.md / pydantic_ai_source_code_deep_dive.md volumes, verified against installed pydantic-ai 2.33.0; those 44 files were deleted and index.mdx updated to match.
Claude routine
1.107.0
June 21, 2026
Version bumped 1.104.0 → 1.107.0 (three minor releases: 1.105.0, 1.106.0, 1.107.0). New features documented: RunContext additions (capabilities, loaded_capability_ids, discovered_tool_names, model_settings, metadata, tool_call_metadata); AgentSpec YAML/JSON agent configuration; TemplateStr Handlebars system prompts; DeferredToolRequests/CallDeferred async human-in-the-loop; SkipModelRequest/SkipToolExecution/SkipToolValidation hook short-circuits; ConcurrencyLimiter observability enhancements. New Vol. 22 class deep dives added covering 10 class groups verified against installed pydantic-ai 1.107.0. All top-level exports confirmed; no DeprecationWarnings.
Claude routine
1.104.0
May 29, 2026
Version bumped 1.102.0 → 1.104.0 (two minor releases: 1.103.0, 1.104.0); Latest: header and **Version:** prose updated; revision history entry added. All core guide symbols verified with -W error::DeprecationWarning against installed pydantic-ai==1.104.0 (.routine-envs/check-0529-pydantic); all PASS. 178 top-level exports confirmed.
Claude routine
1.102.0
May 23, 2026
Version bumped 1.101.0 → 1.102.0; Latest: header and **Version:** prose updated; revision history entry added. All core guide symbols verified with -W error::DeprecationWarning against installed pydantic-ai==1.102.0 (.routine-envs/check-0523-pydantic); all PASS. 179 top-level exports confirmed; API surface unchanged from 1.101.0.
Claude routine
1.101.0
May 22, 2026
Version bumped 1.99.0 → 1.101.0 (two minor releases: 1.100.0, 1.101.0); Latest: header and **Version:** prose updated; Installed comments in snippets updated; revision history entry added. All core guide symbols (Agent, RunContext, ModelRetry, AgentRunResult, StreamedRunResult, UsageLimits, RunUsage, capture_run_messages, limit_model_concurrency, ConcurrencyLimiter) verified with -W error::DeprecationWarning against installed pydantic-ai==1.101.0 (.routine-envs/check-0522-pydantic); all PASS.
Claude routine
1.99.0
May 20, 2026
Version bumped 1.98.0 → 1.99.0; Latest: header and **Version:** prose updated; revision history entry added. All core guide symbols (Agent, RunContext, ModelRetry, AgentRunResult, StreamedRunResult, UsageLimits, RunUsage, capture_run_messages, limit_model_concurrency, ConcurrencyLimiter) verified with -W error::DeprecationWarning against installed pydantic-ai==1.99.0 (.routine-envs/check-0520-pydantic); all PASS.
Claude routine
1.98.0
May 19, 2026
Two minor releases (1.97.0, 1.98.0). pydantic_ai.ag_ui module deprecated in 1.98.x — emits PydanticAIDeprecationWarning; new canonical path is pydantic_ai.ui.ag_ui.AGUIAdapter. AG UI section in this guide updated with deprecation note and migration path. New pydantic_ai.common_tools module (DuckDuckGo, Exa, Tavily, WebFetch, ImageGeneration providers); requires optional extras. All core guide symbols verified against installed pydantic-ai 1.98.0 (.routine-envs/check-0519-py); no DeprecationWarning emissions on standard imports.
Claude routine
1.96.0
May 14, 2026
Minor release; new concurrency management API: ConcurrencyLimiter(max_running, max_queued=None) and limit_model_concurrency(model, limiter). All guide-referenced symbols verified against installed pydantic-ai 1.96.0 (.routine-envs/check-0514-py); no DeprecationWarning emissions.
1.95.0
May 13, 2026
Minor release; all guide-referenced symbols (Agent, RunContext, ModelRetry, AgentRunResult, StreamedRunResult, UsageLimits, RunUsage, capture_run_messages) verified with -W error::DeprecationWarning against installed pydantic-ai 1.95.0 (.routine-envs/check-0513-py); no warnings. Additional exports AgentRunResultEvent, AgentEventStream confirmed in installed source.
1.94.0
May 12, 2026
Minor release; new top-level exports: AgentRun, AgentRunResult, StreamedRunResultSync. All guide-referenced symbols (Agent, RunContext, ModelRetry, AgentRunResult, StreamedRunResult, UsageLimits, RunUsage, capture_run_messages) verified with -W error::DeprecationWarning against installed pydantic-ai 1.94.0 (.routine-envs/check-0512-py); no warnings.
1.93.0
May 9, 2026
Three minor releases (1.91.0, 1.92.0, 1.93.0). Breaking change: TestModel removed from pydantic_ai top-level — correct path is from pydantic_ai.models.test import TestModel (all guide pages already use this path). New top-level exports confirmed: AgentSpec, UploadedFile, WebSearchUserLocation, DeferredLoadingToolset. All existing symbols confirmed present in installed 1.93.0 (.routine-envs/check-0509-py) with no DeprecationWarnings.
1.90.0
May 5, 2026
Patch release; DeferredToolCalls in pydantic_ai.output marked @deprecated — use DeferredToolRequests (guides already use the correct API). Version confirmed against installed pydantic-ai 1.90.0 (.routine-envs/check-0505); Agent (TestModel), FunctionToolset, DeferredToolRequests, HandleDeferredToolCalls, ImageGenerationTool, MemoryTool, XSearchTool, RenamedToolset, WrapperToolset all import successfully with no DeprecationWarnings.
1.89.1
May 2, 2026
Patch release; maintenance and dependency updates. Version confirmed against installed pydantic-ai 1.89.1 (.routine-envs/check-pydantic-0502); Agent, OpenAIModel imports verified with -W error::DeprecationWarning.
1.89.0
May 1, 2026
Patch release; maintenance and dependency updates. Version confirmed against installed pydantic-ai 1.89.0 (.routine-envs/check-pydantic-0501); Agent, OpenAIModel imports verified with -W error::DeprecationWarning.
1.88.0
April 29, 2026
Patch release; maintenance and dependency updates. Version confirmed against installed pydantic-ai 1.88.0 (.routine-envs/main-py-0429); Agent, OpenAIModel imports verified.
1.87.0
April 25, 2026
Expanded Capabilities API: 9 new capability classes (WrapperCapability, ReinjectSystemPrompt, ProcessHistory, ProcessEventStream, HandleDeferredToolCalls, IncludeToolReturnSchemas, PrefixTools, PrepareTools, SetToolMetadata); new type aliases (RawToolArgs, ValidatedToolArgs, CapabilityRef, CapabilityPosition, CapabilityOrdering); CAPABILITY_TYPES registry. New capabilities section added. All symbols confirmed against installed 1.87.0 (pydantic_ai/capabilities/__init__.py).
1.86.1
April 24, 2026
Patch fix for Capabilities API. Snippets executed against installed 1.86.1; Hooks, ModelProfile, DEFAULT_PROFILE all import successfully. New Capabilities API section added to this guide.
1.86.0
April 23, 2026
Introduces capabilities parameter on Agent.__init__; new pydantic_ai.capabilities module (Hooks, AbstractCapability, CombinedCapability, HistoryProcessor, Thinking, ThreadExecutor, WebFetch, WebSearch, ImageGeneration, MCP, Toolset); new pydantic_ai.profiles module (ModelProfile, ModelProfileSpec, DEFAULT_PROFILE); new pydantic_ai.ui module (UIAdapter, UIEventStream, MessagesBuilder).
1.85.1
April 22, 2026
Patch fix; UrlContextTool marked deprecated (use WebFetchTool). Built-in tools, embeddings, AG UI, and ApprovalRequiredToolset verified against installed package. pydantic_ai.common_tools stub corrected to pydantic_ai.builtin_tools with correct class names. Snippets executed against 1.85.1.
1.85.0
April 21, 2026
New embeddings API (Embedder, EmbeddingModel, EmbeddingSettings); AG UI adapter (AGUIApp, AGUIAdapter, run_ag_ui); ApprovalRequired/ApprovalRequiredToolset for HITL; DeferredLoadingToolset; UrlContextTool deprecated in favour of WebFetchTool
1.84.1
April 18, 2026
Skip tool hooks for internal output tools; always pass dict-shaped validated args to hooks for single-BaseModel tools
1.84.0
April 17, 2026
OllamaModel subclass (fixes structured output on Ollama Cloud); XSearchTool/FileSearchTool for xAI (Grok); FastMCPToolset per-call metadata injection; Bedrock prompt cache TTL; Claude Opus 4.7 support (anthropic:claude-opus-4-7); stateful OpenAICompaction; fix exponential-time regex in Google FileSearchTool
1.83.0
April 16, 2026
Hard removal of all result_* → output_* renames (breaking); EvaluationReport API; pydantic-graph expansion with branching/looping; defer_loading for lazy model init; ThreadExecutor for sync-in-async tools; smart instruction caching; CaseLifecycle hooks; local WebFetch tool