Skip to content

Agent Tools#

Tools allow agents to interact with the outside world. By providing tools, you enable your agents to perform actions such as executing code, fetching data from APIs, querying databases, or performing complex calculations.

Under the hood, a tool is a standard Python function accompanied by a schema that describes its purpose, inputs, and outputs to the underlying Large Language Model (LLM).

Creating Agent Tools#

The easiest way to create a tool is by using the @tool decorator. This decorator automatically parses your function's signature, type hints, and docstring to generate a schema that the LLM can understand.

For the best results, always provide clear type hints and a descriptive docstring. The LLM relies heavily on these to know when and how to invoke your tool.

1
2
3
4
5
6
7
8
from autogen.beta import tool

@tool
def calculate_shipping_cost(destination: str, weight_kg: float) -> str:
    """Calculates the shipping cost for a package based
    on its destination and weight.
    """
    return "$15.00"

Once defined, you can equip an agent with this capability by passing the tool to the tools list during the agent's initialization.

from autogen.beta import Agent

agent = Agent(name="ShippingAssistant", tools=[calculate_shipping_cost])

Note

For simpler use cases, you can pass an undecorated Python function directly to the agent's tools list. The framework will automatically convert it into a fully-fledged tool under the hood, extracting the schema from the signature and docstring just like the decorator would.

1
2
3
4
5
6
7
8
from autogen.beta import Agent

def get_weather(location: str) -> str:
    """Returns the current weather for a given location."""
    return "Sunny, 22°C"

# get_weather is automatically converted to a tool
agent = Agent(name="WeatherBot", tools=[get_weather])

Registering Tools via a Decorator#

Alternatively, you can register a tool directly with an agent instance using its @my_agent.tool decorator.

This approach is particularly useful when you need to dynamically add capabilities to an agent after it has been created, or when you are logically organizing your code by attaching specific tools to specific agent instances.

1
2
3
4
5
6
7
8
from autogen.beta import Agent

agent = Agent(name="CalculatorBot")

@agent.tool
def multiply(a: int, b: int) -> int:
    """Multiplies two integers and returns the result."""
    return a * b

Synchronous and Asynchronous Tools#

Agent interactions are naturally asynchronous. To support this, tools are executed within an asynchronous event loop by default. However, you have the flexibility to define your tool functions as either synchronous (def) or asynchronous (async def).

To ensure that heavy computational tasks or blocking I/O operations do not freeze the entire application, synchronous tools are automatically executed in a separate thread by default.

# This synchronous tool runs in a separate thread to prevent blocking
@tool
def fetch_data_sync(url: str) -> str:
    """Fetches data from a URL using a blocking request library."""
    import requests
    return requests.get(url).text

# This native asynchronous tool runs directly in the main event loop
@tool
async def fetch_data_async(url: str) -> str:
    """Fetches data from a URL using an async request library."""
    import aiohttp
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

Disabling Threaded Execution#

If you have a synchronous function that executes very quickly (e.g., simple string manipulation or math) and you want to avoid the minor overhead of thread creation, you can disable threaded execution by passing sync_to_thread=False to the @tool decorator.

1
2
3
4
5
6
# This tool runs synchronously in the main event loop,
# without a separate thread
@tool(sync_to_thread=False)
def format_name(first_name: str, last_name: str) -> str:
    """Formats a full name."""
    return f"{last_name.upper()}, {first_name.capitalize()}"

Warning

When sync_to_thread=False is set, the synchronous tool runs directly within the asynchronous context. If the function performs time-consuming operations (like network requests or large loops), it will block the entire event loop until it finishes, preventing other agents or asynchronous tasks from making progress.

Customizing Tool Schemas#

LLMs perform best when they have precise constraints and detailed instructions. The framework automatically generates a tool's schema from its function signature and docstring. For more granular control, you can define minimum/maximum values, enforce specific formats, or override the tool's name.

You can override the basic properties directly in the decorator:

1
2
3
4
5
6
@tool(
    name="custom_math_tool",
    description="Performs advanced mathematical operations.",
)
def math_op(a: int, b: int) -> int:
    return a + b

Note

Explicitly setting the name and description overrides the automatically generated values.

Deep Schema Customization with Pydantic#

Under the hood, arguments are serialized and validated using Pydantic schemas. This means you can use standard pydantic.Field annotations to deeply customize individual schema parameters, providing the LLM with strict guidelines on what values are acceptable.

from typing import Annotated
from pydantic import Field

@tool
def set_temperature(
    temp: Annotated[
        int,
        Field(
            ...,
            description="The target temperature.",
            ge=10,
            le=30,
        ),
    ],
    mode: Annotated[
        str,
        Field(
            ...,
            description="The thermostat mode.",
            pattern="^(heat|cool|auto)$",
        ),
    ]
) -> str:
    """Sets the thermostat to a specific temperature and mode."""
    return f"Set to {temp}°C in {mode} mode."

Complete Custom Schema Example#

A complete example combining custom tool properties and strict parameter validation looks like this:

from typing import Annotated
from pydantic import Field
from autogen.beta import tool

@tool(
    name="create_user_profile",
    description="Creates a new user profile in the database.",
)
def create_profile(
    username: Annotated[
        str,
        Field(
            ...,
            description="The chosen username. Must be alphanumeric.",
            min_length=3,
            max_length=20,
        )
    ],
    age: Annotated[
        int,
        Field(
            ...,
            description="The user's age. Must be 18 or older.",
            ge=18,
        ),
    ],
) -> str:
    return f"Profile for {username} created."

This configuration generates the following detailed JSON schema, ensuring the LLM understands exactly what inputs are required and valid:

{
    "description": "Creates a new user profile in the database.",
    "name": "create_user_profile",
    "parameters": {
        "properties": {
            "username": {
                "description": "The chosen username. Must be alphanumeric.",
                "maxLength": 20,
                "minLength": 3,
                "title": "Username",
                "type": "string"
            },
            "age": {
                "description": "The user's age. Must be 18 or older.",
                "minimum": 18,
                "title": "Age",
                "type": "integer"
            }
        },
        "required": [
            "username",
            "age"
        ],
        "type": "object"
    }
}

Toolkits#

A Toolkit groups related tools into a single, reusable unit. Instead of passing individual tools one by one, you can bundle them into a toolkit and pass the whole collection to an agent. This is useful for organizing domain-specific capabilities (e.g., all database tools, all file-system tools) and sharing them across multiple agents.

from autogen.beta import Agent
from autogen.beta.tools import Toolkit

def search_orders(query: str) -> str:
    """Searches the order database."""
    return "Order #123"

def cancel_order(order_id: str) -> str:
    """Cancels an order by its ID."""
    return f"Order {order_id} cancelled."

support_tools = Toolkit(search_orders, cancel_order)

agent = Agent(name="SupportBot", tools=[support_tools])

A toolkit accepts both plain functions and @tool-decorated functions in its tools list. Plain functions are automatically converted, just like when passing them directly to an agent.

Registering Tools via a Decorator#

You can also add tools to a toolkit using the @toolkit.tool decorator, following the same pattern as @agent.tool:

from autogen.beta.tools import Toolkit

inventory = Toolkit()

@inventory.tool
def check_stock(item_id: str) -> int:
    """Returns the current stock count for an item."""
    return 42

@inventory.tool(name="reorder_item", description="Places a reorder for a low-stock item.")
def reorder(item_id: str, quantity: int) -> str:
    return f"Reordered {quantity} of {item_id}."

The toolkit can then be passed to any number of agents:

1
2
3
4
from autogen.beta import Agent

warehouse_agent = Agent(name="WarehouseBot", tools=[inventory])
sales_agent = Agent(name="SalesBot", tools=[inventory])

Combining Toolkits with Standalone Tools#

You can freely mix toolkits and individual tools in an agent's tools list:

1
2
3
4
5
6
7
8
9
@tool
def escalate(reason: str) -> str:
    """Escalates the conversation to a human agent."""
    return "Escalated."

agent = Agent(
    name="SupportBot",
    tools=[support_tools, inventory, escalate],
)

Execution Context#

Tools often need access to the broader execution context, such as injected dependencies, variables, or mechanisms for human-in-the-loop interactions. The AG2 framework supports these features natively.

Note

Under the hood, these contextual capabilities are powered by the FastDepends library, ensuring robust and FastAPI-like dependency management.

To access the execution context from within your tool, you can simply type-hint an argument with the Context object.

1
2
3
4
5
6
from autogen.beta import Context, tool

@tool
async def my_tool(context: Context) -> str:
    # Access context variables or dependencies here
    return f"Execution context: {context}"

For more detailed information on specific context features, see Dependency Injection, Context Variables, Depends, Human-in-the-loop.