Claude Agent SDK:
把 Claude Code 裝進你的程式
主線第 5 課你看過 agent 迴圈的骨架:模型吐一段 JSON、你的程式執行工具、 結果塞回對話,再問一次。Claude Agent SDK 把這個迴圈——連同 Claude Code 整套的讀檔、改檔、跑指令工具、 context 管理、權限與 hooks——包成一個 Python 函式 query()。 你不用再寫迴圈;剩下的工作只有一件:設邊界。
下面是同一個任務(「測試沒過,找出 bug、修好、跑測試確認」)、同一個模型, 只改一行設定的三次真實錄影。按「下一步」一則一則播:
實驗場是真的 Python(在你的瀏覽器裡跑,不用安裝任何東西、不需要任何帳號或金鑰)。 首次載入約需 30–60 秒,正好夠你讀完第 1 節。裡面有六種設定的完整錄影、一台權限閘門模擬器、 一台帳單計算機——選單、滑桿、開關隨便玩。
SDK 替你做掉的事
主線第 5 課手寫的是最小迴圈;本站 LLM 應用開發系列把它寫成能跑的程式。 同一件事交給 SDK,分工變成這樣:
| 工作 | 手寫 tool loop | Agent SDK |
|---|---|---|
| 迴圈 | 自己寫 while、自己判斷何時停 | query() 直接吐出一串訊息,停在 ResultMessage |
| 工具 | 每個都自己寫 schema、自己執行 | 內建 Read/Edit/Write/Bash/WebSearch/WebFetch/Agent…(這次錄影的清單有 29 個) |
| context | 自己管 messages、自己算長度 | 自動重送、自動開 prompt caching,快滿時自動壓縮(有 PreCompact hook 可接) |
| 權限 | 自己寫 if | permission_mode、allowed_tools、disallowed_tools、can_use_tool |
| 攔截與稽核 | 自己包一層 | hooks:PreToolUse、PostToolUse、Stop… |
| 帳單 | 自己加總 usage | ResultMessage 給你 num_turns、total_cost_usd、逐模型的 model_usage |
整支 agent 程式就這麼短(參考程式,不在課內執行;需要 pip install claude-agent-sdk 與 API key):
query() 吐出來的訊息串,就是 hero 播放器裡那一則一則的卡片: SystemMessage(init)(這次有哪些工具、什麼模式)→ AssistantMessage(模型說的話 TextBlock、要求的呼叫 ToolUseBlock)→ UserMessage(工具結果 ToolResultBlock)→ …… → ResultMessage(帳單)。 執行工具、把結果塞回去、再問模型——主線第 5 課你手寫的那幾行,全在 SDK 裡面。
兩個預設值要知道:system prompt 預設是空的(SDK 啟動 Claude Code 時傳 --system-prompt "";要 Claude Code 完整的行為準則,寫 system_prompt={"type": "preset", "preset": "claude_code"}); setting_sources 預設會載入你電腦上的設定(~/.claude、 專案的 CLAUDE.md)。錄影時我們一律設 setting_sources=[], 讓結果只反映程式本身——正式上線時也建議這樣做,不然換一台機器行為就變。
六道閘門:誰說了算
- Hooks:你的 PreToolUse 函式最先執行,可以直接擋(hook 的「放行」不會跳過後面的規則)。
- deny 規則:disallowed_tools。裸名稱("Bash")乾脆把工具從清單拿掉;有範圍的("Bash(rm *)")在這裡擋。
- ask 規則:settings.json 才能設,符合的一律送去問 can_use_tool。
- 權限模式:permission_mode(下表)。
- allow 規則:allowed_tools。另外,工作目錄內讀檔、find/ls 這類唯讀指令本來就免審。
- can_use_tool:前面都沒決定的,才來問你的函式;沒設函式就沒人可問——拒絕。
| permission_mode | 行為(官方文件,2026-09 查證) |
|---|---|
| default | 模式本身不放行任何東西;需要核准又沒被 allow 規則放行的,送去問 can_use_tool |
| acceptEdits | 工作目錄內的改檔,以及 mkdir/touch/rm/mv/cp/sed 自動放行 |
| plan | 只看、只規劃:改檔一律送審,連 allow 規則都不算 |
| dontAsk | 不問人:沒被規則放行的一律拒絕,can_use_tool 永遠不會被叫 |
| bypassPermissions | 全部放行——但 hooks、deny、ask 規則排在它前面,照樣擋得住 |
| auto | 由一個模型分類器決定放行或拒絕(可用性見官方文件) |
錄影裡實際撞到的三件反直覺的事:
① headless 預設=能看不能動。錄影①什麼都沒設:Read 和 find 直接跑,改檔卻被拒——「Claude requested permissions to write to ~/shop-demo/shop/cart.py, but you haven't granted it yet.」 因為沒有人可以按「允許」。模型最後只能把修法用文字告訴你,測試照樣是紅的——而 subtype 仍是 success。
② allowed_tools 是放行清單,不是限制清單。設 bypassPermissions 再把 allowed_tools 只寫 ["Read"], Bash 照樣執行(實測:python3 -c "print(6*7)" 輸出 42)。沒列到的工具掉到權限模式, bypass 一律放行。
③ deny 規則和 hook 連 bypass 都擋得住。同樣是 bypassPermissions, 加上 disallowed_tools=["Bash(rm *)"],rm -rf build 被擋下: 「Permission to use Bash with command rm -rf build && echo "清理完成" has been denied.」——它們排在權限模式前面。
hooks 與 can_use_tool
錄影③的 hook:一個記錄每次呼叫,一個規定 Bash 只准跑 unittest(參考程式):
錄影裡它擋了三次:兩個 find、一個 ls -la——模型只是想找檔案。 原因:在 Linux/macOS 上 Claude Code 預設不給 Glob、Grep 工具,搜尋走 Bash 的 find/grep,所以照樣經過你的 Bash 護欄。 擋下的理由會回給模型:它讀到之後改用 Read 讀檔、用 python3 -m unittest discover -v 跑測試,最後照樣修好。
錄影⑤換成審批函式:
函式被問了兩次:一次 python -m pytest(拒絕)、一次改 shop/cart.py(放行); Read 一次都沒來問——它在第⑤道就放行了。 陷阱:同時設 allowed_tools=["Bash"] 和 can_use_tool, Bash 在第⑤道就被放行,你的函式永遠不會被叫到;SDK 會警告你(實測原文見測驗第 4 題)。 要「每一次都檢查」,用 hook。
plan 模式要配審批函式。錄影④只設了 permission_mode="plan": 沒接 can_use_tool 時,init 的工具清單裡根本沒有 ExitPlanMode (29 個;接了函式的⑤⑥是 32 個,多了 AskUserQuestion、EnterPlanMode、 ExitPlanMode)。模型寫好計畫卻交不出去,四處找工具、兩度嘗試改檔被擋, 繞了 22 輪、花了 $0.198,檔案一個字沒改——plan 模式確實守住了「不動手」,但流程卡死。 錄影⑥加上審批函式:模型呼叫 ExitPlanMode 交出計畫 → 函式核准 → 改檔 → 測試全過,14 輪、$0.107。
ResultMessage 與 session
六段錄影的 subtype 全是 success,包括檔案一個字沒改的①④。 判斷有沒有做成要看 permission_denials(①有 4 筆),再自己驗證——跑測試、看 diff。 要設上限:max_turns 用完會得到 subtype="error_max_turns" (實測只給 2 輪:errors=["Reached maximum number of turns (2)"]), max_budget_usd 超過則是 error_max_budget_usd。
花費的大頭是 context。錄影②的第一次 API 呼叫就送出約 1.46 萬個 token——幾乎全是 29 個工具的說明書; 之後每一輪重送全部歷史(主線第 5 課的「上下文帳」),但 Claude Code 自動開了 prompt caching: 整趟送出約 17 萬個輸入 token,其中 16.2 萬是便宜的 cache_read(牌價的 0.1 倍)、 8,504 個是寫入快取(2 倍),完全沒快取的只有 82 個。整趟 $0.0495,用 Haiku 4.5 牌價自己重算對得上。
記憶在 session,不在模型。第一次跟它說「專案代號是藍鯨」,拿到 ResultMessage.session_id; 第二次 resume=session_id 問「代號是什麼」→「藍鯨」。第三次不帶 resume,它不知道—— 兩次錄影一次回「shop-demo」(拿資料夾名稱瞎猜)、一次回「找不到明確的專案代號」。 resume 一個不存在的 session 會直接丟例外:「No conversation found with session ID: …」。
自訂工具、子代理
問「A1023 和 B77 的狀態」:模型呼叫兩次 mcp__shop__lookup_order,B77 拿到 is_error 的「查無訂單」照樣接得住,3 輪、$0.0058——因為 tools=[] 拿掉了內建工具,第一次呼叫只有 1,339 個 token。 (訂單資料是教學用的假資料;MCP 協定本身的細節在補充 D。)
子代理有自己乾淨的 context,只把最終報告交回主代理;它自己的呼叫在串流裡帶 parent_tool_use_id。2.1.281 的坑:子代理預設丟到背景跑—— 第一次錄影主代理回一句「已在後台派遣,請稍候」就結束了(ResultMessage 先到); prompt 寫明「等它跑完拿到結果再回答」,模型才以 run_in_background: false 前景等待。 把「怎麼做某件事」打包成可重用知識的 Skills 在補充 E; 在終端機裡用 Claude Code 開發的實務在補充 F。
SDK 免費,模型要錢——或者換一個
| 跑法 | 要錢嗎 | 我們驗證了什麼 |
|---|---|---|
| Claude API(ANTHROPIC_API_KEY) | 按 token 計費;官方定價頁寫新帳號有少量免費試用額度 | 本課所有 Claude 錄影(Haiku 4.5,修一次 bug 約 $0.05) |
| 本機 Ollama(v0.14 起有 /v1/messages) | 免費,用自己的電腦 | 沒在本機實測;設定照 Ollama 官方文件:ANTHROPIC_BASE_URL=http://localhost:11434、 ANTHROPIC_AUTH_TOKEN=ollama,建議模型 context ≥ 32K |
| Ollama Cloud 免費方案 | 含入門額度、只能用入門模型、同時 1 個請求(ollama.com/pricing,2026-09) | 沒實測 |
| 自架 vLLM(0.26.0 有 /v1/messages) | 免費,用自己的 GPU | ✅ 實測 qwen3.5-2b:見下文與實驗場 5️⃣ |
我們把同一個修 bug 任務接到 vLLM 上的 qwen3.5-2b(20 億參數,context 只有 16,384)錄了三次, 撞到的兩個錯誤就是你自己接小模型時會撞到的:
A. 什麼都不調:Claude Code 對不認得的模型預設要 32,000 個輸出 token,伺服器直接回 500 「max_completion_tokens=32000 cannot be greater than max_model_len…」。 B. 用 CLAUDE_CODE_MAX_OUTPUT_TOKENS=4096 調小:換 prompt 爆掉—— 光內建工具的說明書就「at least 12289 input tokens」。 C. 只開 3 個工具+短 system prompt:第一次呼叫降到 2,068 個 token,小模型真的讀檔、改對公式、跑測試全過 (9 輪、約 3.5 分鐘)。A、B 兩次的 subtype 也是 success (is_error=True):錯誤被包成一句文字,不會丟例外。
接本機模型時 total_cost_usd 沒有意義:錄影 C 報 $0.1306、 costBasis 寫 unknown——剛好是拿 $4/$20(Opus 5.5 的牌價)去乘 token 數算出來的, 你真正付的是電費。完整可跑的錄製腳本在 GitHub: spike_genai_agent_sdk.py (free 段預設就指向本機 Ollama,用環境變數 FREE_BASE_URL/FREE_MODEL 改; 我們實測時指向的是 vLLM)。
另一條規則要知道:你把 Agent SDK 做成產品給別人用時,官方要求用 API key(或 Bedrock/Vertex/Foundry)認證; 除非事先獲准,不能讓你的使用者用 claude.ai 帳號登入、吃他們的訂閱額度(Agent SDK 官方文件,2026-09 查證)。
本課速查卡
| query() / ClaudeSDKClient | 跑一趟 agent,吐出訊息串;ClaudeSDKClient 可多輪對話、中途 interrupt()、改權限模式。 |
| 訊息串 | SystemMessage(init) → AssistantMessage(TextBlock/ToolUseBlock)→ UserMessage(ToolResultBlock)→ … → ResultMessage。 |
| 六道閘門 | hooks → deny → ask → 權限模式 → allow → can_use_tool;第一個做決定的說了算。 |
| allowed_tools / disallowed_tools | 放行清單 / 禁止清單。allowed_tools 不限制 bypassPermissions;要禁止用 disallowed_tools。 |
| hooks | 每次呼叫前(PreToolUse)/後(PostToolUse)跑你的函式;deny 連 bypass 都擋得住。 |
| can_use_tool | 沒被規則放行、需要問人時才叫你的函式;會被整個工具的 allow 規則遮蔽。 |
| @tool + create_sdk_mcp_server | Python 函式變工具,同一個行程裡的 MCP server;名字是 mcp__server__tool。 |
| AgentDefinition | 專職子代理:自己的 prompt、工具、模型;2.1.281 起預設背景執行。 |
| ResultMessage | 收據:subtype、is_error、num_turns、total_cost_usd、permission_denials、session_id。success ≠ 任務完成。 |
| ANTHROPIC_BASE_URL | 把 harness 接到任何 Anthropic 相容端點(Ollama、vLLM);小模型記得調輸出上限、少開工具。 |
換你動手
在實驗場 1️⃣ 選「③ 放行+PreToolUse hook」播到底:hook 擋了幾次、擋的是哪些指令?明明只想管「跑什麼測試」,為什麼連找檔案都被擋?模型怎麼繞過去的?
在 2️⃣ 模擬器選「Bash:rm -rf build」+ bypassPermissions:找出兩種還擋得住它的設定,再找出一種「看起來會擋、其實擋不住」的。
幫 CI 設計一組設定:可以讀檔、可以改 shop/cart.py、可以跑 python3 -m unittest,但 rm -rf build 一定要被擋,而且沒有人在旁邊按「允許」。用 2️⃣ 把五個呼叫逐一驗過。
卡住了?三題在實驗場最後一格都有折疊解答——先自己做,再打開對照。
情境測驗
離開前試試看:下面的情境都真的會遇到。每題選一個你認為的最佳做法,選了馬上看得到解釋。
Q1 情境題
你要在 CI 上讓 agent 自動修測試:可以讀檔、改程式、跑 python3 -m unittest,但絕對不能刪檔,而且 CI 上沒有人可以按「允許」。哪組設定最合適?
B:dontAsk 讓「沒被規則放行的一律拒絕」,不需要有人在場;allowed_tools 精準放行三件事;disallowed_tools 的 Bash(rm *) 是雙保險——deny 規則排在權限模式前面,哪天有人改成 bypass 也擋得住。A 是本課實測過的陷阱:allowed_tools 是放行清單不是限制清單,bypass 底下沒列到的 Bash 照樣執行(錄影 g3)。C 的結果就是錄影①:headless 沒人按允許,改檔被拒、測試還是紅的,而且 prompt 不是護欄。D 最陰險:acceptEdits 會自動放行工作目錄內的 rm、mv 這類檔案操作,「不准刪檔」只剩一句拜託。
Q2 情境題
產品需求:「agent 先交修改計畫,主管在網頁上按核准,它才可以動手改檔。」下面哪個設計做得到?
B,這正是錄影⑥:模型呼叫 ExitPlanMode 交出計畫 → 你的函式(這裡可以換成等主管按鈕)核准 → 改檔 → 測試全過,14 輪、$0.107。A 是錄影④:沒接 can_use_tool 時工具清單裡根本沒有 ExitPlanMode,模型寫好計畫交不出去,繞了 22 輪、花了 $0.198,一個字沒改。C 把「先審後做」變成「先做後審」,需求直接不成立。D 看似合理,其實是把主管的核准步驟自動化掉——allow 規則放行的呼叫不會來問任何人。
Q3 情境題
同事的 CI 腳本這樣判斷 agent 有沒有修好 bug:if msg.subtype == "success": mark_green()。看過本課錄影後,你會建議怎麼改?
C。success 只代表迴圈正常結束:錄影①的 subtype 是 success、is_error 也是 False,但被擋了 4 次(2 次跑測試、2 次改檔)、測試還是紅的。permission_denials 告訴你「它想做卻被擋的事」,而真正的驗收要看世界的狀態——我們每段錄影跑完都重跑 unittest,就是這個原因。A 不夠:①的 is_error 也是 False。B 沒意義:①跑了 10 輪。D 相信模型的自述——模型可能宣稱成功卻沒做到,驗收不能靠它自己說。
Q4 錯誤診斷
你寫了一個審批函式,想讓每一個 Bash 指令都先經過它,結果它一次都沒被叫到、指令照樣執行。啟動時 SDK 印出這段警告(實測原文)。最可能的原因與修法?
B。閘門順序是 hooks → deny → ask → 模式 → allow → can_use_tool:裸名稱的 allow 規則會在第⑤道放行整個工具,第⑥道的函式根本沒機會發言——這正是警告在說的事(本課錄影 g5:函式一次都沒被叫,print(6*7) 照樣執行)。修法二選一:縮小 allow 的範圍,讓其他呼叫掉到函式;或改用 hook,它在第一道、每次都跑。A 是對的好習慣但不是這裡的病因;C 在 0.2.159 不成立,我們實測 query() 搭配 can_use_tool 可以正常執行;D 恰好相反——bypass 會在第④道就全部放行,函式更輪不到。
Q5 錯誤診斷
你用 ANTHROPIC_BASE_URL 把 Agent SDK 接到本機一個 context 16K 的開源模型,也照教學把 CLAUDE_CODE_MAX_OUTPUT_TOKENS 調成 4096,工具沒特別設定。ResultMessage 回來是這樣(實測原文,端點位址已遮)。下一步該怎麼做?
B。任務還沒開始,第一次呼叫就要 12,289 個輸入 token——使用者的 prompt 才幾十字,其餘是 20 個內建工具的說明書。本課實測:只開 3 個工具、配短 system prompt,第一次呼叫降到 2,068 token,qwen3.5-2b 真的把 bug 修好、測試全過;Ollama 官方也建議 context ≥ 32K。A 治標不治本:12,289 + 1,024 塞得下第一輪,但每一輪都重送全部歷史,幾輪之後照樣爆,還讓模型沒空間回答。C 被錯誤訊息的套話誤導:這個 500 是確定性的長度超限,重試一百次結果一樣。D 與實測相反:同一個 2B 模型在精簡設定下完成了讀檔、改檔、跑測試。另外注意 subtype 仍是 success——要看 is_error。