Skip to content

添加工具

本文介绍如何为 Hermes 添加自定义工具(Tool)和斜杠命令(Slash Command),以及本地开发环境的搭建方法。


工具系统概览

Hermes 的工具系统采用自注册模式:

  1. tools/ 目录下创建工具文件
  2. model_tools.py 中添加导入
  3. toolsets.py 中注册到工具集

每个工具处理函数接收参数并返回 JSON 字符串,LLM 通过返回值了解工具执行结果。


添加新工具

第一步:创建工具文件

tools/ 目录下创建新文件,例如 tools/my_tool.py

python
import json
from tools.registry import tool

@tool(
    name="my_custom_tool",
    description="描述这个工具的功能,LLM 会根据此描述决定何时调用它",
    parameters={
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "查询内容"
            },
            "limit": {
                "type": "integer",
                "description": "返回结果数量上限",
                "default": 10
            }
        },
        "required": ["query"]
    }
)
def my_custom_tool(query: str, limit: int = 10) -> str:
    """工具的实际实现逻辑。"""
    try:
        # 执行工具逻辑
        results = do_something(query, limit)

        # 所有工具处理函数必须返回 JSON 字符串
        return json.dumps({
            "success": True,
            "results": results,
            "count": len(results)
        }, ensure_ascii=False)

    except Exception as e:
        # 错误也以 JSON 字符串形式返回
        return json.dumps({
            "success": False,
            "error": str(e)
        }, ensure_ascii=False)


def do_something(query: str, limit: int) -> list:
    # 实际业务逻辑
    return [f"结果 {i}: {query}" for i in range(limit)]

关键约定:

  • 所有处理函数必须返回 JSON 字符串json.dumps() 的结果)
  • 使用 @tool 装饰器自动注册到工具注册表
  • description 字段非常重要,LLM 依靠它决定何时使用此工具
  • parameters 遵循 JSON Schema 规范

第二步:在 model_tools.py 中添加导入

打开 hermes_cli/model_tools.py,添加导入语句:

python
# 现有导入...
from tools.file_tools import *
from tools.shell_tools import *
# ... 其他工具 ...

# 添加你的新工具
from tools.my_tool import *

第三步:在 toolsets.py 中注册工具集

打开 tools/toolsets.py,将工具添加到合适的工具集,或创建新的工具集:

python
TOOLSETS = {
    # 现有工具集...
    "filesystem": ["read_file", "write_file", "list_directory"],
    "shell": ["run_command", "run_script"],

    # 添加到现有工具集
    "web": ["fetch_url", "my_custom_tool"],  # 如果是网络相关工具

    # 或创建新工具集
    "my_tools": ["my_custom_tool"],
}

工具返回值规范

所有工具处理函数必须返回 JSON 字符串,格式建议:

python
# 成功
return json.dumps({
    "success": True,
    "data": result_data
}, ensure_ascii=False)

# 失败
return json.dumps({
    "success": False,
    "error": "错误描述信息"
}, ensure_ascii=False)

# 简单结果(也可以直接返回值)
return json.dumps({"result": "some value"}, ensure_ascii=False)

使用 ensure_ascii=False 以正确处理中文等非 ASCII 字符。


添加斜杠命令

斜杠命令(如 /new/reset)在 hermes_cli/commands.py 中通过 CommandDef 定义:

python
from hermes_cli.commands import CommandDef, register_command

@register_command
class MyCommand(CommandDef):
    name = "mycommand"           # 命令名(不含斜杠)
    aliases = ["mc"]             # 别名(可选)
    description = "我的自定义命令"  # 显示在 /help 中的描述
    requires_agent = False       # 是否需要 Agent 实例

    def execute(self, args: str, context: dict) -> str | None:
        """
        执行命令逻辑。
        args: 斜杠命令后的参数字符串
        context: 当前会话上下文
        返回值:显示给用户的字符串,或 None(无输出)
        """
        if args:
            return f"你输入了参数:{args}"
        return "命令已执行"

用户在聊天框输入 /mycommand/mc 即可触发。


开发环境搭建

克隆代码库

bash
git clone https://github.com/NousResearch/hermes.git
cd hermes

安装 uv(Python 包管理器)

bash
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# 或使用 pip
pip install uv

创建虚拟环境并安装依赖

bash
# 创建虚拟环境
uv venv

# 激活虚拟环境
source .venv/bin/activate  # Linux/macOS
# 或
.venv\Scripts\activate     # Windows

# 以开发模式安装(包含所有可选依赖)
uv pip install -e ".[all]"

运行测试

bash
# 运行所有测试
pytest

# 运行特定测试文件
pytest tests/test_tools.py

# 运行特定测试函数
pytest tests/test_tools.py::test_my_tool

# 显示详细输出
pytest -v

# 生成覆盖率报告
pytest --cov=hermes_cli --cov-report=html

本地运行开发版本

bash
# 虚拟环境激活后直接运行
hermes

# 或使用 Python 模块方式
python -m hermes_cli

测试你的工具

为新工具编写测试(放在 tests/ 目录):

python
# tests/test_my_tool.py
import json
import pytest
from tools.my_tool import my_custom_tool


def test_my_tool_basic():
    result = my_custom_tool(query="测试查询")
    data = json.loads(result)
    assert data["success"] is True
    assert "results" in data


def test_my_tool_with_limit():
    result = my_custom_tool(query="测试", limit=5)
    data = json.loads(result)
    assert data["count"] == 5


def test_my_tool_error_handling():
    result = my_custom_tool(query="")
    data = json.loads(result)
    # 根据你的错误处理逻辑编写断言

工具开发最佳实践

描述要清晰准确

description 是 LLM 选择工具的唯一依据,写清楚工具的功能、适用场景和返回内容:

python
# 不好的描述
description = "处理数据"

# 好的描述
description = "在本地文件系统中搜索匹配特定模式的文件,返回文件路径列表。适合在需要找到特定文件时使用。"

参数说明要详细

python
"parameters": {
    "type": "object",
    "properties": {
        "pattern": {
            "type": "string",
            "description": "搜索模式,支持 glob 通配符(如 '*.py', '**/*.json')"
        },
        "directory": {
            "type": "string",
            "description": "搜索的起始目录路径,默认为当前工作目录",
            "default": "."
        }
    },
    "required": ["pattern"]
}

错误处理要完善

工具执行中的任何异常都应被捕获并以结构化 JSON 返回,避免工具调用失败导致整个 Agent 崩溃:

python
try:
    result = risky_operation()
    return json.dumps({"success": True, "result": result})
except FileNotFoundError as e:
    return json.dumps({"success": False, "error": f"文件不存在: {e}"})
except PermissionError as e:
    return json.dumps({"success": False, "error": f"权限不足: {e}"})
except Exception as e:
    return json.dumps({"success": False, "error": f"未知错误: {e}"})

保持工具单一职责

每个工具只做一件事,拆分复杂逻辑为多个工具,让 LLM 组合使用。

基于 MIT 许可发布 | 由 Nous Research 开发