本文以当前 OpenClaw Java 工程为基础,介绍 Agent 工具系统的功能、组成、注入时机、调用链路和设计思路。

一、前言

在 Agent 系统中,大模型本身只能生成文本。它不能直接读取文件、执行命令、访问网页或操作浏览器。

要让模型具备这些能力,需要引入 Tool Calling(工具调用) 机制:

  1. Java 程序实现工具的具体功能;
  2. Java 程序把工具名称、描述和参数 Schema 发送给大模型;
  3. 大模型根据用户问题决定是否调用工具;
  4. Java 程序执行模型返回的工具调用;
  5. Java 程序把工具执行结果再次发送给大模型;
  6. 大模型结合工具结果生成最终答案。

当前工程的工具系统采用了“统一接口 + 注册中心 + 策略过滤 + Provider 适配”的设计,既支持内置工具,也为插件工具和沙箱策略预留了扩展点。

二、工具系统的整体架构

核心类位于 openclaw-agent 模块中:

AgentTool
    ├── ExecTool
    ├── FileTools
    ├── WebSearchTool
    ├── WebFetchTool
    ├── BrowserTool
    ├── MemoryTool
    ├── MessageTool
    └── 其他内置工具

ToolRegistry
    ├── 注册内置工具
    ├── 注册插件工具
    ├── 查询工具
    ├── 按策略过滤
    └── 转换为模型工具定义

AgentRunner
    ├── 注入插件工具
    ├── 应用沙箱策略
    ├── 构造 ChatRequest.tools
    ├── 调用模型
    ├── 执行工具调用
    └── 进入下一轮模型调用

OpenAICompatibleProvider
    └── 将工具转换为 OpenAI Function Calling 格式

工具的统一抽象定义在 AgentTool.java。

三、统一工具接口设计

所有工具都实现 AgentTool 接口。接口要求每个工具提供四类能力:

String getName();

String getDescription();

JsonNode getParameterSchema();

CompletableFuture<ToolResult> execute(ToolContext context);

1. getName():工具唯一名称

工具名称是模型返回 Tool Call 时使用的标识。例如:

exec
web_search
web_fetch
browser
sessions_spawn

工具名称必须唯一。ToolRegistry 使用工具名称作为 Map 的 Key,重复注册时可能覆盖已有工具,插件工具则会主动跳过冲突名称。

2. getDescription():告诉模型什么时候使用工具

描述不是给 Java 开发者看的,而是模型决策的重要依据。例如 web_search 的描述会告诉模型它可以搜索互联网,read_file 的描述会说明它可以读取文件。

描述应当包含:

  • 工具能做什么;
  • 适合在什么场景使用;
  • 输入参数的大致含义;
  • 使用时需要注意的限制。

3. getParameterSchema():定义工具参数

工具参数使用 JSON Schema 描述。例如文件读取工具要求一个 path 参数:

{
  "type": "object",
  "properties": {
    "path": {
      "type": "string",
      "description": "Absolute file path"
    }
  },
  "required": ["path"]
}

模型会依据这个 Schema 生成参数,Java 端再从 ToolContext.parameters 中读取参数。

4. execute():实际执行逻辑

工具执行接口返回 CompletableFuture<ToolResult>,说明工具系统从设计上支持异步执行。

ToolResult 统一表示成功、失败、文本输出、结构化数据和多模态内容,避免每个工具定义不同的返回格式。

四、当前实现的工具

4.1 文件和命令工具

文件工具集中在 FileTools.java,命令工具位于 ExecTool.java。

工具名功能实现位置
exec执行 Shell 命令并返回标准输出和错误输出ExecTool
read_file读取指定文件内容FileTools.readFile()
write_file写入文件内容并按需创建父目录FileTools.writeFile()
list_dir列出目录下的文件和子目录FileTools.listDir()
grep_search在目录范围内搜索文本FileTools.grepSearch()

这些工具涉及本地文件系统和进程执行,因此必须结合沙箱使用。工具本身负责业务执行,是否允许执行则由沙箱策略进一步决定。

4.2 网络和网页工具

网页工具位于:

两者职责不同:

工具名功能
web_search根据关键词搜索网页,返回搜索结果摘要或链接
web_fetch根据 URL 抓取网页,并提取可读文本

典型调用顺序是:

用户询问实时信息
    → web_search
    → 从搜索结果中选择 URL
    → web_fetch
    → 根据网页内容回答

当前真实运行日志中,模型实际使用过的就是 web_search 和 web_fetch。

4.3 会话和 Agent 工具

会话工具位于 builtin 目录下:

工具名功能
session_status查看当前会话状态、模型和用量
sessions_list查询会话列表
sessions_history查询指定会话历史
sessions_send向另一个会话发送消息
sessions_spawn创建后台子 Agent
agents_list查询可以被使用的 Agent

这些工具使 Agent 具备“管理其他 Agent 或会话”的能力,是多 Agent 协作的基础。

4.4 Gateway 和消息工具

相关实现包括:

gateway 是一个顶层工具,内部通过 action 区分重启、配置读取、配置更新等操作;message 也是一个顶层工具,内部可以处理消息发送、编辑、删除、回复等动作。

这体现了当前工具设计中的一个重要原则:相关操作合并为一个领域工具,通过参数区分具体动作,而不是把每一个动作都注册为独立工具。

4.5 浏览器和 Canvas 工具

浏览器工具位于 BrowserTool.java,Canvas 工具位于 CanvasTool.java。

browser 可以根据参数执行:

  • 查看浏览器状态;
  • 打开和关闭页面;
  • 聚焦标签页;
  • 导航到 URL;
  • 获取页面快照;
  • 截图;
  • 执行页面交互;
  • 处理弹窗和文件上传。

canvas 用于 Canvas 节点展示、隐藏、导航、脚本执行、快照和 A2UI 操作。

浏览器控制服务使用独立端口,工具通过 BrowserClient 与浏览器控制服务通信,而不是直接把浏览器逻辑写进 Agent 循环。

4.6 记忆、图片、语音和节点工具

工具名实现类功能
memory_searchMemoryTool.java搜索 MEMORY.md 和记忆目录中的内容
imageImageTool.java使用视觉模型分析本地图片
ttsTtsTool.java文本转语音
nodesNodesTool.java查看和操作已连接节点
cronCronTool.java创建、更新、删除和执行定时任务

当前 memory_get 不是独立工具,Java 实现只注册了 memory_search。

五、工具在哪里注册

应用启动时,Spring 创建 ToolRegistry,在 AgentBeanConfig.java 中注册内置工具:

registry.register(new ExecTool());
registry.register(FileTools.readFile());
registry.register(FileTools.writeFile());
registry.register(FileTools.listDir());
registry.register(FileTools.grepSearch());

随后调用 OpenClawToolFactory.java,批量创建 OpenClaw 扩展工具:

var extensionTools = OpenClawToolFactory.createTools(options);
registry.registerAll(extensionTools);

当前静态工具数量为:

5 个基础工具 + 17 个扩展工具 = 22 个工具

注册阶段的工具只是保存在内存中,并不代表已经发送给大模型。

六、工具什么时候注入给大模型

真正注入发生在 AgentRunner.java 的 executeLoop() 中。

每一轮模型请求都会执行以下逻辑:

ToolRegistry.listAll()
    → 按沙箱策略过滤
    → 读取工具名称、描述、参数 Schema
    → 构造 ChatRequest.tools
    → 调用 Provider

核心代码的逻辑相当于:

.tools(availableTools(context).stream().map(tool -> {
    Map<String, Object> definition = new LinkedHashMap<>();
    definition.put("name", tool.getName());
    definition.put("description", tool.getDescription());
    definition.put("input_schema", tool.getParameterSchema());
    return definition;
}).toList())

因此,工具不是只在 Agent 启动时注入一次,而是每次调用大模型之前重新生成工具列表。

这样设计的好处是:

  • 每轮都可以重新应用沙箱策略;
  • 动态注册的插件工具可以及时生效;
  • 不同 Agent、不同会话可以拥有不同工具上下文;
  • 后续可以根据模型能力、用户权限和会话状态动态调整工具。

七、插件工具的动态注入

在 AgentRunner.java 的 Agent 开始阶段,系统会解析插件工具:

PluginRegistry
    → PluginToolResolver.resolvePluginTools()
    → ToolRegistry.registerPluginTool()
    → 下一步 availableTools()
    → 注入模型请求

插件工具需要经过名称冲突检查。如果插件工具名称已经存在,系统不会覆盖原有内置工具,而是跳过该插件工具并记录警告。

因此,tools=22 是当前静态工具集合的数量;如果启用了插件,最终数量可能大于 22。

八、沙箱策略如何影响最终工具列表

工具注册数量不等于最终发送数量。AgentRunner.availableTools() 会调用 isToolAllowed(),根据当前 Sandbox 的工具策略过滤工具。

例如:

ToolRegistry 中有 22 个工具
    → 沙箱策略禁止 exec
    → 发给模型的列表变成 21 个工具

这是一种重要的安全设计:

  • 工具实现和安全策略分离;
  • 工具本身不需要知道当前用户权限;
  • 不同会话可以使用不同的工具白名单;
  • 沙箱可以限制文件访问和命令执行能力。

九、Provider 如何发送工具定义

当前 Provider 是 OpenAI 兼容格式实现,位于 OpenAICompatibleProvider.java。

Agent 层使用统一格式:

{
  "name": "web_search",
  "description": "Search the web for information...",
  "input_schema": {
    "type": "object"
  }
}

Provider 层会转换为 OpenAI Function Calling 格式:

{
  "type": "function",
  "function": {
    "name": "web_search",
    "description": "Search the web for information...",
    "parameters": {
      "type": "object"
    }
  }
}

随后放入 /v1/chat/completions 请求体的 tools 字段。

Provider 日志中的:

tools=22

表示本次 HTTP 请求携带了 22 个工具定义,并不表示模型实际调用了 22 个工具。

十、模型调用工具后的执行流程

完整的多轮调用过程如下:

第 1 轮
  Java 发送用户消息 + 22 个工具定义
  → 模型返回普通文本或 tool_calls

如果返回 tool_calls
  → AgentRunner 根据工具名从 ToolRegistry 查找工具
  → 构造 ToolContext
  → 调用 AgentTool.execute()
  → 得到 ToolResult
  → 将工具结果追加到 messages

第 2 轮
  Java 发送历史消息 + 工具结果 + 22 个工具定义
  → 模型继续推理

直到模型返回最终文本,或者达到最大轮数

工具调用本身不会直接产生最终答案。工具只负责提供外部信息或执行外部动作,最终回答仍由大模型生成。

十一、为什么采用顶层工具加 action 的设计

当前很多工具不是把每个动作注册成单独工具,而是使用一个顶层工具配合 action 参数,例如:

browser.open
browser.navigate
browser.screenshot

在 Provider 看来,它们通常仍然是一个 browser 工具;具体操作通过参数区分。

这种设计有几个优点:

1. 减少工具数量

如果浏览器的每个动作都单独注册,工具数量会迅速膨胀,增加模型的选择成本和上下文长度。

2. 统一领域模型

浏览器相关能力集中在 BrowserTool,消息相关能力集中在 MessageTool,后续维护更清晰。

3. 方便权限控制

可以对整个 browser 工具进行控制,也可以在工具内部进一步控制某些 action。

4. 便于协议扩展

新增浏览器动作时,通常只需要扩展 action 枚举和执行逻辑,不必改变整个工具注册体系。

十二、已经实现但当前没有注册的工具

项目中还存在 PatchTool.java,工具名是 patch,用于对文件进行文本替换或补丁操作。

但是当前 AgentBeanConfig.java 没有注册它,因此:

PatchTool 已实现
PatchTool 未进入当前 22 个静态工具
PatchTool 当前不会被发送给模型

如果需要启用,需要在工具注册阶段显式加入,并同时评估:

  • 沙箱路径限制;
  • 文件写入权限;
  • 补丁失败时的错误处理;
  • 是否需要按模型或用户权限开放。

十三、当前设计的优点和可以改进的地方

当前优点

  1. 统一接口:所有工具都遵循 AgentTool,便于扩展。
  2. 注册中心管理:工具查找、列举、去重集中在 ToolRegistry。
  3. Provider 解耦:工具不依赖具体模型格式,由 Provider 负责适配。
  4. 支持异步执行:工具返回 CompletableFuture,适合网络和长耗时任务。
  5. 安全策略独立:沙箱策略在注入模型前过滤工具。
  6. 支持插件扩展:插件工具可以在 Agent 运行时动态加入。
  7. 支持多轮调用:模型可以连续执行多个工具,然后综合结果回答。

建议改进

1. 记录每轮实际发送的工具名称

当前日志主要记录 tools=22,建议增加脱敏后的工具名列表,例如:

Provider tools: count=22, names=[exec, read_file, web_search, web_fetch, ...]

这样可以直接判断是注册数量、过滤数量还是最终发送数量。

2. 区分注册工具数和可用工具数

建议记录:

registeredTools=22
pluginTools=0
filteredTools=1
providerTools=21

这比单独记录 tools=22 更容易排查权限问题。

3. 对高风险工具增加二次确认

对于 exec、write_file、gateway、message 等工具,可以根据操作类型增加用户确认或管理员权限校验。

4. 为工具增加超时、重试和熔断

网络工具、浏览器工具和子 Agent 工具可能长时间等待,建议统一提供:

  • 执行超时;
  • 可重试次数;
  • 取消信号;
  • 熔断和降级策略。
5. 统一工具调用审计

建议为每次工具调用记录:

  • traceId;
  • runId;
  • sessionId;
  • 工具名;
  • 工具调用开始时间;
  • 执行耗时;
  • 成功或失败;
  • 结果长度;
  • 脱敏后的错误摘要。

不建议直接记录完整 Prompt、完整工具参数或网页正文,以免日志泄露敏感信息。

十四、总结

当前 OpenClaw Java 的工具系统可以概括为:

工具实现
  → ToolRegistry 注册
  → Agent 启动时注入插件工具
  → 每轮模型调用前按沙箱策略过滤
  → 构造 ChatRequest.tools
  → Provider 转换为 Function Calling 格式
  → 模型决定是否调用
  → AgentRunner 执行工具
  → 工具结果回传模型
  → 多轮循环直到最终回答

当前静态注册工具共有 22 个,覆盖:

  • Shell 和文件系统;
  • 网络搜索和网页抓取;
  • 会话和子 Agent;
  • Gateway 和消息;
  • 浏览器和 Canvas;
  • 记忆、图片、语音、节点和定时任务。

其中,tools=22 只表示发送给 Provider 的工具定义数量;在实际请求中,模型可能只调用其中一个或几个工具。当前真实请求已经确认实际使用的是 web_search 和 web_fetch。

更多推荐