Get started Tools

Tools

Capabilities agents can invoke during their loop — `@tool`-decorated callables, with idempotency and serializable returns as the disciplines that matter.

A tool is a @tool-decorated callable an agent can invoke during its loop. Other agents passed to tools=[...] are also tools.

from agnt5 import Agent, Context, tool


@tool
async def get_weather(ctx: Context, city: str) -> str:
    """Get the current weather for a city.

    Args:
        city: Name of the city, e.g. "London" or "Tokyo".
    """
    response = await weather_api.fetch(city)
    return f"Weather in {city}: {response.temp}F, {response.condition}"


@tool
async def calculate(ctx: Context, expression: str) -> str:
    """Evaluate a mathematical expression.

    Args:
        expression: A math expression, e.g. "2 + 2" or "15 * 23".
    """
    result = eval(expression, {"__builtins__": {}}, {})
    return f"{expression} = {result}"


assistant = Agent(
    name="assistant",
    model="openai/gpt-4o-mini",
    instructions="Help users plan their day. Use get_weather for weather questions and calculate for math.",
    tools=[get_weather, calculate],
    max_iterations=5,
)

The assistant’s plan picks tools from its tools=[...] list. The runtime executes the tool, feeds the return value back to the model, and the loop continues.

The mental model

A tool extends an agent beyond text generation. The model on its own can produce language; it cannot read live data, perform calculations safely, or affect external systems. Tools fill those gaps. The agent’s plan-act-observe loop relies on tools to be its “act” step: each iteration, the model decides whether to call a tool, the runtime executes the call, and the result becomes context for the next iteration.

Two halves make a tool work. The decorator half registers the callable as agent-invokable: it expects (ctx: Context, ...) as its signature, runs as ordinary async Python, and returns a value the runtime serializes back to the model. The docstring half is the prompt the model sees: the description tells the model what the tool does, and the Args: block tells it how to fill in the arguments. A tool with a vague docstring will be called incorrectly or not at all — the LLM treats the docstring as the contract.

Other agents can be tools. Passing an Agent instance to tools=[...] makes it invokable the same way a @tool callable would be. This is the agents-as-tools pattern: a coordinator agent delegates to specialist agents, picks which one to call based on the question, and synthesizes their outputs. The whole composition runs inside the step boundary that started the coordinator.

Why it works this way

Separating tools from agents lets one capability serve multiple agents. Three different agents — a coordinator, a researcher, an analyst — can all share the same fetch_article tool without re-implementing the HTTP call. The agent decides when to use a capability; the tool implements what the capability does.

The decorator-as-registration pattern is also what allows tools to compose with the rest of the runtime. A @tool is registered the way a @function is registered — through the SDK’s import-time decorator hooks — so the runtime knows the tool exists and can dispatch to it. A handler decorated with both @function and @tool is reachable from clients (registry entry) and from agent loops (tool list); the decorators do different jobs and stack cleanly.

Edge cases and gotchas

  • Tools that mutate state must be idempotent. An agent’s plan may invoke the same tool twice in one iteration. A send_email tool with no idempotency key will send the email twice. Use idempotency keys, conditional updates, or safe-by-design operations.
  • Docstrings are prompts. Write them clearly, describe what the tool does, document each argument with its expected shape. The LLM uses the docstring to decide whether and how to call the tool.
  • Return types must serialize. The runtime sends the tool’s return value back to the model as text. Strings round-trip cleanly. Dicts and lists serialize as JSON. Opaque Python objects do not — keep returns to JSON-compatible shapes.
  • @tool is not @function. A handler can be both, but the decorators do different jobs. @function registers a callable for client invocation and ctx.step wrapping; @tool registers a callable for agent invocation. Decorate accordingly; if you need both, stack the decorators.
  • Tools run inside the agent’s host step. When a workflow calls a function that runs an agent that calls a tool, every layer is inside the workflow’s step boundary. The journal records one result for the step — the function’s return value — not one per tool call.
  • Heavy tools should be made idempotent at the boundary. A tool that triggers a paid API or a long-running job will be re-invoked if the agent decides it needs to call it again. Push the idempotency check to the API itself, not to the tool wrapper.