Update to use toolsets and make them easy to create and configure
This commit is contained in:
parent
c7fa4447b8
commit
17608c1142
5 changed files with 1210 additions and 742 deletions
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
|
## Setup
|
||||||
```
|
```bash
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
git clone git@github.com:NousResearch/hecate.git
|
git clone git@github.com:NousResearch/hecate.git
|
||||||
cd hecate
|
cd hecate
|
||||||
pip install -e .
|
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 \
|
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" \
|
--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 \
|
--max_turns 20 \
|
||||||
|
|
@ -15,3 +67,68 @@ python run_agent.py \
|
||||||
--base_url https://api.anthropic.com/v1/ \
|
--base_url https://api.anthropic.com/v1/ \
|
||||||
--api_key $ANTHROPIC_API_KEY
|
--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'])
|
web_tools = get_tool_definitions(enabled_toolsets=['web_tools'])
|
||||||
|
|
||||||
# Handle function calls from model
|
# 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
|
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 vision_tools import vision_analyze_tool, check_vision_requirements
|
||||||
from mixture_of_agents_tool import mixture_of_agents_tool, check_moa_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 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]]:
|
def get_web_tool_definitions() -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -48,20 +53,13 @@ def get_web_tool_definitions() -> List[Dict[str, Any]]:
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": "web_search",
|
"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": {
|
"parameters": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"query": {
|
"query": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "The search query to look up on the web"
|
"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"]
|
"required": ["query"]
|
||||||
|
|
@ -308,145 +306,146 @@ def get_toolset_for_tool(tool_name: str) -> str:
|
||||||
|
|
||||||
|
|
||||||
def get_tool_definitions(
|
def get_tool_definitions(
|
||||||
enabled_tools: List[str] = None,
|
|
||||||
disabled_tools: List[str] = None,
|
|
||||||
enabled_toolsets: List[str] = None,
|
enabled_toolsets: List[str] = None,
|
||||||
disabled_toolsets: List[str] = None
|
disabled_toolsets: List[str] = None
|
||||||
) -> List[Dict[str, Any]]:
|
) -> 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
|
This function aggregates tool definitions from available toolsets.
|
||||||
and applies filtering based on the provided parameters.
|
All tools must be part of a toolset to be accessible. Individual tool
|
||||||
|
selection is not supported - use toolsets to organize and select tools.
|
||||||
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)
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
enabled_tools (List[str]): Only include these specific tools. If provided,
|
enabled_toolsets (List[str]): Only include tools from these toolsets.
|
||||||
ONLY these tools will be included (overrides all other filters)
|
If None, all available tools are included.
|
||||||
disabled_tools (List[str]): Exclude these specific tools (applied after toolset filtering)
|
disabled_toolsets (List[str]): Exclude tools from these toolsets.
|
||||||
enabled_toolsets (List[str]): Only include tools from these toolsets
|
Applied only if enabled_toolsets is None.
|
||||||
disabled_toolsets (List[str]): Exclude tools from these toolsets
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List[Dict]: Filtered list of tool definitions
|
List[Dict]: Filtered list of tool definitions
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
# Only web tools
|
# Use predefined toolsets
|
||||||
tools = get_tool_definitions(enabled_toolsets=["web_tools"])
|
tools = get_tool_definitions(enabled_toolsets=["research"])
|
||||||
|
tools = get_tool_definitions(enabled_toolsets=["development"])
|
||||||
|
|
||||||
# All tools except terminal
|
# Combine multiple toolsets
|
||||||
tools = get_tool_definitions(disabled_tools=["terminal"])
|
tools = get_tool_definitions(enabled_toolsets=["web", "vision"])
|
||||||
|
|
||||||
# Only specific tools (overrides toolset filters)
|
# All tools except those in terminal toolset
|
||||||
tools = get_tool_definitions(enabled_tools=["web_search", "web_extract"])
|
tools = get_tool_definitions(disabled_toolsets=["terminal"])
|
||||||
|
|
||||||
# Conflicting filters (enabled_tools wins)
|
# Default - all available tools
|
||||||
tools = get_tool_definitions(enabled_toolsets=["web_tools"], enabled_tools=["terminal"])
|
tools = get_tool_definitions()
|
||||||
# Result: Only terminal tool (enabled_tools overrides enabled_toolsets)
|
|
||||||
"""
|
"""
|
||||||
# Detect and warn about potential conflicts
|
# Collect all available tool definitions
|
||||||
conflicts_detected = False
|
all_available_tools_map = {}
|
||||||
|
|
||||||
if enabled_tools and (enabled_toolsets or disabled_toolsets or disabled_tools):
|
# Map tool names to their definitions
|
||||||
print("⚠️ enabled_tools overrides all other filters")
|
if check_firecrawl_api_key():
|
||||||
conflicts_detected = True
|
for tool in get_web_tool_definitions():
|
||||||
|
all_available_tools_map[tool["function"]["name"]] = tool
|
||||||
|
|
||||||
if enabled_toolsets and disabled_toolsets:
|
if check_hecate_requirements():
|
||||||
# Check for overlap
|
for tool in get_terminal_tool_definitions():
|
||||||
enabled_set = set(enabled_toolsets)
|
all_available_tools_map[tool["function"]["name"]] = tool
|
||||||
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 enabled_tools and disabled_tools:
|
if check_vision_requirements():
|
||||||
# Check for overlap
|
for tool in get_vision_tool_definitions():
|
||||||
enabled_set = set(enabled_tools)
|
all_available_tools_map[tool["function"]["name"]] = tool
|
||||||
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
|
|
||||||
|
|
||||||
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
|
if check_image_generation_requirements():
|
||||||
toolset_tools = {
|
for tool in get_image_tool_definitions():
|
||||||
"web_tools": get_web_tool_definitions() if check_firecrawl_api_key() else [],
|
all_available_tools_map[tool["function"]["name"]] = tool
|
||||||
"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 []
|
|
||||||
}
|
|
||||||
|
|
||||||
# HIGHEST PRIORITY: enabled_tools (overrides everything)
|
# Determine which tools to include based on toolsets
|
||||||
if enabled_tools:
|
tools_to_include = set()
|
||||||
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
|
|
||||||
|
|
||||||
# Apply toolset-level filtering first
|
|
||||||
if enabled_toolsets:
|
if enabled_toolsets:
|
||||||
# Only include tools from enabled toolsets
|
# Only include tools from enabled toolsets
|
||||||
for toolset_name in enabled_toolsets:
|
for toolset_name in enabled_toolsets:
|
||||||
if toolset_name in toolset_tools:
|
if validate_toolset(toolset_name):
|
||||||
all_tools.extend(toolset_tools[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:
|
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:
|
elif disabled_toolsets:
|
||||||
# Include all tools except from disabled toolsets
|
# Start with all tools from all toolsets, then remove disabled ones
|
||||||
for toolset_name, tools in toolset_tools.items():
|
# Note: Only tools that are part of toolsets are accessible
|
||||||
if toolset_name not in disabled_toolsets:
|
# We need to get all tools from all defined toolsets
|
||||||
all_tools.extend(tools)
|
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:
|
else:
|
||||||
# Include all available tools
|
# No filtering - include all tools from all defined toolsets
|
||||||
for tools in toolset_tools.values():
|
from toolsets import get_all_toolsets
|
||||||
all_tools.extend(tools)
|
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)
|
# Build final tool list (only include tools that are available)
|
||||||
if disabled_tools:
|
filtered_tools = []
|
||||||
tool_names_to_exclude = set(disabled_tools)
|
for tool_name in tools_to_include:
|
||||||
original_tools = [tool["function"]["name"] for tool in all_tools]
|
if tool_name in all_available_tools_map:
|
||||||
|
filtered_tools.append(all_available_tools_map[tool_name])
|
||||||
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}")
|
|
||||||
|
|
||||||
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:
|
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":
|
if function_name == "web_search":
|
||||||
query = function_args.get("query", "")
|
query = function_args.get("query", "")
|
||||||
limit = function_args.get("limit", 5)
|
# Always use fixed limit of 5
|
||||||
# Ensure limit is within bounds
|
limit = 5
|
||||||
limit = max(1, min(10, limit))
|
|
||||||
return web_search_tool(query, limit)
|
return web_search_tool(query, limit)
|
||||||
|
|
||||||
elif function_name == "web_extract":
|
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 \
|
python run_agent.py \
|
||||||
--query "$PROMPT" \
|
--query "$PROMPT" \
|
||||||
--max_turns 30 \
|
--max_turns 30 \
|
||||||
# --model claude-sonnet-4-20250514 \
|
--model claude-sonnet-4-20250514 \
|
||||||
# --base_url https://api.anthropic.com/v1/ \
|
--base_url https://api.anthropic.com/v1/ \
|
||||||
--model hermes-4-70B \
|
|
||||||
--base_url http://bore.pub:8292/v1 \
|
|
||||||
--api_key $ANTHROPIC_API_KEY \
|
--api_key $ANTHROPIC_API_KEY \
|
||||||
--save_trajectories
|
--save_trajectories \
|
||||||
#--enabled_toolsets=vision_tools
|
--enabled_toolsets=web
|
||||||
|
|
||||||
|
# --model claude-sonnet-4-20250514 \
|
||||||
|
#
|
||||||
#Possible Toolsets:
|
#Possible Toolsets:
|
||||||
#web_tools
|
#web_tools
|
||||||
#vision_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'])}")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue