Tools#

Tools are code that can be executed by an agent to perform actions. A tool can be a simple function such as a calculator, or an API call to a third-party service such as stock price lookup or weather forecast. In the context of AI agents, tools are designed to be executed by agents in response to model-generated function calls.

AutoGen provides the autogen_core.tools module with a suite of built-in tools and utilities for creating and running custom tools.

Built-in Tools#

One of the built-in tools is the PythonCodeExecutionTool, which allows agents to execute Python code snippets.

Here is how you create the tool and use it.

from autogen_core import CancellationToken
from autogen_core.tools import PythonCodeExecutionTool
from autogen_ext.code_executors.docker import DockerCommandLineCodeExecutor

# Create the tool.
code_executor = DockerCommandLineCodeExecutor()
await code_executor.start()
code_execution_tool = PythonCodeExecutionTool(code_executor)
cancellation_token = CancellationToken()

# Use the tool directly without an agent.
code = "print('Hello, world!')"
result = await code_execution_tool.run_json({"code": code}, cancellation_token)
print(code_execution_tool.return_value_as_string(result))
Hello, world!

The DockerCommandLineCodeExecutor class is a built-in code executor that runs Python code snippets in a subprocess in the command line environment of a docker container. The PythonCodeExecutionTool class wraps the code executor and provides a simple interface to execute Python code snippets.

Other built-in tools will be added in the future.

Custom Function Tools#

A tool can also be a simple Python function that performs a specific action. To create a custom function tool, you just need to create a Python function and use the FunctionTool class to wrap it.

The FunctionTool class uses descriptions and type annotations to inform the LLM when and how to use a given function. The description provides context about the function’s purpose and intended use cases, while type annotations inform the LLM about the expected parameters and return type.

For example, a simple tool to obtain the stock price of a company might look like this:

import random

from autogen_core import CancellationToken
from autogen_core.tools import FunctionTool
from typing_extensions import Annotated


async def get_stock_price(ticker: str, date: Annotated[str, "Date in YYYY/MM/DD"]) -> float:
    # Returns a random stock price for demonstration purposes.
    return random.uniform(10, 200)


# Create a function tool.
stock_price_tool = FunctionTool(get_stock_price, description="Get the stock price.")

# Run the tool.
cancellation_token = CancellationToken()
result = await stock_price_tool.run_json({"ticker": "AAPL", "date": "2021/01/01"}, cancellation_token)

# Print the result.
print(stock_price_tool.return_value_as_string(result))
80.44429939059668

Tool-Equipped Agent#

To use tools with an agent, you can use ToolAgent, by using it in a composition pattern. Here is an example tool-use agent that uses ToolAgent as an inner agent for executing tools.

from dataclasses import dataclass
from typing import List

from autogen_core import (
    AgentId,
    AgentInstantiationContext,
    MessageContext,
    RoutedAgent,
    SingleThreadedAgentRuntime,
    message_handler,
)
from autogen_core.models import (
    ChatCompletionClient,
    LLMMessage,
    SystemMessage,
    UserMessage,
)
from autogen_core.tool_agent import ToolAgent, tool_agent_caller_loop
from autogen_core.tools import FunctionTool, Tool, ToolSchema
from autogen_ext.models.openai import OpenAIChatCompletionClient


@dataclass
class Message:
    content: str


class ToolUseAgent(RoutedAgent):
    def __init__(self, model_client: ChatCompletionClient, tool_schema: List[ToolSchema], tool_agent_type: str) -> None:
        super().__init__("An agent with tools")
        self._system_messages: List[LLMMessage] = [SystemMessage(content="You are a helpful AI assistant.")]
        self._model_client = model_client
        self._tool_schema = tool_schema
        self._tool_agent_id = AgentId(tool_agent_type, self.id.key)

    @message_handler
    async def handle_user_message(self, message: Message, ctx: MessageContext) -> Message:
        # Create a session of messages.
        session: List[LLMMessage] = [UserMessage(content=message.content, source="user")]
        # Run the caller loop to handle tool calls.
        messages = await tool_agent_caller_loop(
            self,
            tool_agent_id=self._tool_agent_id,
            model_client=self._model_client,
            input_messages=session,
            tool_schema=self._tool_schema,
            cancellation_token=ctx.cancellation_token,
        )
        # Return the final response.
        assert isinstance(messages[-1].content, str)
        return Message(content=messages[-1].content)

The ToolUseAgent class uses a convenience function tool_agent_caller_loop(), to handle the interaction between the model and the tool agent. The core idea can be described using a simple control flow graph:

ToolUseAgent control flow graph

The ToolUseAgent’s handle_user_message handler handles messages from the user, and determines whether the model has generated a tool call. If the model has generated tool calls, then the handler sends a function call message to the ToolAgent agent to execute the tools, and then queries the model again with the results of the tool calls. This process continues until the model stops generating tool calls, at which point the final response is returned to the user.

By having the tool execution logic in a separate agent, we expose the model-tool interactions to the agent runtime as messages, so the tool executions can be observed externally and intercepted if necessary.

To run the agent, we need to create a runtime and register the agent.

# Create a runtime.
runtime = SingleThreadedAgentRuntime()
# Create the tools.
tools: List[Tool] = [FunctionTool(get_stock_price, description="Get the stock price.")]
# Register the agents.
await ToolAgent.register(runtime, "tool_executor_agent", lambda: ToolAgent("tool executor agent", tools))
await ToolUseAgent.register(
    runtime,
    "tool_use_agent",
    lambda: ToolUseAgent(
        OpenAIChatCompletionClient(model="gpt-4o-mini"), [tool.schema for tool in tools], "tool_executor_agent"
    ),
)
AgentType(type='tool_use_agent')

This example uses the autogen_core.models.OpenAIChatCompletionClient, for Azure OpenAI and other clients, see Model Clients. Let’s test the agent with a question about stock price.

# Start processing messages.
runtime.start()
# Send a direct message to the tool agent.
tool_use_agent = AgentId("tool_use_agent", "default")
response = await runtime.send_message(Message("What is the stock price of NVDA on 2024/06/01?"), tool_use_agent)
print(response.content)
# Stop processing messages.
await runtime.stop()
The stock price of NVDA (NVIDIA Corporation) on June 1, 2024, was approximately $179.46.