PydanticAI & PydanticGraph Expert Skill
PydanticAI is a Python agent framework for building production-grade GenAI applications, built by the team behind Pydantic. PydanticGraph is its companion graph/state-machine library.
Install:
pip install pydantic-ai # Full install (all providers)
pip install "pydantic-ai-slim[openai]" # Minimal install + your provider
Quick Reference
from pydantic_ai import Agent
# Basic agent — one line
agent = Agent('openai:gpt-5.2', instructions='Be concise.')
# Run it
result = agent.run_sync('What is the capital of France?')
print(result.output)
When to Load Which Reference
| Topic |
Load When |
File |
| Agent creation & lifecycle |
You need to create, configure, or run an agent — define tools, deps, output types, run methods, streaming |
references/core-agents.md |
| Capabilities & hooks |
You need built-in capabilities (Thinking, WebSearch, MCP, etc.), on-demand loading, lifecycle hooks, or custom capabilities |
references/capabilities-hooks.md |
| PydanticGraph |
You need a state machine, graph-based control flow, parallel execution, BaseNode subclasses, or GraphBuilder with joins/decisions |
references/graph.md |
| Models, output & streaming |
You need multi-model setups, FallbackModel, streaming output, output functions, or structured output with validation |
references/models-output.md |
| Multi-agent patterns & integrations |
You need agent delegation, programmatic hand-off, MCP servers, durable execution, or UI adapters |
references/patterns.md |
| Testing & evaluation |
You need TestModel, FunctionModel, pytest patterns, overrides, or Pydantic Evals for systematic eval |
references/testing-evals.md |
| Full worked examples |
You want complete runnable examples — bank support agent, email feedback graph, multi-agent flight booking |
references/examples.md |
| Framework boundaries |
You need to compare PydanticAI vs LangGraph for a project, or want to combine them |
references/hybrid-pydanticai-langgraph.md — also load skill_view(name='langgraph') |
| API surface reference |
You need to find the right import path, class name, or method signature quickly |
references/api-reference.md |
Common Patterns at a Glance
Agent with tools and structured output
from pydantic import BaseModel
from pydantic_ai import Agent, RunContext
class WeatherResult(BaseModel):
temperature: float
conditions: str
agent = Agent('openai:gpt-5.2', output_type=WeatherResult)
@agent.tool
async def get_weather(ctx: RunContext, city: str) -> str:
"""Get current weather for a city."""
return f"24°C and sunny in {city}"
result = agent.run_sync('Weather in London?')
print(result.output.temperature)
→ See references/core-agents.md for full agent lifecycle, run methods, and tool patterns.
Agent with dependency injection
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
@dataclass
class MyDeps:
api_key: str
db_conn: str
agent = Agent('openai:gpt-5.2', deps_type=MyDeps)
@agent.tool
async def query_db(ctx: RunContext[MyDeps], sql: str) -> str:
return f"Query results using {ctx.deps.db_conn}"
→ See references/core-agents.md for dependency injection patterns and testing overrides.
Graph with multiple nodes
from dataclasses import dataclass
from pydantic_graph import BaseNode, End, GraphRunContext, GraphBuilder
@dataclass
class MyState:
value: int = 0
@dataclass
class ProcessNode(BaseNode[MyState]):
async def run(self, ctx: GraphRunContext[MyState]) -> End | NextNode:
ctx.state.value += 1
if ctx.state.value >= 5:
return End(ctx.state.value)
return NextNode()
→ See references/graph.md for both BaseNode and GraphBuilder APIs, parallel execution, and join/reducer patterns.
When to use which run method
| When you need… |
Use |
Key behavior |
| A single answer, sync code |
run_sync() |
Blocks until complete, returns RunResult |
| A single answer, async code |
run() |
Async, returns RunResult |
| Stream text as it's generated |
run_stream() |
Async context manager, yields stream_text() / stream_output() |
| See granular events (tool calls, part starts, deltas) |
run_stream_events() |
Yields AgentStreamEvent types — FunctionToolCallEvent, PartStartEvent, FinalResultEvent |
| Manual control over each graph step |
iter() |
Iterate over agent's internal graph nodes (UserPromptNode → ModelRequestNode → CallToolsNode) |
| Tool calls to execute during streaming |
run_stream_events() or run(event_stream_handler=...) |
run_stream() stops at the first output that matches output_type and does NOT execute subsequent tool calls |
Details for each run method in references/core-agents.md.
Graph API: BaseNode vs GraphBuilder
| Factor |
BaseNode (class-based) |
GraphBuilder (function-based) |
| Style |
Subclass BaseNode[StateT], implement async run() |
Decorate async functions with @g.step |
| State mutation |
Via ctx.state inside run() method |
Via ctx.state inside step function |
| Parallelism |
Manual fork/join logic |
Built-in .map() per-element fan-out and .broadcast() same-input-to-multiple |
| Joins / aggregation |
Manual aggregation in return types |
Built-in reducers: reduce_list_append, reduce_sum, reduce_dict_update, etc. |
| Edge declaration |
Inferred from run() return type annotation |
Explicit via g.edge_from(source).to(target) |
| When to use |
Complex node logic, OO patterns, conditional edge logic |
Simple linear flows, parallel data processing, concise syntax |
Both APIs in references/graph.md.
Framework boundaries: PydanticAI vs LangGraph
Both frameworks build agentic systems with graphs and tools, but they differ sharply in design philosophy. The right choice depends on what you're optimizing for.
| Factor |
PydanticAI + PydanticGraph |
LangGraph |
Using both together |
| Design philosophy |
Type-safe, data-schema-driven. Feels like FastAPI. |
Low-level graph primitives (Pregel/Beam inspired). Feels like NetworkX. |
PydanticAI for the agent layer; LangGraph for complex orchestration |
| Agent definition |
Agent(model, tools, deps, output_type) — declarative, one line |
Manual StateGraph nodes with message-passing |
PydanticAI Agent as a node function inside LangGraph StateGraph |
| Tool calling |
@agent.tool decorator, auto-schema from type hints, RunContext DI |
Manual tool registration, tool_node = ToolNode(tools) |
PydanticAI's typed tool definitions used within LangGraph nodes |
| State management |
GraphRunContext.state — mutable dataclass, in-memory |
State with typed reducers, checkpointers (SQLite/Postgres) |
LangGraph checkpointer for the outer flow; PydanticGraph for sub-graph state |
| Multi-agent patterns |
Agent delegation (tool-call), programmatic hand-off, graph-based |
Supervisor (central router), swarm (direct handoff), hierarchical (subgraphs) |
PydanticAI delegation within a LangGraph supervisor node |
| Persistence |
Durable execution via Temporal, Inngest, Prefect, DBOS |
Built-in checkpointers (MemorySaver, SqliteSaver, PostgresSaver) |
LangGraph checkpointer at graph level |
| Streaming |
5 methods: run, run_sync, run_stream, run_stream_events, iter |
.stream() / .astream_events() on compiled graph |
LangGraph .astream_events() wrapping PydanticAI event handlers |
| Learning curve |
Lower — type hints guide everything |
Higher — more manual wiring |
Highest — two mental models |
| Best for |
Single agents, tool-using workflows, type-safe structured output, teams new to agents |
Complex state machines, multi-agent with branching/cycles, HITL, existing LangChain users |
Large systems needing type-safe agents AND sophisticated orchestration |
Boundary conditions — consider LangGraph when:
- You need built-in checkpointing/persistence for long-running conversations (SQLite, Postgres backends built-in)
- Your multi-agent system needs subgraph composition with isolated state namespaces
- You need human-in-the-loop patterns (interrupt/resume, state editing, approval workflows)
- You're already using LangChain and want consistency
- Your graph needs cycles or dynamic fan-out via
Send()
Consider PydanticAI when:
- Type safety and IDE autocomplete are priorities
- You want declarative agents with minimal boilerplate
- You need structured output with automatic validation and retries
- Your multi-agent needs are simple delegation or sequential hand-off
- You value the composable capabilities system (Thinking, WebSearch, MCP as plugins)
Consider using both when:
- You need LangGraph's orchestration (checkpointing, subgraphs, HITL) for the outer loop, but want PydanticAI's type-safe agent definition and tool schema for the inner agent logic
- You have a mixed team: some agents benefit from PydanticAI's typing, others need LangGraph's low-level control
- See
references/hybrid-pydanticai-langgraph.md for a complete worked example.
LangGraph skill: skill_view(name='langgraph') — covers supervisor/swarm/hierarchical patterns, persistence, production deployment, and evals.
Error handling quick-pick
from pydantic_ai import UnexpectedModelBehavior, capture_run_messages
with capture_run_messages() as messages:
try:
result = agent.run_sync('Query')
except UnexpectedModelBehavior as e:
cause = e.__cause__ # Often ModelRetry('reason')
print(f"Root cause: {cause}")
print("Full conversation:", messages) # Inspect every message
# Common recovery: raise ModelRetry from tools with clear instructions
| Exception |
Meaning |
Recovery |
UnexpectedModelBehavior |
Retry limit exceeded or model gave unexpected response |
Inspect e.__cause__, check messages, adjust instructions or tool retries |
ModelRetry (raised from tools) |
Tool wants model to retry with different args |
Let it propagate — PydanticAI handles it automatically up to retries limit |
ModelAPIError |
Provider returned 4xx/5xx |
Check API key, rate limits, model availability |
UsageLimitExceeded |
Token/request budget exhausted |
Increase UsageLimits or optimize prompt |
HookTimeoutError |
A lifecycle hook timed out |
Increase hook timeout or optimize hook logic |
Key CLI Commands
pip install pydantic-ai # Everything
pip install "pydantic-ai-slim[openai]" # Minimal
pip install "pydantic-ai-slim[openai,google,anthropic]" # Multi-provider
Directory Structure
pydanticai/
├── SKILL.md
├── references/
│ ├── core-agents.md # Agent lifecycle, tools, deps, output
│ ├── capabilities-hooks.md # Capabilities system & lifecycle hooks
│ ├── graph.md # PydanticGraph (BaseNode + GraphBuilder)
│ ├── models-output.md # Models, streaming, structured output
│ ├── patterns.md # Multi-agent patterns & integrations
│ ├── testing-evals.md # Testing & evaluation framework
│ ├── examples.md # Complete worked examples
│ ├── hybrid-pydanticai-langgraph.md # PydanticAI as LangGraph node (hybrid pattern)
│ └── api-reference.md # Quick API surface reference
Gotchas
Output type = final only: The output_type constrains the final response. The model can still call tools (function tools) mid-run. Output functions are different — they're forced to be called and end the run.
pydantic-graph has zero dependency on pydantic-ai: It's a standalone library. You can use it for non-GenAI state machines. Install with pip install pydantic-graph.
pydantic-ai-slim vs pydantic-ai: The slim package ships only core deps + OpenTelemetry. The full pydantic-ai is a meta-package that adds openai, anthropic, google, cli, mcp, evals, web, retries, and logfire extras.
Tool calls during streaming by default DON'T execute: run_stream() stops at the first output that matches the output type. Use run_stream_events() or run() with event_stream_handler to keep tool calls executing.
System prompt ≠ instructions: System prompts are part of message history and round-trip. Instructions are server-side and don't appear in messages sent to clients. When reusing message_history, the agent's new system prompt won't automatically be sent unless you add ReinjectSystemPrompt.
conversation_id is manual for forking: Pass conversation_id='new' to start a fresh conversation chain from existing history. It's not automatic.
Models named provider:model_name — PydanticAI auto-resolves the model class from the string prefix. For custom endpoints, use OpenAIChatModel(model_name, provider=OpenAIProvider(base_url=...)).
TestModel can't emulate native tools: Override with agent.override(model=TestModel(), native_tools=[]) in tests if your agent uses WebSearch, etc.
defer_model_check=True for testable module-level agents: When declaring an Agent at module level (outside a function) and using TestModel in tests with agent.override(model=TestModel()), set defer_model_check=True on the constructor. Without it, the agent tries to resolve the model string at import time — which fails without API credentials, even though the real model is overridden before any test runs.
Message history requires pairing: When slicing history, tool calls and their returns must stay paired or the LLM will error.
stream_text() fails with BaseModel output types: When output_type is a BaseModel (structured output), calling result.stream_text() raises UserError('stream_text() can only be used with text responses'). Use result.stream_output() instead to get partial validated objects as they stream in. If you need text-level streaming with structured output, use run_stream_events() and inspect PartDeltaEvent with TextPartDelta deltas. The two methods serve different output modes — text output → stream_text(), structured output → stream_output().
graph.run() returns OutputT, NOT the state object: Despite passing state=MyState() to graph.run(), the return value is the graph's output_type (e.g. list[int]), not the state. The state object IS mutated in-place during execution (since it's a mutable dataclass), so keep a separate reference:
state = MyState(items_processed=0)
result = await graph.run(state=state, inputs=[1, 2, 3])
# result -> [2, 4, 6] (OutputT = list[int])
# state.items_processed -> 3 (state mutated in-place)
This trap is most common with parallel .map() patterns where the reader assumes result.items_processed will work. It won't. The items_processed count lives on the state object you passed in, not on the return value.
1---2name: pydanticai3description: Build type-safe AI agents and graph-based workflows with PydanticAI and PydanticGraph. Agent creation, function tools, capabilities, dependency injection, structured output, streaming, multi-agent patterns, testing, evals, and graph state machines. Use whenever you are building agents, tool-using LLM workflows, or graph-based state machines in Python. Do not use this skill for unrelated requests; route to the nearest named specialist.4license: MIT5---67# PydanticAI & PydanticGraph Expert Skill89PydanticAI is a Python agent framework for building production-grade GenAI applications, built by the team behind Pydantic. PydanticGraph is its companion graph/state-machine library.1011**Install:**12```bash13pip install pydantic-ai # Full install (all providers)14pip install "pydantic-ai-slim[openai]" # Minimal install + your provider15```1617## Quick Reference1819```python20from pydantic_ai import Agent2122# Basic agent — one line23agent = Agent('openai:gpt-5.2', instructions='Be concise.')2425# Run it26result = agent.run_sync('What is the capital of France?')27print(result.output)28```2930## When to Load Which Reference3132| Topic | Load When | File |33|---|---|---|34| **Agent creation & lifecycle** | You need to create, configure, or run an agent — define tools, deps, output types, run methods, streaming | `references/core-agents.md` |35| **Capabilities & hooks** | You need built-in capabilities (Thinking, WebSearch, MCP, etc.), on-demand loading, lifecycle hooks, or custom capabilities | `references/capabilities-hooks.md` |36| **PydanticGraph** | You need a state machine, graph-based control flow, parallel execution, BaseNode subclasses, or GraphBuilder with joins/decisions | `references/graph.md` |37| **Models, output & streaming** | You need multi-model setups, FallbackModel, streaming output, output functions, or structured output with validation | `references/models-output.md` |38| **Multi-agent patterns & integrations** | You need agent delegation, programmatic hand-off, MCP servers, durable execution, or UI adapters | `references/patterns.md` |39| **Testing & evaluation** | You need TestModel, FunctionModel, pytest patterns, overrides, or Pydantic Evals for systematic eval | `references/testing-evals.md` |40| **Full worked examples** | You want complete runnable examples — bank support agent, email feedback graph, multi-agent flight booking | `references/examples.md` |41| **Framework boundaries** | You need to compare PydanticAI vs LangGraph for a project, or want to combine them | `references/hybrid-pydanticai-langgraph.md` — also load `skill_view(name='langgraph')` |42| **API surface reference** | You need to find the right import path, class name, or method signature quickly | `references/api-reference.md` |4344## Common Patterns at a Glance4546### Agent with tools and structured output47```python48from pydantic import BaseModel49from pydantic_ai import Agent, RunContext5051class WeatherResult(BaseModel):52 temperature: float53 conditions: str5455agent = Agent('openai:gpt-5.2', output_type=WeatherResult)5657@agent.tool58async def get_weather(ctx: RunContext, city: str) -> str:59 """Get current weather for a city."""60 return f"24°C and sunny in {city}"6162result = agent.run_sync('Weather in London?')63print(result.output.temperature)64```65→ See `references/core-agents.md` for full agent lifecycle, run methods, and tool patterns.6667### Agent with dependency injection68```python69from dataclasses import dataclass70from pydantic_ai import Agent, RunContext7172@dataclass73class MyDeps:74 api_key: str75 db_conn: str7677agent = Agent('openai:gpt-5.2', deps_type=MyDeps)7879@agent.tool80async def query_db(ctx: RunContext[MyDeps], sql: str) -> str:81 return f"Query results using {ctx.deps.db_conn}"82```83→ See `references/core-agents.md` for dependency injection patterns and testing overrides.8485### Graph with multiple nodes86```python87from dataclasses import dataclass88from pydantic_graph import BaseNode, End, GraphRunContext, GraphBuilder8990@dataclass91class MyState:92 value: int = 09394@dataclass95class ProcessNode(BaseNode[MyState]):96 async def run(self, ctx: GraphRunContext[MyState]) -> End | NextNode:97 ctx.state.value += 198 if ctx.state.value >= 5:99 return End(ctx.state.value)100 return NextNode()101```102→ See `references/graph.md` for both BaseNode and GraphBuilder APIs, parallel execution, and join/reducer patterns.103104### When to use which run method105106| When you need… | Use | Key behavior |107|---|---|---|108| A single answer, sync code | `run_sync()` | Blocks until complete, returns `RunResult` |109| A single answer, async code | `run()` | Async, returns `RunResult` |110| Stream text as it's generated | `run_stream()` | Async context manager, yields `stream_text()` / `stream_output()` |111| See granular events (tool calls, part starts, deltas) | `run_stream_events()` | Yields `AgentStreamEvent` types — `FunctionToolCallEvent`, `PartStartEvent`, `FinalResultEvent` |112| Manual control over each graph step | `iter()` | Iterate over agent's internal graph nodes (`UserPromptNode` → `ModelRequestNode` → `CallToolsNode`) |113| Tool calls to execute during streaming | `run_stream_events()` or `run(event_stream_handler=...)` | `run_stream()` stops at the first output that matches `output_type` and does NOT execute subsequent tool calls |114115*Details for each run method in `references/core-agents.md`.*116117### Graph API: BaseNode vs GraphBuilder118119| Factor | BaseNode (class-based) | GraphBuilder (function-based) |120|---|---|---|121| Style | Subclass `BaseNode[StateT]`, implement `async run()` | Decorate async functions with `@g.step` |122| State mutation | Via `ctx.state` inside `run()` method | Via `ctx.state` inside step function |123| Parallelism | Manual fork/join logic | Built-in `.map()` per-element fan-out and `.broadcast()` same-input-to-multiple |124| Joins / aggregation | Manual aggregation in return types | Built-in reducers: `reduce_list_append`, `reduce_sum`, `reduce_dict_update`, etc. |125| Edge declaration | Inferred from `run()` return type annotation | Explicit via `g.edge_from(source).to(target)` |126| When to use | Complex node logic, OO patterns, conditional edge logic | Simple linear flows, parallel data processing, concise syntax |127128*Both APIs in `references/graph.md`.*129130### Framework boundaries: PydanticAI vs LangGraph131132Both frameworks build agentic systems with graphs and tools, but they differ sharply in design philosophy. The right choice depends on what you're optimizing for.133134| Factor | PydanticAI + PydanticGraph | LangGraph | Using both together |135|---|---|---|---|136| Design philosophy | Type-safe, data-schema-driven. Feels like FastAPI. | Low-level graph primitives (Pregel/Beam inspired). Feels like NetworkX. | PydanticAI for the agent layer; LangGraph for complex orchestration |137| Agent definition | `Agent(model, tools, deps, output_type)` — declarative, one line | Manual `StateGraph` nodes with message-passing | PydanticAI `Agent` as a node function inside LangGraph `StateGraph` |138| Tool calling | `@agent.tool` decorator, auto-schema from type hints, `RunContext` DI | Manual tool registration, `tool_node = ToolNode(tools)` | PydanticAI's typed tool definitions used within LangGraph nodes |139| State management | `GraphRunContext.state` — mutable dataclass, in-memory | `State` with typed reducers, checkpointers (SQLite/Postgres) | LangGraph checkpointer for the outer flow; PydanticGraph for sub-graph state |140| Multi-agent patterns | Agent delegation (tool-call), programmatic hand-off, graph-based | Supervisor (central router), swarm (direct handoff), hierarchical (subgraphs) | PydanticAI delegation within a LangGraph supervisor node |141| Persistence | Durable execution via Temporal, Inngest, Prefect, DBOS | Built-in checkpointers (MemorySaver, SqliteSaver, PostgresSaver) | LangGraph checkpointer at graph level |142| Streaming | 5 methods: run, run_sync, run_stream, run_stream_events, iter | `.stream()` / `.astream_events()` on compiled graph | LangGraph `.astream_events()` wrapping PydanticAI event handlers |143| Learning curve | Lower — type hints guide everything | Higher — more manual wiring | Highest — two mental models |144| Best for | Single agents, tool-using workflows, type-safe structured output, teams new to agents | Complex state machines, multi-agent with branching/cycles, HITL, existing LangChain users | Large systems needing type-safe agents AND sophisticated orchestration |145146**Boundary conditions — consider LangGraph when:**147- You need built-in checkpointing/persistence for long-running conversations (SQLite, Postgres backends built-in)148- Your multi-agent system needs subgraph composition with isolated state namespaces149- You need human-in-the-loop patterns (interrupt/resume, state editing, approval workflows)150- You're already using LangChain and want consistency151- Your graph needs cycles or dynamic fan-out via `Send()`152153**Consider PydanticAI when:**154- Type safety and IDE autocomplete are priorities155- You want declarative agents with minimal boilerplate156- You need structured output with automatic validation and retries157- Your multi-agent needs are simple delegation or sequential hand-off158- You value the composable capabilities system (Thinking, WebSearch, MCP as plugins)159160**Consider using both when:**161- You need LangGraph's orchestration (checkpointing, subgraphs, HITL) for the outer loop, but want PydanticAI's type-safe agent definition and tool schema for the inner agent logic162- You have a mixed team: some agents benefit from PydanticAI's typing, others need LangGraph's low-level control163- See `references/hybrid-pydanticai-langgraph.md` for a complete worked example.164165*LangGraph skill:* `skill_view(name='langgraph')` — covers supervisor/swarm/hierarchical patterns, persistence, production deployment, and evals.166167### Error handling quick-pick168169```python170from pydantic_ai import UnexpectedModelBehavior, capture_run_messages171172with capture_run_messages() as messages:173 try:174 result = agent.run_sync('Query')175 except UnexpectedModelBehavior as e:176 cause = e.__cause__ # Often ModelRetry('reason')177 print(f"Root cause: {cause}")178 print("Full conversation:", messages) # Inspect every message179 # Common recovery: raise ModelRetry from tools with clear instructions180```181182| Exception | Meaning | Recovery |183|---|---|---|184| `UnexpectedModelBehavior` | Retry limit exceeded or model gave unexpected response | Inspect `e.__cause__`, check messages, adjust instructions or tool retries |185| `ModelRetry` (raised from tools) | Tool wants model to retry with different args | Let it propagate — PydanticAI handles it automatically up to `retries` limit |186| `ModelAPIError` | Provider returned 4xx/5xx | Check API key, rate limits, model availability |187| `UsageLimitExceeded` | Token/request budget exhausted | Increase `UsageLimits` or optimize prompt |188| `HookTimeoutError` | A lifecycle hook timed out | Increase hook timeout or optimize hook logic |189190## Key CLI Commands191192```bash193pip install pydantic-ai # Everything194pip install "pydantic-ai-slim[openai]" # Minimal195pip install "pydantic-ai-slim[openai,google,anthropic]" # Multi-provider196```197198## Directory Structure199200```201pydanticai/202├── SKILL.md203├── references/204│ ├── core-agents.md # Agent lifecycle, tools, deps, output205│ ├── capabilities-hooks.md # Capabilities system & lifecycle hooks206│ ├── graph.md # PydanticGraph (BaseNode + GraphBuilder)207│ ├── models-output.md # Models, streaming, structured output208│ ├── patterns.md # Multi-agent patterns & integrations209│ ├── testing-evals.md # Testing & evaluation framework210│ ├── examples.md # Complete worked examples211│ ├── hybrid-pydanticai-langgraph.md # PydanticAI as LangGraph node (hybrid pattern)212│ └── api-reference.md # Quick API surface reference213```214215## Gotchas216217- **Output type = final only:** The `output_type` constrains the *final* response. The model can still call tools (function tools) mid-run. Output functions are different — they're forced to be called and end the run.218- **pydantic-graph has zero dependency on pydantic-ai:** It's a standalone library. You can use it for non-GenAI state machines. Install with `pip install pydantic-graph`.219- **`pydantic-ai-slim` vs `pydantic-ai`:** The slim package ships only core deps + OpenTelemetry. The full `pydantic-ai` is a meta-package that adds openai, anthropic, google, cli, mcp, evals, web, retries, and logfire extras.220- **Tool calls during streaming by default DON'T execute:** `run_stream()` stops at the first output that matches the output type. Use `run_stream_events()` or `run()` with `event_stream_handler` to keep tool calls executing.221- **System prompt ≠ instructions:** System prompts are part of message history and round-trip. Instructions are server-side and don't appear in messages sent to clients. When reusing `message_history`, the agent's new system prompt won't automatically be sent unless you add `ReinjectSystemPrompt`.222- **`conversation_id` is manual for forking:** Pass `conversation_id='new'` to start a fresh conversation chain from existing history. It's not automatic.223- **Models named `provider:model_name`** — PydanticAI auto-resolves the model class from the string prefix. For custom endpoints, use `OpenAIChatModel(model_name, provider=OpenAIProvider(base_url=...))`.224- **`TestModel` can't emulate native tools:** Override with `agent.override(model=TestModel(), native_tools=[])` in tests if your agent uses WebSearch, etc.225- **`defer_model_check=True` for testable module-level agents:** When declaring an `Agent` at module level (outside a function) and using `TestModel` in tests with `agent.override(model=TestModel())`, set `defer_model_check=True` on the constructor. Without it, the agent tries to resolve the model string at import time — which fails without API credentials, even though the real model is overridden before any test runs.226- **Message history requires pairing:** When slicing history, tool calls and their returns must stay paired or the LLM will error.227- **`stream_text()` fails with BaseModel output types:** When `output_type` is a BaseModel (structured output), calling `result.stream_text()` raises `UserError('stream_text() can only be used with text responses')`. Use `result.stream_output()` instead to get partial validated objects as they stream in. If you need text-level streaming with structured output, use `run_stream_events()` and inspect `PartDeltaEvent` with `TextPartDelta` deltas. The two methods serve different output modes — text output → `stream_text()`, structured output → `stream_output()`.228- **`graph.run()` returns OutputT, NOT the state object:** Despite passing `state=MyState()` to `graph.run()`, the return value is the graph's `output_type` (e.g. `list[int]`), not the state. The `state` object IS mutated in-place during execution (since it's a mutable dataclass), so keep a separate reference:229230 ```python231 state = MyState(items_processed=0)232 result = await graph.run(state=state, inputs=[1, 2, 3])233 # result -> [2, 4, 6] (OutputT = list[int])234 # state.items_processed -> 3 (state mutated in-place)235 ```236237 This trap is most common with parallel `.map()` patterns where the reader assumes `result.items_processed` will work. It won't. The `items_processed` count lives on the state object you passed in, not on the return value.