Skip to content

第四章 工具系统:Agent的手、眼与触角

核心提示:如果提示词系统是Agent的大脑和神经系统,那么工具系统就是它的手、眼和触角——让它能够触碰和操作外部世界。这一章我们将深入理解核心基础工具的设计哲学,以及它们如何在Agent Loop中成为连接"思考"与"现实"的桥梁。

1. 引言:从"空想家"到"实干家"

1.1 纯LLM的局限

想象这样一个场景:

你问ChatGPT:"帮我查一下我的项目里有多少个TODO注释。"

它可能会回答:"你可以使用命令grep -r "TODO" .来查找。"然后它就停住了。

它知道方法,但它不能做。它被关在纯文本的牢笼里,只能说话,不能行动。

这就是纯LLM的宿命——一个博学的空想家,有无限的知识,却没有一双手。

1.2 工具:能力的延伸

人类之所以强大,不仅因为我们会思考,更因为我们会使用工具。

同样的,Agent之所以从"聊天机器人"进化为"执行引擎",关键在于它获得了一套可编程的工具系统

但这里有一个设计上的难题:

应该给Agent多少工具?

  • 太少→能力受限,连简单的文件操作都完成不了
  • 太多→选择困难,LLM会被淹没在工具海洋中
  • 太专→每个任务都要开发新工具,失去通用性
  • 太泛→工具描述模糊,LLM不知道如何正确使用

OpenClaw的答案是:四个核心基础工具,构成最小完备集

这个选择背后,是对Unix哲学和LLM能力边界的深刻理解。


2. 核心基础工具:最小完备集的设计哲学

2.1 为什么是"四个"

Unix操作系统的设计哲学是:"做一件事,把它做好"。通过简单的工具组合,完成复杂的任务。

OpenClaw继承了这一哲学,提炼出四个元能力(Meta-capabilities):

核心工具元能力人类感官类比核心作用
read获取(Acquisition)阅读记忆"是什么"——读取文件内容
write创建(Creation)书写记录"写下去"——创建新文件
edit修改(Modification)编辑整理"改哪里"——修改现有文件
exec执行(Execution)手与声音"做得到"——执行命令改变外部世界

这四个工具构成了一个完整的感知-认知-行动闭环:

read获取信息 → 推理思考 → write/edit修改 → exec执行验证
     ↑                                              |
     └──────────────────────────────────────────────┘
                      (反馈循环)

2.2 组合的力量

虽然只有四个基础工具,但它们的组合几乎可以解决任何本地计算任务。

场景一:代码清理

用户:"删掉项目里所有临时的.log文件"

工具链:

  1. exec(find . -name "*.log") → 发现所有日志文件
  2. exec(rm) → 执行删除

场景二:代码审查

用户:"检查项目中所有使用eval的地方"

工具链:

  1. exec(find . -name "*.js") → 发现所有JS文件
  2. exec(grep -r "eval\(") → 定位可疑代码
  3. read → 读取上下文确认安全性

场景三:依赖分析

用户:"看看哪些文件引用了lodash"

工具链:

  1. exec(find . -name "*.{js,ts}") → 发现源码文件
  2. exec(grep -r "from ['"]lodash") → 定位导入语句
  3. exec(sort | uniq -c) → 统计引用次数

这种组合能力的关键在于:工具足够基础,可以灵活组合;又足够强大,每个都能独当一面

2.3 为什么需要专用工具而非只用exec

你可能会问:既然exec可以执行任何命令,为什么还要read、write、edit?直接用catechosed不就行了?

这是因为LLM不是人类程序员,它需要明确的、结构化的反馈。

对比示例

任务exec方式专用工具方式差异
读文件cat file.txtread("file.txt")read智能截断保护上下文,cat可能返回10MB内容
写文件echo "content" > filewrite("file.txt", content)write提供原子性和错误处理
改文件sed -i 's/old/new/g'edit("file.txt", old, new)edit提供精确匹配和确认机制

更重要的是,分离专用工具让LLM的选择更明确

当用户说"读取配置文件",LLM面对明确的read工具,会比面对万能的exec更容易做出正确选择。

这种"约束即自由"的设计,降低了LLM的认知负荷,提高了决策准确性。


3. exec工具:权力的游戏

在所有工具中,exec是最强大,也是最危险的。它赋予了Agent"上帝视角"的操作能力——可以读写任何文件、执行任何命令、访问网络、甚至删除整个系统。

如何驯服这头猛兽,是OpenClaw工具系统设计中最精妙的部分。

3.1 交互式命令环境:不只是执行命令

如果你写过调用Shell的脚本,可能会用subprocess.runos.system。但在Agent场景中,这有个致命缺陷:没有交互性

想象Agent运行npm init创建一个新项目。这个命令会停下来问你:

  • "Package name: (my-project)"
  • "Version: (1.0.0)"
  • "Description:"

用普通的子进程调用,程序会永远卡住——因为Agent看不到提示,也输不了回答。

OpenClaw的解决方案:交互式命令环境

这个环境模拟了一个真实的终端:

特性普通子进程交互式环境
交互性无法响应提示能看到"Press Y to continue"并输入Y
颜色代码原始转义字符正确解析或过滤
进度显示缓冲区延迟实时流式输出
上下文保持每条命令独立cd后下条命令仍在同一目录

注意:交互式环境在Windows平台上不可用,且当初始化失败时会自动回退到标准子进程模式。

这意味着Agent可以像人类一样使用交互式工具:运行npm init并回答提示、执行git add -p并选择hunk、甚至运行vim编辑文件(虽然通常不建议)。

3.2 安全架构:多层防御

既然exec如此强大,如何防止它做坏事?OpenClaw设计了三层安全机制:

第一层:Security模式(执行安全模式)

OpenClaw提供三种安全级别,通过tools.exec.security配置:

模式说明适用场景
deny阻止所有主机执行请求高安全要求环境
allowlist只允许白名单中的命令推荐日常使用
full允许所有命令(相当于提升权限)受信任环境

第二层:Ask模式(审批提示设置)

通过tools.exec.ask配置何时提示用户确认:

模式说明
off从不提示
on-miss命令不在白名单时提示(默认)
always每个命令都提示

第三层:安全命令列表(只读工具)

tools.exec.safeBins定义了一小部分只读的工具,可以在白名单模式下无需显式批准即可运行:

  • 默认安全命令:jq, cut, uniq, head, tail, tr, wc
  • 限制:安全命令在安全加固后,其参数会被当作字面量处理,防止恶意注入
  • 注意:默认安全命令需要配合safeBinProfiles配置才能生效
  • 警告:不要将解释器(如python3, node, bash)添加到安全命令列表

示例白名单配置(~/.openclaw/exec-approvals.json):

json
{
  "agents": {
    "main": {
      "security": "allowlist",
      "ask": "on-miss",
      "allowlist": [
        {
          "pattern": "~/Projects/**/bin/rg",
          "lastUsedCommand": "rg -n TODO"
        }
      ]
    }
  }
}

其他安全机制

执行超时

通过timeout参数防止命令无限运行:

json
{ "tool": "exec", "command": "long-running-task", "timeout": 300 }

注意:当使用background=trueyieldMs参数时,如果没有显式指定timeout,命令将不会超时。

沙盒执行

通过host=sandbox在隔离环境中执行命令(需要启用沙盒功能)。

这种"纵深防御"架构确保了即使某一层被突破,还有其他层保护。

3.3 后台进程:异步的世界

很多任务需要"启动后保持运行":开发服务器、日志监控、后台任务队列。

如果Agent运行npm run dev,命令不会结束——它会一直占用Agent的主线程。

OpenClaw的后台进程管理解决了这个问题:

工作机制

  1. Agent调用exec时标记background: true
  2. 系统启动进程,立即返回一个PID(进程ID)
  3. Agent继续处理其他任务
  4. 随时可以通过PID查看日志、发送信号、或终止进程

生命周期管理

启动 → 运行 → (监控) → 终止
 │       │       │        │
 │       │       │        └─ 正常结束 / 超时终止 / 错误退出
 │       │       └─ 日志收集 / 健康检查 / 资源限制
 │       └─ 后台运行 / 异步处理 / 不阻塞主循环
 └─ 分配PID / 设置环境 / 记录元数据

这让Agent可以:

  • 启动一个开发服务器,然后继续写代码
  • 运行测试套件在后台,同时分析其他文件
  • 监控日志输出,出现错误时立即报告

3.4 环境一致性:会话管理

人类操作终端时,有一个隐式的状态:

  • 当前在哪个目录
  • 设置了什么环境变量
  • 之前执行了什么命令影响了现在

OpenClaw通过持久化Shell会话模拟这种状态:

命令1: cd /home/user/project
命令2: npm install    (在project目录下执行)
命令3: npm run build  (仍然在project目录,能看到node_modules)

如果没有会话保持,每条命令都是独立的,Agent将不得不在每条命令中重复完整路径,效率极低且容易出错。


4. 文件系统工具:read、write、edit

如果说exec是Agent的手,那么read、write、edit就是Agent的精细操作工具——让它能够精确地操作文件内容。

4.1 read工具:知识的入口

read是最基础也最复杂的工具。基础在于它的功能很简单——读取文件内容;复杂在于它面临上下文窗口限制的挑战。

上下文窗口的硬约束

LLM的上下文窗口是有限的。这个限制对read工具提出了严峻挑战:

问题场景

  • package-lock.json可能非常大
  • 日志文件可能无限增长
  • 数据库导出可能达到GB级

如果无脑读取,后果是灾难性的:

  1. 一次读取就耗尽Token预算
  2. 挤掉系统提示词和对话历史
  3. LLM"失忆",忘记自己是谁、该做什么

智能读取策略

OpenClaw的read工具实现了多层防御策略:

第一层:预算感知

读取前计算可用预算:

总窗口: 模型上下文上限
已用: 系统提示词 + 对话历史
安全余量: 预留缓冲
可用读取预算: 剩余可用空间

read工具会根据模型上下文窗口大小动态调整读取预算。如果文件超过预算,自动触发截断。

第二层:智能截断

不是简单截断到固定长度,而是根据文件类型智能处理:

文件类型截断策略原因
代码文件保留开头(导入/定义)+ 函数签名列表开头定义接口,后面是实现细节
日志文件保留尾部(最新日志)最近的日志最有价值
配置文件完整保留(通常不大)配置项相互依赖,不能截断
JSON数据保留结构,截断数组内容保留schema,牺牲具体数据

第三层:分页机制

当文件被截断时,read返回:

[文件: src/large-file.ts]
[显示: 第1-200行 / 共5000行]
[提示: 使用 offset=201 读取下一页]

... (内容) ...

如果LLM觉得还没读够,它可以主动发起第二次read调用,传入offset=201参数继续读取。

这种按需加载机制让Agent可以处理任意大小的文件,而不用担心上下文爆炸。

内容清理

现代文件中常常包含非文本内容:

  • Base64编码的图片(数据URI)
  • 二进制数据转储
  • 过长的SVG路径

这些内容对LLM毫无价值,却大量消耗Token。read工具会自动清理:

清理规则

  • Base64图片 → 替换为[Image: 大小]
  • 超长行 → 截断并提示[Line truncated]
  • 二进制内容 → 检测编码并警告[Binary content detected]

4.2 write工具:创建的力量

write工具用于创建新文件。看似简单,但在Agent场景中有特殊考量:

原子性写入

  • 先写入临时文件,确认成功后再重命名
  • 避免写入过程中断导致文件损坏

路径自动创建

  • 如果父目录不存在,自动创建
  • Agent无需关心mkdir -p

编码智能处理

  • 自动检测并使用合适的文件编码
  • 处理BOM头、换行符差异

4.3 edit工具:精确的修改

edit工具是Agent修改现有文件的主要方式。与exec调用sed不同,edit提供了:

精确匹配

  • 必须提供old_stringnew_string
  • 只有完全匹配时才替换,避免误改

多行支持

  • 可以匹配和替换多行内容
  • 适合修改函数、代码块

失败安全

  • 如果找不到匹配内容,返回错误而非静默失败
  • Agent可以据此调整策略

5. 通过exec实现文件发现与搜索

虽然Glob和Grep不是独立的工具,但Agent可以通过exec调用系统命令实现相同的功能。

5.1 文件发现:find命令

使用exec调用find命令实现文件发现:

json
{ "tool": "exec", "command": "find . -name \"*.ts\" -type f" }

返回结构化文件列表,Agent可以进一步处理。

场景:理解项目结构

Agent刚进入一个新项目,它需要快速建立对项目的认知:

  1. exec("ls -la") → 根目录有什么?(看到package.json→这是Node项目)
  2. exec("find src -type f | head -50") → 源码结构如何?(看到components/, utils/, pages/
  3. exec("find . -name '*.test.ts'") → 测试覆盖如何?(看到20个测试文件)

这些信息构成了Agent对项目的心智模型,后续的所有决策都基于此。

5.2 内容搜索:grep命令

使用exec调用grep命令实现内容搜索:

json
{ "tool": "exec", "command": "grep -r \"TODO\" . --include=\"*.js\"" }

场景:安全审计

用户:"检查项目中有没有使用eval"

  1. exec("find . -name '*.js'") → 找到所有JS文件(可能有100个)
  2. exec("grep -r 'eval\\(' . --include='*.js'") → 精确定位使用eval的位置(可能只有3处)

如果没有grep,Agent需要read所有100个文件,消耗大量Token;有了grep,它只需要读取那3个相关文件。

5.3 组合命令的威力

exec支持管道和组合命令:

json
{ "tool": "exec", "command": "find . -name '*.ts' | xargs grep -l 'lodash' | wc -l" }

这展示了Unix哲学的力量——简单命令的组合产生强大功能。


6. 工具描述:LLM的选择依据

现在我们知道四个核心工具分别是做什么的。但关键问题是:LLM怎么知道该用哪个?

LLM看不到工具的源代码。它能看到的,只有一段文本描述(Tool Description)。这是LLM理解工具的唯一途径。

6.1 描述的质量决定选择的准确性

糟糕的描述

Tool: exec
Description: "Run commands"

LLM的反应:"Run commands... 什么命令?什么限制?什么格式?算了,我试试再说。"

良好的描述

Tool: exec
Description: "Execute shell commands with background continuation.
Use yieldMs/background to continue later via process tool.
Use pty=true for TTY-required commands (terminal UIs, coding agents).
Dangerous operations require human approval."

LLM的反应:"明白了,这是一个交互式Shell,可以做大多数事情,但危险操作会被拦截。我可以放心使用。"

6.2 描述的结构化要素

一个好的工具描述应该包含:

要素作用示例
功能定义这个工具是做什么的"Find files matching a pattern"
参数说明需要什么输入"pattern: glob pattern like '**/*.js'"
使用场景什么时候用它"Use when you need to discover files"
限制说明不能做什么"Max 1000 results, excludes node_modules"
返回格式输出什么"Returns array of file paths"

6.3 工具选择的认知过程

当用户说"找一下配置文件",LLM的选择过程是:

  1. 解析意图:用户想要"查找"某类文件
  2. 匹配工具
    • exec: "可以,用find命令" → 但需要通过命令实现
    • read: "用于读取具体文件" → 不合适
    • write/edit: "用于修改文件" → 不合适
  3. 决策:使用exec调用find命令
  4. 参数生成command: "find . -name '*.json' -o -name '*.yaml' -o -name '*.yml'"

如果工具描述不清晰,第2步就会出错,导致"该用read的时候用了exec"或"该用edit的时候用了write"。

6.4 优化工具描述的技巧

使用关键词

LLM对特定词汇敏感。在描述中使用这些词可以提高匹配准确度:

  • 发现/查找/定位 → 触发exec调用find
  • 搜索/匹配/包含 → 触发exec调用grep
  • 读取/查看/获取内容 → 触发read
  • 写入/创建 → 触发write
  • 修改/替换/编辑 → 触发edit
  • 执行/运行/操作 → 触发exec

明确边界

告诉LLM什么时候不要用这个工具:

"Use read to get file contents.
Do NOT use read if you need to search across files - use exec with grep instead."

提供示例

在描述中加入使用示例:

"Examples:
- Find all tests: exec with command: 'find . -name '*.test.ts''
- Find config files: exec with command: 'ls *.json *.yaml'"

7. Agent Loop中的工具调用

现在我们把视角拉高,看看工具是如何在Agent的Agent Loop(智能体循环)中流转的。

7.1 完整的循环流程

┌─────────────────────────────────────────────────────────────────────┐
│ 第1轮:观察与理解                                                    │
├─────────────────────────────────────────────────────────────────────┤
│ 用户输入: "帮我找出项目中所有使用console.log的文件"                    │
│                                                                     │
│ Agent理解: 用户想清理调试代码。需要:1)找到JS/TS文件 2)搜索console.log │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ 第2轮:选择与行动(工具调用)                                        │
├─────────────────────────────────────────────────────────────────────┤
│ Thought: "先用exec find找到所有JS文件,然后用exec grep搜索console.log" │
│                                                                     │
│ Action: 调用exec                                                    │
│ {                                                                   │
│   "tool": "exec",                                                   │
│   "args": {"command": "find . -name '*.{js,ts}' -type f"}           │
│ }                                                                   │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ 第3轮:观察结果                                                      │
├─────────────────────────────────────────────────────────────────────┤
│ Observation: ["src/index.js", "src/utils.js", "src/app.ts", ...]    │
│                                                                     │
│ Thought: "找到了50个文件。现在需要搜索哪些包含console.log"            │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ 第4轮:再次行动                                                      │
├─────────────────────────────────────────────────────────────────────┤
│ Action: 调用exec                                                    │
│ {                                                                   │
│   "tool": "exec",                                                   │
│   "args": {"command": "grep -r 'console\\.(log|warn|error)' . --include='*.js' --include='*.ts' -l"} │
│ }                                                                   │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│ 第5轮:生成回复                                                      │
├─────────────────────────────────────────────────────────────────────┤
│ Observation: {"matched": ["src/index.js", "src/utils.js", ...]}     │
│                                                                     │
│ Thought: "找到了5处。任务完成,总结给用户"                            │
│                                                                     │
│ Final Answer: "在以下5个文件中发现console.log使用:\n1. src/index.js │
第42行..."                                                          │
└─────────────────────────────────────────────────────────────────────┘

7.2 循环的终止条件

Agent Loop不会无限进行。它会在以下情况终止:

正常终止

  • 任务完成,生成最终回复
  • 达到最大循环次数(防止死循环)
  • 用户明确要求停止

异常终止

  • 工具调用错误且无法恢复
  • 上下文窗口耗尽
  • 执行超时

7.3 工具链:多工具的协作

复杂任务需要多个工具按顺序协作:

场景:重构一个函数

Step 1: exec("find . -name '*.ts'")              → 发现文件
Step 2: exec("grep -r 'function oldName' .")     → 定位函数
Step 3: read("src/utils.ts", offset=40)          → 读取上下文
Step 4: [LLM推理] 生成新代码
Step 5: edit("src/utils.ts", old_string, new_string) → 执行修改
Step 6: read("src/utils.ts")                     → 验证修改
Step 7: exec("npm test")                         → 运行测试确认

这种工具链的编排完全由LLM根据任务需求动态决定,无需预编程。


8. 总结

这一章我们深入探讨了OpenClaw的工具系统——这套让Agent从"空想家"进化为"实干家"的执行架构。

核心洞察回顾

  1. 四个核心基础工具的最完备性:read(获取)、write(创建)、edit(修改)、exec(执行)构成感知-认知-行动闭环

  2. exec的双刃剑特性:交互式环境提供交互能力,三层安全机制(security模式、ask模式、安全命令列表)保证安全性

  3. 智能读取的防御性:预算感知、智能截断、分页机制,保护上下文窗口不被大文件撑爆

  4. 描述即接口:工具描述的质量直接决定LLM选择的准确性,结构化描述包含功能、参数、场景、限制、返回格式

  5. Agent Loop的工具链:观察→思考→行动→观察的循环,多工具动态协作完成复杂任务

设计哲学升华

OpenClaw的工具系统体现了"约束即自由"的工程智慧:

  • 四个基础工具看似限制了能力,实际上赋予的是组合的自由
  • 安全审批看似限制了效率,实际上赋予的是信任的自由
  • 上下文预算看似限制了信息,实际上赋予的是聚焦的自由

当一个系统给用户的不是无限的选择,而是经过深思熟虑的、有良好设计的有限选择时,它反而能够释放出更大的创造力。

これが OpenClaw ツール システムの本質です。これは、エージェントにさらに多くのツールを提供することではなく、適切なツールを提供することです


次のステップ:

次の章では、メッセージ ループとイベント ドライブについて詳しく説明します。エージェントの「ハートビート」がどのように機能するか、およびアーキテクチャ レベルでのエージェント ループの完全な実装について説明します。