building-pydantic-ai-agents

Build AI agents with Pydantic AI — tools, capabilities (including on-demand loading), structured output, streaming, testing, and multi-agent patterns. Use when the user mentions Pydantic AI, imports pydantic_ai, or asks to build an AI agent, add tools/capabilities, defer capability loading, stream output, define agents from YAML, or test agent behavior.

Install
npx skills add 'https://github.com/pydantic/pydantic-ai/tree/main/pydantic_ai_slim/pydantic_ai/.agents/skills/building-pydantic-ai-agents'
Download bundle ↓
main · 9e9fdc4Scanned 2026-09-17

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md

Tools Core

Read this file when the user wants to add function tools, organize toolsets, connect MCP servers, or use explicit common search tools.

Add Tools to an Agent

Use @agent.tool_plain for pure functions and @agent.tool for tools that need RunContext.

import random

from pydantic_ai import Agent, RunContext

agent = Agent('google:gemini-3-flash-preview', name='dice_tools_agent', deps_type=str)


@agent.tool_plain
def roll_dice() -> str:
    return str(random.randint(1, 6))


@agent.tool
def get_player_name(ctx: RunContext[str]) -> str:
    return ctx.deps

Use Tool(fn) when tools are defined outside the agent file or shared between agents.

Choosing a Tool Registration Method

Default choices:

  • @agent.tool when the tool needs deps, usage, retry count, or message history
  • @agent.tool_plain when the tool is a plain function
  • Tool(...) in tools=[...] when the tool should be reusable across agents
  • FunctionToolset when multiple related tools should be managed as a group

Organize or Restrict Which Tools an Agent Can Use

Use toolsets when the user has multiple related tools or wants cross-cutting behavior applied to a group.

Examples:

  • FunctionToolset for a bundle of Python tools
  • MCP servers, which are themselves toolsets
  • wrapper toolsets for approval, deferred loading, or other cross-cutting behavior

Access Usage Stats, Message History, or Retry Count in Tools

Route this to @agent.tool with RunContext.

Useful RunContext fields include:

  • ctx.deps
  • ctx.usage
  • ctx.messages
  • ctx.retry
  • ctx.realtime — whether the run is a realtime session
  • ctx.realtime_session — the live RealtimeSession once connected (None in classic runs and before connect)

Inside a realtime tool, await ctx.realtime_session.close() hangs up cleanly: the calling tool does not resume, and its call is recorded as interrupted. Use ctx.cancel() instead when the session context should raise RunCancelled; that route also records the call as interrupted.

Use MCP Servers

For URL-based MCP servers, use the MCP capability — it runs the MCP server locally by default and lets you opt into the model provider's native MCP support with native=True. See the MCP capability docs.

from pydantic_ai import Agent
from pydantic_ai.capabilities import MCP

agent = Agent(
    'openai:gpt-5.2',
    name='mcp_agent',
    capabilities=[
        MCP('https://mcp.example.com/api'),               # Streamable HTTP, local-only by default
        MCP('https://mcp.example.com/other', native=True),  # opt into native with local fallback
    ],
)

The capability's first positional arg is the URL. To pass any other MCPToolset input — a fastmcp.client.transports.*Transport, pre-built fastmcp.Client, in-process FastMCP server, or local script path — use the local= keyword:

from fastmcp.client.transports import StdioTransport

from pydantic_ai import Agent
from pydantic_ai.capabilities import MCP

agent = Agent(
    'openai:gpt-5.2',
    name='mcp_stdio_agent',
    capabilities=[MCP(local=StdioTransport(command='python', args=['mcp_server.py']))],
)

When you need to manage the toolset lifecycle yourself, share an MCP server across multiple agents, or use FastMCP-specific configuration that doesn't fit the capability shape, use MCPToolset directly and pass it via toolsets=[...]. Its tool_error_behavior controls how a tool error from the server surfaces: 'retry' (default) raises ModelRetry, 'failed' raises ToolFailed (recorded as outcome='failed'), and 'error' raises the raw fastmcp ToolError. For SEP-1686 tools with optional task support, set prefer_tasks=False to use normal calls; required tasks still use task-augmented execution.

Search with DuckDuckGo, Tavily, or Exa

Use common tools when the user wants explicit search tools rather than provider-adaptive capabilities.

from pydantic_ai import Agent
from pydantic_ai.common_tools.duckduckgo import duckduckgo_search_tool

agent = Agent(
    'openai:gpt-5.2',
    name='duckduckgo_search_agent',
    tools=[duckduckgo_search_tool()],
    instructions='Search DuckDuckGo for the given query and return the results.',
)

Good default split:

  • use WebSearch() capability when the user wants model-agnostic search with native fallback
  • use duckduckgo_search_tool() / Tavily / Exa when the user explicitly wants those engines as tools
Referenced from SKILL.md