# How to Add and Configure Custom Tools for Agno Agents: A Complete Guide

> Learn how to add and configure custom tools for Agno agents. Easily integrate any Python function to enhance your agent's capabilities with the @tool decorator.

- Repository: [Agno/agno](https://github.com/agno-agi/agno)
- Tags: how-to-guide
- Published: 2026-02-23

---

**Any Python function can become an Agno agent tool using the `@tool` decorator, which wraps it into a `Function` model and registers it in a `Toolkit` for LLM invocation.**

Agno treats a tool as any callable Python function—synchronous or asynchronous—that an agent can invoke when it decides the operation is needed. According to the agno-agi/agno source code, the framework provides a lightweight decorator-based API to convert plain functions into structured tools with caching, hooks, and confirmation flows. This guide walks through the architecture and implementation details for adding and configuring custom tools for Agno agents.

## Understanding the Tool Architecture

Agno's tool system consists of three core components working together to expose functions to LLMs.

**`Function` Model**: Defined in [`libs/agno/agno/tools/function.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/tools/function.py), this Pydantic model stores metadata required by the LLM (name, description, JSON schema) plus execution configuration (hooks, caching settings, and the entrypoint callable).

**`Toolkit` Registry**: Located in [`libs/agno/agno/tools/toolkit.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/tools/toolkit.py), the `Toolkit` class (also aliased as `ToolRegistry`) maintains dictionaries of `Function` objects—`functions` for sync and `async_functions` for async. It handles auto-registration, applies include/exclude filters, and provides the merged view used by agents.

**`@tool` Decorator**: Implemented in [`libs/agno/agno/tools/decorator.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/tools/decorator.py), this decorator validates configuration flags, builds the wrapper, and returns a `Function` instance via `Function.from_callable`.

The execution flow works as follows: when you pass tools to an `Agent`, the `Toolkit` constructor calls `_register_tools()` to create `Function` objects from each callable. The agent then queries `Toolkit.get_functions()` to build the LLM payload. When the LLM requests a call, `FunctionCall.execute()` (or `aexecute()` for async) runs the entrypoint, applies hooks, manages caching, and returns a `FunctionExecutionResult`.

## Creating Custom Tools with the @tool Decorator

### Basic Synchronous Tools

The simplest way to add and configure custom tools for Agno agents is decorating a plain function with `@tool`. The decorator automatically generates the JSON schema from type hints and docstrings.

```python

# file: my_tools.py

from agno.tools import tool

@tool(show_result=True)  # Return result to the model

def echo(text: str) -> str:
    """Return the same text back to the agent."""
    return text

```

Pass the decorated function directly to the agent's `tools` parameter:

```python
from agno.agent import Agent
from agno.models.openai import OpenAI
from my_tools import echo

agent = Agent(
    name="EchoBot",
    model=OpenAI(model="gpt-4o-mini"),
    tools=[echo],  # Custom tool passed directly

)

agent.print_response("Please repeat: hello world")

```

### Asynchronous Tools with Caching

For I/O-bound operations, define async functions and enable result caching to avoid redundant computations. The `cache_results` flag stores outputs based on input arguments, while `cache_ttl` sets expiration in seconds.

```python

# file: async_tools.py

from agno.tools import tool
import httpx

@tool(cache_results=True, cache_ttl=86400)  # Cache for 1 day

async def fetch_title(url: str) -> str:
    """Download a page and return its <title> tag."""
    async with httpx.AsyncClient() as client:
        resp = await client.get(url, timeout=10)
        resp.raise_for_status()
        start = resp.text.find("<title>")
        end = resp.text.find("</title>", start)
        return resp.text[start + 7 : end].strip()

```

```python
from agno.agent import Agent
from agno.models.openai import OpenAI
from async_tools import fetch_title

agent = Agent(
    name="WebInfo",
    model=OpenAI(model="gpt-4o-mini"),
    tools=[fetch_title],
)

agent.print_response("What is the title of https://example.com ?")

# First call hits the network; subsequent calls use the cache.

```

## Configuring Tool Behavior

### Requiring User Confirmation

For destructive or sensitive operations, set `requires_confirmation=True` to pause execution and prompt the user before running. Combine with `stop_after_tool_call=True` to halt the agent after the tool executes.

```python
from agno.tools import tool

@tool(requires_confirmation=True, stop_after_tool_call=True)
def delete_all_files() -> str:
    """Dangerous! Removes every file in the working directory."""
    # Implementation omitted for safety

    return "All files deleted."

```

When the LLM invokes this tool, Agno displays a confirmation prompt: "Are you sure you want to run `delete_all_files`?" If approved, the agent executes the tool and stops due to the `stop_after_tool_call` flag.

### Adding Pre and Post Hooks

Inject custom behavior around tool execution using `pre_hook` and `post_hook` parameters. These receive the function name, arguments, and result, enabling logging, metrics, or validation.

```python
from agno.tools import tool

def log_start(name: str, **_):
    print(f"[HOOK] Starting tool: {name}")

def log_end(name: str, result, **_):
    print(f"[HOOK] Finished {name} – result: {result!r}")

@tool(pre_hook=log_start, post_hook=log_end)
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

```

The hooks execute automatically: `pre_hook` runs before the function entrypoint, and `post_hook` runs after successful completion. For more complex chains, use the `tool_hooks` parameter to inject behavior at specific lifecycle points.

## Building Reusable Toolkits

For organized collections of related tools, subclass `Toolkit` (aliased as `ToolRegistry` in [`libs/agno/agno/tools/toolkit.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/tools/toolkit.py)). This approach groups tools under a namespace and applies shared configuration.

```python

# file: my_toolkit.py

from agno.tools import Toolkit, tool

class MathToolkit(Toolkit):
    def __init__(self):
        super().__init__(name="math", tools=[self.add, self.mul])

    @tool(show_result=True)
    def add(self, x: int, y: int) -> int:
        """Add two numbers."""
        return x + y

    @tool(show_result=True, requires_confirmation=True)
    def mul(self, x: int, y: int) -> int:
        """Multiply two numbers (requires confirmation)."""
        return x * y

```

Instantiate the toolkit and pass it to the agent:

```python
from agno.agent import Agent
from agno.models.openai import OpenAI
from my_toolkit import MathToolkit

agent = Agent(
    name="MathBot",
    model=OpenAI(model="gpt-4o-mini"),
    tools=[MathToolkit()],  # Register the entire toolkit

)

agent.print_response("What is 7 times 6?")

```

The `Toolkit` constructor auto-registers methods decorated with `@tool`, respecting any `include_tools` or `exclude_tools` filters passed during initialization.

## Registering Tools with Agents

In [`libs/agno/agno/agent/agent.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/agent.py), the `Agent` class coordinates tool registration and execution. When you initialize an agent with `tools=[...]`, the framework:

1. Creates a `Toolkit` instance if a list is provided
2. Calls `Toolkit.register()` for each callable, which distinguishes sync vs async functions
3. Stores the resulting `Function` objects in internal dictionaries
4. On each run, builds the function-calling payload via `Toolkit.get_functions()` and `Toolkit.get_async_functions()`

The agent then handles `FunctionCall` execution, applying any configured hooks, cache checks, and confirmation flows before returning results to the LLM conversation.

## Summary

- **Use the `@tool` decorator** from [`libs/agno/agno/tools/decorator.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/tools/decorator.py) to convert any Python function into a structured `Function` model with JSON schema generation.
- **Leverage configuration flags** like `show_result`, `requires_confirmation`, `cache_results`, and `stop_after_tool_call` to control tool behavior without writing boilerplate.
- **Implement hooks** via `pre_hook` and `post_hook` parameters to inject logging, metrics, or validation logic around tool execution.
- **Organize related tools** by subclassing `Toolkit` in [`libs/agno/agno/tools/toolkit.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/tools/toolkit.py) to create reusable, namespaced tool collections.
- **Pass tools directly** to the `Agent` constructor in [`libs/agno/agno/agent/agent.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/agent.py)—the framework handles auto-registration and provides the merged function view to the LLM.

## Frequently Asked Questions

### How do I make a tool ask for user approval before executing?

Set `requires_confirmation=True` in the `@tool` decorator. When the LLM calls the tool, Agno pauses and prompts the user for approval before running the function entrypoint. This is implemented in the `FunctionCall.execute()` method in [`libs/agno/agno/tools/function.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/tools/function.py).

### Can I cache expensive tool results to avoid redundant API calls?

Yes. Add `cache_results=True` to the decorator, optionally with `cache_ttl` (seconds) to set expiration. The framework stores results in a key-value cache keyed by function name and arguments, checking the cache before execution and storing the result after completion.

### What is the difference between passing a function and a Toolkit to an agent?

Passing a bare function automatically wraps it in a `Toolkit` instance behind the scenes. Passing a `Toolkit` subclass allows you to group multiple related tools, apply filters via `include_tools`/`exclude_tools`, and share initialization logic. Both approaches ultimately populate the agent's function registry.

### How do I add logging or metrics to tool execution?

Use the `pre_hook` and `post_hook` parameters on `@tool`. `pre_hook` receives the function name and arguments before execution; `post_hook` receives the name, result, and arguments after successful completion. For more granular control, use `tool_hooks` to inject behavior at specific lifecycle stages.