第四章 工具系統: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工具系統的精髓:不是給Agent更多的工具,而是給它更對的工具。
下一步:
在下一章,我們將深入探討消息循環與事件驅動——看看Agent的"心跳"是如何運作的,以及Agent Loop在架構層面的完整實現。