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

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, 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, 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, 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.


# 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:

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.


# 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()
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.

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.

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). This approach groups tools under a namespace and applies shared configuration.


# 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:

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, 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 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 to create reusable, namespaced tool collections.
  • Pass tools directly to the Agent constructor in 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.

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →