第 18 章

工具调用协议与 40+ 内置工具体系

第18章:工具调用协议与 40+ 内置工具体系

Hermes Agent 的核心竞争力之一,是其开箱即用的 40+ 内置工具。这些工具覆盖了从网络访问、文件操作到代码执行、多媒体处理的全方位能力,构成了 Agent 感知和改变世界的"手脚"。本章将完整梳理工具分类体系、JSON 调用协议、自定义注册方法和权限控制机制。


18.1 工具分类体系

Hermes 的内置工具按功能领域分为六大类:

Hermes 工具体系
├── 系统工具(System)        — 终端、进程、环境变量
├── 网络工具(Network)       — 搜索、HTTP、抓取
├── 文件工具(File)          — 读写、目录、压缩
├── 代码工具(Code)          — 执行、调试、分析
├── 多媒体工具(Multimedia)  — 图像、音频、视频、PDF
└── 平台工具(Platform)      — Git、Docker、数据库、API

18.1.1 完整工具列表

系统工具(8个)

工具名 功能描述 典型用例
terminal 执行 shell 命令 运行脚本、安装包
process_manager 管理后台进程 启动/停止服务
env_manager 读写环境变量 配置管理
cron_scheduler 定时任务调度 周期性任务
system_info 获取系统信息 CPU/内存/磁盘监控
clipboard 剪贴板读写 内容传递
notification 发送系统通知 任务完成提醒
sleep 等待指定时间 轮询间隔控制

网络工具(9个)

工具名 功能描述 典型用例
web_search 多引擎网页搜索 信息检索
browser_navigate 浏览器导航(Playwright) 动态页面访问
browser_click 点击页面元素 表单填写、交互
browser_screenshot 截取网页截图 视觉验证
http_request 发送 HTTP 请求 API 调用
rss_reader 读取 RSS 订阅 新闻聚合
html_extract 提取网页内容 数据抓取
link_checker 检查链接可用性 死链检测
dns_lookup DNS 查询 网络诊断

文件工具(7个)

工具名 功能描述 典型用例
file_read 读取文件内容 代码分析、配置读取
file_write 写入文件 生成报告、保存结果
file_search 文件模糊搜索 定位代码文件
directory_list 列出目录内容 项目结构探索
file_compress 压缩/解压文件 打包分发
file_diff 对比文件差异 代码审查
file_watch 监听文件变化 热重载触发

代码工具(6个)

工具名 功能描述 典型用例
code_execute 执行代码片段 Python/JS 沙箱执行
code_lint 代码静态分析 风格检查
code_format 代码格式化 black/prettier
code_test 运行测试套件 pytest/jest
code_debug 调试代码 断点追踪
repl 交互式 REPL 实验性代码测试

多媒体工具(6个)

工具名 功能描述 典型用例
image_analyze 图像内容分析(视觉) OCR、物体识别
image_generate 生成图像 DALL-E/Stable Diffusion
image_edit 编辑图像 裁剪、滤镜
audio_transcribe 语音转文字(Whisper) 会议记录
pdf_extract PDF 内容提取 文档分析
chart_generate 生成图表(Mermaid/matplotlib) 数据可视化

平台工具(8个)

工具名 功能描述 典型用例
git_operations Git 版本控制操作 提交、分支、合并
docker_manage Docker 容器管理 部署、测试环境
database_query 数据库查询(SQL/NoSQL) 数据分析
api_call 调用外部 API 第三方服务集成
email_send 发送邮件 自动化通知
calendar_manage 日历管理 会议安排
spreadsheet 表格操作(Excel/CSV) 数据处理
cloud_storage 云存储(S3/OSS) 文件上传下载

18.2 工具调用的 JSON 协议格式

Hermes 使用严格定义的 JSON 协议进行工具调用,分为"调用请求"和"调用响应"两个对象。

18.2.1 工具调用请求格式

{
  "tool_calls": [
    {
      "id": "call_abc123",
      "type": "function",
      "function": {
        "name": "web_search",
        "arguments": {
          "query": "Hermes Agent 最新版本特性",
          "max_results": 10,
          "search_engine": "google",
          "date_range": "past_month",
          "safe_search": true
        }
      }
    }
  ]
}

18.2.2 工具调用响应格式

{
  "tool_results": [
    {
      "tool_call_id": "call_abc123",
      "role": "tool",
      "name": "web_search",
      "content": {
        "status": "success",
        "results": [
          {
            "title": "Hermes 4: NousResearch's Latest Agent Model",
            "url": "https://nousresearch.com/hermes-4",
            "snippet": "Hermes 4 introduces improved function calling...",
            "score": 0.95
          }
        ],
        "total_results": 847000,
        "query_time_ms": 342
      }
    }
  ]
}

18.2.3 完整的多工具调用序列示例

以下展示了一个涉及三个工具的完整调用序列:

// Step 1: Agent 决策,发起工具调用
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_001",
      "type": "function",
      "function": {
        "name": "web_search",
        "arguments": {
          "query": "Python asyncio best practices 2024",
          "max_results": 5
        }
      }
    }
  ]
}

// Step 2: 工具执行结果返回
{
  "role": "tool",
  "tool_call_id": "call_001",
  "content": "Found 5 results: [...]"
}

// Step 3: Agent 基于结果决定继续调用第二个工具
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_002",
      "type": "function",
      "function": {
        "name": "file_write",
        "arguments": {
          "path": "/output/asyncio_summary.md",
          "content": "# Python AsyncIO Best Practices\n\n..."
        }
      }
    }
  ]
}

// Step 4: 第二个工具结果
{
  "role": "tool",
  "tool_call_id": "call_002",
  "content": "{\"success\": true, \"path\": \"/output/asyncio_summary.md\"}"
}

// Step 5: Agent 最终回复用户
{
  "role": "assistant",
  "content": "我已完成调研并将结果保存到 /output/asyncio_summary.md 文件中..."
}

18.2.4 工具 Schema 定义格式

每个工具的参数结构遵循 JSON Schema Draft 7:

{
  "name": "http_request",
  "description": "发送 HTTP 请求并返回响应内容",
  "parameters": {
    "type": "object",
    "properties": {
      "url": {
        "type": "string",
        "description": "目标 URL,必须以 http:// 或 https:// 开头",
        "format": "uri"
      },
      "method": {
        "type": "string",
        "enum": ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"],
        "default": "GET",
        "description": "HTTP 请求方法"
      },
      "headers": {
        "type": "object",
        "description": "请求头键值对",
        "additionalProperties": {"type": "string"}
      },
      "body": {
        "type": ["string", "object", "null"],
        "description": "请求体(POST/PUT 使用)"
      },
      "timeout_seconds": {
        "type": "integer",
        "minimum": 1,
        "maximum": 300,
        "default": 30,
        "description": "请求超时时间(秒)"
      },
      "follow_redirects": {
        "type": "boolean",
        "default": true,
        "description": "是否自动跟随重定向"
      }
    },
    "required": ["url"]
  }
}

18.3 自定义工具注册方法

除了 40+ 内置工具,Hermes 支持通过三种方式注册自定义工具。

18.3.1 方式一:Python 装饰器注册

最简便的注册方式,适合快速原型:

from hermes.tools import tool, ToolResult

@tool(
    name="weather_query",
    description="查询指定城市的当前天气和未来预报",
    tags=["weather", "external-api"],
    timeout_seconds=15,
)
def get_weather(
    city: str,
    days: int = 3,
    unit: str = "celsius",
) -> ToolResult:
    """
    Args:
        city: 城市名称(支持中英文)
        days: 预报天数(1-7)
        unit: 温度单位,celsius 或 fahrenheit
    """
    import requests
    
    api_key = os.environ["WEATHER_API_KEY"]
    response = requests.get(
        f"https://api.weatherapi.com/v1/forecast.json",
        params={
            "key": api_key,
            "q": city,
            "days": days,
            "aqi": "no",
        },
        timeout=10,
    )
    
    data = response.json()
    return ToolResult(
        success=True,
        output={
            "city": data["location"]["name"],
            "current_temp": data["current"]["temp_c"],
            "condition": data["current"]["condition"]["text"],
            "forecast": [
                {
                    "date": day["date"],
                    "max": day["day"]["maxtemp_c"],
                    "min": day["day"]["mintemp_c"],
                }
                for day in data["forecast"]["forecastday"]
            ],
        }
    )

18.3.2 方式二:YAML 配置文件注册

适合 DevOps 流程,无需修改代码:

# tools/custom_tools.yaml
tools:
  - name: jira_create_ticket
    description: "在 Jira 中创建工单"
    type: http_wrapper          # 包装 HTTP API 调用
    endpoint: "https://your-org.atlassian.net/rest/api/3/issue"
    method: POST
    auth:
      type: basic
      username_env: JIRA_USER
      password_env: JIRA_API_TOKEN
    parameters:
      - name: summary
        type: string
        required: true
        description: "工单标题"
      - name: description
        type: string
        required: false
        description: "详细描述"
      - name: priority
        type: string
        enum: [Highest, High, Medium, Low, Lowest]
        default: Medium
      - name: project_key
        type: string
        required: true
        description: "Jira 项目 Key(如 PROJ)"
    request_template: |
      {
        "fields": {
          "project": {"key": "{{project_key}}"},
          "summary": "{{summary}}",
          "description": {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "{{description}}"}]}]},
          "issuetype": {"name": "Task"},
          "priority": {"name": "{{priority}}"}
        }
      }
    response_mapping:
      success_field: id
      output_fields: [id, key, self]
    tags: [jira, project-management, ticketing]
    timeout_seconds: 30
# 加载自定义工具配置
hermes tool register --config ./tools/custom_tools.yaml

18.3.3 方式三:继承 BaseTool 类

适合复杂工具,需要完整的生命周期控制:

from hermes.tools import BaseTool, ToolResult, ToolContext
from hermes.tools.schema import ToolSchema, ParameterDef

class DatabaseQueryTool(BaseTool):
    """支持多数据库方言的查询工具"""
    
    name = "database_query_advanced"
    description = "执行 SQL 或 NoSQL 查询,支持 PostgreSQL/MySQL/MongoDB"
    
    schema = ToolSchema(
        parameters=[
            ParameterDef("connection_string", str, required=True,
                        description="数据库连接字符串"),
            ParameterDef("query", str, required=True,
                        description="查询语句(SQL 或 MongoDB JSON)"),
            ParameterDef("dialect", str, required=False,
                        enum=["postgresql", "mysql", "mongodb", "sqlite"],
                        default="postgresql"),
            ParameterDef("max_rows", int, required=False,
                        default=1000, description="最大返回行数"),
            ParameterDef("timeout_ms", int, required=False,
                        default=30000),
        ]
    )
    
    async def setup(self):
        """工具初始化(连接池预热)"""
        self.connection_pool = {}
    
    async def run(self, params: dict, context: ToolContext) -> ToolResult:
        dialect = params.get("dialect", "postgresql")
        
        # 获取或创建连接
        conn = await self._get_connection(
            params["connection_string"], dialect
        )
        
        try:
            if dialect == "mongodb":
                result = await self._mongo_query(conn, params["query"])
            else:
                result = await self._sql_query(conn, params["query"],
                                               params["max_rows"])
            return ToolResult(
                success=True,
                output={
                    "rows": result.rows,
                    "row_count": len(result.rows),
                    "columns": result.columns,
                    "query_time_ms": result.execution_time_ms,
                }
            )
        except Exception as e:
            return ToolResult(success=False, error=str(e))
    
    async def teardown(self):
        """清理资源"""
        for conn in self.connection_pool.values():
            await conn.close()

18.4 工具权限控制

Hermes 实现了细粒度的工具权限控制系统,防止 Agent 执行未授权操作。

18.4.1 权限层级

全局权限(Global Permissions)
    └── 会话权限(Session Permissions)
            └── 工具权限(Tool Permissions)
                    └── 参数级限制(Parameter-level Restrictions)

18.4.2 权限类型

class ToolPermission(Enum):
    # 文件系统权限
    FILE_READ = "file:read"
    FILE_WRITE = "file:write"
    FILE_DELETE = "file:delete"
    
    # 网络权限
    NETWORK_FETCH = "network:fetch"
    NETWORK_SEARCH = "network:search"
    NETWORK_WEBSOCKET = "network:websocket"
    
    # 系统权限
    TERMINAL_EXECUTE = "terminal:execute"
    PROCESS_MANAGE = "process:manage"
    ENV_READ = "env:read"
    ENV_WRITE = "env:write"
    
    # 外部服务
    EXTERNAL_API = "external:api"
    DATABASE_READ = "database:read"
    DATABASE_WRITE = "database:write"
    
    # 平台权限
    GIT_READ = "git:read"
    GIT_WRITE = "git:write"
    DOCKER_RUN = "docker:run"

18.4.3 权限配置文件

# hermes_permissions.yaml
profiles:
  # 只读研究模式(适合敏感环境)
  readonly_research:
    allow:
      - network:search
      - network:fetch
      - file:read
      - env:read
    deny:
      - file:write
      - file:delete
      - terminal:execute
      - database:write
  
  # 全功能开发模式
  full_developer:
    allow: ["*"]
    deny:
      - env:write           # 保护环境变量
    restrictions:
      file:write:
        allowed_paths:
          - /workspace/**
          - /tmp/**
        denied_paths:
          - /workspace/.env
          - /workspace/secrets/**
      terminal:execute:
        denied_commands:
          - "rm -rf /"
          - ":(){ :|:& };:"  # Fork bomb
          
  # 生产部署模式
  production_deploy:
    allow:
      - git:read
      - docker:run
      - network:fetch
    deny:
      - terminal:execute
      - file:delete
    approval_required:
      - docker:run          # 需要人工审批
      - database:write

18.4.4 运行时权限检查

# 工具调用前的权限检查流程
class PermissionChecker:
    def check(
        self,
        tool_name: str,
        params: dict,
        context: AgentContext,
    ) -> PermissionResult:
        
        # 1. 获取工具所需权限
        required = self._get_tool_permissions(tool_name)
        
        # 2. 获取当前会话允许的权限
        granted = context.permission_profile.allowed
        
        # 3. 检查是否有被拒绝的权限
        for perm in required:
            if perm in context.permission_profile.denied:
                return PermissionResult(
                    allowed=False,
                    reason=f"权限 {perm} 在当前模式下被禁止",
                    suggestion="请切换到 full_developer 模式"
                )
        
        # 4. 参数级限制检查
        if tool_name == "file_write":
            path = params.get("path", "")
            if not self._is_path_allowed(path, context):
                return PermissionResult(
                    allowed=False,
                    reason=f"路径 {path} 不在允许的写入范围内"
                )
        
        # 5. 检查是否需要审批
        if perm in context.permission_profile.approval_required:
            return PermissionResult(
                allowed=False,
                requires_approval=True,
                approval_prompt=f"工具 {tool_name} 需要人工确认,请审批..."
            )
        
        return PermissionResult(allowed=True)

18.5 实战:构建工具调用的监控仪表板

以下示例演示如何收集所有工具调用的遥测数据:

from hermes.tools import ToolRegistry
from hermes.monitoring import TelemetryCollector
import json

class ToolUsageDashboard:
    def __init__(self):
        self.registry = ToolRegistry()
        self.collector = TelemetryCollector()
    
    def get_report(self, period_hours: int = 24) -> dict:
        stats = self.collector.query(period_hours)
        
        return {
            "period": f"Last {period_hours}h",
            "total_calls": stats.total_calls,
            "success_rate": f"{stats.success_rate:.1%}",
            "top_tools": [
                {
                    "name": t.name,
                    "calls": t.call_count,
                    "avg_latency_ms": t.avg_latency,
                    "error_rate": f"{t.error_rate:.1%}",
                }
                for t in stats.top_tools[:10]
            ],
            "error_breakdown": stats.errors_by_type,
            "token_consumption": {
                "total": stats.total_tokens,
                "by_tool": stats.tokens_by_tool,
            }
        }

# 使用示例
dashboard = ToolUsageDashboard()
report = dashboard.get_report(24)
print(json.dumps(report, indent=2, ensure_ascii=False))

18.6 小结

本章系统介绍了 Hermes Agent 的工具体系:

思考题

  1. 当 Agent 同时调用多个工具时(并行工具调用),如何保证工具结果的顺序与 tool_call_id 正确对应?

  2. file_write 工具的参数级路径限制使用了 glob 模式匹配(/workspace/**)。如果用户请求写入 /workspace/../etc/passwd,路径遍历攻击如何被防御?

  3. 自定义工具的 YAML 配置方式不需要重启 Hermes 即可热加载。热加载时,正在执行中的工具调用如何保证不中断?

本章评分
4.7  / 5  (17 评分)

💬 留言讨论