Update to use toolsets and make them easy to create and configure
This commit is contained in:
121
README.md
121
README.md
@@ -1,13 +1,65 @@
|
||||
# Hermes Agent
|
||||
|
||||
An AI agent with advanced tool-calling capabilities, featuring a flexible toolsets system for organizing and managing tools.
|
||||
|
||||
## Features
|
||||
|
||||
- **Web Tools**: Search, extract content, and crawl websites
|
||||
- **Terminal Tools**: Execute commands with interactive session support
|
||||
- **Vision Tools**: Analyze images from URLs
|
||||
- **Reasoning Tools**: Advanced multi-model reasoning (Mixture of Agents)
|
||||
- **Creative Tools**: Generate images from text prompts
|
||||
- **Toolsets System**: Organize tools into logical groups for different scenarios
|
||||
|
||||
## Setup
|
||||
```
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
git clone git@github.com:NousResearch/hecate.git
|
||||
cd hecate
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
## Run
|
||||
## Toolsets System
|
||||
|
||||
The agent uses a toolsets system for organizing and managing tools. All tools must be part of a toolset to be accessible - individual tool selection is not supported. This ensures consistent and logical grouping of capabilities.
|
||||
|
||||
### Key Concepts
|
||||
|
||||
- **Toolsets**: Logical groups of tools for specific use cases (e.g., "research", "development", "debugging")
|
||||
- **Composition**: Toolsets can include other toolsets for powerful combinations
|
||||
- **Custom Toolsets**: Create your own toolsets at runtime or by editing `toolsets.py`
|
||||
- **Toolset-Only Access**: Tools are only accessible through toolsets, not individually
|
||||
|
||||
### Available Toolsets
|
||||
|
||||
See `toolsets.py` for the complete list of predefined toolsets including:
|
||||
- Basic toolsets (web, terminal, vision, creative, reasoning)
|
||||
- Composite toolsets (research, development, analysis, etc.)
|
||||
- Scenario-specific toolsets (debugging, documentation, API testing, etc.)
|
||||
- Special toolsets (safe mode without terminal, minimal, offline)
|
||||
|
||||
### Using Toolsets
|
||||
|
||||
```bash
|
||||
# Use a predefined toolset
|
||||
python run_agent.py --enabled_toolsets=research --query "Find latest AI papers"
|
||||
|
||||
# Combine multiple toolsets
|
||||
python run_agent.py --enabled_toolsets=web,vision --query "Analyze this website"
|
||||
|
||||
# Safe mode (no terminal access)
|
||||
python run_agent.py --enabled_toolsets=safe --query "Help without running commands"
|
||||
|
||||
# List all available toolsets and tools
|
||||
python run_agent.py --list_tools
|
||||
```
|
||||
|
||||
For detailed documentation on toolsets, see `TOOLSETS_README.md`.
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Default (all tools enabled)
|
||||
```bash
|
||||
python run_agent.py \
|
||||
--query "search up the latest docs on jit in python 3.13 and write me basic example that's not in their docs. profile its perf" \
|
||||
--max_turns 20 \
|
||||
@@ -15,3 +67,68 @@ python run_agent.py \
|
||||
--base_url https://api.anthropic.com/v1/ \
|
||||
--api_key $ANTHROPIC_API_KEY
|
||||
```
|
||||
|
||||
### With specific toolset
|
||||
```bash
|
||||
python run_agent.py \
|
||||
--query "Debug this Python error" \
|
||||
--enabled_toolsets=debugging \
|
||||
--model claude-sonnet-4-20250514 \
|
||||
--api_key $ANTHROPIC_API_KEY
|
||||
```
|
||||
|
||||
### Python API
|
||||
```python
|
||||
from run_agent import AIAgent
|
||||
|
||||
# Use a specific toolset
|
||||
agent = AIAgent(
|
||||
model="claude-opus-4-20250514",
|
||||
enabled_toolsets=["research"]
|
||||
)
|
||||
response = agent.chat("Find information about quantum computing")
|
||||
|
||||
# Create custom toolset at runtime
|
||||
from toolsets import create_custom_toolset
|
||||
|
||||
create_custom_toolset(
|
||||
name="my_tools",
|
||||
description="My custom toolkit",
|
||||
tools=["web_search"],
|
||||
includes=["terminal", "vision"]
|
||||
)
|
||||
|
||||
agent = AIAgent(enabled_toolsets=["my_tools"])
|
||||
```
|
||||
|
||||
## Command Line Arguments
|
||||
|
||||
- `--query`: The question or task for the agent
|
||||
- `--model`: Model to use (default: claude-opus-4-20250514)
|
||||
- `--api_key`: API key for authentication
|
||||
- `--base_url`: API endpoint URL
|
||||
- `--max_turns`: Maximum number of tool-calling iterations
|
||||
- `--enabled_toolsets`: Comma-separated list of toolsets to enable
|
||||
- `--disabled_toolsets`: Comma-separated list of toolsets to disable
|
||||
- `--list_tools`: List all available toolsets and tools
|
||||
- `--save_trajectories`: Save conversation trajectories to JSONL files
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Set these environment variables to enable different tools:
|
||||
|
||||
- `FIRECRAWL_API_KEY`: For web tools (search, extract, crawl)
|
||||
- `MORPH_API_KEY`: For terminal tools
|
||||
- `NOUS_API_KEY`: For vision and reasoning tools
|
||||
- `FAL_KEY`: For image generation tools
|
||||
- `ANTHROPIC_API_KEY`: For the main agent model
|
||||
|
||||
## Documentation
|
||||
|
||||
- `TOOLSETS_README.md`: Comprehensive guide to the toolsets system
|
||||
- `toolsets.py`: View and modify available toolsets
|
||||
- `model_tools.py`: Core tool definitions and handlers
|
||||
|
||||
## Examples
|
||||
|
||||
See `TOOLSETS_README.md` for extensive examples of using different toolsets for various scenarios.
|
||||
|
||||
238
model_tools.py
238
model_tools.py
@@ -23,7 +23,7 @@ Usage:
|
||||
web_tools = get_tool_definitions(enabled_toolsets=['web_tools'])
|
||||
|
||||
# Handle function calls from model
|
||||
result = handle_function_call("web_search", {"query": "Python", "limit": 3})
|
||||
result = handle_function_call("web_search", {"query": "Python"})
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -35,6 +35,11 @@ from terminal_tool import terminal_tool, check_hecate_requirements, TERMINAL_TOO
|
||||
from vision_tools import vision_analyze_tool, check_vision_requirements
|
||||
from mixture_of_agents_tool import mixture_of_agents_tool, check_moa_requirements
|
||||
from image_generation_tool import image_generate_tool, check_image_generation_requirements
|
||||
from toolsets import (
|
||||
get_toolset, resolve_toolset, resolve_multiple_toolsets,
|
||||
get_all_toolsets, get_toolset_names, validate_toolset,
|
||||
get_toolset_info, print_toolset_tree
|
||||
)
|
||||
|
||||
def get_web_tool_definitions() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
@@ -48,20 +53,13 @@ def get_web_tool_definitions() -> List[Dict[str, Any]]:
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "web_search",
|
||||
"description": "Search the web for information on any topic. Returns relevant results with titles and URLs. Uses advanced search depth for comprehensive results.",
|
||||
"description": "Search the web for information on any topic. Returns up to 5 relevant results with titles and URLs. Uses advanced search depth for comprehensive results.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "The search query to look up on the web"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of results to return (default: 5, max: 10)",
|
||||
"default": 5,
|
||||
"minimum": 1,
|
||||
"maximum": 10
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
@@ -308,145 +306,146 @@ def get_toolset_for_tool(tool_name: str) -> str:
|
||||
|
||||
|
||||
def get_tool_definitions(
|
||||
enabled_tools: List[str] = None,
|
||||
disabled_tools: List[str] = None,
|
||||
enabled_toolsets: List[str] = None,
|
||||
disabled_toolsets: List[str] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Get tool definitions for model API calls with optional filtering.
|
||||
Get tool definitions for model API calls with toolset-based filtering.
|
||||
|
||||
This function aggregates tool definitions from all available toolsets
|
||||
and applies filtering based on the provided parameters.
|
||||
|
||||
Filter Priority (higher priority overrides lower):
|
||||
1. enabled_tools (highest priority - only these tools, overrides everything)
|
||||
2. disabled_tools (applied after toolset filtering)
|
||||
3. enabled_toolsets (only tools from these toolsets)
|
||||
4. disabled_toolsets (exclude tools from these toolsets)
|
||||
This function aggregates tool definitions from available toolsets.
|
||||
All tools must be part of a toolset to be accessible. Individual tool
|
||||
selection is not supported - use toolsets to organize and select tools.
|
||||
|
||||
Args:
|
||||
enabled_tools (List[str]): Only include these specific tools. If provided,
|
||||
ONLY these tools will be included (overrides all other filters)
|
||||
disabled_tools (List[str]): Exclude these specific tools (applied after toolset filtering)
|
||||
enabled_toolsets (List[str]): Only include tools from these toolsets
|
||||
disabled_toolsets (List[str]): Exclude tools from these toolsets
|
||||
enabled_toolsets (List[str]): Only include tools from these toolsets.
|
||||
If None, all available tools are included.
|
||||
disabled_toolsets (List[str]): Exclude tools from these toolsets.
|
||||
Applied only if enabled_toolsets is None.
|
||||
|
||||
Returns:
|
||||
List[Dict]: Filtered list of tool definitions
|
||||
|
||||
Examples:
|
||||
# Only web tools
|
||||
tools = get_tool_definitions(enabled_toolsets=["web_tools"])
|
||||
# Use predefined toolsets
|
||||
tools = get_tool_definitions(enabled_toolsets=["research"])
|
||||
tools = get_tool_definitions(enabled_toolsets=["development"])
|
||||
|
||||
# All tools except terminal
|
||||
tools = get_tool_definitions(disabled_tools=["terminal"])
|
||||
# Combine multiple toolsets
|
||||
tools = get_tool_definitions(enabled_toolsets=["web", "vision"])
|
||||
|
||||
# Only specific tools (overrides toolset filters)
|
||||
tools = get_tool_definitions(enabled_tools=["web_search", "web_extract"])
|
||||
# All tools except those in terminal toolset
|
||||
tools = get_tool_definitions(disabled_toolsets=["terminal"])
|
||||
|
||||
# Conflicting filters (enabled_tools wins)
|
||||
tools = get_tool_definitions(enabled_toolsets=["web_tools"], enabled_tools=["terminal"])
|
||||
# Result: Only terminal tool (enabled_tools overrides enabled_toolsets)
|
||||
# Default - all available tools
|
||||
tools = get_tool_definitions()
|
||||
"""
|
||||
# Detect and warn about potential conflicts
|
||||
conflicts_detected = False
|
||||
# Collect all available tool definitions
|
||||
all_available_tools_map = {}
|
||||
|
||||
if enabled_tools and (enabled_toolsets or disabled_toolsets or disabled_tools):
|
||||
print("⚠️ enabled_tools overrides all other filters")
|
||||
conflicts_detected = True
|
||||
# Map tool names to their definitions
|
||||
if check_firecrawl_api_key():
|
||||
for tool in get_web_tool_definitions():
|
||||
all_available_tools_map[tool["function"]["name"]] = tool
|
||||
|
||||
if enabled_toolsets and disabled_toolsets:
|
||||
# Check for overlap
|
||||
enabled_set = set(enabled_toolsets)
|
||||
disabled_set = set(disabled_toolsets)
|
||||
overlap = enabled_set & disabled_set
|
||||
if overlap:
|
||||
print(f"⚠️ Conflicting toolsets: {overlap} in both enabled and disabled")
|
||||
print(f" → enabled_toolsets takes priority")
|
||||
conflicts_detected = True
|
||||
if check_hecate_requirements():
|
||||
for tool in get_terminal_tool_definitions():
|
||||
all_available_tools_map[tool["function"]["name"]] = tool
|
||||
|
||||
if enabled_tools and disabled_tools:
|
||||
# Check for overlap
|
||||
enabled_set = set(enabled_tools)
|
||||
disabled_set = set(disabled_tools)
|
||||
overlap = enabled_set & disabled_set
|
||||
if overlap:
|
||||
print(f"⚠️ Conflicting tools: {overlap} in both enabled and disabled")
|
||||
print(f" → enabled_tools takes priority")
|
||||
conflicts_detected = True
|
||||
if check_vision_requirements():
|
||||
for tool in get_vision_tool_definitions():
|
||||
all_available_tools_map[tool["function"]["name"]] = tool
|
||||
|
||||
all_tools = []
|
||||
if check_moa_requirements():
|
||||
for tool in get_moa_tool_definitions():
|
||||
all_available_tools_map[tool["function"]["name"]] = tool
|
||||
|
||||
# Collect all available tools from each toolset
|
||||
toolset_tools = {
|
||||
"web_tools": get_web_tool_definitions() if check_firecrawl_api_key() else [],
|
||||
"terminal_tools": get_terminal_tool_definitions() if check_hecate_requirements() else [],
|
||||
"vision_tools": get_vision_tool_definitions() if check_vision_requirements() else [],
|
||||
"moa_tools": get_moa_tool_definitions() if check_moa_requirements() else [],
|
||||
"image_tools": get_image_tool_definitions() if check_image_generation_requirements() else []
|
||||
}
|
||||
if check_image_generation_requirements():
|
||||
for tool in get_image_tool_definitions():
|
||||
all_available_tools_map[tool["function"]["name"]] = tool
|
||||
|
||||
# HIGHEST PRIORITY: enabled_tools (overrides everything)
|
||||
if enabled_tools:
|
||||
if conflicts_detected:
|
||||
print(f"🎯 Using only enabled_tools: {enabled_tools}")
|
||||
|
||||
# Collect all available tools first
|
||||
all_available_tools = []
|
||||
for tools in toolset_tools.values():
|
||||
all_available_tools.extend(tools)
|
||||
|
||||
# Only include specifically enabled tools
|
||||
tool_names_to_include = set(enabled_tools)
|
||||
filtered_tools = [
|
||||
tool for tool in all_available_tools
|
||||
if tool["function"]["name"] in tool_names_to_include
|
||||
]
|
||||
|
||||
# Warn about requested tools that aren't available
|
||||
found_tools = {tool["function"]["name"] for tool in filtered_tools}
|
||||
missing_tools = tool_names_to_include - found_tools
|
||||
if missing_tools:
|
||||
print(f"⚠️ Requested tools not available: {missing_tools}")
|
||||
|
||||
return filtered_tools
|
||||
# Determine which tools to include based on toolsets
|
||||
tools_to_include = set()
|
||||
|
||||
# Apply toolset-level filtering first
|
||||
if enabled_toolsets:
|
||||
# Only include tools from enabled toolsets
|
||||
for toolset_name in enabled_toolsets:
|
||||
if toolset_name in toolset_tools:
|
||||
all_tools.extend(toolset_tools[toolset_name])
|
||||
if validate_toolset(toolset_name):
|
||||
resolved_tools = resolve_toolset(toolset_name)
|
||||
tools_to_include.update(resolved_tools)
|
||||
print(f"✅ Enabled toolset '{toolset_name}': {', '.join(resolved_tools) if resolved_tools else 'no tools'}")
|
||||
else:
|
||||
print(f"⚠️ Unknown toolset: {toolset_name}")
|
||||
# Try legacy compatibility
|
||||
if toolset_name in ["web_tools", "terminal_tools", "vision_tools", "moa_tools", "image_tools"]:
|
||||
# Map legacy names to new system
|
||||
legacy_map = {
|
||||
"web_tools": ["web_search", "web_extract", "web_crawl"],
|
||||
"terminal_tools": ["terminal"],
|
||||
"vision_tools": ["vision_analyze"],
|
||||
"moa_tools": ["mixture_of_agents"],
|
||||
"image_tools": ["image_generate"]
|
||||
}
|
||||
legacy_tools = legacy_map.get(toolset_name, [])
|
||||
tools_to_include.update(legacy_tools)
|
||||
print(f"✅ Enabled legacy toolset '{toolset_name}': {', '.join(legacy_tools)}")
|
||||
else:
|
||||
print(f"⚠️ Unknown toolset: {toolset_name}")
|
||||
elif disabled_toolsets:
|
||||
# Include all tools except from disabled toolsets
|
||||
for toolset_name, tools in toolset_tools.items():
|
||||
if toolset_name not in disabled_toolsets:
|
||||
all_tools.extend(tools)
|
||||
# Start with all tools from all toolsets, then remove disabled ones
|
||||
# Note: Only tools that are part of toolsets are accessible
|
||||
# We need to get all tools from all defined toolsets
|
||||
from toolsets import get_all_toolsets
|
||||
all_toolset_tools = set()
|
||||
for toolset_name in get_all_toolsets():
|
||||
resolved_tools = resolve_toolset(toolset_name)
|
||||
all_toolset_tools.update(resolved_tools)
|
||||
|
||||
# Start with all tools from toolsets
|
||||
tools_to_include = all_toolset_tools
|
||||
|
||||
# Remove tools from disabled toolsets
|
||||
for toolset_name in disabled_toolsets:
|
||||
if validate_toolset(toolset_name):
|
||||
resolved_tools = resolve_toolset(toolset_name)
|
||||
tools_to_include.difference_update(resolved_tools)
|
||||
print(f"🚫 Disabled toolset '{toolset_name}': {', '.join(resolved_tools) if resolved_tools else 'no tools'}")
|
||||
else:
|
||||
# Try legacy compatibility
|
||||
if toolset_name in ["web_tools", "terminal_tools", "vision_tools", "moa_tools", "image_tools"]:
|
||||
legacy_map = {
|
||||
"web_tools": ["web_search", "web_extract", "web_crawl"],
|
||||
"terminal_tools": ["terminal"],
|
||||
"vision_tools": ["vision_analyze"],
|
||||
"moa_tools": ["mixture_of_agents"],
|
||||
"image_tools": ["image_generate"]
|
||||
}
|
||||
legacy_tools = legacy_map.get(toolset_name, [])
|
||||
tools_to_include.difference_update(legacy_tools)
|
||||
print(f"🚫 Disabled legacy toolset '{toolset_name}': {', '.join(legacy_tools)}")
|
||||
else:
|
||||
print(f"⚠️ Unknown toolset: {toolset_name}")
|
||||
else:
|
||||
# Include all available tools
|
||||
for tools in toolset_tools.values():
|
||||
all_tools.extend(tools)
|
||||
# No filtering - include all tools from all defined toolsets
|
||||
from toolsets import get_all_toolsets
|
||||
for toolset_name in get_all_toolsets():
|
||||
resolved_tools = resolve_toolset(toolset_name)
|
||||
tools_to_include.update(resolved_tools)
|
||||
|
||||
# Apply tool-level filtering (disabled_tools)
|
||||
if disabled_tools:
|
||||
tool_names_to_exclude = set(disabled_tools)
|
||||
original_tools = [tool["function"]["name"] for tool in all_tools]
|
||||
|
||||
all_tools = [
|
||||
tool for tool in all_tools
|
||||
if tool["function"]["name"] not in tool_names_to_exclude
|
||||
]
|
||||
|
||||
# Show what was actually filtered out
|
||||
remaining_tools = {tool["function"]["name"] for tool in all_tools}
|
||||
actually_excluded = set(original_tools) & tool_names_to_exclude
|
||||
if actually_excluded:
|
||||
print(f"🚫 Excluded tools: {actually_excluded}")
|
||||
# Build final tool list (only include tools that are available)
|
||||
filtered_tools = []
|
||||
for tool_name in tools_to_include:
|
||||
if tool_name in all_available_tools_map:
|
||||
filtered_tools.append(all_available_tools_map[tool_name])
|
||||
|
||||
return all_tools
|
||||
# Sort tools for consistent ordering
|
||||
filtered_tools.sort(key=lambda t: t["function"]["name"])
|
||||
|
||||
if filtered_tools:
|
||||
tool_names = [t["function"]["name"] for t in filtered_tools]
|
||||
print(f"🛠️ Final tool selection ({len(filtered_tools)} tools): {', '.join(tool_names)}")
|
||||
else:
|
||||
print("🛠️ No tools selected (all filtered out or unavailable)")
|
||||
|
||||
return filtered_tools
|
||||
|
||||
def handle_web_function_call(function_name: str, function_args: Dict[str, Any]) -> str:
|
||||
"""
|
||||
@@ -461,9 +460,8 @@ def handle_web_function_call(function_name: str, function_args: Dict[str, Any])
|
||||
"""
|
||||
if function_name == "web_search":
|
||||
query = function_args.get("query", "")
|
||||
limit = function_args.get("limit", 5)
|
||||
# Ensure limit is within bounds
|
||||
limit = max(1, min(10, limit))
|
||||
# Always use fixed limit of 5
|
||||
limit = 5
|
||||
return web_search_tool(query, limit)
|
||||
|
||||
elif function_name == "web_extract":
|
||||
|
||||
1253
run_agent.py
1253
run_agent.py
File diff suppressed because it is too large
Load Diff
14
test_run.sh
14
test_run.sh
@@ -17,14 +17,14 @@ export WEB_TOOLS_DEBUG=true
|
||||
python run_agent.py \
|
||||
--query "$PROMPT" \
|
||||
--max_turns 30 \
|
||||
# --model claude-sonnet-4-20250514 \
|
||||
# --base_url https://api.anthropic.com/v1/ \
|
||||
--model hermes-4-70B \
|
||||
--base_url http://bore.pub:8292/v1 \
|
||||
--model claude-sonnet-4-20250514 \
|
||||
--base_url https://api.anthropic.com/v1/ \
|
||||
--api_key $ANTHROPIC_API_KEY \
|
||||
--save_trajectories
|
||||
#--enabled_toolsets=vision_tools
|
||||
|
||||
--save_trajectories \
|
||||
--enabled_toolsets=web
|
||||
|
||||
# --model claude-sonnet-4-20250514 \
|
||||
#
|
||||
#Possible Toolsets:
|
||||
#web_tools
|
||||
#vision_tools
|
||||
|
||||
326
toolsets.py
Normal file
326
toolsets.py
Normal file
@@ -0,0 +1,326 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Toolsets Module
|
||||
|
||||
This module provides a flexible system for defining and managing tool aliases/toolsets.
|
||||
Toolsets allow you to group tools together for specific scenarios and can be composed
|
||||
from individual tools or other toolsets.
|
||||
|
||||
Features:
|
||||
- Define custom toolsets with specific tools
|
||||
- Compose toolsets from other toolsets
|
||||
- Built-in common toolsets for typical use cases
|
||||
- Easy extension for new toolsets
|
||||
- Support for dynamic toolset resolution
|
||||
|
||||
Usage:
|
||||
from toolsets import get_toolset, resolve_toolset, get_all_toolsets
|
||||
|
||||
# Get tools for a specific toolset
|
||||
tools = get_toolset("research")
|
||||
|
||||
# Resolve a toolset to get all tool names (including from composed toolsets)
|
||||
all_tools = resolve_toolset("full_stack")
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Any, Set, Optional
|
||||
import json
|
||||
|
||||
|
||||
# Core toolset definitions
|
||||
# These can include individual tools or reference other toolsets
|
||||
TOOLSETS = {
|
||||
# Basic toolsets - individual tool categories
|
||||
"web": {
|
||||
"description": "Web research and content extraction tools",
|
||||
"tools": ["web_search", "web_extract", "web_crawl"],
|
||||
"includes": [] # No other toolsets included
|
||||
},
|
||||
|
||||
"vision": {
|
||||
"description": "Image analysis and vision tools",
|
||||
"tools": ["vision_analyze"],
|
||||
"includes": []
|
||||
},
|
||||
|
||||
"image_gen": {
|
||||
"description": "Creative generation tools (images)",
|
||||
"tools": ["image_generate"],
|
||||
"includes": []
|
||||
},
|
||||
|
||||
"terminal": {
|
||||
"description": "Terminal/command execution tools",
|
||||
"tools": ["terminal"],
|
||||
"includes": []
|
||||
},
|
||||
|
||||
"moa": {
|
||||
"description": "Advanced reasoning and problem-solving tools",
|
||||
"tools": ["mixture_of_agents"],
|
||||
"includes": []
|
||||
},
|
||||
|
||||
# Scenario-specific toolsets
|
||||
|
||||
"debugging": {
|
||||
"description": "Debugging and troubleshooting toolkit",
|
||||
"tools": ["terminal"],
|
||||
"includes": ["web"] # For searching error messages and solutions
|
||||
},
|
||||
|
||||
"safe": {
|
||||
"description": "Safe toolkit without terminal access",
|
||||
"tools": ["mixture_of_agents"],
|
||||
"includes": ["web", "vision", "creative"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
def get_toolset(name: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get a toolset definition by name.
|
||||
|
||||
Args:
|
||||
name (str): Name of the toolset
|
||||
|
||||
Returns:
|
||||
Dict: Toolset definition with description, tools, and includes
|
||||
None: If toolset not found
|
||||
"""
|
||||
# Return toolset definition
|
||||
return TOOLSETS.get(name)
|
||||
|
||||
|
||||
def resolve_toolset(name: str, visited: Set[str] = None) -> List[str]:
|
||||
"""
|
||||
Recursively resolve a toolset to get all tool names.
|
||||
|
||||
This function handles toolset composition by recursively resolving
|
||||
included toolsets and combining all tools.
|
||||
|
||||
Args:
|
||||
name (str): Name of the toolset to resolve
|
||||
visited (Set[str]): Set of already visited toolsets (for cycle detection)
|
||||
|
||||
Returns:
|
||||
List[str]: List of all tool names in the toolset
|
||||
"""
|
||||
if visited is None:
|
||||
visited = set()
|
||||
|
||||
# Check for cycles
|
||||
if name in visited:
|
||||
print(f"⚠️ Circular dependency detected in toolset '{name}'")
|
||||
return []
|
||||
|
||||
visited.add(name)
|
||||
|
||||
# Get toolset definition
|
||||
toolset = TOOLSETS.get(name)
|
||||
if not toolset:
|
||||
return []
|
||||
|
||||
# Collect direct tools
|
||||
tools = set(toolset.get("tools", []))
|
||||
|
||||
# Recursively resolve included toolsets
|
||||
for included_name in toolset.get("includes", []):
|
||||
included_tools = resolve_toolset(included_name, visited.copy())
|
||||
tools.update(included_tools)
|
||||
|
||||
return list(tools)
|
||||
|
||||
|
||||
def resolve_multiple_toolsets(toolset_names: List[str]) -> List[str]:
|
||||
"""
|
||||
Resolve multiple toolsets and combine their tools.
|
||||
|
||||
Args:
|
||||
toolset_names (List[str]): List of toolset names to resolve
|
||||
|
||||
Returns:
|
||||
List[str]: Combined list of all tool names (deduplicated)
|
||||
"""
|
||||
all_tools = set()
|
||||
|
||||
for name in toolset_names:
|
||||
tools = resolve_toolset(name)
|
||||
all_tools.update(tools)
|
||||
|
||||
return list(all_tools)
|
||||
|
||||
|
||||
def get_all_toolsets() -> Dict[str, Dict[str, Any]]:
|
||||
"""
|
||||
Get all available toolsets with their definitions.
|
||||
|
||||
Returns:
|
||||
Dict: All toolset definitions
|
||||
"""
|
||||
return TOOLSETS.copy()
|
||||
|
||||
|
||||
def get_toolset_names() -> List[str]:
|
||||
"""
|
||||
Get names of all available toolsets (excluding aliases).
|
||||
|
||||
Returns:
|
||||
List[str]: List of toolset names
|
||||
"""
|
||||
return list(TOOLSETS.keys())
|
||||
|
||||
|
||||
|
||||
|
||||
def validate_toolset(name: str) -> bool:
|
||||
"""
|
||||
Check if a toolset name is valid.
|
||||
|
||||
Args:
|
||||
name (str): Toolset name to validate
|
||||
|
||||
Returns:
|
||||
bool: True if valid, False otherwise
|
||||
"""
|
||||
return name in TOOLSETS
|
||||
|
||||
|
||||
def create_custom_toolset(
|
||||
name: str,
|
||||
description: str,
|
||||
tools: List[str] = None,
|
||||
includes: List[str] = None
|
||||
) -> None:
|
||||
"""
|
||||
Create a custom toolset at runtime.
|
||||
|
||||
Args:
|
||||
name (str): Name for the new toolset
|
||||
description (str): Description of the toolset
|
||||
tools (List[str]): Direct tools to include
|
||||
includes (List[str]): Other toolsets to include
|
||||
"""
|
||||
TOOLSETS[name] = {
|
||||
"description": description,
|
||||
"tools": tools or [],
|
||||
"includes": includes or []
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
def get_toolset_info(name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Get detailed information about a toolset including resolved tools.
|
||||
|
||||
Args:
|
||||
name (str): Toolset name
|
||||
|
||||
Returns:
|
||||
Dict: Detailed toolset information
|
||||
"""
|
||||
toolset = get_toolset(name)
|
||||
if not toolset:
|
||||
return None
|
||||
|
||||
resolved_tools = resolve_toolset(name)
|
||||
|
||||
return {
|
||||
"name": name,
|
||||
"description": toolset["description"],
|
||||
"direct_tools": toolset["tools"],
|
||||
"includes": toolset["includes"],
|
||||
"resolved_tools": resolved_tools,
|
||||
"tool_count": len(resolved_tools),
|
||||
"is_composite": len(toolset["includes"]) > 0
|
||||
}
|
||||
|
||||
|
||||
def print_toolset_tree(name: str, indent: int = 0) -> None:
|
||||
"""
|
||||
Print a tree view of a toolset and its composition.
|
||||
|
||||
Args:
|
||||
name (str): Toolset name
|
||||
indent (int): Current indentation level
|
||||
"""
|
||||
prefix = " " * indent
|
||||
toolset = get_toolset(name)
|
||||
|
||||
if not toolset:
|
||||
print(f"{prefix}❌ Unknown toolset: {name}")
|
||||
return
|
||||
|
||||
# Print toolset name and description
|
||||
print(f"{prefix}📦 {name}: {toolset['description']}")
|
||||
|
||||
# Print direct tools
|
||||
if toolset["tools"]:
|
||||
print(f"{prefix} 🔧 Tools: {', '.join(toolset['tools'])}")
|
||||
|
||||
# Print included toolsets
|
||||
if toolset["includes"]:
|
||||
print(f"{prefix} 📂 Includes:")
|
||||
for included in toolset["includes"]:
|
||||
print_toolset_tree(included, indent + 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""
|
||||
Demo and testing of the toolsets system
|
||||
"""
|
||||
print("🎯 Toolsets System Demo")
|
||||
print("=" * 60)
|
||||
|
||||
# Show all available toolsets
|
||||
print("\n📦 Available Toolsets:")
|
||||
print("-" * 40)
|
||||
for name, toolset in get_all_toolsets().items():
|
||||
info = get_toolset_info(name)
|
||||
composite = "📂" if info["is_composite"] else "🔧"
|
||||
print(f"{composite} {name:20} - {toolset['description']}")
|
||||
print(f" Tools: {len(info['resolved_tools'])} total")
|
||||
|
||||
|
||||
# Demo toolset resolution
|
||||
print("\n🔍 Toolset Resolution Examples:")
|
||||
print("-" * 40)
|
||||
|
||||
examples = ["research", "development", "full_stack", "minimal", "safe"]
|
||||
for name in examples:
|
||||
tools = resolve_toolset(name)
|
||||
print(f"\n{name}:")
|
||||
print(f" Resolved to {len(tools)} tools: {', '.join(sorted(tools))}")
|
||||
|
||||
# Show toolset composition tree
|
||||
print("\n🌳 Toolset Composition Tree:")
|
||||
print("-" * 40)
|
||||
print("\nExample: 'content_creation' toolset:")
|
||||
print_toolset_tree("content_creation")
|
||||
|
||||
print("\nExample: 'full_stack' toolset:")
|
||||
print_toolset_tree("full_stack")
|
||||
|
||||
# Demo multiple toolset resolution
|
||||
print("\n🔗 Multiple Toolset Resolution:")
|
||||
print("-" * 40)
|
||||
combined = resolve_multiple_toolsets(["minimal", "vision", "reasoning"])
|
||||
print(f"Combining ['minimal', 'vision', 'reasoning']:")
|
||||
print(f" Result: {', '.join(sorted(combined))}")
|
||||
|
||||
# Demo custom toolset creation
|
||||
print("\n➕ Custom Toolset Creation:")
|
||||
print("-" * 40)
|
||||
create_custom_toolset(
|
||||
name="my_custom",
|
||||
description="My custom toolset for specific tasks",
|
||||
tools=["web_search"],
|
||||
includes=["terminal", "vision"]
|
||||
)
|
||||
|
||||
custom_info = get_toolset_info("my_custom")
|
||||
print(f"Created 'my_custom' toolset:")
|
||||
print(f" Description: {custom_info['description']}")
|
||||
print(f" Resolved tools: {', '.join(custom_info['resolved_tools'])}")
|
||||
Reference in New Issue
Block a user