第四章 工具系统: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文件"
工具链:
- exec(
find . -name "*.log") → 发现所有日志文件 - exec(
rm) → 执行删除
场景二:代码审查
用户:"检查项目中所有使用eval的地方"
工具链:
- exec(
find . -name "*.js") → 发现所有JS文件 - exec(
grep -r "eval\(") → 定位可疑代码 - read → 读取上下文确认安全性
场景三:依赖分析
用户:"看看哪些文件引用了lodash"
工具链:
- exec(
find . -name "*.{js,ts}") → 发现源码文件 - exec(
grep -r "from ['"]lodash") → 定位导入语句 - exec(
sort | uniq -c) → 统计引用次数
这种组合能力的关键在于:工具足够基础,可以灵活组合;又足够强大,每个都能独当一面。
2.3 为什么需要专用工具而非只用exec
你可能会问:既然exec可以执行任何命令,为什么还要read、write、edit?直接用cat、echo、sed不就行了?
这是因为LLM不是人类程序员,它需要明确的、结构化的反馈。
对比示例:
| 任务 | exec方式 | 专用工具方式 | 差异 |
|---|---|---|---|
| 读文件 | cat file.txt | read("file.txt") | read智能截断保护上下文,cat可能返回10MB内容 |
| 写文件 | echo "content" > file | write("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.run或os.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):
{
"agents": {
"main": {
"security": "allowlist",
"ask": "on-miss",
"allowlist": [
{
"pattern": "~/Projects/**/bin/rg",
"lastUsedCommand": "rg -n TODO"
}
]
}
}
}其他安全机制
执行超时:
通过timeout参数防止命令无限运行:
{ "tool": "exec", "command": "long-running-task", "timeout": 300 }注意:当使用background=true或yieldMs参数时,如果没有显式指定timeout,命令将不会超时。
沙盒执行:
通过host=sandbox在隔离环境中执行命令(需要启用沙盒功能)。
这种"纵深防御"架构确保了即使某一层被突破,还有其他层保护。
3.3 后台进程:异步的世界
很多任务需要"启动后保持运行":开发服务器、日志监控、后台任务队列。
如果Agent运行npm run dev,命令不会结束——它会一直占用Agent的主线程。
OpenClaw的后台进程管理解决了这个问题:
工作机制:
- Agent调用exec时标记
background: true - 系统启动进程,立即返回一个
PID(进程ID) - Agent继续处理其他任务
- 随时可以通过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级
如果无脑读取,后果是灾难性的:
- 一次读取就耗尽Token预算
- 挤掉系统提示词和对话历史
- 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_string和new_string - 只有完全匹配时才替换,避免误改
多行支持:
- 可以匹配和替换多行内容
- 适合修改函数、代码块
失败安全:
- 如果找不到匹配内容,返回错误而非静默失败
- Agent可以据此调整策略
5. 通过exec实现文件发现与搜索
虽然Glob和Grep不是独立的工具,但Agent可以通过exec调用系统命令实现相同的功能。
5.1 文件发现:find命令
使用exec调用find命令实现文件发现:
{ "tool": "exec", "command": "find . -name \"*.ts\" -type f" }返回结构化文件列表,Agent可以进一步处理。
场景:理解项目结构
Agent刚进入一个新项目,它需要快速建立对项目的认知:
exec("ls -la")→ 根目录有什么?(看到package.json→这是Node项目)exec("find src -type f | head -50")→ 源码结构如何?(看到components/,utils/,pages/)exec("find . -name '*.test.ts'")→ 测试覆盖如何?(看到20个测试文件)
这些信息构成了Agent对项目的心智模型,后续的所有决策都基于此。
5.2 内容搜索:grep命令
使用exec调用grep命令实现内容搜索:
{ "tool": "exec", "command": "grep -r \"TODO\" . --include=\"*.js\"" }场景:安全审计
用户:"检查项目中有没有使用eval"
exec("find . -name '*.js'")→ 找到所有JS文件(可能有100个)exec("grep -r 'eval\\(' . --include='*.js'")→ 精确定位使用eval的位置(可能只有3处)
如果没有grep,Agent需要read所有100个文件,消耗大量Token;有了grep,它只需要读取那3个相关文件。
5.3 组合命令的威力
exec支持管道和组合命令:
{ "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的选择过程是:
- 解析意图:用户想要"查找"某类文件
- 匹配工具:
- exec: "可以,用find命令" → 但需要通过命令实现
- read: "用于读取具体文件" → 不合适
- write/edit: "用于修改文件" → 不合适
- 决策:使用exec调用find命令
- 参数生成:
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从"空想家"进化为"实干家"的执行架构。
核心洞察回顾:
四个核心基础工具的最完备性:read(获取)、write(创建)、edit(修改)、exec(执行)构成感知-认知-行动闭环
exec的双刃剑特性:交互式环境提供交互能力,三层安全机制(security模式、ask模式、安全命令列表)保证安全性
智能读取的防御性:预算感知、智能截断、分页机制,保护上下文窗口不被大文件撑爆
描述即接口:工具描述的质量直接决定LLM选择的准确性,结构化描述包含功能、参数、场景、限制、返回格式
Agent Loop的工具链:观察→思考→行动→观察的循环,多工具动态协作完成复杂任务
设计哲学升华:
OpenClaw的工具系统体现了"约束即自由"的工程智慧:
- 四个基础工具看似限制了能力,实际上赋予的是组合的自由
- 安全审批看似限制了效率,实际上赋予的是信任的自由
- 上下文预算看似限制了信息,实际上赋予的是聚焦的自由
当一个系统给用户的不是无限的选择,而是经过深思熟虑的、有良好设计的有限选择时,它反而能够释放出更大的创造力。
これが OpenClaw ツール システムの本質です。これは、エージェントにさらに多くのツールを提供することではなく、適切なツールを提供することです。
次のステップ:
次の章では、メッセージ ループとイベント ドライブについて詳しく説明します。エージェントの「ハートビート」がどのように機能するか、およびアーキテクチャ レベルでのエージェント ループの完全な実装について説明します。