Claude Agent SDK 實戰
第 2 課你已經知道工具介面怎麼設計,知道 agent loop 的每一轉在做什麼。現在打開一個新的 TypeScript 專案想動手——你要 import Anthropic、建 messages 陣列、寫 while 迴圈、處理 tool_use 訊息、把工具結果塞回去、再呼叫 API……光讓「第一轉」跑起來就要 100 行。還沒算錯誤處理、context 超出限制、並發工具執行、以及這個 loop 永不結束的無限迴圈 bug。每個問題都是一個排查一小時的深坑。
這就是 Claude Agent SDK 存在的理由。它把整個 agent loop 封裝成一個函式呼叫:你描述任務、宣告哪些工具可用,SDK 就自動跑完整個迴圈——包含工具執行、結果回填、context 管理、串流輸出。它不是另一個第三方框架,是 Anthropic 官方維護的函式庫,而且它就是 Claude Code 本身使用的同款引擎,2025 年 9 月從 Claude Code SDK 改名為 Claude Agent SDK,正式定位為「不只給寫 coding agent 用」的通用 agent 開發工具。
這堂學什麼
- SDK 定位:Claude Agent SDK 和 Anthropic Client SDK 的分工,以及什麼情境下不需要用它
- 安裝與第一個 agent:從零建立專案,用
query()跑出第一個真正有工具呼叫的 agent - 內建工具全覽:Read/Write/Edit/Bash/WebSearch/WebFetch——每個的適用場景與限制
- hooks 與權限控制:PreToolUse/PostToolUse 的實作,以及
permissionMode+allowedTools如何鎖定範圍 - 取捨判斷:Agent SDK vs 自寫 loop,什麼時候用哪個
- 實戰:一個自動掃描專案、產出 PROJECT_OVERVIEW.md 的文件整理 agent
觀念一:SDK 定位——它包了什麼、留了什麼給你

圖:Claude Agent SDK 官方文件(2026 年 7 月實況),來源:code.claude.com/docs
Anthropic 維護兩個不同層級的函式庫:
Anthropic Client SDK (@anthropic-ai/sdk) |
Claude Agent SDK (@anthropic-ai/claude-agent-sdk) |
|
|---|---|---|
| 你管什麼 | 所有事:loop、tool dispatch、messages | 只管任務描述和工具白名單 |
| loop | 你自己寫 | SDK 自動跑 |
| 內建工具 | 無,你全部自己實作 | Read/Write/Edit/Bash/WebSearch 等開箱即用 |
| 適合場景 | 單次呼叫、高度客製化 loop | 需要多步驟自主執行的 agent |
| 與 MCP 整合 | 需自行處理 | 原生支援 |
一句話判斷原則:如果你發現自己正在寫一個 while 迴圈來反覆呼叫 LLM 並執行工具,那就是 Agent SDK 最擅長的場景。如果你只是做一次分類、摘要、或提取,用 Client SDK 就夠,Agent SDK 反而多餘。
觀念二:Agent Loop 在 SDK 裡長什麼樣
從你呼叫 query() 到拿到結果,SDK 內部會自動跑這條路:

你的程式碼只需要處理 SDK 從 async generator yield 出來的訊息,不需要知道裡面跑了幾轉。這和第 1 課學到的 agent loop 理論完全一致——SDK 只是把那個 loop 的樣板程式碼幫你包好了。
訊息類型與它的意義:SDK 會 yield 四種主要訊息類型,理解它們才能寫出正確的輸出處理邏輯:
system:session 開始時傳送的系統訊息,通常不需要處理assistant:模型的輸出,content陣列裡可能包含text(文字輸出)或tool_use(模型決定呼叫工具);工具執行部分 SDK 自動處理,你通常只需要取textuser:工具執行結果被回填進 messages 時會觸發,SDK 內部使用,外部程式通常不需要特別處理result:任務結束訊號。subtype是"success"或"error";若是 error,error欄位會有詳細訊息
開發時有一個常見疑問:「什麼時候我應該把 tool_use 從 assistant 訊息裡取出來自己處理?」答案是:幾乎從不。query() 的設計前提就是讓你不碰工具執行層,如果你需要攔截工具呼叫,該用 hooks,不是手動解析 tool_use block。
手把手實戰
安裝 SDK、建立專案
先確認環境:Node.js 20+ 或 Bun 1.1+,TypeScript 5+。
mkdir my-agent && cd my-agent
npm init -y
npm install @anthropic-ai/claude-agent-sdk
npm install -D typescript tsx @types/node
npx tsc --init --target ES2022 --moduleResolution bundler --esModuleInterop true
設定 API 金鑰(SDK 會自動讀取 ANTHROPIC_API_KEY 環境變數):
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
Python 用戶改裝:
pip install claude-agent-sdk # 需要 Python 3.10+
本堂課以 TypeScript 為主,Python 的 API 介面幾乎一一對應,語法差異在每個程式碼範例後會補一行說明。
第一個 agent:Hello, 工具世界
建立 hello-agent.ts:
import { query } from "@anthropic-ai/claude-agent-sdk";
async function main() {
const messages = query(
"列出當前目錄下所有 .ts 檔案,然後告訴我總共有幾個",
{
cwd: process.cwd(),
allowedTools: ["Bash", "Glob"],
permissionMode: "dontAsk",
}
);
for await (const message of messages) {
// assistant 訊息:模型輸出(包含文字和工具呼叫)
if (message.type === "assistant") {
for (const block of message.message?.content ?? []) {
if (block.type === "text" && block.text.trim()) {
process.stdout.write(block.text);
}
}
}
// result 訊息:任務結束
if (message.type === "result") {
console.log(`\n\n[完成] subtype: ${message.subtype}`);
}
}
}
main().catch(console.error);
跑起來:
npx tsx hello-agent.ts
你會看到 agent 呼叫 Glob 找出 .ts 檔案、然後用自然語言回答你——整個工具呼叫的過程 SDK 全自動處理,你什麼都沒做。
訊息類型速查:assistant(模型輸出)、user(工具結果回填)、system(系統訊息)、result(任務結束,含 success 或 error subtype)。只需要關注 assistant.text 和 result.subtype 就能處理大多數場景。
Python 等效:把 query() 換成 async for message in query(...): 同樣邏輯。
認識內建工具全覽

SDK 開箱即用的工具(2026 年 7 月):
| 工具名稱 | 功能 | 需要開放的 allowedTools 值 |
|---|---|---|
Read |
讀取檔案內容 | "Read" |
Write |
建立或覆寫整個檔案 | "Write" |
Edit |
精確替換檔案中的片段 | "Edit" |
Glob |
依路徑模式找出檔案列表 | "Glob" |
Grep |
在檔案內容中搜尋字串 | "Grep" |
Bash |
執行 shell 指令 | "Bash" |
WebSearch |
Bing 搜尋取得摘要結果 | "WebSearch" |
WebFetch |
抓取並解析指定 URL 的內容 | "WebFetch" |
Agent |
派遣子 agent 執行子任務 | "Agent" |
選工具的黃金原則:只開放任務真正需要的工具。開了 Bash 就等於給了 agent 一個 shell,確定你知道它會執行什麼指令再開放。Bash + Write 組合威力最強,也是最容易出事的組合。
幾個常見搭配的邏輯:
- 純讀取分析任務:只開
Read + Glob + Grep,model 不能改任何東西,風險最低 - 文件整理任務:開
Read + Write + Edit + Glob,不開Bash,避免 agent 執行系統指令 - 程式碼生成任務:開
Read + Write + Edit + Bash,但用 PreToolUse hook 過濾危險 Bash 指令 - 研究任務:開
Read + WebSearch + WebFetch,不給寫檔權限,讓 agent 只輸出文字結果
每次增加一個新工具之前,都應該問自己:「這個工具讓 agent 多做了什麼?如果它亂用這個工具,最壞的情況是什麼?」這個思路和第 2 課的工具設計原則是同一件事——工具的邊界決定了 agent 的邊界。
Hooks 與權限控制
permissionMode 控制整體授權策略,allowedTools 列白名單,hooks 則讓你在每一次工具呼叫前後插入邏輯——這三層組合起來才是完整的權限架構。

permissionMode 速查:
"default" → 標準模式,依 settings.json 規則決定是否需要確認
"acceptEdits" → 自動同意所有檔案編輯(Read/Write/Edit),其他照預設
"dontAsk" → 白名單外的工具直接拒絕,不詢問
"bypassPermissions"→ 跳過所有權限檢查(開發測試用,絕對不進生產)
"auto" → 用分類模型自動評估每次工具呼叫是否允許
建立 hooks-demo.ts,展示 PreToolUse 過濾敏感檔案 + PostToolUse 寫稽核日誌:
import { query } from "@anthropic-ai/claude-agent-sdk";
import { appendFile } from "fs/promises";
async function main() {
const SENSITIVE_PATTERN = /\.env|secret|credential|private_key/i;
const messages = query("讀取專案所有設定檔並列出內容摘要", {
cwd: process.cwd(),
allowedTools: ["Read", "Glob", "Grep"],
permissionMode: "dontAsk",
hooks: {
PreToolUse: [
{
// 攔截所有 Read 工具呼叫
matcher: "Read",
callback: async ({ tool_name, tool_input }) => {
const filePath: string = tool_input.file_path ?? "";
if (SENSITIVE_PATTERN.test(filePath)) {
console.warn(`[BLOCKED] ${tool_name} 嘗試讀取敏感檔案: ${filePath}`);
return {
permissionDecision: "deny" as const,
permissionDecisionReason: `安全限制:禁止讀取敏感檔案 ${filePath}`,
};
}
return { permissionDecision: "allow" as const };
},
},
],
PostToolUse: [
{
// 記錄所有工具使用到稽核日誌
matcher: ".*",
callback: async ({ tool_name, tool_input }) => {
const timestamp = new Date().toISOString();
const target = tool_input.file_path ?? tool_input.query ?? "";
await appendFile(
"agent-audit.log",
`${timestamp}\t${tool_name}\t${target}\n`
);
},
},
],
},
});
for await (const message of messages) {
if (message.type === "assistant") {
for (const block of message.message?.content ?? []) {
if (block.type === "text") process.stdout.write(block.text);
}
}
}
}
main().catch(console.error);
PreToolUse 可以做的事:
- 回傳
permissionDecision: "deny"阻止工具執行 - 回傳
permissionDecision: "allow"放行 - 回傳
updatedInput修改工具的輸入參數後再執行
PostToolUse 在工具已成功執行後觸發,input 同時包含 tool_input(送進去的參數)和 tool_response(工具回傳值),適合做日誌、監控、或把結果轉換成不同格式再送回 context。
實戰:自動整理專案文件的 agent
這個 agent 會:掃描任意專案目錄 → 分析結構與重要檔案 → 在根目錄產出 PROJECT_OVERVIEW.md。
建立 doc-organizer.ts:
import { query } from "@anthropic-ai/claude-agent-sdk";
import { appendFile } from "fs/promises";
import { resolve } from "path";
async function organizeProjectDocs(projectPath: string) {
const absPath = resolve(projectPath);
const prompt = `
你是一個專案文件整理助理,目標是讓任何新進開發者 5 分鐘內看懂這個專案。
請按照以下步驟完成任務(每步前先說明你要做什麼):
1. 用 Glob 列出根目錄的所有檔案(不含 node_modules、.git、dist)
2. 讀取 package.json 或 pyproject.toml 取得專案名稱與依賴
3. 找出所有現有的 .md 文件,列出它們的標題
4. 判斷哪些目錄是核心原始碼、哪些是測試、哪些是設定
5. 在專案根目錄寫入 PROJECT_OVERVIEW.md,結構如下:
# {專案名稱} — 總覽
## 專案簡介
(一段說明,從 package.json 或 README 提取)
## 目錄結構
(文字版樹狀結構,只列兩層深度)
## 重要檔案速查
| 檔案 | 用途 |
(列出 10 個以內最重要的檔案)
## 開始開發
(安裝指令、本機跑起來的指令,從 package.json scripts 提取)
## 文件索引
(列出所有現有 .md 的連結與一行說明)
如果 PROJECT_OVERVIEW.md 已存在,請先讀取再決定是否更新,不要無腦覆寫。
`.trim();
const messages = query(prompt, {
cwd: absPath,
model: "claude-opus-4-5", // 複雜分析任務選 Opus
allowedTools: ["Read", "Write", "Edit", "Glob", "Grep", "Bash"],
permissionMode: "acceptEdits", // 自動接受檔案寫入
hooks: {
PreToolUse: [
{
matcher: "Bash",
callback: async ({ tool_input }) => {
const cmd: string = tool_input.command ?? "";
// 只允許唯讀 shell 指令,禁止 rm、curl、sudo 等
const DANGEROUS = /\b(rm|rmdir|curl|wget|sudo|chmod|chown|dd|mkfs)\b/;
if (DANGEROUS.test(cmd)) {
console.warn(`[BLOCKED Bash] 指令包含危險操作: ${cmd}`);
return {
permissionDecision: "deny" as const,
permissionDecisionReason: `不允許在文件整理任務中執行系統操作指令`,
};
}
return { permissionDecision: "allow" as const };
},
},
],
PostToolUse: [
{
matcher: "Write|Edit",
callback: async ({ tool_input }) => {
const path: string = tool_input.file_path ?? "";
await appendFile(
"doc-organizer-audit.log",
`${new Date().toISOString()}\tWRITE\t${path}\n`
);
console.log(` [已寫入] ${path}`);
},
},
],
},
});
console.log(`\n開始整理專案文件:${absPath}\n${"─".repeat(50)}`);
for await (const message of messages) {
if (message.type === "assistant") {
for (const block of message.message?.content ?? []) {
if (block.type === "text" && block.text.trim()) {
process.stdout.write(block.text);
}
}
}
if (message.type === "result") {
if (message.subtype === "success") {
console.log(`\n${"─".repeat(50)}\n任務完成!PROJECT_OVERVIEW.md 已更新。`);
} else {
console.error(`\n任務失敗:${message.subtype}`);
}
}
}
}
// 從命令列參數取目錄路徑,預設當前目錄
const targetPath = process.argv[2] ?? ".";
organizeProjectDocs(targetPath).catch(console.error);
使用方式:
# 整理當前目錄
npx tsx doc-organizer.ts .
# 整理指定專案
npx tsx doc-organizer.ts /path/to/your-project
這個實戰展示了三個關鍵設計決策:
- model 選 Opus:文件分析需要多步驟推理,Opus 在複雜任務上比 Sonnet 更可靠,成本換正確率值得。
- acceptEdits + Bash 過濾:整個任務需要寫檔,用
acceptEdits省掉反覆確認;但 Bash 指令用 hook 加白名單過濾,防止意外的系統操作。 - 判斷再寫、不覆寫:prompt 明確要求 agent 先讀取現有文件再決定怎麼更新,避免反覆執行時每次都重寫一遍。
Agent SDK vs 自寫 Loop:選哪個?

實際開發中有一種常見的錯誤衝動:「Agent SDK 太黑盒了,我要自己寫才放心」。但黑盒本身不是問題,問題是你不熟悉它的邊界。以下是真正需要自寫 loop 的場景:
- 工具的並發策略需要完全掌控:例如你需要同時執行 20 個 API 呼叫並合併結果,Agent SDK 的並發行為不符合你的需求
- context 管理需要高度客製化:例如你需要對 messages 做複雜的壓縮或語意去重,而不是簡單的截斷
- 成本控制極度嚴格:Agent SDK 讓 agent 自主決定用幾轉,如果你需要硬性限制每個任務最多 N 次 API 呼叫,自寫 loop 更好控制
以上三點之外,Agent SDK + hooks 幾乎都能處理。第 4 課會進一步討論當任務大到需要 orchestrator + subagent 架構時,SDK 的 Agent 工具如何串接。
還有一個常見的誤判:「我需要自訂工具所以要自己寫 loop」。這個邏輯不成立。Agent SDK 完全支援客製化工具,你可以在 MCP server 裡定義自己的工具然後透過 mcpServers 選項接入,或直接在 prompt 裡描述工具行為讓 SDK 執行對應的 Bash 指令。自寫 loop 的真正理由只有上面三個,「我有自訂工具」不是其中之一。
常見坑
坑 1:bypassPermissions 進了生產環境
症狀:agent 在本機測試順利,你為了方便改成 permissionMode: "bypassPermissions",部署後 agent 刪了它不該刪的檔案,或執行了危險的 bash 指令。
這個 mode 的設計用途是本機快速驗證邏輯,不是生產設定。生產環境一律用 "dontAsk" + 明確的 allowedTools 白名單。如果某個動作需要彈性,用 PreToolUse hook 動態判斷,不要用 bypass 一刀切。
坑 2:cwd 設錯導致 agent 找不到任何檔案
症狀:agent 跑了很久,最後回應「找不到指定的目錄」或「沒有符合條件的檔案」,但你知道檔案明明在那裡。
Error: ENOENT: no such file or directory, scandir '/wrong/path'
原因通常有兩個:一是傳入了相對路徑,而 SDK 在不同執行環境下的工作目錄不一樣;二是 cwd 省略了,預設是 process.cwd(),但 CI 環境或 Docker 容器裡 cwd 不是你以為的地方。
修法:永遠傳入 resolve(targetPath) 的絕對路徑。
import { resolve } from "path";
const messages = query(prompt, {
cwd: resolve(process.argv[2] ?? "."), // 一律轉成絕對路徑
// ...
});
坑 3:allowedTools 沒包含 "Agent" 導致多代理任務失敗
症狀:你的 prompt 期待 agent 把複雜任務拆給子 agent,但 agent 始終自己硬做或回應「無法派遣子任務」。
原因:Agent 工具本身也在 allowedTools 白名單管轄範圍內。搭配 dontAsk 模式時,沒有列在白名單的工具一律被拒絕,Agent 工具也不例外。
allowedTools: ["Read", "Glob", "Agent"], // 記得加 "Agent"
permissionMode: "dontAsk",
這個坑在第 4 課多代理架構時會反覆踩到,先記住這個規則。
坑 4:async generator 沒完整消費導致任務被截斷
症狀:agent 明明還沒完成任務,程式就結束了;或只收到部分輸出,沒有看到 result 訊息。
原因:query() 回傳的是 async generator,必須用 for await...of 完整迭代到底。如果你在中途 break 或忘記 await,generator 不會繼續推進,底層的 API 呼叫也會被中斷。
// 錯誤:只取第一個訊息就結束
const gen = query(prompt, options);
const first = await gen.next(); // ← 任務在這裡停住,不繼續跑
// 正確:完整迭代
for await (const message of query(prompt, options)) {
// 處理每個訊息,讓 generator 自然結束
}
坑 5:長任務 context 爆掉,agent 在中途就停下來
症狀:任務做到一半,agent 突然輸出「由於 context 限制我無法繼續」或直接以不完整的結果結束。result.subtype 是 "error" 且錯誤訊息含 context_window_exceeded。
根本原因:每一次工具呼叫的結果都會被追加進 messages 陣列,如果你讓 agent 讀了很多大檔案,context 很快就滿了。
應對策略:
- 縮小工具輸入範圍:prompt 明確指示「只讀需要的那幾行,不要讀整個檔案」
- 拆分任務:把一個大任務分成幾個小的
query()呼叫,每次只做一段 - 過濾工具輸出:用 PostToolUse hook 把工具回傳的巨大輸出截斷再送回 context
- 用 Grep 取代 Read:先搜尋定位再讀取,避免把整個大檔案塞進 context
作業
- 複製本課的
doc-organizer.ts,對你自己的一個專案執行它,確認產出的PROJECT_OVERVIEW.md內容準確。 - 加一條 PostToolUse hook,讓每次
Write或Edit之後印出「已寫入多少 bytes」的提示(提示:tool_response.content裡有寫入資訊)。 - 試試把
model改成claude-sonnet-4-5重跑一次,比較速度與品質的差異,然後根據你的任務複雜度做出選擇。 - 進階挑戰:讓
doc-organizer接受第二個命令列參數--dry-run,在此模式下只印出「我打算寫什麼」但不實際執行 Write/Edit(提示:用 PreToolUse hook 攔截並 deny)。
下一課預告
你已經有了一個可以自主操作檔案、執行指令的 agent。但當任務規模繼續擴大——要同時研究三個主題、分析五個資料夾、寫十份文件——單一 agent 處理起來又慢又容易 context 爆掉。
第 4 課進入多代理架構:orchestrator 怎麼把任務分解派給 subagent、subagent 的結果怎麼合併回主流程、以及最重要的一個問題——多個 agent 同時對同一份檔案操作時怎麼不互相踩壞。你在這課做的 doc-organizer 會在第 4 課被改造成一個可以同時整理多個專案的並行版本。