Skills pydantic-ai-tool-system
Register and implement PydanticAI tools with proper context handling, type annotations, and docstrings. Use when adding tool capabilities to agents, implementing function calling, or creating agent actions.
install
source · Clone the upstream repo
git clone https://github.com/openclaw/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/openclaw/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/anderskev/pydantic-ai-tool-system" ~/.claude/skills/clawdbot-skills-pydantic-ai-tool-system && rm -rf "$T"
manifest:
skills/anderskev/pydantic-ai-tool-system/SKILL.mdsource content
PydanticAI Tool System
Tool Registration
Two decorators based on whether you need context:
from pydantic_ai import Agent, RunContext agent = Agent('openai:gpt-4o') # @agent.tool - First param MUST be RunContext @agent.tool async def get_user_data(ctx: RunContext[MyDeps], user_id: int) -> str: """Get user data from database. Args: ctx: The run context with dependencies. user_id: The user's ID. """ return await ctx.deps.db.get_user(user_id) # @agent.tool_plain - NO context parameter allowed @agent.tool_plain def calculate_total(prices: list[float]) -> float: """Calculate total price. Args: prices: List of prices to sum. """ return sum(prices)
Critical Rules
- @agent.tool: First parameter MUST be
RunContext[DepsType] - @agent.tool_plain: MUST NOT have
parameterRunContext - Docstrings: Required for LLM to understand tool purpose
- Google-style docstrings: Used for parameter descriptions
Docstring Formats
Google style (default):
@agent.tool_plain async def search(query: str, limit: int = 10) -> list[str]: """Search for items. Args: query: The search query. limit: Maximum results to return. """
Sphinx style:
@agent.tool_plain(docstring_format='sphinx') async def search(query: str) -> list[str]: """Search for items. :param query: The search query. """
Tool Return Types
Tools can return various types:
# String (direct) @agent.tool_plain def get_info() -> str: return "Some information" # Pydantic model (serialized to JSON) @agent.tool_plain def get_user() -> User: return User(name="John", age=30) # Dict (serialized to JSON) @agent.tool_plain def get_data() -> dict[str, Any]: return {"key": "value"} # ToolReturn for custom content types from pydantic_ai import ToolReturn, ImageUrl @agent.tool_plain def get_image() -> ToolReturn: return ToolReturn(content=[ImageUrl(url="https://...")])
Accessing Context
RunContext provides:
@agent.tool async def my_tool(ctx: RunContext[MyDeps]) -> str: # Dependencies db = ctx.deps.db api = ctx.deps.api_client # Model info model_name = ctx.model.model_name # Usage tracking tokens_used = ctx.usage.total_tokens # Retry info attempt = ctx.retry # Current retry attempt (0-based) max_retries = ctx.max_retries # Message history messages = ctx.messages return "result"
Tool Prepare Functions
Dynamically modify tools per-request:
from pydantic_ai.tools import ToolDefinition async def prepare_tools( ctx: RunContext[MyDeps], tool_defs: list[ToolDefinition] ) -> list[ToolDefinition]: """Filter or modify tools based on context.""" if ctx.deps.user_role != 'admin': # Hide admin tools from non-admins return [t for t in tool_defs if not t.name.startswith('admin_')] return tool_defs agent = Agent('openai:gpt-4o', prepare_tools=prepare_tools)
Toolsets
Group and compose tools:
from pydantic_ai import FunctionToolset, CombinedToolset # Create a toolset db_tools = FunctionToolset() @db_tools.tool def query_users(name: str) -> list[dict]: """Query users by name.""" ... @db_tools.tool def update_user(id: int, data: dict) -> bool: """Update user data.""" ... # Use in agent agent = Agent('openai:gpt-4o', toolsets=[db_tools]) # Combine toolsets all_tools = CombinedToolset([db_tools, api_tools])
Common Mistakes
Wrong: Context in tool_plain
@agent.tool_plain async def bad_tool(ctx: RunContext[MyDeps]) -> str: # ERROR! ...
Wrong: Missing context in tool
@agent.tool def bad_tool(user_id: int) -> str: # ERROR! ...
Wrong: Context not first parameter
@agent.tool def bad_tool(user_id: int, ctx: RunContext[MyDeps]) -> str: # ERROR! ...
Async vs Sync
Both work, but async is preferred for I/O:
# Async (preferred for I/O operations) @agent.tool async def fetch_data(ctx: RunContext[Deps]) -> str: return await ctx.deps.client.get('/data') # Sync (fine for CPU-bound operations) @agent.tool_plain def compute(x: int, y: int) -> int: return x * y