| name | pydantic-ai-common-pitfalls |
| description | Avoid common mistakes and debug issues in PydanticAI agents. Use when encountering errors, unexpected behavior, or when reviewing agent implementations. |
PydanticAI Common Pitfalls and Debugging
Tool Decorator Errors
Wrong: RunContext in tool_plain
@agent.tool_plain
async def bad_tool(ctx: RunContext[MyDeps]) -> str:
return "oops"
Fix: Use @agent.tool if you need context:
@agent.tool
async def good_tool(ctx: RunContext[MyDeps]) -> str:
return "works"
Wrong: Missing RunContext in tool
@agent.tool
def bad_tool(user_id: int) -> str:
return "oops"
Fix: Add RunContext as first parameter:
@agent.tool
def good_tool(ctx: RunContext[MyDeps], user_id: int) -> str:
return "works"
Wrong: RunContext not first
@agent.tool
def bad_tool(user_id: int, ctx: RunContext[MyDeps]) -> str:
return "oops"
Fix: RunContext must always be the first parameter.
Valid Patterns (Not Errors)
Raw Function Tool Registration
The following pattern IS valid and supported by pydantic-ai:
from pydantic_ai import Agent, RunContext
async def search_db(ctx: RunContext[MyDeps], query: str) -> list[dict]:
"""Search the database."""
return await ctx.deps.db.search(query)
async def get_user(ctx: RunContext[MyDeps], user_id: int) -> dict:
"""Get user by ID."""
return await ctx.deps.db.get_user(user_id)
agent = Agent(
'openai:gpt-4o',
deps_type=MyDeps,
tools=[search_db, get_user]
)
Why this works: PydanticAI inspects function signatures. If the first parameter is RunContext[T], it's treated as a context-aware tool. No decorator required.
Reference: https://ai.pydantic.dev/agents/#registering-tools-via-the-tools-argument
Do NOT flag code that passes functions with RunContext signatures to Agent(tools=[...]). This is equivalent to using @agent.tool and is explicitly documented.
Dependency Type Mismatches
Wrong: Missing deps at runtime
agent = Agent('openai:gpt-4o', deps_type=MyDeps)
result = agent.run_sync('Hello')
Fix: Always provide deps when deps_type is set:
result = agent.run_sync('Hello', deps=MyDeps(...))
Wrong: Wrong deps type
@dataclass
class AppDeps:
db: Database
@dataclass
class WrongDeps:
api: ApiClient
agent = Agent('openai:gpt-4o', deps_type=AppDeps)
result = agent.run_sync('Hello', deps=WrongDeps(...))
Output Type Issues
Pydantic validation fails
class Response(BaseModel):
count: int
items: list[str]
agent = Agent('openai:gpt-4o', output_type=Response)
result = agent.run_sync('List items')
Fix: Increase retries or improve prompt:
agent = Agent(
'openai:gpt-4o',
output_type=Response,
retries=3,
instructions='Return JSON with count (int) and items (list of strings).'
)
Complex nested types
class Complex(BaseModel):
nested: dict[str, list[tuple[int, str]]]
Fix: Simplify or use intermediate models:
class Item(BaseModel):
id: int
name: str
class Simple(BaseModel):
items: list[Item]
Async vs Sync Mistakes
Wrong: Calling async in sync context
def handler():
result = await agent.run('Hello')
Fix: Use run_sync or make handler async:
def handler():
result = agent.run_sync('Hello')
async def handler():
result = await agent.run('Hello')
Wrong: Blocking in async tools
@agent.tool
async def slow_tool(ctx: RunContext[Deps]) -> str:
time.sleep(5)
return "done"
Fix: Use async I/O:
@agent.tool
async def slow_tool(ctx: RunContext[Deps]) -> str:
await asyncio.sleep(5)
return "done"
Model Configuration Errors
Missing API key
agent = Agent('openai:gpt-4o')
result = agent.run_sync('Hello')
Fix: Set environment variable or use defer_model_check:
agent = Agent('openai:gpt-4o', defer_model_check=True)
with agent.override(model=TestModel()):
result = agent.run_sync('Hello')
Invalid model string
agent = Agent('unknown:model')
Fix: Use valid provider:model format.
Streaming Issues
Wrong: Using result before stream completes
async with agent.run_stream('Hello') as response:
print(response.output)
print(response.output)
Wrong: Not iterating stream
async with agent.run_stream('Hello') as response:
pass
Fix: Always consume the stream:
async with agent.run_stream('Hello') as response:
async for chunk in response.stream_output():
print(chunk, end='')
Tool Return Issues
Wrong: Returning non-serializable
@agent.tool_plain
def bad_return() -> object:
return CustomObject()
Fix: Return serializable types (str, dict, Pydantic model):
@agent.tool_plain
def good_return() -> dict:
return {"key": "value"}
Debugging Tips
Gates (ambiguous failures)
When the agent misbehaves but the stack trace or error string is unclear, follow in order; do not skip ahead.
- Capture evidence — Re-run with
capture_run_messages() or, after run/run_sync, inspect result.all_messages() (or print message types in order). Pass: You can name the message part type and one line of content that explains the failure (e.g. RetryPromptPart, tool return, model text).
- Separate model from schema — If validation or
RetryPromptPart appears, compare the last model message to your output_type fields and types. Pass: You identified a concrete mismatch (missing field, wrong type, refusal) before only raising retries or changing the model.
- Confirm deps on every path — If tools or deps fail, verify each call site (
run, run_sync, run_stream) supplies deps=... when deps_type is set. Pass: A minimal repro with explicit deps matches or rules out a deps wiring bug.
Enable tracing
import logfire
logfire.configure()
logfire.instrument_pydantic_ai()
agent = Agent('openai:gpt-4o', instrument=True)
Capture messages
from pydantic_ai import capture_run_messages
with capture_run_messages() as messages:
result = agent.run_sync('Hello')
for msg in messages:
print(type(msg).__name__, msg)
Check model responses
result = agent.run_sync('Hello')
print(result.all_messages())
print(result.response)
print(result.usage())
Common Error Messages
| Error | Cause | Fix |
|---|
First parameter... RunContext | @agent.tool missing ctx | Add ctx: RunContext[...] |
RunContext... only... context | @agent.tool_plain has ctx | Remove ctx or use @agent.tool |
Unknown model provider | Invalid model string | Use valid provider:model |
ModelAPIError | API auth/quota | Check API key, limits |
RetryPromptPart in messages | Validation failed | Check output_type, increase retries |