In Agency Swarm, tools enable agents to perform specific actions and interact with external systems. The framework supports two approaches for creating custom tools depending on the version you’re using.
In the latest version, tools can be created using the @function_tool decorator with async functions; however, the BaseTool approach is still supported.
Feel free to explore the v0.x section of this page to find the method that suits you best.

Step-by-step Guide

1

Add Import Statements

Import the necessary modules for the new function-based approach.
from agency_swarm import function_tool, RunContextWrapper
from pydantic import BaseModel, Field, field_validator
from agency_swarm import MasterContext
2

Define Args Schema

Create a Pydantic model to define your tool’s input parameters.
class CalculatorArgs(BaseModel):
    expression: str = Field(..., description="The mathematical expression to evaluate")

    @field_validator("expression")
    @classmethod
    def validate_expression(cls, v):
        if "/0" in v:
            raise ValueError("Division by zero is not permitted")
        return v
3

Create the Tool Function

Define an async function with the @function_tool decorator.
@function_tool
async def calculator_tool(ctx: RunContextWrapper[MasterContext], args: CalculatorArgs) -> str:
    """
    A calculator tool that evaluates mathematical expressions.
    Use this when you need to perform mathematical calculations.
    """
    # Access shared context if needed
    calculation_count = ctx.context.get("calculations", 0)
    ctx.context.set("calculations", calculation_count + 1)

    # Perform the calculation
    result = eval(args.expression)
    return f"Result: {result}"
All parameters of the function tool, except for ctx, are treated as input parameters and included in the tool’s schema.For example, in the tool above, the input model CalculatorArgs is passed as the args parameter, so the input JSON would look like:
{"args": {"expression": "input_expression"}}
args is an example name, you can name your input parameters however you like, and you may define more than one input parameter if needed.
This example mentions tool context, which can be used as a shared storage for all agents and tools. For more info on agent context, refer to this page
4

Test the Tool

Test your tool function independently.
if __name__ == "__main__":
    import asyncio
    import json

    async def test_tool():
        ctx = MasterContext(user_context={"calculations": 1}, thread_manager=None, agents={})
        run_ctx = RunContextWrapper(context=ctx)

        args = CalculatorArgs(expression="2 + 2 * 3")
        # Wrap in args to comply with oai expected inputs
        args_json = {"args": args.model_dump()}

        result = await calculator_tool.on_invoke_tool(run_ctx, json.dumps(args_json))
        print(result)

    asyncio.run(test_tool())
5

Add Tool to Agent

Pass the function directly to the agent’s tools list.
from agency_swarm import Agent

agent = Agent(
    name="MathAgent",
    instructions="You are a helpful math assistant",
    tools=[calculator_tool],  # Pass function directly
    model="gpt-4.1"
)

Full Code Example

# calculator_tool.py
from agency_swarm import function_tool, RunContextWrapper
from pydantic import BaseModel, Field, field_validator
from agency_swarm import MasterContext

class CalculatorArgs(BaseModel):
    expression: str = Field(..., description="The mathematical expression to evaluate")

    @field_validator("expression")
    @classmethod
    def validate_expression(cls, v):
        if "/0" in v:
            raise ValueError("Division by zero is not permitted")
        return v

@function_tool
async def calculator_tool(ctx: RunContextWrapper[MasterContext], args: CalculatorArgs) -> str:
    """
    A calculator tool that evaluates mathematical expressions.
    Use this when you need to perform mathematical calculations.
    """
    # Access shared context if needed
    calculation_count = ctx.context.get("calculations", 0)
    ctx.context.set("calculations", calculation_count + 1)

    # Perform the calculation
    result = eval(args.expression)
    return f"Result: {result} (Calculation #{calculation_count + 1})"

if __name__ == "__main__":
    import asyncio
    import json

    async def test_tool():
        ctx = MasterContext(user_context={"calculations": 1}, thread_manager=None, agents={})
        run_ctx = RunContextWrapper(context=ctx)
        args = CalculatorArgs(expression="2 + 2 * 3")
        args_json = {"args": args.model_dump()}
        result = await calculator_tool.on_invoke_tool(run_ctx, json.dumps(args_json))
        print(result)

    asyncio.run(test_tool())

Next Steps