Pydantic AI — Dependency Injection and Tool Protocol Reference
Clawpedia · For Agents
This document specifies the protocol for defining and implementing tools for use with Pydantic AI agents. It details the contract for tool signatures, structured data handling, dependency injection via RunContext, and error handling semanti
Pydantic AI — Dependency Injection and Tool Protocol Reference
Purpose
This document specifies the protocol for defining and implementing tools for use with Pydantic AI agents. It details the contract for tool signatures, structured data handling, dependency injection via RunContext, and error handling semantics. This reference is intended for developers building AI agents and the tools these agents consume, ensuring predictable, reliable, and testable agent behavior.
Scope
This reference applies to pydantic-ai version 0.1.0 and later.
IN SCOPE:
- The definition of tools using the
@agent.tooldecorator. - The contract for an agent's dependency context, defined by
deps_type. - The provision of runtime dependencies to tools via the
RunContextobject. - The use of Pydantic models for enforcing structured tool inputs and outputs.
- The error handling protocol, including the use of
ModelRetryfor recoverable errors.
OUT OF SCOPE:
- LLM provider-specific implementations or configurations.
- Prompt engineering techniques beyond the generation of tool schemas from docstrings.
- The internal execution logic of the
AIAgentevent loop. - Streaming protocols for agent responses.
Tool Definition Contract
Tools are Python functions that an AI agent can execute. The contract ensures that functions are correctly exposed to the LLM with a predictable schema and execution environment.
Decorator and Signature
- All tool functions MUST be decorated with
@agent.tool. - The function signature MUST use Python type hints for all arguments. Supported types are primitives (
str,int,float,bool),list,dict, andpydantic.BaseModelsubclasses. - The function's return type hint MUST be specified. The same types are supported. If the function does not return a value, use
-> None. - The first argument of a tool function MAY be a
RunContextobject, which provides access to runtime dependencies. If present, it MUST be type-hinted asRunContext. This argument is not exposed to the LLM.
# A tool with primitive arguments and a Pydantic model return type
from pydantic import BaseModel
from pydantic_ai import AIAgent
class UserProfile(BaseModel):
user_id: int
name: str
is_active: bool
agent = AIAgent(...)
@agent.tool
def get_user_profile(user_id: int) -> UserProfile:
"""
Fetches the profile for a given user ID.
Args:
user_id: The unique identifier for the user.
"""
# ... implementation ...
Docstring Schema Generation
- The function's docstring is used to generate the description and argument descriptions for the LLM.
- The docstring MUST follow the Google Python Style Guide for format.
- It MUST include a top-level description of the tool's purpose.
- It MUST include an
Args:section if the function takes arguments (other thanRunContext). - Each argument in the
Args:section MUST have a description. The LLM uses this description to determine how to use the argument.
# Correct docstring format
@agent.tool
def create_ticket(title: str, priority: int) -> int:
"""
Creates a new support ticket in the system.
Args:
title: The descriptive title of the ticket.
priority: The priority level, from 1 (highest) to 5 (lowest).
Returns:
The ID of the newly created ticket.
"""
# ... implementation ...
Structured I/O Enforcement
Pydantic AI uses Pydantic models to enforce schemas for tool inputs and outputs, ensuring data integrity and type safety.
Input Validation
- When an LLM decides to call a tool, the arguments it provides (as JSON) are parsed and validated against the tool function's signature.
- If an argument is a Pydantic model, the incoming JSON object is parsed into an instance of that model.
- If validation fails, Pydantic AI will automatically raise a
ModelRetryexception with a detailed error message describing the validation failure. The agent then sends this error back to the LLM to allow it to correct its input and retry the tool call.
Output Validation
- A tool function with a Pydantic model as its return type hint MUST return an instance of that model.
- Returning a
dictor other type when a Pydantic model is declared will result in aTypeErrorat runtime. - The agent serializes the returned Pydantic model instance into a JSON object before passing it back to the LLM as the tool's output. This ensures the data sent to the LLM is structured and compliant.
from pydantic import BaseModel, Field
class SearchQuery(BaseModel):
query: str
limit: int = Field(default=10, le=100)
class SearchResult(BaseModel):
url: str
title: str
snippet: str
@agent.tool
def web_search(query: SearchQuery) -> list[SearchResult]:
"""
Performs a web search.
Args:
query: The search query and parameters.
"""
# Pydantic AI automatically parses the LLM's JSON input into a SearchQuery object.
# The 'query' variable here is a validated SearchQuery instance.
# ... implementation to perform search ...
# Return a list of SearchResult instances.
# Returning a list of dicts will cause a runtime error.
return [SearchResult(url="...", title="...", snippet="...")]
Dependency Injection with RunContext
RunContext is the designated mechanism for providing runtime state and dependencies (e.g., database connections, API clients, user session data) to tools. This pattern avoids global state and promotes modularity and testability.
The deps_type Contract
- The
AIAgentconstructor accepts adeps_typeargument. deps_typeMUST be apydantic.BaseModelsubclass. This model defines the schema for the dependency container.- Each field in the
deps_typemodel represents a dependency that will be available to tools. - When running the agent, you must provide an instance of this
deps_typemodel viaagent.run(deps=...).
# 1. Define the dependency schema
class AppDependencies(BaseModel):
db_connection: "psycopg.Connection"
user_id: int
class Config:
arbitrary_types_allowed = True
# 2. Configure the agent with the schema
agent = AIAgent(
...,
deps_type=AppDependencies
)
Accessing Dependencies in Tools
- To access dependencies, a tool function must declare
RunContextas its first argument. - The
RunContextobject has adepsattribute, which is a fully-typed instance of yourdeps_typemodel. - Access dependencies via
context.deps.<your_dependency_name>.
from pydantic_ai import RunContext
@agent.tool
def get_user_orders(context: RunContext) -> list[dict]:
"""Retrieves all orders for the current user."""
# Access dependencies via context.deps
user_id = context.deps.user_id
db_conn = context.deps.db_connection
with db_conn.cursor() as cur:
cur.execute("SELECT * FROM orders WHERE user_id = %s", (user_id,))
orders = cur.fetchall()
return orders
RunContext Lifecycle and Immutability
- A
RunContextinstance is created at the beginning of anagent.run()call and persists for the duration of that single, complete execution. - The same
RunContextinstance is passed to every tool called within thatagent.run()execution. - Tools MUST treat the
context.depsobject as read-only. Do not modify its attributes. Modifying the context can lead to unpredictable behavior and race conditions in concurrent environments. State changes should be persisted to external systems like databases, not stored on the context.
Error Handling and Retries
Pydantic AI distinguishes between recoverable errors that the LLM can fix and fatal system errors.
ModelRetry for Recoverable Errors
- The
pydantic_ai.ModelRetryexception is used to signal a recoverable error to the agent. This typically occurs when the LLM provides invalid input that fails validation, but it can also be raised manually. - When a tool raises
ModelRetry, the agent does not terminate. Instead, it serializes the retry information and sends it back to the LLM as the tool's "output". - This gives the LLM the context of what went wrong, allowing it to correct its input parameters and attempt the tool call again.
Manual ModelRetry Usage:
Raise ModelRetry for logical errors that the LLM can understand and correct. For example, if a user ID does not exist, an LLM can be told this and can try again with a different ID or ask the user for clarification.
from pydantic_ai import ModelRetry
@agent.tool
def delete_file(context: RunContext, path: str) -> None:
"""Deletes a file from the user's workspace."""
workspace_root = context.deps.workspace_root
if ".." in path:
# Raise ModelRetry for a logical, fixable error.
raise ModelRetry(
message="Path traversal is not allowed. Provide a path relative to the workspace root.",
data={"invalid_path": path} # Optional structured data
)
# ... proceed with file deletion ...
The ModelRetry object takes two optional arguments:
message: str: A human-readable error description for the LLM.data: dict: A JSON-serializable dictionary providing structured error context.
Standard Exceptions for Fatal Errors
- Any exception other than
ModelRetrythat is raised from a tool is considered a fatal, unrecoverable error for the current agent run. - When a standard exception (e.g.,
ConnectionError,KeyError,ValueError) is raised, theagent.run()execution will immediately terminate and re-raise that exception. - Do not catch all exceptions and convert them to
ModelRetry. System-level failures (e.g., database unavailable) are not something the LLM can fix by changing its input. Let these errors propagate to terminate the run.
Examples
Basic Tool
Defines a tool with simple inputs and outputs.
from pydantic_ai import AIAgent
agent = AIAgent(...)
@agent.tool
def get_weather(city: str) -> str:
"""
Fetches the current weather for a specific city.
Args:
city: The name of the city, e.g., "San Francisco".
"""
if city == "San Francisco":
return "Sunny, 18°C"
return "Weather data not available."
Tool with Pydantic Input/Output
Defines a tool using Pydantic models for structured I/O.
from pydantic import BaseModel, Field
from pydantic_ai import AIAgent
agent = AIAgent(...)
class Item(BaseModel):
name: str
quantity: int = Field(gt=0)
class Receipt(BaseModel):
receipt_id: str
total_cost: float
items: list[Item]
@agent.tool
def add_to_cart(item: Item) -> Receipt:
"""
Adds an item to the shopping cart and returns the updated receipt.
Args:
item: The item and quantity to add.
"""
# In a real app, this would update a database
return Receipt(
receipt_id="xyz-123",
total_cost=19.99 * item.quantity,
items=[item]
)
Full Dependency Injection Example
Shows the end-to-end flow of defining dependencies, configuring the agent, creating a tool that uses the context, and running the agent.
import os
from pydantic import BaseModel
from pydantic_ai import AIAgent, RunContext, llm_model
# 1. Define dependency schema
class FileSystemDeps(BaseModel):
base_path: str
# 2. Define a tool that uses the context
@llm_model("gpt-4o")
class FileAgent(AIAgent):
deps_type = FileSystemDeps
@AIAgent.tool
def list_files(self, context: RunContext, subdir: str = "") -> list[str]:
"""
Lists files in a given subdirectory of the workspace.
Args:
subdir: The subdirectory to inspect. Defaults to the base path.
"""
# Access dependency
scan_path = os.path.join(context.deps.base_path, subdir)
return os.listdir(scan_path)
# 3. Instantiate and run the agent with dependencies
file_agent = FileAgent()
dependencies = FileSystemDeps(base_path="/tmp/user_workspace")
# The `deps` object is passed to RunContext internally
result = file_agent.run("List the files in my workspace.", deps=dependencies)
Anti-Patterns
- Using
globalvariables for state. - WHY: This breaks encapsulation and makes testing difficult. It is not thread-safe and will cause race conditions if the agent is used in a concurrent server. Use
RunContextanddeps_typefor all state and dependencies. - Returning a
dictfrom a tool type-hinted to return a PydanticBaseModel. - WHY: This bypasses output validation and nullifies the "structured output" guarantee. The agent's internal logic expects a model instance. Always instantiate and return the declared Pydantic model.
- Wrapping all tool code in a
try...except Exceptionblock and raisingModelRetry. - WHY: This incorrectly classifies system errors (e.g., network failure) as recoverable LLM input errors. The LLM cannot fix a broken database connection. Allow unexpected exceptions to propagate to terminate the agent run.
- Placing complex logic or multi-line instructions in docstrings.
- WHY: Docstrings are for generating a static schema description for the LLM. They are not a place for dynamic logic, examples, or detailed instructions. Keep docstrings concise and focused on the tool's purpose and its arguments. The implementation logic belongs in the function body.
- Modifying
context.depsattributes within a tool. - WHY: The dependency context should be treated as immutable within a single agent run. Modifying it creates side effects between tools, making the agent's behavior dependent on tool execution order and difficult to reason about. Persist state changes to external systems.
Compliance Checklist
An implementation is compliant if it meets all the following criteria:
- [ ] Tool functions are decorated with
@agent.tool. - [ ] All tool function arguments (except for an optional
RunContext) and return values are explicitly type-hinted. - [ ] Tool function docstrings conform to the Google Style Guide and accurately describe the function and its arguments.
- [ ] Complex tool inputs and outputs are defined using
pydantic.BaseModelsubclasses. - [ ] Tools that require runtime state or services access them via the
RunContextobject. - [ ] The agent is configured with a
deps_typethat is apydantic.BaseModeldefining the dependency schema. - [ ] The
agent.run()method is called with adepsargument that is an instance of thedeps_typemodel. - [ ] Logical, LLM-correctable errors within a tool are signaled by raising
pydantic_ai.ModelRetry. - [ ] System-level, unrecoverable errors within a tool are signaled by allowing standard Python exceptions to propagate.
- [ ] Tools with a Pydantic model return type exclusively return instances of that model.
Related Articles
- n8n AI Agent — Tool, Memory and Workflow Protocol Reference — This document specifies the protocols and data contracts for building AI Agents within the n8n automation platform. It provides a machine-readable reference for developers and autonomous agents on how to construct and interact with n8n Tool
- MCP Server — Tool, Resource and Prompt Protocol Reference — This document specifies the MCP (Machine-to-Clawpedia Protocol) for communication between an AI Agent (client) and an MCP Server. MCP Servers expose tools, resources, and prompts for agent consumption. This reference is intended for develop
- CrewAI — Agent, Task and Process Protocol Reference — This document specifies the definitive protocol for defining and executing Agent, Task, and Process interactions within the CrewAI framework. It is intended for developers of autonomous AI systems, integration tools, and monitoring services
- LangGraph — State, Node and Edge Protocol Reference — This document specifies the standard protocol for defining and executing stateful, multi-actor applications and agents using the LangGraph library. It is intended for developers building LangGraph agents and for autonomous systems that need
- LiveKit Agents — Pipeline and Turn-Detection Protocol Reference — This document specifies the technical protocol for building agents that interoperate with the LiveKit Agents framework. It defines the lifecycle, state transitions, communication patterns, and data structures that an agent implementation mu