Claude Agent SDK Python 架構深度解析:Anthropic 官方 Agent 開發框架


0. 為什麼要關注 Claude Agent SDK?

Claude Agent SDK 是 Anthropic 官方提供的 Python SDK,用於與 Claude Code CLI 進行程式化交互。與其他框架相比,它有幾個獨特的特點:

  1. 官方支持:直接由 Anthropic 維護,與 Claude 模型深度整合
  2. 輕量設計:核心代碼精簡,專注於與 Claude Code 的交互
  3. 雙模式交互:支持一次性查詢和交互式客戶端兩種模式
  4. 進程內 MCP:無需外部進程,直接在 Python 中定義和運行 MCP 工具
  5. 完整的權限控制:Hook 系統和權限回調支持細粒度控制

整體架構可以用一張圖表示:

flowchart TD
  classDef pad fill:transparent,stroke:transparent,color:transparent;

  subgraph UserAPI["用戶 API 層"]
    direction TB
    api_pad[" "]:::pad
    Query["query()"]
    Client["ClaudeSDKClient"]
  end

  subgraph Internal["內部實現層"]
    direction TB
    int_pad[" "]:::pad
    InternalClient["InternalClient"]
    QueryClass["Query"]
    MessageParser["MessageParser"]
  end

  subgraph Transport["傳輸層"]
    direction TB
    trans_pad[" "]:::pad
    TransportABC["Transport (ABC)"]
    SubprocessCLI["SubprocessCLITransport"]
  end

  subgraph CLI["Claude Code CLI"]
    direction TB
    cli_pad[" "]:::pad
    Binary["打包的二進制文件"]
  end

  UserAPI --> Internal
  Internal --> Transport
  Transport --> CLI

接下來深入探討 Claude Agent SDK 的核心架構。


1. 專案結構與規模

Claude Agent SDK 是一個精簡的 SDK,核心代碼約 2,000 行

claude-agent-sdk-python/
├── src/claude_agent_sdk/
│   ├── __init__.py                 # 主入口和公共 API 導出
│   ├── client.py                   # ClaudeSDKClient - 交互式客戶端
│   ├── query.py                    # query() - 一次性查詢函數
│   ├── types.py                    # 類型定義 (754 行)
│   ├── _errors.py                  # 錯誤類定義
│   ├── _version.py                 # 版本信息
│   ├── py.typed                    # PEP 561 類型標記
│   ├── _bundled/                   # 打包的 Claude Code CLI 二進制文件
│   └── _internal/
│       ├── __init__.py
│       ├── client.py               # InternalClient - 內部客戶端實現
│       ├── message_parser.py       # 消息解析和類型轉換
│       ├── query.py                # Query - 控制協議處理
│       └── transport/
│           ├── __init__.py         # Transport 抽象基類
│           └── subprocess_cli.py   # 子進程 CLI 傳輸實現
├── tests/                          # 單元測試套件 (12 個測試文件)
├── e2e-tests/                      # 端到端測試
├── examples/                       # 使用示例 (16 個示例文件)
└── scripts/                        # 構建和發布腳本

版本資訊

  • 當前版本:0.1.17
  • 許可證:MIT
  • Python 要求:>= 3.10

2. 核心架構:分層設計

2.1 三層架構

Claude Agent SDK 採用清晰的三層架構:

flowchart TD
  subgraph Layer1["用戶 API 層"]
    Query["query()"]
    Client["ClaudeSDKClient"]
    Options["ClaudeAgentOptions"]
    Tool["@tool decorator"]
  end

  subgraph Layer2["內部實現層"]
    InternalClient["InternalClient"]
    QueryClass["Query"]
    MessageParser["MessageParser"]
    HookHandler["Hook 處理"]
    PermHandler["權限管理"]
  end

  subgraph Layer3["傳輸層"]
    Transport["Transport (ABC)"]
    SubprocessCLI["SubprocessCLITransport"]
    IO["I/O 流處理"]
  end

  subgraph External["外部進程"]
    CLI["Claude Code CLI"]
  end

  Layer1 --> Layer2
  Layer2 --> Layer3
  Layer3 --> External

2.2 核心類別

類/函數位置用途
query()query.py一次性、無狀態的異步查詢函數
ClaudeSDKClientclient.py雙向、有狀態的交互式客戶端
ClaudeAgentOptionstypes.py配置選項數據類
@tool__init__.pyMCP 工具裝飾器
create_sdk_mcp_server()__init__.py創建進程內 MCP 服務器

2.3 消息類型系統

SDK 定義了豐富的消息類型:

# 消息類型
UserMessage        # 用戶輸入消息
AssistantMessage   # Claude 響應消息
SystemMessage      # 系統消息
ResultMessage      # 結果和成本信息
StreamEvent        # 流式部分消息

# 內容塊類型
TextBlock          # 文本內容
ThinkingBlock      # 思考過程 (Extended Thinking)
ToolUseBlock       # 工具調用
ToolResultBlock    # 工具結果

消息流程示意:

flowchart LR
  User["UserMessage"]
  Assistant["AssistantMessage"]
  Tool["ToolUseBlock"]
  Result["ToolResultBlock"]
  Final["ResultMessage"]

  User --> Assistant
  Assistant --> Tool
  Tool --> Result
  Result --> Assistant
  Assistant --> Final

2.4 錯誤類型層次

ClaudeSDKError (基類)
├── CLIConnectionError
│   └── CLINotFoundError
├── ProcessError
├── CLIJSONDecodeError
└── MessageParseError

3. 兩種交互模式

3.1 query() - 一次性查詢

最簡單的使用方式,適合單次問答:

import anyio
from claude_agent_sdk import query

async def main():
    async for message in query(prompt="What is 2 + 2?"):
        print(message)

anyio.run(main)

函數簽名

async def query(
    *,
    prompt: str | AsyncIterable[dict[str, Any]],
    options: ClaudeAgentOptions | None = None,
    transport: Transport | None = None,
) -> AsyncIterator[Message]

執行流程

flowchart LR
  User["用戶"] --> Prompt["提示詞"]
  Prompt --> Query["query()"]
  Query --> Transport["傳輸層"]
  Transport --> CLI["Claude Code CLI"]
  CLI --> Response["流式響應"]
  Response --> Iterator["消息迭代器"]
  Iterator --> User

特點

  • 單向、無狀態
  • 所有消息預先發送,然後接收所有響應
  • 適合簡單問題、批處理、腳本自動化

使用場景

  • 一次性問題
  • CI/CD 管道中的代碼生成
  • 批量處理任務

3.2 ClaudeSDKClient - 交互式客戶端

支持多輪對話和動態控制:

from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock

async def main():
    async with ClaudeSDKClient() as client:
        # 第一個查詢
        await client.query("What is Python?")
        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

        # 後續查詢(保持對話歷史)
        await client.query("Tell me more about its libraries")
        async for msg in client.receive_response():
            # 處理響應...
            pass

anyio.run(main)

核心方法

class ClaudeSDKClient:
    # 基本交互
    async def connect(prompt: str | None = None) -> None
    async def query(prompt: str, session_id: str = "default") -> None
    async def receive_response() -> AsyncIterator[Message]
    async def receive_messages() -> AsyncIterator[Message]

    # 高級控制
    async def interrupt() -> None                           # 中斷當前操作
    async def set_permission_mode(mode: str) -> None        # 設置權限模式
    async def set_model(model: str | None = None) -> None   # 切換模型
    async def rewind_files(user_message_id: str) -> None    # 回滾文件
    async def get_server_info() -> dict[str, Any] | None    # 獲取服務器信息

執行流程

flowchart TD
  Connect["connect()"]
  Query1["query('問題1')"]
  Receive1["receive_response()"]
  Query2["query('問題2')"]
  Receive2["receive_response()"]
  Close["close()"]

  Connect --> Query1 --> Receive1
  Receive1 --> Query2 --> Receive2
  Receive2 --> Close

特點

  • 雙向通信
  • 有狀態對話(保持對話歷史)
  • 支持中斷操作
  • 支持動態配置更改
  • 支持文件檢查點和回滾

4. 配置選項詳解

4.1 ClaudeAgentOptions

from claude_agent_sdk import ClaudeAgentOptions

options = ClaudeAgentOptions(
    # 基本配置
    system_prompt="You are a Python expert",
    model="claude-opus-4-1-20250805",
    cwd="/path/to/project",

    # 工具配置
    allowed_tools=["Read", "Write", "Bash"],
    mcp_servers={"calc": calculator_server},

    # 權限控制
    permission_mode="acceptEdits",
    can_use_tool=my_permission_callback,

    # 限制
    max_turns=10,
    max_budget_usd=1.0,

    # 高級功能
    hooks={...},
    sandbox=SandboxSettings(...),
    enable_file_checkpointing=True,
)

4.2 主要選項說明

選項類型用途
system_promptstr系統提示詞,定義 Claude 的行為
modelstr模型選擇 (claude-sonnet-4-20250514, claude-opus-4-1-20250805 等)
allowed_toolslist[str]允許 Claude 使用的工具列表
mcp_serversdictMCP 服務器配置
permission_modestr權限模式:default, acceptEdits, bypassPermissions
max_turnsint最大對話輪數
max_budget_usdfloat最大花費預算(美元)
hooksdictHook 配置
can_use_toolcallable工具權限回調函數
sandboxSandboxSettings沙箱配置
cwdstr|Path工作目錄
enable_file_checkpointingbool啟用文件檢查點

4.3 權限模式

模式描述
default默認模式,需要確認危險操作
acceptEdits自動接受文件編輯
bypassPermissions跳過所有權限檢查(謹慎使用)

5. MCP 工具系統

5.1 進程內 MCP 服務器

Claude Agent SDK 的一大亮點是支持進程內 MCP 服務器,無需啟動外部進程:

from claude_agent_sdk import tool, create_sdk_mcp_server

# 定義工具
@tool("add", "Add two numbers", {"a": float, "b": float})
async def add_numbers(args: dict[str, Any]) -> dict[str, Any]:
    result = args["a"] + args["b"]
    return {"content": [{"type": "text", "text": f"Result: {result}"}]}

@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
async def multiply_numbers(args: dict[str, Any]) -> dict[str, Any]:
    result = args["a"] * args["b"]
    return {"content": [{"type": "text", "text": f"Result: {result}"}]}

# 創建服務器
calculator = create_sdk_mcp_server(
    name="calculator",
    tools=[add_numbers, multiply_numbers]
)

# 使用服務器
options = ClaudeAgentOptions(
    mcp_servers={"calc": calculator},
    allowed_tools=[
        "mcp__calc__add",
        "mcp__calc__multiply",
    ]
)

5.2 @tool 裝飾器

@tool(
    name: str,              # 工具名稱
    description: str,       # 工具描述
    input_schema: dict,     # 輸入參數 schema
)
async def tool_function(args: dict[str, Any]) -> dict[str, Any]:
    """
    返回格式:
    {
        "content": [
            {"type": "text", "text": "結果文本"},
            {"type": "image", "data": "base64...", "mimeType": "image/png"}
        ]
    }
    """
    pass

5.3 進程內 MCP 的優勢

flowchart LR
  subgraph Traditional["傳統 MCP"]
    SDK1["SDK"] --> IPC["IPC"] --> Server["外部進程"]
  end

  subgraph InProcess["進程內 MCP"]
    SDK2["SDK"] --> Direct["直接調用"] --> Tools["工具函數"]
  end
傳統 MCP進程內 MCP
需要外部進程單一 Python 進程
IPC 延遲無 IPC 開銷
進程管理複雜簡化部署
調試困難容易調試
獨立狀態直接訪問應用狀態

6. Hook 系統

6.1 支持的 Hook 事件

事件觸發時機用途
PreToolUse工具使用前權限控制、參數驗證
PostToolUse工具執行後處理輸出、日誌記錄
UserPromptSubmit用戶提示提交時輸入過濾、預處理
Stop停止事件清理資源
SubagentStop子代理停止子代理管理
PreCompact上下文壓縮前自定義壓縮邏輯

6.2 Hook 執行流程

flowchart TD
  User["用戶輸入"]
  Submit["UserPromptSubmit Hook"]
  Process["Claude 處理"]
  PreTool["PreToolUse Hook"]
  Tool["工具執行"]
  PostTool["PostToolUse Hook"]
  Response["響應"]

  User --> Submit --> Process
  Process --> PreTool --> Tool --> PostTool --> Process
  Process --> Response

6.3 Hook 配置示例

from claude_agent_sdk import ClaudeAgentOptions, HookMatcher

# 定義 Hook 函數
async def check_bash_command(
    input_data: HookInput,
    tool_use_id: str | None,
    context: HookContext
) -> HookJSONOutput:
    """檢查 Bash 命令的安全性"""
    if input_data["tool_name"] == "Bash":
        command = input_data["tool_input"].get("command", "")

        # 檢查危險命令
        dangerous_patterns = ["rm -rf", "sudo", "chmod 777"]
        for pattern in dangerous_patterns:
            if pattern in command:
                return {
                    "hookSpecificOutput": {
                        "hookEventName": "PreToolUse",
                        "permissionDecision": "deny",
                        "permissionDecisionReason": f"Blocked: contains '{pattern}'"
                    }
                }

    # 允許執行
    return {}

async def log_tool_result(
    input_data: HookInput,
    tool_use_id: str | None,
    context: HookContext
) -> HookJSONOutput:
    """記錄工具執行結果"""
    print(f"Tool: {input_data['tool_name']}")
    print(f"Result: {input_data.get('tool_output', {})}")
    return {}

# 配置 Hooks
options = ClaudeAgentOptions(
    allowed_tools=["Bash", "Read", "Write"],
    hooks={
        "PreToolUse": [
            HookMatcher(matcher="Bash", hooks=[check_bash_command])
        ],
        "PostToolUse": [
            HookMatcher(matcher="*", hooks=[log_tool_result])
        ]
    }
)

6.4 Hook 返回值

# PreToolUse Hook 可以返回權限決策
return {
    "hookSpecificOutput": {
        "hookEventName": "PreToolUse",
        "permissionDecision": "allow" | "deny" | "ask",
        "permissionDecisionReason": "原因說明",
        # 可選:修改工具輸入
        "updatedInput": {"modified": "value"}
    }
}

# PostToolUse Hook 可以修改輸出
return {
    "hookSpecificOutput": {
        "hookEventName": "PostToolUse",
        "output": "modified output"
    }
}

7. 工具權限回調

除了 Hook 系統,SDK 還提供了專門的工具權限回調:

from claude_agent_sdk import (
    ClaudeAgentOptions,
    ToolPermissionContext,
    PermissionResultAllow,
    PermissionResultDeny
)

async def my_permission_callback(
    tool_name: str,
    input_data: dict,
    context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
    """細粒度的工具權限控制"""

    # 保護系統文件
    if tool_name == "Write":
        file_path = input_data.get("file_path", "")
        if file_path.startswith("/etc/") or file_path.startswith("/sys/"):
            return PermissionResultDeny(
                message="System files are protected"
            )

    # 限制 Bash 命令
    if tool_name == "Bash":
        command = input_data.get("command", "")
        if "curl" in command or "wget" in command:
            return PermissionResultDeny(
                message="Network commands are not allowed"
            )

    # 修改輸入(例如重定向文件路徑)
    if tool_name == "Write" and input_data.get("file_path", "").startswith("/tmp/"):
        modified_input = input_data.copy()
        modified_input["file_path"] = "/safe/dir/" + input_data["file_path"].split("/")[-1]
        return PermissionResultAllow(updated_input=modified_input)

    # 默認允許
    return PermissionResultAllow()

options = ClaudeAgentOptions(
    can_use_tool=my_permission_callback,
    permission_prompt_tool_name="stdio"
)

8. 控制協議

8.1 子進程通信架構

SDK 通過 JSON 控制協議與 Claude Code CLI 進行雙向通信:

flowchart TD
  subgraph SDK["Python SDK"]
    Query["Query/Client"]
    Transport["SubprocessCLITransport"]
  end

  subgraph Protocol["JSON 控制協議"]
    Stdin["stdin (SDK → CLI)"]
    Stdout["stdout (CLI → SDK)"]
  end

  subgraph CLI["Claude Code CLI"]
    Process["子進程"]
  end

  Query --> Transport
  Transport --> Stdin --> Process
  Process --> Stdout --> Transport

8.2 協議消息類型

請求類型(SDK → CLI):

# 初始化請求
SDKControlInitializeRequest = {
    "type": "initialize",
    "options": {...}
}

# 權限響應
SDKControlPermissionRequest = {
    "type": "permission_response",
    "allowed": True,
    "updated_input": {...}  # 可選
}

# 中斷信號
SDKControlInterruptRequest = {
    "type": "interrupt"
}

# Hook 回調響應
SDKHookCallbackRequest = {
    "type": "hook_callback",
    "output": {...}
}

響應類型(CLI → SDK):

# 消息流
Message = {
    "type": "assistant_message" | "user_message" | "system_message",
    "content": [...]
}

# 控制請求
SDKControlRequest = {
    "type": "permission_request" | "hook_callback",
    ...
}

# 結果
ResultMessage = {
    "type": "result",
    "cost_usd": 0.05,
    "tokens_used": {...}
}

8.3 通信流程示例

sequenceDiagram
  participant SDK as Python SDK
  participant CLI as Claude Code CLI

  SDK->>CLI: InitializeRequest (options)
  CLI->>SDK: InitializeResponse

  SDK->>CLI: UserMessage (prompt)
  CLI->>SDK: AssistantMessage (thinking...)
  CLI->>SDK: ToolUseBlock (Bash)

  CLI->>SDK: PermissionRequest (Bash)
  SDK->>CLI: PermissionResponse (allowed: true)

  CLI->>SDK: ToolResultBlock (output)
  CLI->>SDK: AssistantMessage (final response)
  CLI->>SDK: ResultMessage (cost, tokens)

9. 文件檢查點與回滾

9.1 啟用文件檢查點

options = ClaudeAgentOptions(
    enable_file_checkpointing=True,
    extra_args={"replay-user-messages": None}
)

9.2 使用檢查點回滾

async with ClaudeSDKClient(options=options) as client:
    # 第一次交互 - 記錄檢查點 ID
    await client.query("Create a new file called test.py")
    checkpoint_id = None
    async for msg in client.receive_response():
        if isinstance(msg, UserMessage) and msg.uuid:
            checkpoint_id = msg.uuid
        # 處理響應...

    # 第二次交互 - 修改文件
    await client.query("Add a function to test.py")
    async for msg in client.receive_response():
        # 處理響應...

    # 回滾到第一個檢查點
    if checkpoint_id:
        await client.rewind_files(checkpoint_id)
        print("Files rolled back to checkpoint")

9.3 檢查點流程

flowchart TD
  Q1["Query 1: 創建文件"]
  CP1["Checkpoint 1"]
  Q2["Query 2: 修改文件"]
  CP2["Checkpoint 2"]
  Rewind["rewind_files(CP1)"]
  State1["狀態回滾到 CP1"]

  Q1 --> CP1 --> Q2 --> CP2
  CP2 --> Rewind --> State1

10. 完整示例

10.1 快速開始

import anyio
from claude_agent_sdk import query

async def main():
    async for message in query(prompt="Explain Python decorators"):
        print(message)

anyio.run(main)

10.2 帶配置的查詢

from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        system_prompt="You are a Python expert. Be concise.",
        allowed_tools=["Read", "Bash"],
        permission_mode="acceptEdits",
        max_turns=5,
        model="claude-sonnet-4-20250514"
    )

    async for message in query(
        prompt="List all Python files in the current directory",
        options=options
    ):
        if hasattr(message, 'content'):
            for block in message.content:
                if hasattr(block, 'text'):
                    print(block.text)

anyio.run(main)

10.3 帶 MCP 工具的交互式客戶端

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    tool,
    create_sdk_mcp_server,
    AssistantMessage,
    TextBlock
)

# 定義計算器工具
@tool("calculate", "Perform calculations", {"expression": str})
async def calculate(args: dict) -> dict:
    try:
        result = eval(args["expression"])  # 注意:生產環境應使用安全的解析器
        return {"content": [{"type": "text", "text": f"Result: {result}"}]}
    except Exception as e:
        return {"content": [{"type": "text", "text": f"Error: {e}"}]}

# 創建 MCP 服務器
calculator_server = create_sdk_mcp_server(
    name="calculator",
    tools=[calculate]
)

async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"calc": calculator_server},
        allowed_tools=["mcp__calc__calculate"],
        system_prompt="You have access to a calculator. Use it for math."
    )

    async with ClaudeSDKClient(options=options) as client:
        await client.query("What is 123 * 456 + 789?")

        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

anyio.run(main)

10.4 帶 Hook 的安全執行

from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    HookMatcher
)

# 安全檢查 Hook
async def security_check(input_data, tool_use_id, context):
    tool_name = input_data.get("tool_name", "")
    tool_input = input_data.get("tool_input", {})

    # 記錄所有工具調用
    print(f"[AUDIT] Tool: {tool_name}, Input: {tool_input}")

    # 阻止危險操作
    if tool_name == "Bash":
        command = tool_input.get("command", "")
        if any(danger in command for danger in ["rm", "sudo", ">"]):
            return {
                "hookSpecificOutput": {
                    "hookEventName": "PreToolUse",
                    "permissionDecision": "deny",
                    "permissionDecisionReason": "Dangerous command blocked"
                }
            }

    return {}

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Bash", "Read", "Write"],
        hooks={
            "PreToolUse": [
                HookMatcher(matcher="*", hooks=[security_check])
            ]
        }
    )

    async with ClaudeSDKClient(options=options) as client:
        await client.query("List files and show system info")

        async for msg in client.receive_response():
            print(msg)

anyio.run(main)

11. 設計特點總結

特性描述優勢
異步優先基於 anyio 的跨運行時支持支持 asyncio 和 trio
類型安全完整的 Python 類型注解IDE 支持、編譯時檢查
分層架構用戶 API / 內部實現 / 傳輸層關注點分離、可測試
雙模式交互query() 和 ClaudeSDKClient靈活適應不同場景
進程內 MCP無需外部進程簡化部署、降低延遲
Hook 系統細粒度的生命週期控制安全審計、自定義邏輯
權限回調工具級別的權限控制精細安全控制
文件檢查點支持回滾安全實驗、錯誤恢復

12. 與其他框架比較

特性Claude Agent SDKLangGraphGoogle ADK
核心理念簡潔優先圖優先代碼優先
狀態管理CLI 子進程BSP/Channel無狀態 Runner
工具系統進程內 MCPLangChain Tools50+ 內建工具
多代理子代理 HookStateGraph代理樹
持久化文件檢查點CheckpointerSessionService
代碼量~2,000 行~15,000 行~79,500 行

13. 結語

Claude Agent SDK 是一個精簡而強大的官方 SDK,特別適合:

  • 需要官方支持:由 Anthropic 維護,與 Claude 模型深度整合
  • 快速原型開發:API 簡潔,開箱即用
  • 安全敏感場景:完整的 Hook 和權限控制系統
  • 進程內工具開發:無需管理外部 MCP 進程

如果你正在構建基於 Claude 的 AI Agent 應用,Claude Agent SDK 是一個非常值得考慮的選擇。