
Pydantic Ai Common Pitfalls
- 220 installs
- 74 repo stars
- Updated July 21, 2026
- existential-birds/beagle
Avoid frequent Pydantic AI mistakes in tool schemas, model settings, dependency injection, and streaming when building typed agent endpoints.
About
Catalogs common Pydantic AI pitfalls for beagle agent backends: invalid tool models, dependency wiring mistakes, streaming edge cases, and validation gaps. Helps teams ship typed LLM agents with fewer runtime failures and cleaner observability.
- Tool schema validation traps
- Model config and dependency pitfalls
- Streaming and result typing issues
- Runtime error prevention
- Typed agent endpoint hygiene
Pydantic Ai Common Pitfalls by the numbers
- 220 all-time installs (skills.sh)
- Ranked #2,746 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
npx skills add https://github.com/existential-birds/beagle --skill pydantic-ai-common-pitfallsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 220 |
|---|---|
| repo stars | ★ 74 |
| Last updated | July 21, 2026 |
| Repository | existential-birds/beagle ↗ |
What it does
Avoid frequent Pydantic AI mistakes in tool schemas, model settings, dependency injection, and streaming when building typed agent endpoints.
Files
PydanticAI Common Pitfalls and Debugging
Tool Decorator Errors
Wrong: RunContext in tool_plain
# ERROR: RunContext not allowed in tool_plain
@agent.tool_plain
async def bad_tool(ctx: RunContext[MyDeps]) -> str:
return "oops"
# UserError: RunContext annotations can only be used with tools that take contextFix: Use @agent.tool if you need context:
@agent.tool
async def good_tool(ctx: RunContext[MyDeps]) -> str:
return "works"Wrong: Missing RunContext in tool
# ERROR: First param must be RunContext
@agent.tool
def bad_tool(user_id: int) -> str:
return "oops"
# UserError: First parameter of tools that take context must be annotated with RunContext[...]Fix: Add RunContext as first parameter:
@agent.tool
def good_tool(ctx: RunContext[MyDeps], user_id: int) -> str:
return "works"Wrong: RunContext not first
# ERROR: RunContext must be first parameter
@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)
# Valid: Pass raw functions to Agent(tools=[...])
agent = Agent(
'openai:gpt-4o',
deps_type=MyDeps,
tools=[search_db, get_user] # RunContext detected from signature
)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)
# ERROR: deps required but not provided
result = agent.run_sync('Hello') # Missing deps!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)
# Type error: WrongDeps != 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')
# May fail if LLM returns wrong structureFix: Increase retries or improve prompt:
agent = Agent(
'openai:gpt-4o',
output_type=Response,
retries=3, # More attempts
instructions='Return JSON with count (int) and items (list of strings).'
)Complex nested types
# May cause schema issues with some models
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
# ERROR: Can't await in sync function
def handler():
result = await agent.run('Hello') # SyntaxError!Fix: Use run_sync or make handler async:
def handler():
result = agent.run_sync('Hello')
# Or
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) # WRONG: Blocks event loop!
return "done"Fix: Use async I/O:
@agent.tool
async def slow_tool(ctx: RunContext[Deps]) -> str:
await asyncio.sleep(5) # Correct
return "done"Model Configuration Errors
Missing API key
# ERROR: OPENAI_API_KEY not set
agent = Agent('openai:gpt-4o')
result = agent.run_sync('Hello')
# ModelAPIError: Authentication failedFix: Set environment variable or use defer_model_check:
# For testing
agent = Agent('openai:gpt-4o', defer_model_check=True)
with agent.override(model=TestModel()):
result = agent.run_sync('Hello')Invalid model string
# ERROR: Unknown provider
agent = Agent('unknown:model')
# ValueError: Unknown model providerFix: Use valid provider:model format.
Streaming Issues
Wrong: Using result before stream completes
async with agent.run_stream('Hello') as response:
# DON'T access .output before streaming completes
print(response.output) # May be incomplete!
# Correct: access after context manager
print(response.output) # Complete resultWrong: Not iterating stream
async with agent.run_stream('Hello') as response:
pass # Never consumed!
# Stream was never read - output may be incompleteFix: 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() # Can't serialize!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.
1. 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). 2. 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. 3. 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()
# Or per-agent
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()) # Full message history
print(result.response) # Last model response
print(result.usage()) # Token usageCommon 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 |