diff --git a/src/content/docs/pydanticai-guide/index.mdx b/src/content/docs/pydanticai-guide/index.mdx index f1b863fe..5ef496a3 100644 --- a/src/content/docs/pydanticai-guide/index.mdx +++ b/src/content/docs/pydanticai-guide/index.mdx @@ -19,8 +19,8 @@ import { Card, CardGrid, LinkCard, Aside } from '@astrojs/starlight/components'; ``` - **v2.43.0** · September 2026 · Python 3.10+ - _Each page carries its own "verified against" stamp: the newest deep dives are on 2.43.0, the class reference on 2.33.0, and some older topic pages still sit on the 1.10x line._ + **v2.46.0** · September 2026 · Python 3.10+ + _Each page carries its own "verified against" stamp: the newest deep dives are on 2.46.0, the class reference on 2.33.0, and some older topic pages still sit on the 1.10x line._ Typed APIs, structured output, validated data pipelines, evals-driven agents. @@ -97,6 +97,7 @@ import { Card, CardGrid, LinkCard, Aside } from '@astrojs/starlight/components'; + @@ -134,7 +135,8 @@ Condensed version history (superseded the previous per-volume changelog — full | 2.33.0 | Aug 2026 | Consolidated [Class & API Reference](./pydantic_ai_comprehensive_guide/#class--api-reference) added to the comprehensive guide — 44 sprawling deep-dive volumes folded into 16 organised sections; guide version currency refreshed end-to-end | | 2.36.0 | Aug 2026 | New [10-class deep-dive page (v2.36.0)](./pydantic_ai_class_deep_dives_v2_36/) — `AgentRun`, `AgentRunResult`, `StreamedRunResult`, `ModelSettings`, `Tool`, `ToolDefinition`, `RunUsage`/`RequestUsage`, `ConcurrencyLimiter`/`ConcurrencyLimit`, `MCPToolset`, `ApprovalRequiredToolset`/`DynamicToolset`. | | 2.40.0 | Sep 2026 | New [10-class deep-dive page (v2.40.0)](./pydantic_ai_class_deep_dives_v2_40/) — `RealtimeSession`, `RealtimeModelSettings`/`TurnDetection`, `AgentRealtime`, `Capability`, `Hooks`, `TemplateStr`, `ExternalToolset`, `RetryConfig`/`HTTPX2TenacityTransport`, `DuckDuckGoSearchTool`, `ImageGenerationSubagentTool` — all source-verified with runnable examples. | -| **2.43.0** | Sep 2026 | **Current.** New [10-class deep-dive page (v2.43.0)](./pydantic_ai_class_deep_dives_v2_43/) — `ToolFailed`, `RunCancelled`, `ToolSelector`, `ToolOrOutput`, `ServiceTier`/`ThinkingLevel`, `AgentStream`/`StreamedRunResult`, `SkipModelRequest`/`SkipToolValidation`/`SkipToolExecution`, `ToolDefinition.sequential`, `OutputContext`, `ApprovalRequired`/`DeferredToolRequests` — all source-verified with runnable examples. | +| 2.43.0 | Sep 2026 | New [10-class deep-dive page (v2.43.0)](./pydantic_ai_class_deep_dives_v2_43/) — `ToolFailed`, `RunCancelled`, `ToolSelector`, `ToolOrOutput`, `ServiceTier`/`ThinkingLevel`, `AgentStream`/`StreamedRunResult`, `SkipModelRequest`/`SkipToolValidation`/`SkipToolExecution`, `ToolDefinition.sequential`, `OutputContext`, `ApprovalRequired`/`DeferredToolRequests` — all source-verified with runnable examples. | +| **2.46.0** | Sep 2026 | **Current.** New [10-class deep-dive page (v2.46.0)](./pydantic_ai_class_deep_dives_v2_46/) — `FallbackModel` (response handlers), `FunctionToolset` (full constructor), `TextOutput`/`ToolOutput`/`NativeOutput`/`PromptedOutput` output quartet, `Embedder`/`EmbeddingResult`, `MCPToolset` FastMCP 4, `load_mcp_toolsets`, `CombinedToolset`, `StructuredDict`, `ModelSettings` lesser-known fields, `AdvisorTool` — all source-verified with runnable examples. | ## Install, minimal example @@ -162,6 +164,7 @@ Condensed — one row per meaningfully distinct change to this index/guide pairi | Date | Framework version | Summary | |------|-------------------|---------| +| 2026-09-21 | pydantic-ai 2.46.0 | Added `pydantic_ai_class_deep_dives_v2_46.md` — 10 source-verified deep dives for 2.46.0 (`FallbackModel` with response handlers, `FunctionToolset` full constructor, output quartet `TextOutput`/`ToolOutput`/`NativeOutput`/`PromptedOutput`, `Embedder`/`EmbeddingResult`, `MCPToolset` FastMCP 4 API, `load_mcp_toolsets`, `CombinedToolset`, `StructuredDict`, `ModelSettings` lesser-known fields, `AdvisorTool`). Sources read: `pydantic_ai/models/fallback.py`, `pydantic_ai/toolsets/function.py`, `pydantic_ai/output.py`, `pydantic_ai/embeddings/base.py`, `pydantic_ai/embeddings/__init__.py`, `pydantic_ai/mcp.py`, `pydantic_ai/toolsets/combined.py`, `pydantic_ai/settings.py`, `pydantic_ai/native_tools/__init__.py`. Version bumped from 2.43.0 → 2.46.0. Reference grid and changelog updated. | | 2026-09-14 | pydantic-ai 2.43.0 | Added `pydantic_ai_class_deep_dives_v2_43.md` — 10 source-verified deep dives for 2.43.0 (`ToolFailed`, `RunCancelled`, `ToolSelector`, `ToolOrOutput`, `ServiceTier`/`ThinkingLevel`, `AgentStream`/`StreamedRunResult`, `SkipModelRequest`/`SkipToolValidation`/`SkipToolExecution`, `ToolDefinition.sequential`, `OutputContext`, `ApprovalRequired`/`DeferredToolRequests`). Sources read: `pydantic_ai/exceptions.py`, `pydantic_ai/tools.py`, `pydantic_ai/settings.py`, `pydantic_ai/result.py`, `pydantic_ai/output.py`, `pydantic_ai/tool_manager.py`. Version bumped from 2.40.0 → 2.43.0. Comprehensive guide addendum and v2.40.0 cross-link added. | | 2026-09-07 | pydantic-ai 2.40.0 | Added `pydantic_ai_class_deep_dives_v2_40.md` — 10 source-verified deep dives for 2.40.0 (`RealtimeSession`, `RealtimeModelSettings`/`TurnDetection`, `AgentRealtime`, `Capability`, `Hooks`, `TemplateStr`, `ExternalToolset`, `RetryConfig`/`HTTPX2TenacityTransport`, `DuckDuckGoSearchTool`, `ImageGenerationSubagentTool`). Sources read: `pydantic_ai/realtime/_session.py`, `pydantic_ai/realtime/settings.py`, `pydantic_ai/agent/abstract.py`, `pydantic_ai/capabilities/capability.py`, `pydantic_ai/capabilities/hooks.py`, `pydantic_ai/template.py`, `pydantic_ai/toolsets/external.py`, `pydantic_ai/retries.py`, `pydantic_ai/common_tools/duckduckgo.py`, `pydantic_ai/common_tools/image_generation.py`. Version bumped from 2.36.0 → 2.40.0. | | 2026-08-31 | pydantic-ai 2.36.0 | Added `pydantic_ai_class_deep_dives_v2_36.md` — 10 more source-verified deep dives with runnable code examples (`AgentRun`, `AgentRunResult`, `StreamedRunResult`, `ModelSettings`, `Tool`, `ToolDefinition`, `RunUsage`/`RequestUsage`, `ConcurrencyLimiter`/`ConcurrencyLimit`, `MCPToolset`, `ApprovalRequiredToolset`/`DynamicToolset`). Linked from the Reference grid. Sources read: `pydantic_ai/run.py`, `pydantic_ai/result.py`, `pydantic_ai/settings.py`, `pydantic_ai/tools.py`, `pydantic_ai/usage.py`, `pydantic_ai/concurrency.py`, `pydantic_ai/mcp.py`, `pydantic_ai/toolsets/{approval_required,_dynamic}.py`. Version bumped from 2.33.0 → 2.36.0. | diff --git a/src/content/docs/pydanticai-guide/pydantic_ai_class_deep_dives_v2_46.md b/src/content/docs/pydanticai-guide/pydantic_ai_class_deep_dives_v2_46.md new file mode 100644 index 00000000..d1fa54a2 --- /dev/null +++ b/src/content/docs/pydanticai-guide/pydantic_ai_class_deep_dives_v2_46.md @@ -0,0 +1,1505 @@ +--- +title: "PydanticAI: 10 Source-Verified Class Deep Dives (2.46.0)" +description: "Runnable, source-verified code examples for FallbackModel, FunctionToolset, TextOutput/ToolOutput/NativeOutput/PromptedOutput, Embedder/EmbeddingResult, MCPToolset (FastMCP 4), load_mcp_toolsets, CombinedToolset, StructuredDict, ModelSettings lesser-known fields, and AdvisorTool — verified against pydantic-ai 2.46.0." +framework: pydanticai +language: python +sidebar: + order: 128 +--- + +# 10 Source-Verified Class Deep Dives — v2.46.0 + +Verified against **pydantic-ai 2.46.0** (installed package, sources read directly). +Modules consulted: `pydantic_ai/models/fallback.py`, `pydantic_ai/toolsets/function.py`, +`pydantic_ai/output.py`, `pydantic_ai/embeddings/base.py`, `pydantic_ai/embeddings/__init__.py`, +`pydantic_ai/mcp.py`, `pydantic_ai/toolsets/combined.py`, `pydantic_ai/settings.py`, +`pydantic_ai/native_tools/__init__.py`. + +This page covers classes that are **new since 2.43.0** or were only **thinly documented** in +earlier pages. Cross-references to the earlier series appear at the end. + +```bash +pip install "pydantic-ai==2.46.0" +python -c "import pydantic_ai; print(pydantic_ai.__version__)" +#> 2.46.0 +``` + +--- + +## 1. `FallbackModel` — multi-model resilience with response-level routing + +**Module:** `pydantic_ai.models.fallback` + +`FallbackModel` wraps several models and tries them in sequence when the primary fails. What +changed in 2.2.x–2.5.x (now stable in 2.46.0) is the `fallback_on` parameter: it now accepts +**response handlers** in addition to exception types, letting you fall through to the next model +based on the *content* of a response — not just whether the call crashed. + +### Constructor signature (from source) + +```python +def __init__( + self, + default_model: Model | KnownModelName | str, + *fallback_models: Model | KnownModelName | str, + fallback_on: FallbackOn = (ModelAPIError,), +): ... +``` + +`FallbackOn` is a flexible union: +- A **tuple of exception types** — `(ModelAPIError,)` is the default. +- A **single callable** — auto-detected as an exception handler (first param ≠ `ModelResponse`) + or a response handler (first param typed as `ModelResponse`). +- A **sequence mixing** exception types and callables. + +> **Gotcha — tuples mean exception types only**: any `tuple` passed to `fallback_on` is treated as +> a tuple of exception types. Pass handlers as a bare callable or inside a **list** — +> `fallback_on=(my_handler,)` either raises `TypeError` or silently ignores the handler. + +### Example 1 — basic provider failover + +```python +import asyncio +from pydantic_ai import Agent +from pydantic_ai.models.fallback import FallbackModel +from pydantic_ai.exceptions import ModelAPIError + +model = FallbackModel( + 'openai:gpt-5', + 'anthropic:claude-sonnet-5', # first fallback + 'google:gemini-2.5-pro', # second fallback +) + +agent = Agent(model) + +async def main(): + result = await agent.run('Hello from the most available model') + print(result.output) + +asyncio.run(main()) +``` + +### Example 2 — exception handler as callable + +```python +import asyncio +from pydantic_ai import Agent +from pydantic_ai.exceptions import ModelAPIError +from pydantic_ai.models.fallback import FallbackModel + + +def should_fallback(exc: Exception) -> bool: + """Fall through on any API error or rate limit.""" + if isinstance(exc, ModelAPIError): + status = getattr(exc, 'status_code', None) + return status is None or status in {429, 500, 502, 503, 504} + return False + + +model = FallbackModel( + 'openai:gpt-5', + 'anthropic:claude-sonnet-5', + fallback_on=[should_fallback], # list, not tuple +) + +agent = Agent(model, output_type=str) + +async def main(): + result = await agent.run('Which provider answered?') + print(result.output) + +asyncio.run(main()) +``` + +### Example 3 — response handler: skip empty answers + +A **response handler** receives a `ModelResponse` (not an exception) and returns `True` to +trigger fallback. The handler is identified automatically: if its first parameter is typed as +`ModelResponse`, it is treated as a response handler. + +```python +import asyncio +from pydantic_ai import Agent +from pydantic_ai.messages import ModelResponse +from pydantic_ai.models.fallback import FallbackModel + + +def response_too_short(response: ModelResponse) -> bool: + """Fall through if the model returned fewer than 10 meaningful characters.""" + from pydantic_ai.messages import TextPart + text = ''.join( + p.content for p in response.parts if isinstance(p, TextPart) + ) + return len(text.strip()) < 10 + + +model = FallbackModel( + 'openai:gpt-5', + 'anthropic:claude-sonnet-5', + fallback_on=response_too_short, # bare callable (or a list) +) + +agent = Agent(model, output_type=str) + +async def main(): + result = await agent.run('Describe the Eiffel Tower in two sentences.') + print(result.output) + +asyncio.run(main()) +``` + +### Example 4 — async handlers + +Both exception handlers and response handlers can be `async`: + +```python +import asyncio +import httpx +from pydantic_ai.exceptions import ModelAPIError +from pydantic_ai.models.fallback import FallbackModel + + +async def is_provider_down(exc: Exception) -> bool: + """Probe a health endpoint before deciding to fall through.""" + if not isinstance(exc, ModelAPIError): + return False + try: + async with httpx.AsyncClient() as client: + r = await client.get('https://status.openai.com/api/v2/status.json', timeout=2) + data = r.json() + return data.get('status', {}).get('indicator') != 'none' + except Exception: + return True + + +model = FallbackModel( + 'openai:gpt-5', + 'anthropic:claude-sonnet-5', + fallback_on=[is_provider_down], +) +``` + +### Example 5 — mixing exception types and callables + +```python +from pydantic_ai.exceptions import ModelAPIError +from pydantic_ai.messages import ModelResponse +from pydantic_ai.models.fallback import FallbackModel + + +def low_confidence(response: ModelResponse) -> bool: + from pydantic_ai.messages import TextPart + text = ''.join(p.content for p in response.parts if isinstance(p, TextPart)) + return 'I am not sure' in text or 'I cannot' in text + + +model = FallbackModel( + 'openai:gpt-4o-mini', + 'openai:gpt-5', + 'anthropic:claude-opus-5', + fallback_on=[ModelAPIError, low_confidence], # list mixing type and handler +) +``` + +> **Gotcha — `FallbackExceptionGroup`**: when *all* models fail, `FallbackModel` raises a +> `FallbackExceptionGroup` that contains the individual exceptions from each model attempt. +> Catch it with `except* ModelAPIError` (Python 3.11+) or iterate `.exceptions` on the group. + +--- + +## 2. `FunctionToolset` — building reusable, configurable tool groups + +**Module:** `pydantic_ai.toolsets.function` + +`FunctionToolset` is the backbone of every `@agent.tool` decorator. Using it explicitly lets you: +- share a toolset across multiple agents, +- set defaults for `max_retries`, `timeout`, `sequential`, and `requires_approval` per group, +- attach `instructions` — extra system-prompt text injected whenever the toolset is active, +- hide tools from the model until discovered via tool search or `load_capability` with `defer_loading=True`. + +### Constructor (from source, condensed) + +```python +FunctionToolset( + tools: Sequence[Tool | ToolFuncEither] = [], + *, + max_retries: int | None = None, + timeout: float | None = None, + docstring_format: DocstringFormat = 'auto', + require_parameter_descriptions: bool = False, + schema_generator: type[GenerateJsonSchema] = GenerateToolJsonSchema, + strict: bool | None = None, + sequential: bool = False, + requires_approval: bool = False, + metadata: dict[str, Any] | None = None, + defer_loading: bool = False, + include_return_schema: bool | None = None, + id: str | None = None, + instructions: AgentInstructions = None, +) +``` + +### Example 1 — shared toolset across two agents + +```python +import asyncio +from pydantic_ai import Agent, RunContext +from pydantic_ai import FunctionToolset + + +weather_tools = FunctionToolset() + + +@weather_tools.tool +def get_temperature(ctx: RunContext[None], city: str) -> str: + """Return the current temperature for a city.""" + return f'22°C in {city}' + + +@weather_tools.tool +def get_humidity(ctx: RunContext[None], city: str) -> str: + """Return the current humidity for a city.""" + return f'65% humidity in {city}' + + +# Reuse across agents +agent_en = Agent('openai:gpt-5', toolsets=[weather_tools], system_prompt='Answer in English.') +agent_fr = Agent('openai:gpt-5', toolsets=[weather_tools], system_prompt='Répondez en français.') + + +async def main(): + r = await agent_en.run('What is the weather in London?') + print(r.output) + +asyncio.run(main()) +``` + +### Example 2 — `instructions` parameter (extra system-prompt text) + +```python +from pydantic_ai import Agent, FunctionToolset, RunContext + +db_tools = FunctionToolset( + instructions='You have access to a read-only database. Never modify data. Always cite the table you queried.', +) + + +@db_tools.tool +def query_users(ctx: RunContext[None], name: str) -> list[dict]: + """Query the users table by name.""" + return [{'id': 1, 'name': name, 'email': f'{name.lower()}@example.com'}] + + +agent = Agent('openai:gpt-5', toolsets=[db_tools]) +result = agent.run_sync('Find users named Alice') +print(result.output) +``` + +### Example 3 — `sequential=True` for serialized execution + +`sequential=True` makes every tool in the toolset a **barrier**: if the model requests multiple +tools in the same response, each tool in this toolset runs alone — it won't overlap with other +tool calls in that step. This prevents intra-step races when tools share mutable state such as a +database transaction or file handle. It does **not** protect against races from concurrent +`agent.run()` calls; use an external lock for that. + +```python +from pydantic_ai import Agent, FunctionToolset, RunContext + +# Each tool runs alone (no overlap) if the model calls multiple tools in one step. +counter_state = {'value': 0} + +serial_tools = FunctionToolset(sequential=True) + + +@serial_tools.tool +def increment(ctx: RunContext[None], amount: int) -> int: + """Add amount to the shared counter and return the new value.""" + counter_state['value'] += amount + return counter_state['value'] + + +@serial_tools.tool +def reset(ctx: RunContext[None]) -> int: + """Reset the shared counter to zero.""" + counter_state['value'] = 0 + return counter_state['value'] + + +agent = Agent('openai:gpt-5', toolsets=[serial_tools]) +result = agent.run_sync('Increment the counter by 3, then by 7') +print(result.output) +``` + +### Example 4 — `requires_approval=True` for HITL at the toolset level + +When `requires_approval=True` the entire toolset is wrapped with approval gating — every call +returns a `DeferredToolRequests` unless explicitly approved. + +```python +from pydantic_ai import Agent, DeferredToolRequests, FunctionToolset, RunContext + +sensitive_tools = FunctionToolset(requires_approval=True) + + +@sensitive_tools.tool +def send_email(ctx: RunContext[None], to: str, body: str) -> str: + """Send an email to a recipient.""" + return f'Email sent to {to}: {body}' + + +agent = Agent('openai:gpt-5', toolsets=[sensitive_tools], output_type=[str, DeferredToolRequests]) + +result = agent.run_sync('Send a welcome email to alice@example.com') + +if isinstance(result.output, DeferredToolRequests): + print('Pending approvals:', [c.tool_name for c in result.output.approvals]) + # build DeferredToolResults and pass to next run to approve +``` + +### Example 5 — `defer_loading=True` to hide tools until discovered + +`defer_loading=True` **hides** the toolset's tools from the model until discovered. The +`ToolSearch` capability is auto-injected into every agent, so discovery works without extra +setup; pass `ToolSearch(...)` explicitly only to configure it (strategy, `max_results`). On providers that support native search (Anthropic +BM25/regex, OpenAI Responses), the provider exposes hidden tools once discovered; elsewhere a +local `search_tools` function is added to the model's tool list. + +```python +from pydantic_ai import Agent, FunctionToolset, RunContext +from pydantic_ai.capabilities import ToolSearch + +# Tools are HIDDEN from the model until ToolSearch reveals them. +hidden_tools = FunctionToolset(defer_loading=True, id='hidden-ops') + + +@hidden_tools.tool +def secret_lookup(ctx: RunContext[None], query: str) -> str: + """Look up internal records (only available after tool discovery).""" + return f'Internal result for {query}' + + +# ToolSearch() is auto-injected; passing it explicitly lets you configure the strategy. +agent = Agent( + 'openai:gpt-5', + toolsets=[hidden_tools], + capabilities=[ToolSearch()], # default: native search where supported, else local keywords +) +``` + +--- + +## 3. Output strategy quartet — `TextOutput`, `ToolOutput`, `NativeOutput`, `PromptedOutput` + +**Module:** `pydantic_ai.output` + +These four marker classes control *how* the agent asks the model for structured output. They wrap +the same `output_type` value but tell the runtime which extraction strategy to use. + +| Class | Mechanism | When to use | +|---|---|---| +| `ToolOutput` | Tool call | Explicit tool-call strategy. Best compatibility across providers. | +| `NativeOutput` | Provider JSON mode | JSON schema enforced by provider. Faster, fewer tokens. | +| `PromptedOutput` | Prompt injection | Works on any model, even those without native JSON mode. | +| `TextOutput` | Plain text + function | Transform free text into a Python value. | + +### `ToolOutput` — named tool with custom description + +```python +from pydantic import BaseModel +from pydantic_ai import Agent, ToolOutput + + +class Report(BaseModel): + """A structured business report.""" + title: str + summary: str + risk_level: int # 1-10 + + +agent = Agent( + 'openai:gpt-5', + output_type=ToolOutput( + Report, + name='submit_report', + description='Submit the final structured report. Always call this at the end.', + ), +) + +result = agent.run_sync('Analyse the Q3 revenue drop and produce a report.') +print(result.output.title) +``` + +### `NativeOutput` — provider JSON mode, union of types + +```python +from pydantic import BaseModel +from pydantic_ai import Agent, NativeOutput + + +class Fruit(BaseModel): + name: str + color: str + + +class Vehicle(BaseModel): + name: str + wheels: int + + +agent = Agent( + 'openai:gpt-5', + output_type=NativeOutput( + [Fruit, Vehicle], + name='fruit_or_vehicle', # machine-safe name (letters/digits/underscores only) + description='Classify the item as a fruit or vehicle.', + strict=False, # strict=True rejects undiscriminated root unions on OpenAI + ), +) + +result = agent.run_sync('What is a banana?') +print(repr(result.output)) # Fruit(name='banana', color='yellow') +``` + +### `PromptedOutput` — works on every model + +```python +from pydantic import BaseModel +from pydantic_ai import Agent, PromptedOutput + + +class Summary(BaseModel): + one_line: str + key_points: list[str] + + +agent = Agent( + 'ollama:llama3.2', # PromptedOutput works on any model, including self-hosted Ollama + output_type=PromptedOutput( + Summary, + template='Return valid JSON matching this schema:\n{schema}\n\nNow respond:', + ), +) + +result = agent.run_sync('Summarise the French Revolution.') +print(result.output.one_line) +``` + +### `TextOutput` — transform the model's text response + +```python +from pydantic_ai import Agent, TextOutput + + +def parse_csv(text: str) -> list[list[str]]: + return [row.split(',') for row in text.strip().splitlines()] + + +agent = Agent( + 'openai:gpt-5', + output_type=TextOutput(parse_csv), + system_prompt='Output exactly one CSV row per country, columns: name,capital,population.', +) + +result = agent.run_sync('Give me 3 European countries.') +for row in result.output: + print(row) +# ['France', 'Paris', '67000000'] +# ... +``` + +> **Streaming note**: `TextOutput` functions are **not applied** during `stream_text()`. Use +> `stream_output()` to receive the transformed value incrementally. + +### Choosing between the four + +```python +# Rule of thumb +from pydantic import BaseModel +from pydantic_ai import Agent, NativeOutput, PromptedOutput, TextOutput, ToolOutput + + +class Answer(BaseModel): + value: str + confidence: float + + +# Default — agent selects output mode based on model profile (may be tool, native, or prompted) +agent_default = Agent('openai:gpt-5', output_type=Answer) + +# Explicit NativeOutput — faster on OpenAI/Google, requires provider support +agent_native = Agent('openai:gpt-5', output_type=NativeOutput(Answer, strict=True)) + +# PromptedOutput — use on models without JSON mode +agent_prompted = Agent('ollama:llama3.2', output_type=PromptedOutput(Answer)) + +# TextOutput — when you just need a string transformation +def shout(text: str) -> str: + return text.upper() + + +agent_text = Agent('openai:gpt-5', output_type=TextOutput(shout)) +``` + +--- + +## 4. `Embedder` + `EmbeddingResult` — high-level embedding generation + +**Module:** `pydantic_ai.embeddings` + +The `Embedder` class (added in 2.2.x) is the high-level interface for generating vector +embeddings. It supports OpenAI, Cohere, Google, Bedrock, and sentence-transformers, with +optional OpenTelemetry instrumentation. + +### Constructor signature + +```python +Embedder( + model: EmbeddingModel | KnownEmbeddingModelName | str, + *, + settings: EmbeddingSettings | None = None, + instrument: InstrumentationSettings | bool | None = None, +) +``` + +### Example 1 — simple single-input embedding + +```python +import asyncio +from pydantic_ai import Embedder + + +async def main(): + embedder = Embedder('openai:text-embedding-3-small') + result = await embedder.embed_query('What is machine learning?') + print(f'Vector dimension: {len(result.embeddings[0])}') + print(f'First 5 values: {result.embeddings[0][:5]}') + print(f'Model: {result.model_name}') + print(f'Tokens used: {result.usage.total_tokens}') + + +asyncio.run(main()) +``` + +### Example 2 — batch embedding for a document corpus + +```python +import asyncio +from pydantic_ai import Embedder + + +async def main(): + embedder = Embedder('openai:text-embedding-3-large') + + documents = [ + 'Pydantic AI is a framework for building type-safe AI agents.', + 'FastAPI is a web framework for building APIs with Python.', + 'LangChain is an orchestration framework for LLM applications.', + ] + + result = await embedder.embed_documents(documents) + print(f'Embedded {len(result.embeddings)} documents') + print(f'Dimension: {len(result.embeddings[0])}') + + # Compute cosine similarity between first two + import math + a, b = result.embeddings[0], result.embeddings[1] + dot = sum(x * y for x, y in zip(a, b)) + mag_a = math.sqrt(sum(x**2 for x in a)) + mag_b = math.sqrt(sum(x**2 for x in b)) + similarity = dot / (mag_a * mag_b) + print(f'Similarity between doc[0] and doc[1]: {similarity:.4f}') + + +asyncio.run(main()) +``` + +### Example 3 — Cohere with document vs. query input types + +Different embedding providers distinguish between query and document embedding. `embed_query` +and `embed_documents` pass the right `input_type` automatically: + +```python +import asyncio +from pydantic_ai import Embedder + + +async def main(): + # Cohere recommends different input_type for queries vs. indexed docs + embedder = Embedder('cohere:embed-v4.0') + + query_vec = await embedder.embed_query('What causes inflation?') + doc_vec = await embedder.embed_documents(['Central banks control money supply.']) + + print('Query embedding shape:', len(query_vec.embeddings[0])) + print('Doc embedding shape:', len(doc_vec.embeddings[0])) + + +asyncio.run(main()) +``` + +### Example 4 — `EmbeddingResult` attributes + +```python +import asyncio +from pydantic_ai import Embedder +from pydantic_ai.embeddings import EmbeddingResult + + +async def main() -> None: + embedder = Embedder('openai:text-embedding-3-small') + result: EmbeddingResult = await embedder.embed_query('Hello') + + # Core attributes + print(result.embeddings) # list[list[float]] + print(result.model_name) # 'text-embedding-3-small' + print(result.usage.total_tokens) + + # Normalise vectors manually for cosine similarity (no built-in helper) + def normalise(v: list[float]) -> list[float]: + mag = sum(x**2 for x in v) ** 0.5 + return [x / mag for x in v] + + norms = [normalise(list(e)) for e in result.embeddings] + print(f'Norm of first vector: {sum(x**2 for x in norms[0])**0.5:.4f}') # ~1.0 + + +asyncio.run(main()) +``` + +### Example 5 — `Embedder.instrument_all` for global tracing + +```python +from pydantic_ai import Embedder +import logfire + +logfire.configure() +Embedder.instrument_all() # traces all Embedder calls in this process + +embedder = Embedder('openai:text-embedding-3-small') +# All calls are now instrumented with Logfire spans automatically +``` + +--- + +## 5. `MCPToolset` v2.46.0 — FastMCP 4 integration + +**Module:** `pydantic_ai.mcp` + +`MCPToolset` was rewritten around the **FastMCP Client** in 2.5.x and stabilised through 2.46.0. +The previous per-transport classes (`MCPServerStdio`, `MCPServerSSE`, `MCPServerStreamableHTTP`) +are gone — pass anything FastMCP can build a transport from: a URL, a script path, a `FastMCP` +server instance, or a pre-built `fastmcp.Client`. + +### Constructor signature (condensed from source) + +```python +MCPToolset( + client_or_server, # URL, path, FastMCP server, or Client + *, + tool_error_behavior: Literal['retry', 'error', 'failed'] = 'retry', + max_retries: int | None = None, + prefer_tasks: bool = True, + cache_tools: bool = True, # default True — fetched once, reused + ... +) +``` + +### Key parameters + +| Parameter | Default | Effect | +|---|---|---| +| `tool_error_behavior` | `'retry'` | `'retry'` → `ModelRetry`; `'error'` → propagate `ToolError`; `'failed'` → `ToolFailed` | +| `prefer_tasks` | `True` | Prefer task-augmented (SEP-1686) execution when supported | +| `cache_tools` | `True` | Cache tool list across `get_tools()` calls; set `False` if server changes tools mid-session | + +### Example 1 — HTTP (Streamable HTTP / SSE) server + +```python +import asyncio +from pydantic_ai import Agent +from pydantic_ai.mcp import MCPToolset + + +async def main(): + toolset = MCPToolset('http://localhost:8000/mcp') + agent = Agent('openai:gpt-5', toolsets=[toolset]) + + async with agent: + result = await agent.run('List available files') + print(result.output) + + +asyncio.run(main()) +``` + +### Example 2 — stdio MCP server (script path) + +```python +import asyncio +from pydantic_ai import Agent +from pydantic_ai.mcp import MCPToolset + + +async def main(): + # Pydantic AI launches and manages the subprocess + toolset = MCPToolset('path/to/my_mcp_server.py') + agent = Agent('openai:gpt-5', toolsets=[toolset]) + + async with agent: + result = await agent.run('Run the data pipeline') + print(result.output) + + +asyncio.run(main()) +``` + +### Example 3 — in-process FastMCP server for testing + +```python +import asyncio +from fastmcp import FastMCP +from pydantic_ai import Agent +from pydantic_ai.mcp import MCPToolset + + +mcp_server = FastMCP('test-server') + + +@mcp_server.tool() +def add(a: int, b: int) -> int: + """Add two numbers.""" + return a + b + + +async def main(): + toolset = MCPToolset(mcp_server) + agent = Agent('openai:gpt-5', toolsets=[toolset]) + + async with agent: + result = await agent.run('What is 7 + 13?') + print(result.output) # 20 + + +asyncio.run(main()) +``` + +### Example 4 — `tool_error_behavior` and allowlisting via `FilteredToolset` + +`MCPToolset` itself has no `allowed_tools` parameter. To restrict which tools the model can call, +wrap the toolset with `FilteredToolset`: + +```python +import asyncio +from pydantic_ai import Agent, FilteredToolset +from pydantic_ai.mcp import MCPToolset + + +async def main(): + raw = MCPToolset( + 'http://localhost:8000/mcp', + tool_error_behavior='failed', # model sees the error, no retry + cache_tools=False, # fetch fresh list each get_tools() call + ) + # Allow only safe read-only tools; deny write/delete tools + toolset = FilteredToolset(raw, lambda _ctx, tool: tool.name in {'read_file', 'list_dir'}) + agent = Agent('openai:gpt-5', toolsets=[toolset]) + + async with agent: + result = await agent.run('Read README.md') + print(result.output) + + +asyncio.run(main()) +``` + +### Example 5 — pre-built `fastmcp.Client` for full control (OAuth, custom headers) + +```python +import asyncio +from fastmcp.client import Client +from fastmcp.client.transports import StreamableHttpTransport +from pydantic_ai import Agent +from pydantic_ai.mcp import MCPToolset + + +async def main(): + transport = StreamableHttpTransport( + 'https://api.example.com/mcp', + headers={'Authorization': 'Bearer my-token'}, + ) + client = Client(transport) + toolset = MCPToolset(client) + + agent = Agent('openai:gpt-5', toolsets=[toolset]) + async with agent: + result = await agent.run('Analyse the latest report') + print(result.output) + + +asyncio.run(main()) +``` + +--- + +## 6. `load_mcp_toolsets` — JSON config-based MCP loading + +**Module:** `pydantic_ai.mcp` + +`load_mcp_toolsets` reads the same `mcpServers` JSON shape used by Claude Desktop, Claude Code, +and Cursor. Each server entry produces one `MCPToolset`, wrapped in a `PrefixedToolset` using the +server name as prefix to prevent tool name collisions. + +### Signature (from source) + +```python +def load_mcp_toolsets(config_path: str | Path) -> list[AbstractToolset[Any]]: + ... +``` + +### Config file format + +```json +{ + "mcpServers": { + "filesystem": { + "command": "python", + "args": ["-m", "mcp_filesystem_server", "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/allowed/path"], + "env": { + "LOG_LEVEL": "${LOG_LEVEL:-info}", + "API_KEY": "${FILESYSTEM_API_KEY}" + } + }, + "search": { + "url": "http://localhost:9000/mcp" + } + } +} +``` + +Environment variables use `${VAR}` (required) or `${VAR:-default}` (with fallback) syntax. + +### Example 1 — loading toolsets from config + +```python +import asyncio +from pydantic_ai import Agent +from pydantic_ai.mcp import load_mcp_toolsets + + +async def main(): + toolsets = load_mcp_toolsets('.claude/mcp.json') + agent = Agent('openai:gpt-5', toolsets=toolsets) + + async with agent: + result = await agent.run('List files in /allowed/path then search for Python projects') + print(result.output) + + +asyncio.run(main()) +``` + +### Example 2 — tool names after prefix wrapping + +Each server's tools are prefixed with the server name. A tool named `read_file` from the +`filesystem` server becomes `filesystem_read_file`: + +```python +import asyncio +from pydantic_ai import Agent +from pydantic_ai.mcp import load_mcp_toolsets + + +async def describe_available_tools(): + toolsets = load_mcp_toolsets('mcp_config.json') + # Pass to an agent and ask it to list what it can do + agent = Agent('openai:gpt-5', toolsets=toolsets, output_type=str) + async with agent: + result = await agent.run('List all the tools you have access to, grouped by prefix.') + print(result.output) + + +asyncio.run(describe_available_tools()) +``` + +### Example 3 — combining with programmatic toolsets + +```python +import asyncio +from pydantic_ai import Agent, FunctionToolset, RunContext +from pydantic_ai.mcp import load_mcp_toolsets + + +custom = FunctionToolset() + + +@custom.tool +def get_current_user(ctx: RunContext[None]) -> str: + """Return the current authenticated user.""" + return 'alice@example.com' + + +async def main(): + mcp_toolsets = load_mcp_toolsets('mcp_config.json') + agent = Agent('openai:gpt-5', toolsets=[custom, *mcp_toolsets]) + async with agent: + result = await agent.run('Who am I and what files can I access?') + print(result.output) + + +asyncio.run(main()) +``` + +--- + +## 7. `CombinedToolset` — composing toolsets without name collisions + +**Module:** `pydantic_ai.toolsets.combined` + +`CombinedToolset` merges multiple toolsets into one. Unlike just passing `toolsets=[a, b]` to +`Agent`, using it explicitly lets you wrap the combined set in further toolset decorators +(e.g., `FilteredToolset`, `PrefixedToolset`). + +### Signature (from source) + +```python +@dataclass +class CombinedToolset(AbstractToolset[AgentDepsT]): + toolsets: Sequence[AbstractToolset[AgentDepsT]] +``` + +### Name conflict resolution + +When two toolsets expose the same tool name, `CombinedToolset` raises `UserError` when the tools +are listed. Wrapping each toolset in a `PrefixedToolset` renames its tools so the names no longer +clash. The right pattern is to prefix before combining: + +```python +from pydantic_ai import Agent, FunctionToolset, PrefixedToolset, CombinedToolset, RunContext + + +db_tools = FunctionToolset() +api_tools = FunctionToolset() + + +@db_tools.tool +def search(ctx: RunContext[None], query: str) -> list[str]: + """Search the database.""" + return [f'db:{query}:result1', f'db:{query}:result2'] + + +@api_tools.tool +def search(ctx: RunContext[None], query: str) -> list[str]: + """Search the external API.""" + return [f'api:{query}:result1'] + + +# Prefix before combining to avoid the name clash +combined = CombinedToolset([ + PrefixedToolset(db_tools, prefix='db'), + PrefixedToolset(api_tools, prefix='api'), +]) + +agent = Agent('openai:gpt-5', toolsets=[combined]) +result = agent.run_sync('Search both database and API for "pydantic"') +print(result.output) +``` + +### Example 2 — wrapping combined with FilteredToolset + +```python +from pydantic_ai import ( + Agent, CombinedToolset, FilteredToolset, FunctionToolset, RunContext +) +from pydantic_ai.tools import ToolDefinition + + +read_tools = FunctionToolset() +write_tools = FunctionToolset() + + +@read_tools.tool +def read_document(ctx: RunContext[None], doc_id: str) -> str: + """Read a document by ID.""" + return f'Content of {doc_id}' + + +@write_tools.tool +def create_document(ctx: RunContext[None], title: str, body: str) -> str: + """Create a new document.""" + return f'Created: {title}' + + +combined = CombinedToolset([read_tools, write_tools]) + +# Explicit allowlist — safer than a deny-list: only listed tools are exposed +TRUSTED_TOOLS = {'read_document'} + +def is_read_tool(_ctx: RunContext[str], tool_def: ToolDefinition) -> bool: + return tool_def.name in TRUSTED_TOOLS + + +safe_toolset = FilteredToolset(combined, filter_func=is_read_tool) + +agent = Agent('openai:gpt-5', toolsets=[safe_toolset], deps_type=str) +result = agent.run_sync('Read doc abc-123', deps='untrusted_user') +print(result.output) +``` + +### Example 3 — `for_run` lifecycle + +`CombinedToolset` delegates `for_run` and `for_run_step` to each child toolset, enabling +dynamic toolsets inside the combined set to refresh per-step: + +```python +import asyncio +from pydantic_ai import Agent, CombinedToolset, FunctionToolset, RunContext + + +static_tools = FunctionToolset() +dynamic_tools = FunctionToolset() + + +@static_tools.tool +def static_helper(ctx: RunContext[None]) -> str: + """Always available.""" + return 'static result' + + +@dynamic_tools.tool +def step_counter(ctx: RunContext[None]) -> str: + """Track how many model steps have occurred.""" + return f'Currently on run step {ctx.run_step}' + + +combined = CombinedToolset([static_tools, dynamic_tools]) +agent = Agent('openai:gpt-5', toolsets=[combined]) + +result = agent.run_sync('Use both tools and tell me the results.') +print(result.output) +``` + +--- + +## 8. `StructuredDict` — raw JSON schema as output type + +**Module:** `pydantic_ai.output` (factory function, not a class) + +`StructuredDict` is a factory that creates a `dict[str, Any]` subclass with an attached JSON +schema. Use it when you have an existing JSON schema (from an API, a form spec, an external +system) and don't want to define a Pydantic model. + +### Signature (from source) + +```python +def StructuredDict( + json_schema: JsonSchemaValue, + name: str | None = None, + description: str | None = None, +) -> type[dict[str, Any]]: + ... +``` + +### Example 1 — basic usage + +```python +from pydantic_ai import Agent, StructuredDict + +person_schema = { + 'type': 'object', + 'title': 'Person', + 'properties': { + 'name': {'type': 'string'}, + 'age': {'type': 'integer', 'minimum': 0}, + 'email': {'type': 'string', 'format': 'email'}, + }, + 'required': ['name', 'age'], +} + +PersonDict = StructuredDict(person_schema, name='PersonDict') # explicit name; falls back to schema title otherwise +agent = Agent('openai:gpt-5', output_type=PersonDict) + +result = agent.run_sync('Create a person called John who is 30 years old') +print(result.output) # {'name': 'John', 'age': 30} +print(type(result.output)) # +``` + +### Example 2 — nested schemas with `$defs` + +`StructuredDict` automatically inlines `$defs` references to work around a Pydantic limitation: + +```python +from pydantic_ai import Agent, StructuredDict + +order_schema = { + 'type': 'object', + 'title': 'Order', + '$defs': { + 'Item': { + 'type': 'object', + 'properties': { + 'sku': {'type': 'string'}, + 'qty': {'type': 'integer'}, + }, + 'required': ['sku', 'qty'], + } + }, + 'properties': { + 'order_id': {'type': 'string'}, + 'items': { + 'type': 'array', + 'items': {'$ref': '#/$defs/Item'}, + }, + }, + 'required': ['order_id', 'items'], +} + +OrderDict = StructuredDict(order_schema) +agent = Agent('openai:gpt-5', output_type=OrderDict) + +result = agent.run_sync('Create an order ORD-001 with 2 units of SKU-A and 5 of SKU-B') +print(result.output) +# {'order_id': 'ORD-001', 'items': [{'sku': 'SKU-A', 'qty': 2}, {'sku': 'SKU-B', 'qty': 5}]} +``` + +### Example 3 — using alongside Pydantic models + +```python +from pydantic import BaseModel +from pydantic_ai import Agent, StructuredDict + + +class FallbackAnswer(BaseModel): + text: str + confidence: float + + +dynamic_schema = { + 'type': 'object', + 'properties': { + 'answer': {'type': 'string'}, + 'sources': {'type': 'array', 'items': {'type': 'string'}}, + }, + 'required': ['answer'], +} + +DynamicAnswer = StructuredDict(dynamic_schema, name='DynamicAnswer') + +# Union of a Pydantic model and a StructuredDict +agent = Agent('openai:gpt-5', output_type=[FallbackAnswer, DynamicAnswer]) +result = agent.run_sync('What is the capital of France?') +print(result.output) +``` + +### Example 4 — loading schema from an external JSON file + +```python +import json +from pathlib import Path +from pydantic_ai import Agent, StructuredDict + + +schema = json.loads(Path('schemas/invoice.json').read_text()) +InvoiceDict = StructuredDict(schema, description='Extract invoice data from the text.') +agent = Agent('openai:gpt-5', output_type=InvoiceDict) + +invoice_text = """ +Invoice #INV-2026-001 +Date: 2026-09-21 +Total: $1,234.56 +Vendor: Acme Corp +""" + +result = agent.run_sync(f'Extract invoice data:\n{invoice_text}') +print(result.output) +``` + +--- + +## 9. `ModelSettings` lesser-known fields + +**Module:** `pydantic_ai.settings` + +`ModelSettings` is a `TypedDict` with `total=False` (all fields optional). The comprehensive +guide covers `max_tokens`, `temperature`, and `top_p`; this section documents the fields that +are less commonly used but highly useful in production. + +### `parallel_tool_calls` — control multi-tool fan-out + +When `False`, the model issues at most one tool call per response instead of fanning out in +parallel. This limits concurrent side-effects — it does **not** enforce call ordering between +different tools; the model still chooses which tool to call first. Providers that do not support +this setting silently ignore it, so verify your target provider's adapter before relying on it. + +```python +from pydantic_ai import Agent, FunctionToolset, RunContext +from pydantic_ai.settings import ModelSettings + + +sequential_settings = ModelSettings(parallel_tool_calls=False) +tools = FunctionToolset() + + +@tools.tool +def step_a(ctx: RunContext[None]) -> str: + """First step.""" + return 'A done' + + +@tools.tool +def step_b(ctx: RunContext[None]) -> str: + """Second step (model decides call order; one-at-a-time fan-out).""" + return 'B done' + + +agent = Agent('openai:gpt-5', toolsets=[tools], model_settings=sequential_settings) +result = agent.run_sync('Run step A then step B in order.') +print(result.output) +``` + +### `seed` — reproducible outputs for testing + +```python +from pydantic_ai import Agent +from pydantic_ai.settings import ModelSettings + +agent = Agent( + 'openai:gpt-5', + model_settings=ModelSettings(seed=42, temperature=0.0), +) + +r1 = agent.run_sync('Roll a dice') +r2 = agent.run_sync('Roll a dice') +# r1.output == r2.output with seed + temperature=0 (best effort, not guaranteed) +print(r1.output, r2.output) +``` + +### `stop_sequences` — early termination tokens + +```python +from pydantic_ai import Agent +from pydantic_ai.settings import ModelSettings + +agent = Agent( + 'openai:gpt-5', + model_settings=ModelSettings(stop_sequences=['###END###', '']), +) + +result = agent.run_sync( + 'Write a short poem. End with ###END### when done.\n' + 'Roses are red,' +) +print(result.output) +``` + +### `extra_headers` — inject custom HTTP headers + +```python +from pydantic_ai import Agent +from pydantic_ai.settings import ModelSettings + +# Pass a request ID for distributed tracing +agent = Agent( + 'openai:gpt-5', + model_settings=ModelSettings( + extra_headers={ + 'X-Request-ID': 'req-2026-09-21-abc', + 'X-User-Tier': 'enterprise', + } + ), +) +``` + +### `presence_penalty` + `frequency_penalty` — reduce repetition + +```python +from pydantic_ai import Agent +from pydantic_ai.settings import ModelSettings + +agent = Agent( + 'openai:gpt-5', + model_settings=ModelSettings( + presence_penalty=0.6, # penalise reusing tokens that appeared before + frequency_penalty=0.4, # penalise reusing frequently appearing tokens + ), +) + +result = agent.run_sync( + 'Write a 200-word product description for an ergonomic chair. ' + 'Avoid repeating phrases.' +) +print(result.output) +``` + +### `logit_bias` — steer token probabilities + +```python +import tiktoken +from pydantic_ai import Agent +from pydantic_ai.settings import ModelSettings + + +def token_ids(model: str, words: list[str]) -> dict[str, int]: + enc = tiktoken.encoding_for_model(model) # use the exact model's tokenizer + bias: dict[str, int] = {} + for word in words: + for tok in enc.encode(word): + bias[str(tok)] = 100 # strongly encourage + return bias + + +# Force the model to favour certain words +agent = Agent( + 'openai:gpt-5', + model_settings=ModelSettings( + logit_bias=token_ids('gpt-5', ['Python', 'pydantic', 'type']), + ), +) + +result = agent.run_sync('Name three benefits of this AI framework.') +print(result.output) +``` + +### `extra_body` — pass provider-specific fields not in `ModelSettings` + +```python +from pydantic_ai import Agent +from pydantic_ai.settings import ModelSettings + +# Example: OpenAI "store: true" for model distillation +agent = Agent( + 'openai:gpt-5', + model_settings=ModelSettings( + extra_body={'store': True, 'metadata': {'project': 'finetune-v2'}}, + ), +) +``` + +--- + +## 10. `AdvisorTool` — native Anthropic executor/advisor pattern + +**Module:** `pydantic_ai.native_tools` + +`AdvisorTool` is a **native tool** (not a Python function tool) that lets a fast executor model +consult a more powerful advisor model mid-generation. Anthropic introduced this as a first-party +API primitive; OpenRouter exposes a compatible subset. + +This is different from chaining agents in your Python code: the executor model decides *when* to +call the advisor without your code being involved — the round-trip happens at the provider layer. + +### Signature (from source, condensed) + +```python +@dataclass(kw_only=True) +class AdvisorTool(AbstractNativeTool): + model: AdvisorModelName + max_uses: int | None = None # Anthropic only; OpenRouter ignores + max_tokens: int | None = None # min 1024; maps to max_completion_tokens on OpenRouter + caching: Literal['5m', '1h'] | None = None # Anthropic only; ephemeral context cache TTL + kind: str = 'advisor' # fixed sentinel +``` + +### Example 1 — fast executor with powerful advisor + +```python +import asyncio +from pydantic_ai import Agent, AdvisorTool +from pydantic_ai.capabilities import NativeTool + + +advisor = AdvisorTool( + model='claude-opus-5', # Advisor: powerful, slow + max_uses=3, # consult at most 3 times per request + max_tokens=1024, +) + +agent = Agent( + 'anthropic:claude-haiku-4-5-20251001', # Executor: fast, cheap + capabilities=[NativeTool(advisor)], # wrap in NativeTool to register as a capability +) + + +async def main(): + result = await agent.run( + 'Explain the proof of Fermat\'s Last Theorem at a high level, ' + 'then give one paragraph suitable for a general audience.' + ) + print(result.output) + + +asyncio.run(main()) +``` + +### Example 2 — ephemeral `caching` to reduce repeated advisor context cost + +The `caching` field tells Anthropic to cache the advisor's context for `'5m'` or `'1h'`. +Warm requests skip re-tokenising the advisor's system context, cutting latency and cost on +repeated queries. OpenRouter ignores this field. + +```python +import asyncio +from pydantic_ai import Agent, AdvisorTool +from pydantic_ai.capabilities import NativeTool + + +advisor = AdvisorTool( + model='claude-opus-5', + max_tokens=1024, + caching='5m', # cache advisor context for 5 minutes (Anthropic only) +) + +agent = Agent('anthropic:claude-haiku-4-5-20251001', capabilities=[NativeTool(advisor)]) + + +async def main(): + # First call cold; subsequent calls within 5 min benefit from cached advisor context. + for question in ['Derive the quadratic formula.', 'What is the binomial theorem?']: + result = await agent.run(question) + print(result.output) + + +asyncio.run(main()) +``` + +### Example 3 — OpenRouter gateway + +```python +from pydantic_ai import Agent, AdvisorTool +from pydantic_ai.capabilities import NativeTool + +advisor = AdvisorTool( + model='anthropic/claude-opus-4.8', # OpenRouter catalog slug (no prefix for advisor model) + max_tokens=1024, +) + +agent = Agent( + 'openrouter:anthropic/claude-haiku-4-5', # executor via OpenRouter prefix + capabilities=[NativeTool(advisor)], +) + +result = agent.run_sync('What is the Riemann hypothesis and why does it matter?') +print(result.output) +``` + +### `AdvisorTool` vs. multi-agent orchestration + +| | `AdvisorTool` | Multi-agent (Python) | +|---|---|---| +| Round-trip location | Provider API | Your Python code | +| Executor control | None (provider decides) | Full | +| Latency | Lower (no Python hop) | Higher | +| Observability | Provider traces only | Full traces in your code | +| When to use | Speed-critical with trusted provider | Full control needed | + +--- + +## Quick-reference: what each deep-dive series covers + +| Series | Classes | +|--------|---------| +| [Aug 2026 (2.33.0)](./pydantic_ai_class_examples_2026_08/) | Agent, RunContext, UsageLimits, ToolReturn, DeferredToolRequests/Results, CachePoint, PrefixedToolset/FilteredToolset/RenamedToolset, WebSearchTool, ModelRetry/UnexpectedModelBehavior | +| [v2.36.0](./pydantic_ai_class_deep_dives_v2_36/) | AgentRun, AgentRunResult, StreamedRunResult, ModelSettings, Tool, ToolDefinition, RunUsage/RequestUsage, ConcurrencyLimiter, MCPToolset, ApprovalRequiredToolset/DynamicToolset | +| [v2.40.0](./pydantic_ai_class_deep_dives_v2_40/) | RealtimeSession, RealtimeModelSettings/TurnDetection, AgentRealtime, Capability, Hooks, TemplateStr, ExternalToolset, RetryConfig/HTTPX2TenacityTransport, DuckDuckGoSearchTool, ImageGenerationSubagentTool | +| [v2.43.0](./pydantic_ai_class_deep_dives_v2_43/) | ToolFailed, RunCancelled, ToolSelector, ToolOrOutput, ServiceTier/ThinkingLevel, AgentStream/StreamedRunResult, SkipModelRequest/SkipToolValidation/SkipToolExecution, ToolDefinition.sequential, OutputContext, ApprovalRequired | +| **v2.46.0 (this page)** | **FallbackModel, FunctionToolset, TextOutput/ToolOutput/NativeOutput/PromptedOutput, Embedder/EmbeddingResult, MCPToolset v2.46.0/FastMCP 4, load_mcp_toolsets, CombinedToolset, StructuredDict, ModelSettings lesser-known fields, AdvisorTool** | diff --git a/src/content/docs/pydanticai-guide/pydantic_ai_comprehensive_guide.md b/src/content/docs/pydanticai-guide/pydantic_ai_comprehensive_guide.md index ed683b50..e5c28eaf 100644 --- a/src/content/docs/pydanticai-guide/pydantic_ai_comprehensive_guide.md +++ b/src/content/docs/pydanticai-guide/pydantic_ai_comprehensive_guide.md @@ -1,14 +1,14 @@ --- title: "Pydantic AI: Comprehensive Technical Guide" -description: "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" +description: "Version: 2.46.0 (September 2026) Framework: Pydantic AI - GenAI Agent Framework, the Pydantic Way Author Notes: Exhaustive technical documentation with production patterns, type safety" framework: pydanticai --- -Latest: pydantic-ai 2.43.0 | Class & API Reference verified against: 2.33.0 · new classes in 2.43.0 addendum | Updated: September 2026 +Latest: pydantic-ai 2.46.0 | Class & API Reference verified against: 2.33.0 · new classes in 2.43.0 addendum · 2.46.0 classes in companion guide | Updated: September 2026 # Pydantic AI: Comprehensive Technical Guide ## From Beginner to Expert Level -**Version:** 2.43.0 (September 2026) +**Version:** 2.46.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.