refactor: update tool registration and documentation
- Enhanced tool registration process by implementing a self-registering mechanism in each tool file via `tools/registry.py`. - Updated `model_tools.py` to serve as a thin orchestration layer, simplifying tool discovery and registration. - Revised documentation to clarify the steps for adding new tools, emphasizing the importance of schema, handler, and registration consistency. - Improved dependency resolution in environments by ensuring toolsets are queried from `tools/registry.py`.
This commit is contained in:
103
AGENTS.md
103
AGENTS.md
@@ -41,7 +41,8 @@ hermes-agent/
|
|||||||
├── skills/ # Knowledge documents
|
├── skills/ # Knowledge documents
|
||||||
├── cli.py # Interactive CLI (Rich UI)
|
├── cli.py # Interactive CLI (Rich UI)
|
||||||
├── run_agent.py # Agent runner with AIAgent class
|
├── run_agent.py # Agent runner with AIAgent class
|
||||||
├── model_tools.py # Tool schemas and handlers
|
├── model_tools.py # Tool orchestration (thin layer over tools/registry.py)
|
||||||
|
├── tools/registry.py # Central tool registry (schemas, handlers, dispatch)
|
||||||
├── toolsets.py # Tool groupings
|
├── toolsets.py # Tool groupings
|
||||||
├── toolset_distributions.py # Probability-based tool selection
|
├── toolset_distributions.py # Probability-based tool selection
|
||||||
└── batch_runner.py # Parallel batch processing
|
└── batch_runner.py # Parallel batch processing
|
||||||
@@ -59,14 +60,16 @@ hermes-agent/
|
|||||||
## File Dependency Chain
|
## File Dependency Chain
|
||||||
|
|
||||||
```
|
```
|
||||||
tools/*.py → tools/__init__.py → model_tools.py → toolsets.py → toolset_distributions.py
|
tools/registry.py (no deps — imported by all tool files)
|
||||||
↑
|
↑
|
||||||
run_agent.py ──────────────────────────┘
|
tools/*.py (each calls registry.register() at import time)
|
||||||
cli.py → run_agent.py (uses AIAgent with quiet_mode=True)
|
↑
|
||||||
batch_runner.py → run_agent.py + toolset_distributions.py
|
model_tools.py (imports tools/registry + triggers tool discovery)
|
||||||
|
↑
|
||||||
|
run_agent.py, cli.py, batch_runner.py, environments/
|
||||||
```
|
```
|
||||||
|
|
||||||
Always ensure consistency between tools, model_tools.py, and toolsets.py when changing any of them.
|
Each tool file co-locates its schema, handler, and registration. `model_tools.py` is a thin orchestration layer.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -459,51 +462,21 @@ terminal(command="pytest -v tests/", background=true)
|
|||||||
- In the gateway, sessions with active background processes are exempt from idle reset
|
- In the gateway, sessions with active background processes are exempt from idle reset
|
||||||
- The process registry checkpoints to `~/.hermes/processes.json` for crash recovery
|
- The process registry checkpoints to `~/.hermes/processes.json` for crash recovery
|
||||||
|
|
||||||
Files: `tools/process_registry.py` (registry), `model_tools.py` (tool definition + handler), `tools/terminal_tool.py` (spawn integration)
|
Files: `tools/process_registry.py` (registry + handler), `tools/terminal_tool.py` (spawn integration)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Adding New Tools
|
## Adding New Tools
|
||||||
|
|
||||||
Follow this strict order to maintain consistency:
|
Adding a tool requires changes in **2 files** (the tool file and `toolsets.py`):
|
||||||
|
|
||||||
1. Create `tools/your_tool.py` with:
|
1. **Create `tools/your_tool.py`** with handler, schema, check function, and registry call:
|
||||||
- Handler function (sync or async) returning a JSON string via `json.dumps()`
|
|
||||||
- `check_*_requirements()` function to verify dependencies (e.g., API keys)
|
|
||||||
- Schema definition following OpenAI function-calling format
|
|
||||||
|
|
||||||
2. Export in `tools/__init__.py`:
|
|
||||||
- Import the handler and check function
|
|
||||||
- Add to `__all__` list
|
|
||||||
|
|
||||||
3. Register in `model_tools.py`:
|
|
||||||
- Add to `TOOLSET_REQUIREMENTS` if it needs API keys
|
|
||||||
- Create `get_*_tool_definitions()` function or add to existing
|
|
||||||
- Add routing in `handle_function_call()` dispatcher
|
|
||||||
- Update `get_all_tool_names()` with the tool name
|
|
||||||
- Update `get_toolset_for_tool()` mapping
|
|
||||||
- Update `get_available_toolsets()` and `check_toolset_requirements()`
|
|
||||||
|
|
||||||
4. Add to toolset in `toolsets.py`:
|
|
||||||
- Add to existing toolset or create new one in TOOLSETS dict
|
|
||||||
|
|
||||||
5. If the tool requires an API key:
|
|
||||||
- Add to `OPTIONAL_ENV_VARS` in `hermes_cli/config.py`
|
|
||||||
- The tool will be auto-disabled if the key is missing
|
|
||||||
|
|
||||||
6. Add `"todo"` to the relevant platform toolsets (`hermes-cli`, `hermes-telegram`, etc.)
|
|
||||||
|
|
||||||
7. Optionally add to `toolset_distributions.py` for batch processing
|
|
||||||
|
|
||||||
**Special case: tools that need agent-level state** (like `todo`):
|
|
||||||
If your tool needs access to the AIAgent instance (e.g., in-memory state per session), intercept it directly in `run_agent.py`'s tool dispatch loop *before* `handle_function_call()`. Add a fallback error in `handle_function_call()` for safety. See `todo_tool.py` and the `if function_name == "todo":` block in `run_agent.py` for the pattern. For RL environments, add the same intercept in `environments/agent_loop.py`.
|
|
||||||
|
|
||||||
### Tool Implementation Pattern
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# tools/example_tool.py
|
# tools/example_tool.py
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
from tools.registry import registry
|
||||||
|
|
||||||
def check_example_requirements() -> bool:
|
def check_example_requirements() -> bool:
|
||||||
"""Check if required API keys/dependencies are available."""
|
"""Check if required API keys/dependencies are available."""
|
||||||
@@ -516,24 +489,46 @@ def example_tool(param: str, task_id: str = None) -> str:
|
|||||||
return json.dumps(result, ensure_ascii=False)
|
return json.dumps(result, ensure_ascii=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
||||||
|
|
||||||
|
EXAMPLE_SCHEMA = {
|
||||||
|
"name": "example_tool",
|
||||||
|
"description": "Does something useful.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"param": {"type": "string", "description": "The parameter"}
|
||||||
|
},
|
||||||
|
"required": ["param"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registry.register(
|
||||||
|
name="example_tool",
|
||||||
|
toolset="example",
|
||||||
|
schema=EXAMPLE_SCHEMA,
|
||||||
|
handler=lambda args, **kw: example_tool(
|
||||||
|
param=args.get("param", ""), task_id=kw.get("task_id")),
|
||||||
|
check_fn=check_example_requirements,
|
||||||
|
requires_env=["EXAMPLE_API_KEY"],
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
All tool handlers MUST return a JSON string. Never return raw dicts.
|
2. **Add to `toolsets.py`**: Add `"example_tool"` to `_HERMES_CORE_TOOLS` if it should be in all platform toolsets, or create a new toolset entry.
|
||||||
|
|
||||||
|
3. **Add discovery import** in `model_tools.py`'s `_discover_tools()` list: `"tools.example_tool"`.
|
||||||
|
|
||||||
|
That's it. The registry handles schema collection, dispatch, availability checking, and error wrapping automatically. No edits to `TOOLSET_REQUIREMENTS`, `handle_function_call()`, `get_all_tool_names()`, or any other data structure.
|
||||||
|
|
||||||
|
**Optional:** Add to `OPTIONAL_ENV_VARS` in `hermes_cli/config.py` for the setup wizard, and to `toolset_distributions.py` for batch processing.
|
||||||
|
|
||||||
|
**Special case: tools that need agent-level state** (like `todo`, `memory`):
|
||||||
|
These are intercepted by `run_agent.py`'s tool dispatch loop *before* `handle_function_call()`. The registry still holds their schemas, but dispatch returns a stub error as a safety fallback. See `todo_tool.py` for the pattern.
|
||||||
|
|
||||||
|
All tool handlers MUST return a JSON string. The registry's `dispatch()` wraps all exceptions in `{"error": "..."}` automatically.
|
||||||
|
|
||||||
### Dynamic Tool Availability
|
### Dynamic Tool Availability
|
||||||
|
|
||||||
Tools are automatically disabled when their API keys are missing:
|
Tools declare their requirements at registration time via `check_fn` and `requires_env`. The registry checks `check_fn()` when building tool definitions -- tools whose check fails are silently excluded.
|
||||||
|
|
||||||
```python
|
|
||||||
# In model_tools.py
|
|
||||||
TOOLSET_REQUIREMENTS = {
|
|
||||||
"web": {"env_vars": ["FIRECRAWL_API_KEY"]},
|
|
||||||
"browser": {"env_vars": ["BROWSERBASE_API_KEY", "BROWSERBASE_PROJECT_ID"]},
|
|
||||||
"creative": {"env_vars": ["FAL_KEY"]},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `check_tool_availability()` function determines which tools to include.
|
|
||||||
|
|
||||||
### Stateful Tools
|
### Stateful Tools
|
||||||
|
|
||||||
|
|||||||
@@ -621,7 +621,7 @@ Hermes-Agent/
|
|||||||
│ ├── quarantine/
|
│ ├── quarantine/
|
||||||
│ ├── audit.log
|
│ ├── audit.log
|
||||||
│ └── taps.json
|
│ └── taps.json
|
||||||
├── model_tools.py # MODIFY — register new hub tools
|
├── model_tools.py # ADD discovery import for new tool module
|
||||||
└── toolsets.py # MODIFY — add skills_hub toolset
|
└── toolsets.py # MODIFY — add skills_hub toolset
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -633,7 +633,7 @@ Hermes-Agent/
|
|||||||
| `tools/skills_guard.py` | ~300 | Medium — pattern matching, report generation, trust scoring |
|
| `tools/skills_guard.py` | ~300 | Medium — pattern matching, report generation, trust scoring |
|
||||||
| `hermes_cli/skills_hub.py` | ~400 | Medium — argparse, Rich output, user prompts, tap management |
|
| `hermes_cli/skills_hub.py` | ~400 | Medium — argparse, Rich output, user prompts, tap management |
|
||||||
| `tools/skills_tool.py` changes | ~50 | Low — pyyaml upgrade, `assets/` support, `compatibility` field |
|
| `tools/skills_tool.py` changes | ~50 | Low — pyyaml upgrade, `assets/` support, `compatibility` field |
|
||||||
| `model_tools.py` changes | ~80 | Low — register tools, add handler |
|
| `model_tools.py` changes | ~1 | Low — add discovery import line |
|
||||||
| `toolsets.py` changes | ~10 | Low — add toolset entry |
|
| `toolsets.py` changes | ~10 | Low — add toolset entry |
|
||||||
| **Total** | **~1,340** | |
|
| **Total** | **~1,340** | |
|
||||||
|
|
||||||
@@ -690,7 +690,7 @@ Fix any issues (likely just the `tags` and `related_skills` fields, which should
|
|||||||
- [ ] `hermes skills search` CLI command
|
- [ ] `hermes skills search` CLI command
|
||||||
- [ ] `hermes skills install` from GitHub repos (with quarantine + scan)
|
- [ ] `hermes skills install` from GitHub repos (with quarantine + scan)
|
||||||
- [ ] Lock file management
|
- [ ] Lock file management
|
||||||
- [ ] Wire into model_tools.py and toolsets.py
|
- [ ] Add registry.register() calls in tool file + discovery import in model_tools.py + toolset in toolsets.py
|
||||||
|
|
||||||
### Phase 2: Registry Sources — 1-2 days
|
### Phase 2: Registry Sources — 1-2 days
|
||||||
- [ ] ClawHub HTTP API adapter (search + install)
|
- [ ] ClawHub HTTP API adapter (search + install)
|
||||||
|
|||||||
248
docs/tools.md
248
docs/tools.md
@@ -58,58 +58,224 @@ async def web_search(query: str) -> dict:
|
|||||||
|
|
||||||
## Tool Registration
|
## Tool Registration
|
||||||
|
|
||||||
Tools are registered in `model_tools.py`:
|
Each tool file self-registers via `tools/registry.py`:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# model_tools.py
|
# tools/example_tool.py
|
||||||
TOOL_SCHEMAS = [
|
from tools.registry import registry
|
||||||
*WEB_TOOL_SCHEMAS,
|
|
||||||
*TERMINAL_TOOL_SCHEMAS,
|
|
||||||
*BROWSER_TOOL_SCHEMAS,
|
|
||||||
# ...
|
|
||||||
]
|
|
||||||
|
|
||||||
TOOL_HANDLERS = {
|
EXAMPLE_SCHEMA = {
|
||||||
"web_search": web_search,
|
"name": "example_tool",
|
||||||
"terminal": terminal_tool,
|
"description": "Does something useful.",
|
||||||
"browser_navigate": browser_navigate,
|
"parameters": { ... }
|
||||||
# ...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registry.register(
|
||||||
|
name="example_tool",
|
||||||
|
toolset="example",
|
||||||
|
schema=EXAMPLE_SCHEMA,
|
||||||
|
handler=lambda args, **kw: example_tool(args.get("param", "")),
|
||||||
|
check_fn=check_example_requirements,
|
||||||
|
requires_env=["EXAMPLE_API_KEY"],
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`model_tools.py` is a thin orchestration layer that imports all tool modules (triggering registration), then delegates to the registry for schema collection and dispatch.
|
||||||
|
|
||||||
## Toolsets
|
## Toolsets
|
||||||
|
|
||||||
Tools are grouped into **toolsets** for logical organization (see `toolsets.py`):
|
Tools are grouped into **toolsets** for logical organization (see `toolsets.py`). All platforms share a `_HERMES_CORE_TOOLS` list; messaging platforms add `send_message`.
|
||||||
|
|
||||||
```python
|
|
||||||
TOOLSETS = {
|
|
||||||
"web": {
|
|
||||||
"description": "Web search and content extraction",
|
|
||||||
"tools": ["web_search", "web_extract", "web_crawl"]
|
|
||||||
},
|
|
||||||
"terminal": {
|
|
||||||
"description": "Command execution",
|
|
||||||
"tools": ["terminal", "process"]
|
|
||||||
},
|
|
||||||
"todo": {
|
|
||||||
"description": "Task planning and tracking for multi-step work",
|
|
||||||
"tools": ["todo"]
|
|
||||||
},
|
|
||||||
"memory": {
|
|
||||||
"description": "Persistent memory across sessions (personal notes + user profile)",
|
|
||||||
"tools": ["memory"]
|
|
||||||
},
|
|
||||||
# ...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding a New Tool
|
## Adding a New Tool
|
||||||
|
|
||||||
1. Create handler function in `tools/your_tool.py`
|
### Overview
|
||||||
2. Define JSON schema following OpenAI format
|
|
||||||
3. Register in `model_tools.py` (schemas and handlers)
|
Adding a tool touches 3 files:
|
||||||
4. Add to appropriate toolset in `toolsets.py`
|
|
||||||
5. Update `tools/__init__.py` exports
|
1. **`tools/your_tool.py`** -- handler, schema, check function, `registry.register()` call
|
||||||
|
2. **`toolsets.py`** -- add tool name to `_HERMES_CORE_TOOLS` (or a specific toolset)
|
||||||
|
3. **`model_tools.py`** -- add `"tools.your_tool"` to the `_discover_tools()` list
|
||||||
|
|
||||||
|
### Step 1: Create the tool file
|
||||||
|
|
||||||
|
Every tool file follows the same structure: handler function, availability check, schema constant, and registry registration.
|
||||||
|
|
||||||
|
```python
|
||||||
|
# tools/weather_tool.py
|
||||||
|
"""Weather Tool -- look up current weather for a location."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Availability check ---
|
||||||
|
|
||||||
|
def check_weather_requirements() -> bool:
|
||||||
|
"""Return True if the tool's dependencies are available."""
|
||||||
|
return bool(os.getenv("WEATHER_API_KEY"))
|
||||||
|
|
||||||
|
|
||||||
|
# --- Handler ---
|
||||||
|
|
||||||
|
def weather_tool(location: str, units: str = "metric") -> str:
|
||||||
|
"""Fetch weather for a location. Returns JSON string."""
|
||||||
|
api_key = os.getenv("WEATHER_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
return json.dumps({"error": "WEATHER_API_KEY not configured"})
|
||||||
|
try:
|
||||||
|
# ... call weather API ...
|
||||||
|
return json.dumps({"location": location, "temp": 22, "units": units})
|
||||||
|
except Exception as e:
|
||||||
|
return json.dumps({"error": str(e)})
|
||||||
|
|
||||||
|
|
||||||
|
# --- Schema ---
|
||||||
|
|
||||||
|
WEATHER_SCHEMA = {
|
||||||
|
"name": "weather",
|
||||||
|
"description": "Get current weather for a location.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"location": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "City name or coordinates (e.g. 'London' or '51.5,-0.1')"
|
||||||
|
},
|
||||||
|
"units": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["metric", "imperial"],
|
||||||
|
"description": "Temperature units (default: metric)",
|
||||||
|
"default": "metric"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["location"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- Registration ---
|
||||||
|
|
||||||
|
from tools.registry import registry
|
||||||
|
|
||||||
|
registry.register(
|
||||||
|
name="weather",
|
||||||
|
toolset="weather",
|
||||||
|
schema=WEATHER_SCHEMA,
|
||||||
|
handler=lambda args, **kw: weather_tool(
|
||||||
|
location=args.get("location", ""),
|
||||||
|
units=args.get("units", "metric")),
|
||||||
|
check_fn=check_weather_requirements,
|
||||||
|
requires_env=["WEATHER_API_KEY"],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key rules:**
|
||||||
|
|
||||||
|
- Handlers MUST return a JSON string (via `json.dumps()`), never raw dicts.
|
||||||
|
- Errors MUST be returned as `{"error": "message"}`, never raised as exceptions. The registry's `dispatch()` also wraps unexpected exceptions automatically.
|
||||||
|
- The `check_fn` is called when building tool definitions -- if it returns `False`, the tool is silently excluded from the schema sent to the LLM.
|
||||||
|
- The `handler` receives `(args: dict, **kwargs)` where `args` is the LLM's tool call arguments and `kwargs` may include `task_id`, `user_task`, `store`, etc. depending on what the caller passes.
|
||||||
|
|
||||||
|
### Step 2: Add to a toolset
|
||||||
|
|
||||||
|
In `toolsets.py`, add the tool name to the appropriate place:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# If it should be available on all platforms (CLI + messaging):
|
||||||
|
_HERMES_CORE_TOOLS = [
|
||||||
|
...
|
||||||
|
"weather", # <-- add here
|
||||||
|
]
|
||||||
|
|
||||||
|
# Or create a new standalone toolset:
|
||||||
|
"weather": {
|
||||||
|
"description": "Weather lookup tools",
|
||||||
|
"tools": ["weather"],
|
||||||
|
"includes": []
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Add discovery import
|
||||||
|
|
||||||
|
In `model_tools.py`, add the module to the `_discover_tools()` list:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _discover_tools():
|
||||||
|
_modules = [
|
||||||
|
...
|
||||||
|
"tools.weather_tool", # <-- add here
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
This import triggers the `registry.register()` call at the bottom of the tool file.
|
||||||
|
|
||||||
|
### Async handlers
|
||||||
|
|
||||||
|
If your handler needs to call async code (e.g., `aiohttp`, async SDK), mark it with `is_async=True`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def weather_tool_async(location: str) -> str:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
...
|
||||||
|
return json.dumps(result)
|
||||||
|
|
||||||
|
registry.register(
|
||||||
|
name="weather",
|
||||||
|
toolset="weather",
|
||||||
|
schema=WEATHER_SCHEMA,
|
||||||
|
handler=lambda args, **kw: weather_tool_async(args.get("location", "")),
|
||||||
|
check_fn=check_weather_requirements,
|
||||||
|
is_async=True, # <-- registry calls _run_async() automatically
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The registry handles async bridging transparently via `_run_async()` -- you never call `asyncio.run()` yourself. This works correctly in CLI mode (no event loop), the gateway (running async loop), and RL environments (Atropos event loop + thread pool wrapping).
|
||||||
|
|
||||||
|
### Handlers that need task_id
|
||||||
|
|
||||||
|
Tools that manage per-session state (terminal, browser, file ops) receive `task_id` via `**kwargs`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _handle_weather(args, **kw):
|
||||||
|
task_id = kw.get("task_id") # may be None in CLI mode
|
||||||
|
return weather_tool(args.get("location", ""), task_id=task_id)
|
||||||
|
|
||||||
|
registry.register(
|
||||||
|
name="weather",
|
||||||
|
...
|
||||||
|
handler=_handle_weather,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Use a named function instead of a lambda when the arg unpacking is complex.
|
||||||
|
|
||||||
|
### Agent-loop intercepted tools
|
||||||
|
|
||||||
|
Some tools (todo, memory, session_search, delegate_task) need access to per-session agent state (TodoStore, MemoryStore, etc.) that doesn't flow through `handle_function_call`. These are intercepted by `run_agent.py` before reaching the registry. The registry still holds their schemas (so they appear in the tool list), but `dispatch()` returns a fallback error if the intercept is bypassed. See `todo_tool.py` for the pattern.
|
||||||
|
|
||||||
|
### Optional: setup wizard integration
|
||||||
|
|
||||||
|
If your tool requires an API key, add it to `hermes_cli/config.py`'s `OPTIONAL_ENV_VARS` dict so the setup wizard can prompt for it:
|
||||||
|
|
||||||
|
```python
|
||||||
|
OPTIONAL_ENV_VARS = {
|
||||||
|
...
|
||||||
|
"WEATHER_API_KEY": {
|
||||||
|
"description": "Weather API key for weather lookup",
|
||||||
|
"prompt": "Weather API key",
|
||||||
|
"url": "https://weatherapi.com/",
|
||||||
|
"tools": ["weather"],
|
||||||
|
"password": True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optional: batch processing
|
||||||
|
|
||||||
|
Add to `toolset_distributions.py` if the tool should be available in specific batch processing distributions.
|
||||||
|
|
||||||
## Stateful Tools
|
## Stateful Tools
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ This directory contains the integration layer between **hermes-agent's** tool-ca
|
|||||||
|
|
||||||
**HermesAgentBaseEnv** (`hermes_base_env.py`) extends BaseEnv with hermes-agent specifics:
|
**HermesAgentBaseEnv** (`hermes_base_env.py`) extends BaseEnv with hermes-agent specifics:
|
||||||
- Sets `os.environ["TERMINAL_ENV"]` to configure the terminal backend (local, docker, modal, ssh, singularity)
|
- Sets `os.environ["TERMINAL_ENV"]` to configure the terminal backend (local, docker, modal, ssh, singularity)
|
||||||
- Resolves hermes-agent toolsets via `_resolve_tools_for_group()` (calls `get_tool_definitions()` from `model_tools.py`)
|
- Resolves hermes-agent toolsets via `_resolve_tools_for_group()` (calls `get_tool_definitions()` which queries `tools/registry.py`)
|
||||||
- Implements `collect_trajectory()` which runs the full agent loop and computes rewards
|
- Implements `collect_trajectory()` which runs the full agent loop and computes rewards
|
||||||
- Supports two-phase operation (Phase 1: OpenAI server, Phase 2: VLLM ManagedServer)
|
- Supports two-phase operation (Phase 1: OpenAI server, Phase 2: VLLM ManagedServer)
|
||||||
- Applies monkey patches for async-safe tool operation at import time
|
- Applies monkey patches for async-safe tool operation at import time
|
||||||
@@ -60,7 +60,7 @@ Concrete environments inherit from `HermesAgentBaseEnv` and implement:
|
|||||||
`HermesAgentLoop` is the reusable multi-turn agent engine. It runs the same pattern as hermes-agent's `run_agent.py`:
|
`HermesAgentLoop` is the reusable multi-turn agent engine. It runs the same pattern as hermes-agent's `run_agent.py`:
|
||||||
|
|
||||||
1. Send messages + tools to the API via `server.chat_completion()`
|
1. Send messages + tools to the API via `server.chat_completion()`
|
||||||
2. If the response contains `tool_calls`, execute each one via `handle_function_call()` from `model_tools.py`
|
2. If the response contains `tool_calls`, execute each one via `handle_function_call()` (which delegates to `tools/registry.py`'s `dispatch()`)
|
||||||
3. Append tool results to the conversation and go back to step 1
|
3. Append tool results to the conversation and go back to step 1
|
||||||
4. If the response has no tool_calls, the agent is done
|
4. If the response has no tool_calls, the agent is done
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user