iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
Claude AI

Claude Code 下班之後:30 天把 CLI 工具養成會自己交差的 AI 員工系列 第 6

砍掉 88% 的固定開銷,但不能砍掉那個 hook

  • 分享至 

  • xImage
  •  

Day 6|砍掉 88% 的固定開銷,但不能砍掉那個 hook

https://ithelp.ithome.com.tw/upload/images/20260822/201836344ORKnoR967.png

昨天那筆帳:空目錄裡回一句 OK,51,673 個 token,0.335 美元。

今天同一件事,同一個空目錄,換兩個參數:

$ claude -p "回覆 OK" --output-format json \
    --tools "Read,Grep,Glob" \
    --setting-sources project,local

結果:6,171 個 token,0.035 美元

token 成本
預設 51,673 $0.3354
精簡 6,171 $0.0352 省 88%

https://ithelp.ithome.com.tw/upload/images/20260822/20183634h6CIwaTgKx.jpg

一天叫 100 次,一個月省下來的錢是 900 美元。今天講這兩個參數怎麼用,以及其中一個的地雷版本。


--tools:最大的一塊肥肉

--tools 指定這次 spawn 要載入哪些內建工具。不給它,就是全部載入。

--tools "Read,Grep,Glob"        # 只讀,適合巡檢型 agent
--tools ""                      # 全部關掉,純文字生成
--tools "default"               # 預設全載(等於不寫)

工具 schema 是固定開銷裡最大的一塊,因為每個工具都要把完整的 JSON Schema 送進去。砍掉十個用不到的工具,省下來的量非常可觀。

怎麼決定留哪些?我的做法是照任務類型分成幾組常數,寫死在程式裡,不要每次現想:

# 巡檢型:只讀,不改任何東西
TOOLS_INSPECT = "Read,Grep,Glob"

# 分析型:可以讀、可以查網路,仍然不能寫
TOOLS_RESEARCH = "Read,Grep,Glob,WebFetch,WebSearch"

# 開發型:完整能力
TOOLS_DEV = "Read,Write,Edit,Bash,Grep,Glob,TodoWrite"

# 純生成:不需要任何工具(摘要、翻譯、格式轉換)
TOOLS_NONE = ""

TOOLS_NONE 特別值得一提。如果你的任務只是「把這段文字摘要成三行」,一個工具都不需要。這種任務在自動化流程裡佔比很高,而它們預設卻都揹著整套工具鏈在跑。

--setting-sources:省得漂亮,但有個坑

Claude Code 啟動時會載入三個層級的設定:user(你家目錄的全域設定)、project(專案的 .claude/settings.json)、local(本機覆寫)。

--setting-sources 決定載入哪幾層。

我原本是這樣寫的:

--setting-sources ""      # ← 這個版本有問題

全部不載入,省最多。跑起來也很正常,token 數字很漂亮。

問題是,專案的 .claude/settings.json 裡有我的 hook

我在那份設定裡掛了一個 PreToolUse hook,用來擋住 agent 修改自己的身分設定檔(防止它改寫自己的權限宣告)。--setting-sources "" 把設定源全部關掉,那個 hook 就沒有載入,於是這道防線在所有自動化路徑上默默消失了。

而且它消失得無聲無息。沒有警告,沒有錯誤,agent 跑得比以前還順,因為少了一個會攔它的東西。

改成這樣就好:

--setting-sources project,local     # 保留專案與本機設定,跳過全域

實測下來,project,local 跟空字串省下的 token 幾乎一樣多(差別在 user 層那份全域設定),但 hook 還在。

這是我在整個優化過程裡學到最重要的一課:省錢的參數和關掉防護的參數,長得很像。你在改成本相關的旗標時,要逐一問「這個旗標還順手關掉了什麼」。

MCP 工具:另一塊看不見的大石頭

如果你掛了 MCP server,工具 schema 的量會再上一個層級。

我自己的平台有 200 個以上的 MCP 工具。全部宣告出去的話,光工具定義就是好幾萬 token,而任何一個 agent 實際上用得到的可能只有二十個。

處理方式有兩層。

第一層,在 CLI 端隔離。 只掛需要的 MCP server:

claude -p "$PROMPT" \
  --mcp-config ./minimal-mcp.json \
  --strict-mcp-config          # 忽略其他所有 MCP 設定來源

--strict-mcp-config 很關鍵。沒有它,你指定的設定會跟全域設定合併,你以為只掛了一個 server,實際上把家目錄那五個也帶進來了。

第二層,在 server 端過濾。 這是我後來的主要做法:MCP server 回應 tools/list 的時候,依照呼叫端的能力宣告過濾,只回傳這個 agent 有權呼叫的工具。

def handle_tools_list(caller_capabilities: set[str]) -> list[dict]:
    """只宣告這個呼叫端真的能用的工具。"""
    return [
        tool.schema for tool in ALL_TOOLS
        if tool.required_capability in caller_capabilities
    ]

這裡有個原則要守住:可發現的工具集合,必須是可呼叫的子集合

換句話說,你可以隱藏一個工具不讓它出現在清單裡,但不能讓一個出現在清單裡的工具在被呼叫時因為權限被拒。前者是省 token,後者是設計缺陷,會讓模型不斷嘗試一個它以為可用的工具,浪費好幾輪。

反過來說也不成立:隱藏不等於禁用。有些協定允許呼叫沒有列在清單裡的工具,所以工具過濾是省錢機制,不是權限機制。真正的權限要在執行的那一刻再檢查一次。這件事 Day 9 會單獨講。

完整的精簡版 spawn

把這幾天的東西合起來:

import subprocess

TOOLS_INSPECT = "Read,Grep,Glob"

def spawn_lean(prompt: str, cwd: str, tools: str = TOOLS_INSPECT) -> str:
    cmd = [
        which_claude(), "-p", prompt,
        "--output-format", "json",
        "--tools", tools,                      # 只帶需要的工具
        "--setting-sources", "project,local",  # 保留 hook,跳過全域
        "--strict-mcp-config",                 # 不要偷偷合併其他 MCP
        "--disallowedTools", "Bash",           # 雙重保險:白名單之外再加黑名單
    ]
    proc = subprocess.run(cmd, cwd=cwd, capture_output=True,
                          text=True, timeout=900)
    if proc.returncode != 0:
        raise RuntimeError(proc.stderr[:500])
    return proc.stdout

--disallowedTools 那行是刻意的冗餘。--tools 已經沒給 Bash 了,再加一次黑名單看起來多餘。我還是留著,因為這兩個參數的解析路徑不同,而我不想賭它們在所有版本裡的交互行為都跟我想的一樣。安全設定寧可重複,不要留給推論。

量測前後對比

改完之後一定要量,不然你不知道自己省到還是省壞了。

measure() {
  claude -p "OK" --output-format json "$@" \
    | jq '[.usage.input_tokens, .usage.cache_creation_input_tokens,
           .usage.cache_read_input_tokens] | add'
}

echo "預設:   $(measure)"
echo "精簡:   $(measure --tools "Read,Grep,Glob" --setting-sources project,local)"
echo "無工具: $(measure --tools "" --setting-sources project,local)"

我的機器上跑出來是 51,673 / 6,171 / 更低。你的環境數字會不一樣,重點是看比例。

代價

砍工具就是砍能力,這件事沒有例外。

我用 TOOLS_INSPECT 跑巡檢任務時,遇過一次 agent 需要看某個 log 的最後 100 行,但它只有 Read(會讀整個檔案)跟 Grep。那個 log 有 40 萬行。它讀了,然後 context 爆掉,任務失敗。

這種失敗的處理方式有兩種。差的做法是把 Bash 加回去,能力恢復了,防線也沒了。好的做法是給它一個專用的小工具,例如一個只能 tail 指定檔案最後 N 行的 MCP 工具。這樣它拿到能力,你保留邊界。

另一個代價比較隱晦:精簡過的 agent 比較不會自己想辦法。工具多的時候,模型會嘗試繞路(讀不到就搜尋,搜不到就執行指令查)。工具少的時候它就直接卡住或放棄。

所以工具清單要跟任務描述一起設計。如果你砍了工具卻還在 prompt 裡寫「必要時可以自行探索」,你是在要求它做不到的事。


第一幕到這裡結束。我們有了一個會自己醒來、記得住上一句、開機不那麼貴的 agent。

它現在還是很笨,因為它的手腳只有那三個內建工具。明天開始第二幕,講怎麼給它一雙自己的手:MCP 到底在解決什麼問題,以及怎麼在四十行內手刻一個。


上一篇
把 CLI 當 runtime 要付多少過路費
下一篇
MCP 到底解決什麼問題
系列文
Claude Code 下班之後:30 天把 CLI 工具養成會自己交差的 AI 員工7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言