Tools

Define and use tools in model and agent workflows.

AI SDK for Python supports multiple types of tools, including function tools defined in code using @ai.tool, as well as built-in provider-side tools.

Declare function tools

Decorate an async function with @ai.tool:

import ai


@ai.tool
async def contact_mothership(query: str) -> str:
    """Contact the mothership for important decisions."""
    return "Soon."

The tool name comes from the function name. The model receives the function parameters as a JSON schema and the docstring as the tool description.

Function tools will only be automatically executed by the SDK when used in the context of the agent. Provider-side tools will always get executed by the provider, even when passed to ai.stream.

Handle tool errors

Tool exceptions become ToolCallResult events with is_error=True. The model sees the error text on the next turn:

async with agent.run(model, messages) as stream:
    async for event in stream:
        if isinstance(event, ai.events.ToolCallResult):
            for result in event.results:
                if result.is_error:
                    print(f"{result.tool_name} failed: {result.result}")

The original exception is available on event.exception for logging.

Declare tools that stream output

AI SDK for Python supports tools that return async iterables. Use a streaming tool when it needs to return partial output, such as when wrapping a subagent.

Streaming tools use aggregators. An aggregator solves two problems: the tool can yield many values over time, but the agent still needs one final tool result; and your app may want a rich stored result while the model needs a simpler value on the next turn.

The core interface is:

class Aggregator[Item, Result, ModelInput]:
    def feed(self, item: Item) -> None: ...
    def snapshot(self) -> Result: ...

    @classmethod
    def to_model_input(cls, snapshot: Result) -> ModelInput: ...

Use ai.StreamingTextTool when yielded strings should concatenate into the tool result:

@ai.tool
async def draft_reply(topic: str) -> ai.StreamingTextTool:
    """Draft a reply."""
    yield "Checking "
    yield "records for "
    yield topic

Use ai.StreamingStatusTool[T] when intermediate yields are progress updates and the last yielded value is the final result.

Use ai.SubAgentTool when a tool streams events from a nested agent. The stored result is a message bundle, and the model sees the final assistant text.

The agent emits PartialToolCallResult events for those values, then sends the aggregated result back to the model on the next turn.

Every tool must return an awaitable or an async iterable. Every async iterable tool needs an aggregator, either through its return annotation or the aggregator= argument.

Understand tool anatomy

ai.Tool represents tools of all kinds in the SDK.

Each tool carries an optional spec, which is a description consumed by the model, as well as a tool_config that contains tool-specific execution config (e.g. retry policy).

AgentTool wraps ai.Tool together with its corresponding Python function, which allows agent.run to find and call that function when the model requests to do so.

Pass ai.Tool objects directly to ai.stream when you want the model to emit tool calls but you do not want the SDK to execute them:

tool = ai.Tool(
    kind="function",
    name="contact_mothership",
    spec=ai.tools.ToolSpec(
        description="Contact the mothership.",
        params={
            "type": "object",
            "properties": {"query": {"type": "string"}},
            "required": ["query"],
        },
    ),
)

async with ai.stream(model, messages, tools=[tool]) as stream:
    async for event in stream:
        if isinstance(event, ai.events.ToolEnd):
            print(event.tool_call.tool_args)

Use provider-executed tools

Provider-executed tools run on the provider side. Pass them to ai.stream in the tools list:

messages = [
    ai.user_message("Check the latest mothership telemetry reports."),
]

async with ai.stream(
    model,
    messages,
    tools=[ai.providers.anthropic.tools.web_search(max_uses=3)],
) as stream:
    async for event in stream:
        if isinstance(event, ai.events.TextDelta):
            print(event.chunk, end="", flush=True)

When you route through AI Gateway, you can use provider-specific tool factories and AI Gateway tool factories:

tools = [
    ai.providers.anthropic.tools.web_search(max_uses=3),
    ai.providers.ai_gateway.tools.perplexity_search(max_results=5),
]

Use MCP tools

The Model Context Protocol (MCP) adapter converts server tools into agent tools:

uv add "ai[mcp]"
tools = await ai.mcp.get_http_tools(
    "http://localhost:3000/mcp",
    headers={"Authorization": "Bearer your_access_token_here"},
)

agent = ai.Agent(tools=tools)

Use ai.mcp.get_stdio_tools for subprocess-based MCP servers.