Custom Toolsets#
Creating custom toolsets allows you to extend Pantheon agents with domain-specific capabilities. All toolsets inherit from the ToolSet base class and use the @tool decorator to expose methods as tools.
Overview#
The toolset framework provides:
Automatic Tool Registration: Methods with
@tooldecorator are automatically exposedService Infrastructure: Built-in support for running as distributed services
Flexible Execution: Tools can run as local, thread, or process jobs
Lifecycle Management: Setup and cleanup hooks for resource management
MCP Compatibility: Optional Model Context Protocol server support
Creating a Custom Toolset#
Basic Structure#
All custom toolsets inherit from ToolSet:
from pantheon.toolsets.utils.toolset import ToolSet, tool
class MyCustomToolSet(ToolSet):
def __init__(self, name: str = "my_tools", **kwargs):
super().__init__(name, **kwargs)
# Initialize any resources
@tool
async def my_tool(self, param: str) -> str:
"""Tool description for the agent."""
return f"Processed: {param}"
async def run_setup(self):
"""Optional: Setup before the toolset starts."""
# Initialize connections, load models, etc.
pass
Tool Decorator Options#
The @tool decorator supports execution parameters:
class DataToolSet(ToolSet):
@tool(job_type="local") # Runs in the same process
async def quick_calc(self, x: int) -> int:
return x * 2
@tool(job_type="thread") # Runs in a thread pool
async def io_operation(self, url: str) -> str:
# Good for I/O-bound operations
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
@tool(job_type="process") # Runs in a separate process
async def heavy_computation(self, data: list) -> dict:
# Good for CPU-bound operations
result = expensive_computation(data)
return result
Resource Management#
Toolsets can manage resources with lifecycle hooks:
class DatabaseToolSet(ToolSet):
def __init__(self, db_url: str, **kwargs):
super().__init__("db_tools", **kwargs)
self.db_url = db_url
self.connection = None
async def run_setup(self):
"""Called before the toolset starts."""
self.connection = await asyncpg.connect(self.db_url)
@tool
async def query(self, sql: str) -> list[dict]:
"""Execute a SQL query."""
rows = await self.connection.fetch(sql)
return [dict(row) for row in rows]
# Cleanup happens automatically when the service stops
Running Toolsets#
As a Standalone Service#
Run your toolset as an independent service:
# In your toolset file
if __name__ == "__main__":
from pantheon.toolsets.utils.toolset import toolset_cli
toolset_cli(MyCustomToolSet, "my_custom_tools")
Then from command line:
# Run as Magique service
python -m my_toolset --service-name my_tools
# Run as MCP server
python -m my_toolset --mcp
With Context Manager#
For testing or temporary use:
from pantheon.toolsets.utils.toolset import run_toolsets
async with run_toolsets([MyCustomToolSet("tools")]):
# Toolsets are running
agent = Agent(name="assistant")
await agent.remote_toolset(toolset.service_id)
# Use the agent
Connecting from Agents#
Agents connect to toolsets using their service ID:
# Method 1: Direct connection
agent = Agent(name="assistant")
await agent.remote_toolset("your-service-id")
# Method 2: Auto-discovery by name
await agent.remote_toolset(service_name="my_tools")
Best Practices#
Type Annotations: Always use type hints for parameters and returns
Docstrings: Provide clear descriptions for agent understanding
Error Handling: Return informative error messages
Security: Validate inputs, especially for system operations
Stateless Design: Prefer stateless tools when possible
Resource Cleanup: Use lifecycle hooks for proper resource management
Common Patterns#
Stateful Tools#
For tools that need to maintain state:
class SessionToolSet(ToolSet):
def __init__(self, **kwargs):
super().__init__("session_tools", **kwargs)
self.sessions = {}
@tool
async def create_session(self, user_id: str) -> str:
"""Create a new session for a user."""
session_id = str(uuid.uuid4())
self.sessions[session_id] = {"user": user_id, "data": {}}
return session_id
@tool
async def update_session(self, session_id: str, key: str, value: any) -> bool:
"""Update session data."""
if session_id in self.sessions:
self.sessions[session_id]["data"][key] = value
return True
return False
Tool Composition#
Combine multiple capabilities:
class CompositeToolSet(ToolSet):
def __init__(self, **kwargs):
super().__init__("composite_tools", **kwargs)
self.text_processor = TextProcessor()
self.data_analyzer = DataAnalyzer()
@tool
async def analyze_document(self, doc_path: str) -> dict:
"""Extract and analyze document content."""
# Extract text
text = await self.text_processor.extract(doc_path)
# Analyze content
analysis = await self.data_analyzer.analyze(text)
return {
"word_count": len(text.split()),
"analysis": analysis
}