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工具系統的精髓:不是給Agent更多的工具,而是給它更對的工具


下一步

在下一章,我們將深入探討消息循環與事件驅動——看看Agent的"心跳"是如何運作的,以及Agent Loop在架構層面的完整實現。