Skip to main content
Tools are functions that agents can call to interact with the world. The agent decides which tools to use based on the task at hand.

Defining Tools

from opper_agents import tool

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"Weather in {city}: Sunny, 22°C"

@tool
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email to a recipient."""
    # ... send email logic
    return f"Email sent to {to}"
TypeScript tool patterns: The SDK offers two ways to define tools:
  • createFunctionTool - Simple function-based approach (shown above). Best for getting started.
  • @tool decorator with classes - Groups related tools in a class, extracted with extractTools(). Better for organizing many tools. See Class Pattern below.

Using Tools with Agents

Pass tools to the agent constructor:
from opper_agents import Agent, tool

@tool
def search(query: str) -> str:
    """Search the web for information."""
    return f"Results for: {query}"

@tool
def calculate(expression: str) -> float:
    """Evaluate a math expression."""
    return eval(expression)

agent = Agent(
    name="AssistantAgent",
    tools=[search, calculate]
)

Tool Parameters

Simple Types

Tools can accept basic types directly:
@tool
def greet(name: str, formal: bool = False) -> str:
    """Greet a person."""
    if formal:
        return f"Good day, {name}."
    return f"Hey {name}!"

Complex Types with Schemas

For complex inputs, use structured schemas:
from pydantic import BaseModel, Field

class EmailParams(BaseModel):
    to: list[str] = Field(description="List of recipients")
    subject: str = Field(description="Email subject line")
    body: str = Field(description="Email body content")
    priority: str = Field(default="normal", description="Priority level")

@tool
def send_email(params: EmailParams) -> str:
    """Send an email with the given parameters."""
    recipients = ", ".join(params.to)
    return f"Email sent to {recipients}"

Async Tools

Tools can be async for I/O operations:
import httpx

@tool
async def fetch_url(url: str) -> str:
    """Fetch content from a URL."""
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        return response.text

Tool Results

Tools return values directly. Errors are caught automatically and passed to the LLM:
@tool
def divide(a: float, b: float) -> float:
    """Divide two numbers."""
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

Tool Execution Context

Tools receive execution context with useful information:
from opper_agents import tool
from opper_agents.base.context import ToolExecutionContext

@tool
def log_action(message: str, context: ToolExecutionContext) -> str:
    """Log an action with context."""
    print(f"[{context.agent_name}] Iteration {context.iteration}: {message}")
    return f"Logged: {message}"
The context includes:
  • agent_name / agentName: Name of the executing agent
  • iteration: Current loop iteration
  • span_id / spanId: Current trace span ID

Class Pattern (TypeScript)

For organizing many related tools, use the @tool decorator with classes:
import { Agent, tool, extractTools } from "@opperai/agents";
import { z } from "zod";

class MathTools {
  @tool({
    name: "add",
    description: "Add two numbers",
    schema: z.object({ a: z.number(), b: z.number() })
  })
  add({ a, b }: { a: number; b: number }) {
    return a + b;
  }

  @tool({
    name: "multiply",
    description: "Multiply two numbers",
    schema: z.object({ a: z.number(), b: z.number() })
  })
  multiply({ a, b }: { a: number; b: number }) {
    return a * b;
  }
}

// Extract all decorated methods as tools
const agent = new Agent({
  name: "MathAgent",
  tools: extractTools(new MathTools())
});
This pattern is useful when you have many tools that share context or belong together logically.

Best Practices

  1. Write clear descriptions: The LLM uses descriptions to decide when to call tools
  2. Use specific names: get_user_email is better than get_data
  3. Validate inputs: Use schemas to catch errors early
  4. Handle errors gracefully: Return helpful error messages
  5. Keep tools focused: One tool should do one thing well

Next Steps