Skip to main content

Overview

Combine different types of tools in a single task.

Usage

from upsonic import Agent, Task
from upsonic.tools import tool, ToolKit

# Function tool
@tool
def fetch_data(source: str) -> str:
    """Fetch data from source."""
    return f"Data from {source}"

# ToolKit
class DataProcessor(ToolKit):
    @tool
    def transform(self, data: str) -> str:
        """Transform data."""
        return data.upper()

    @tool
    def validate(self, data: str) -> bool:
        """Validate data."""
        return len(data) > 0

# Specialized agent
analyzer_agent = Agent(
    name="Data Analyzer",
    model="openai/gpt-4o",
    system_prompt="Analyze data patterns"
)

# Combine all tools
task = Task(
    description="Fetch, process, validate, and analyze data",
    tools=[
        fetch_data,
        DataProcessor(),
        analyzer_agent
    ]
)

agent = Agent(model="openai/gpt-4o", name="Main Agent")
result = agent.do(task)
print(result)

Supported Tool Types

The tools list accepts any combination of:
TypeExampleRegistered As
Function tool@tool decorated functionFunction name
ToolKit instanceDataProcessor()All @tool methods
Class instanceCalculator()All public methods
Agent instanceanalyzer_agentask_{agent_name}
MCP HandlerMCPHandler(...)All MCP-provided tools
KnowledgeBaseKnowledgeBase(...)search_{kb_name}
Builtin toolsWebSearchTool()Provider-specific

Deduplication

When combining tools, duplicate registrations are automatically ignored:
from upsonic.tools import tool

@tool
def shared_tool(x: int) -> int:
    """A tool used by multiple tasks."""
    return x * 2

# Adding the same tool multiple times is safe
task = Task(
    description="Process data",
    tools=[shared_tool, shared_tool, DataProcessor()]  # shared_tool registered once
)

Tool Name Conflicts

If multiple tools have the same name, the last one registered wins:
class Kit1(ToolKit):
    @tool
    def process(self, x: str) -> str:
        """Process from Kit1."""
        return f"kit1: {x}"

class Kit2(ToolKit):
    @tool
    def process(self, x: str) -> str:
        """Process from Kit2."""
        return f"kit2: {x}"

# Warning: Both have 'process' - Kit2's version overwrites Kit1's
task = Task(tools=[Kit1(), Kit2()])  # Only Kit2.process is available
Solution: Use unique method names or separate tasks.

Best Practices

  1. Group related tools: Combine tools that work together for a task
  2. Avoid name conflicts: Use unique, descriptive tool names
  3. Consider performance: More tools = more tokens in the prompt
  4. Remove unused tools: Keep the tool set focused for better agent performance
# Good: Focused tool set for a specific task
task = Task(
    description="Analyze sales data and create a report",
    tools=[database_tools, chart_generator, report_writer]
)

# Avoid: Too many unrelated tools
task = Task(
    description="Analyze sales data",
    tools=[database_tools, email_tools, calendar_tools, 
           chart_generator, weather_api, translation_tools]  # Unfocused
)