Claude Agent SDK 使用指南:把 Claude Code 当库来跑(Python / TypeScript)

Claude Agent SDK 使用指南:把 Claude Code 当库来跑(Python / TypeScript)

先决定"谁跑这个 Agent",再谈怎么写代码——这四行对照表能省掉半天弯路。

接了 Claude Agent SDK 之后最常见的第一个困惑不是"怎么写",而是"我到底该用哪个":CLI?Client SDK?Managed Agents?还是它?官方在 Agent SDK overview 开头就用一张表把四种东西分开了,这张表值得放在最前面。

这篇是使用向的教程:从安装、第一个可运行的 Agent,到权限链、自定义工具、hooks、子 Agent、会话、流式与结构化输出,最后是成本、部署和排障。代码以 Python 为主,关键处给出 TypeScript 对应写法。

本文提纲

  1. 先选对工具:Agent SDK 与另外三者的区别
  2. 安装与第一个能跑的 Agent
  3. 两个入口:query() 与 ClaudeSDKClient
  4. 配置:ClaudeAgentOptions 该填什么
  5. 权限:六步评估链与规则的准确语义
  6. 自定义工具:进程内 MCP server
  7. Hooks:在生命周期上拦截与改写
  8. 子 Agent:程序化定义专家
  9. 会话:continue / resume / fork
  10. 输入输出:流式、结构化输出与用户审批
  11. 成本、部署与排障

先选对工具:Agent SDK 与另外三者的区别

你想要 用什么 你会得到
把 Claude Code 的 Agent 嵌进你自己的 Python / TypeScript 应用,进程由你掌握 Agent SDK 一个会启动 Claude Code 二进制的库,自带内置工具、权限、会话、hooks 等能力
在终端里交互式开发、跑一次性任务 Claude Code CLI 为日常交互设计的终端界面
从自己的代码里直接调 Claude API Client SDK 直接访问 API;工具循环要你自己写,或用 beta 的 tool runner
让 Anthropic 托管这个 Agent,用 Claude API 配置 Managed Agents 托管 harness,会话跑在 Anthropic 沙箱或你自托管的基础设施里

一句话记法:Agent SDK = 库 + 你运行进程;CLI = 终端给人用;Client SDK = 裸 API;Managed Agents = 托管。

另一个常见需求是"我想用 Go/Rust 驱动同一套循环"——官方的答案是把 CLI 当子进程跑(-p 加 --output-format json),不必非得用 Python/TS。

还有一条必须提前知道的合规限制:除非事先获批,Anthropic 不允许第三方开发者在自己的产品里提供 claude.ai 登录或速率限制,要按文档用 API key 认证。

安装与第一个能跑的 Agent

前置条件是 Node.js 18+ 或 Python 3.10+,以及一个 Anthropic 账号。以 Python(uv)为例:

mkdir my-agent && cd my-agent
uv init
uv add claude-agent-sdk

用 pip 的话:

python3 -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdk

TypeScript 则是 npm install @anthropic-ai/claude-agent-sdk 加 npm install --save-dev tsx(新项目建议 npm pkg set type=module)。

两个 SDK 都捆绑了 Claude Code 原生二进制,大多数情况不需要单独装。例外有两种:pip 装到了源码分发包(比如 ARM64 Windows)时不自带二进制;npm ci --omit=optional 会跳过可选依赖导致没有二进制。这两种情况都装一次原生 Claude Code,Python SDK 会从 PATH 找到它。

然后设置密钥——注意 SDK 不会自动读 .env 文件,它读的是运行进程的环境变量:

export ANTHROPIC_API_KEY=your-api-key

它同时支持第三方认证:Bedrock(CLAUDE_CODE_USE_BEDROCK=1)、Claude Platform on AWS、Google Cloud 的 Agent Platform(CLAUDE_CODE_USE_VERTEX=1)、Microsoft Foundry(CLAUDE_CODE_USE_FOUNDRY=1)。

来一个真实的例子:让它修 bug

新建 utils.py,故意留两个 bug:

def calculate_average(numbers):
    total = 0
    for num in numbers:
        total += num
    return total / len(numbers)


def get_user_name(user):
    return user["name"].upper()
  • calculate_average([]) 会除零崩溃;
  • get_user_name(None) 会抛 TypeError。

然后是 agent.py:

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ResultMessage


async def main():
    # Agentic loop: streams messages as Claude works
    async for message in query(
        prompt="Review utils.py for bugs that would cause crashes. Fix any issues you find.",
        options=ClaudeAgentOptions(
            allowed_tools=["Read", "Edit", "Glob"],  # Auto-approve these tools
            permission_mode="acceptEdits",           # Auto-approve file edits
        ),
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if hasattr(block, "text"):
                    print(block.text)            # Claude 的推理
                elif hasattr(block, "name"):
                    print(f"Tool: {block.name}") # 正在调用的工具
        elif isinstance(message, ResultMessage):
            print(f"Done: {message.subtype}")


asyncio.run(main())

运行 python agent.py 就行。TypeScript 版结构完全一样:

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Review utils.py for bugs that would cause crashes. Fix any issues you find.",
  options: {
    allowedTools: ["Read", "Edit", "Glob"],
    permissionMode: "acceptEdits"
  }
})) {
  if (message.type === "assistant" && message.message?.content) {
    for (const block of message.message.content) {
      if ("text" in block) console.log(block.text);
      else if ("name" in block) console.log(`Tool: ${block.name}`);
    }
  } else if (message.type === "result") {
    console.log(`Done: ${message.subtype}`);
  }
}

这段代码里三个东西要理解:

  1. query() 是主入口,它创建 agentic loop 并返回 async iterator——所以用 async for 边跑边收消息;
  2. prompt 是你要它做的事,用哪个工具由 Claude 自己判断;
  3. options 是配置,这里用 allowed_tools 预批准三个工具,permission_mode="acceptEdits" 自动批准文件修改。

async for 循环会一直转:Claude 思考、调工具、看结果、决定下一步。编排、工具执行、上下文管理、重试都由 SDK 负责,你只消费这条流。 循环在任务完成或出错时结束。

另外注意:循环里那些 isinstance 判断是在过滤出人类可读的部分。不过滤的话你会看到系统初始化和内部状态消息——调试时有用,平时很吵。

两个入口:query() 与 ClaudeSDKClient

query() ClaudeSDKClient
形态 异步生成器,一次性 上下文管理器,可多轮
适合 单次任务、后台作业、CI 交互式对话、需要中途改配置
自定义工具 / hooks 不支持 支持

Python 的双向会话长这样:

import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, ResultMessage, TextBlock

async def main():
    options = ClaudeAgentOptions(
        system_prompt="You are a helpful assistant",
        max_turns=4,
    )
    async with ClaudeSDKClient(options=options) as client:
        await client.query("帮我看看这个项目")
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(block.text)
            elif isinstance(message, ResultMessage):
                print(message.result)

消息类型固定就那几个:AssistantMessage、UserMessage、SystemMessage、ResultMessage;内容块是 TextBlock、ToolUseBlock、ToolResultBlock。ResultMessage 是最后一条,携带结果、成本与错误状态。

TypeScript 没有 ClaudeSDKClient 这个概念对等的类,多轮通过 query() 的流式输入(yield 用户消息)或 continue: true 实现。

配置:ClaudeAgentOptions 该填什么

常用选项一张表:

选项(Python / TS) 作用
model / model 指定模型,如 "claude-sonnet-5"
fallback_model / fallbackModel 降级模型;TS 可用逗号分隔多个
allowed_tools / allowedTools 自动批准清单(不是白名单,见下节)
disallowed_tools / disallowedTools 禁用工具或加作用域 deny 规则
permission_mode / permissionMode 权限模式
max_turns / maxTurns 限制循环轮数
cwd 工作目录(默认能访问该目录及子目录)
env 传给子进程的环境变量,比如指向自建网关 ANTHROPIC_BASE_URL
system_prompt 系统提示词(preset 或自定义,见下)
mcp_servers / mcpServers 接入 MCP server(外部进程或进程内)
agents 程序化定义子 Agent
hooks 生命周期拦截
output_format / outputFormat 结构化输出(JSON Schema)
include_partial_messages / includePartialMessages 开启逐 token 流式
cli_path / pathToClaudeCodeExecutable 指定 Claude Code 二进制位置

一段典型的配置:

options = ClaudeAgentOptions(
    model="claude-sonnet-5",
    allowed_tools=["Read", "Glob", "Grep"],
    max_turns=8,
    cwd="/path/to/repo",
)

会话中途也能改配置(这是 ClaudeSDKClient 的独有能力):

async with ClaudeSDKClient(options=ClaudeAgentOptions(model="claude-sonnet-5")) as client:
    await client.query("Reply with exactly: ready")
    async for message in client.receive_response():
        ...

    await client.set_model("claude-opus-5")
    await client.set_permission_mode("acceptEdits")

    await client.query("Reply with exactly: done")
    async for message in client.receive_response():
        ...

系统提示词有个容易踩的缓存语义:默认情况下 Claude Code 在会话的第一次请求时构建系统提示词并记录,之后每个请求(包括 resume 之后)都复用它。所以你改了自定义提示词、或改了 claude_code preset 的 append 文本,在会话被压缩或新开会话之前不会生效。要每个请求都重建(比如正在反复调措辞),把 snapshot 设为 False:

options = ClaudeAgentOptions(
    system_prompt={"type": "custom", "prompt": "You are a release bot.", "snapshot": False}
)

这个选项需要 Claude Code CLI 2.1.257 或更高版本。

权限:六步评估链与规则的准确语义

这块是 Agent SDK 最需要认真读的部分。每次工具请求,SDK 按固定六步评估:

  1. Hooks 先跑。hook 可以直接拒绝或放行往下走。注意:hook 返回 allow 并不能跳过后面的 deny / ask 规则;而且 PreToolUse hook 的 allow 也无法批准针对关键路径的 rm / rmdir。
  2. Deny 规则(来自 disallowed_tools 和 settings.json)。命中即拦截,即使在 bypassPermissions 模式下也拦。裸名字的 deny 规则(如 Bash)会在这个步骤之前就把工具从 Claude 的上下文里移除,所以这一步只检查带作用域的规则(如 Bash(rm *))。
  3. Ask 规则(来自 settings.json)。命中就落到你的 canUseTool 回调去确认,同样在 bypassPermissions 下也生效。需要用户交互的工具(AskUserQuestion、标注了 _meta["anthropic/requiresUserInteraction"] 的 MCP 工具)永远走回调;在 dontAsk 模式下则直接拒绝,因为该模式从不提示。
  4. 权限模式。bypassPermissions 批准一切走到这步的调用(关键路径的 rm/rmdir 除外);acceptEdits 批准文件编辑类操作;plan 模式把文件编辑和写类 shell 工具一律送进回调,所以规划阶段不可能被自动批准。
  5. Allow 规则(来自 allowed_tools 和 settings.json)。命中即批准。有些调用不需要规则也会在这步解决,比如工作目录内的文件读取、只读的 Bash 命令。
  6. canUseTool 回调。前面都没解决就问你。dontAsk 模式下这一步跳过、直接拒绝。

TypeScript 有个细节:如果你在"回调之前就会被自动批准"的配置下传了 canUseTool,SDK 会在构造 query 时打一次进程警告,代码是 CLAUDE_SDK_CAN_USE_TOOL_SHADOWED。两种配置会触发它:permissionMode: 'bypassPermissions',或者 allowedTools 里出现裸名字(如 "Read")。想让每一次工具调用都必须过你的关,正确做法是用 PreToolUse hook,而不是 canUseTool。

规则的语义差异(很容易搞错)

写法 效果
allowed_tools=["Read", "Grep"] 这两个工具自动批准;其他工具依然存在,需要批准的调用会落到权限模式和 canUseTool
disallowed_tools=["Bash"] Bash 的工具定义从请求里移除,Claude 看不到也调不了
disallowed_tools=["Bash(rm *)"] Bash 仍可用;匹配 rm * 的调用在任何模式(含 bypassPermissions)下都被拒绝,其他 Bash 调用(包括 /bin/rm)照常走流程

一句话:allowed_tools 是"预批准清单",不是"可用工具白名单";要禁用工具得用 disallowed_tools。 这条被无数教程写反。

自定义工具:进程内 MCP server

这是 SDK 里最实用的扩展点:自定义工具用进程内 MCP server 实现,不用起子进程。

from typing import Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server


# 定义工具:名字、描述、输入 schema、处理函数
@tool(
    "get_temperature",
    "Get the current temperature at a location",
    {"latitude": float, "longitude": float},
)
async def get_temperature(args: dict[str, Any]) -> dict[str, Any]:
    async with httpx.AsyncClient() as client:
        response = await client.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": args["latitude"],
                "longitude": args["longitude"],
                "current": "temperature_2m",
                "temperature_unit": "fahrenheit",
            },
        )
        data = response.json()
    return {"content": [{"type": "text", "text": f"Temperature: {data['current']['temperature_2m']}°F"}]}


weather_server = create_sdk_mcp_server(
    name="weather",
    version="1.0.0",
    tools=[get_temperature],
)

挂到 Agent 上,并在 allowed_tools 里用命名空间形式预批准:

options = ClaudeAgentOptions(
    mcp_servers={"weather": weather_server},
    allowed_tools=["mcp__weather__get_temperature"],
)

命名规则是 mcp__<server 名>__<工具名>。TypeScript 版用 Zod 定义 schema,.describe() 写的字段描述 Claude 能看到,.optional() 让参数可省;Python 里则把不在 schema 里的参数用 args.get() 取默认值。

还有一个细节很值钱:工具注解。给只读工具标上 readOnlyHint,Claude 就可以把它和其他只读调用批量并发:

from claude_agent_sdk import tool, ToolAnnotations

@tool(
    "get_temperature",
    "Get the current temperature at a location",
    {"latitude": float, "longitude": float},
    annotations=ToolAnnotations(readOnlyHint=True),
)
async def get_temperature(args):
    ...

进程内 server 的好处官方列得很清楚:不用管子进程、没有 IPC 开销、单进程部署、同进程调试、类型安全;而且可以和外部 MCP server 混用——mcp_servers 里同时放进程内 server 和 stdio/HTTP server 都行。外部 MCP 的选型规则很简单:文档给的是命令就用 stdio,给的是 URL 就用 HTTP 或 SSE,自己写代码就用 SDK MCP server。

Hooks:在生命周期上拦截与改写

Hook 是由 Claude Code 这个应用(不是 Claude)在固定节点调用的回调,用于确定性地拦截和反馈。典型用法是保护敏感文件:

from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, HookMatcher


async def protect_env_files(input_data, tool_use_id, context):
    file_path = input_data["tool_input"].get("file_path", "")
    file_name = file_path.split("/")[-1]

    if file_name == ".env":
        return {
            "hookSpecificOutput": {
                "hookEventName": input_data["hook_event_name"],
                "permissionDecision": "deny",
                "permissionDecisionReason": "Cannot modify .env files",
            }
        }
    return {}


options = ClaudeAgentOptions(
    hooks={"PreToolUse": [HookMatcher(matcher="Edit", hooks=[protect_env_files])]},
)

因为权限链的第一步就是 hooks,它能做规则做不到的事:基于参数内容判断,而不是基于工具名;也能改写或注入上下文。配合权限回调(canUseTool)一起用时记住分工:hook 是确定性的程序判断,回调是"要人拍板"的路径。

子 Agent:程序化定义专家

官方推荐用程序化定义(另一种是文件系统定义,把 agent 放进 .claude/agents/):

from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Grep", "Glob", "Agent"],   # 注意要放行 Agent 工具
    agents={
        "code-reviewer": AgentDefinition(
            description="Expert code review specialist. Use for quality, security, and maintainability reviews.",
            prompt="You are a code review specialist with expertise in security, performance, and best practices.",
            tools=["Read", "Grep"],   # 只给只读工具
            model="sonnet",            # 可以指定更便宜的模型
        ),
    },
)

async for message in query(
    prompt="Review the authentication module for security issues",
    options=options,
):
    ...

三个要点:description 是给主 Agent 看的,它决定"什么时候该派这个子 Agent";prompt 定义子 Agent 的行为和专业领域;tools 和 model 可以单独收窄——只读工具 + 便宜模型做审查是典型的省钱配置。

调用方式有两种:自动(主 Agent 根据 description 自己决定)和显式——直接在 prompt 里说"Use the code-reviewer agent to check the authentication module"。

会话:continue / resume / fork

会话负责跨轮次保留历史。三种用法:

  • 自动管理:Python 用 ClaudeSDKClient 的多轮上下文;TypeScript 在第二次 query() 里传 continue: true 续接最近一次会话;
  • resume:拿到 session ID 后按 ID 恢复某次历史运行;
  • fork:从某个会话分叉出新的分支——适合"在同一个上下文里试两条不同路线"。

如果要跨主机恢复,会话还可以镜像到外部存储(对象存储、KV、数据库),文档里给了专门的一页(session storage),Python 仓库里也有 Postgres / Redis / S3 的示例实现。

输入输出:流式、结构化输出与用户审批

流式输出

默认你拿到的是"一条条完整消息"。要逐 token 流,把 include_partial_messages=True 打开,然后处理 StreamEvent:

from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import StreamEvent

options = ClaudeAgentOptions(
    include_partial_messages=True,
    allowed_tools=["Bash", "Read"],
)

async for message in query(prompt="List the files in my project", options=options):
    if isinstance(message, StreamEvent):
        event = message.event
        if event.get("type") == "content_block_delta":
            delta = event.get("delta", {})
            if delta.get("type") == "text_delta":
                print(delta.get("text", ""), end="", flush=True)

注意这是三层嵌套类型检查(StreamEvent → content_block_delta → text_delta),第一次写容易少一层。

另外要区分两个概念:流式输入模式(你持续把用户消息喂进去)与流式输出(SDK 持续把事件吐出来)是两件事,文档把它们分成两页,别混。

结构化输出

要让 Agent 返回可校验的 JSON,用 output_format 传 JSON Schema:

schema = {
    "type": "object",
    "properties": {
        "company_name": {"type": "string"},
        "founded_year": {"type": "number"},
        "headquarters": {"type": "string"},
    },
    "required": ["company_name"],
}

options = ClaudeAgentOptions(output_format={"type": "json_schema", "schema": schema})

TypeScript 里可以直接用 Zod 定义再交给 SDK;Python 侧同理可用 Pydantic 模型生成 schema。这样多轮工具调用之后拿到的就是类型安全的结构化数据,而不是一段需要自己解析的自然语言。

用户审批与提问

两条路径要分清:审批(工具要不要执行)走 canUseTool 回调;提问(Claude 需要澄清)走 AskUserQuestion 工具。文档专门有一页讲怎么把这两类请求呈现给用户、再把决定送回 SDK——这是做交互式产品时绕不过去的一段。

成本、部署与排障

成本:先记住它不是账单

ResultMessage.total_cost_usd(TS 是 costUSD)是客户端估算,不是权威账单——SDK 用构建时打包的价格表在本地算,会和你实际付费产生偏差。要用它做开发期观察和粗略预算,但不要用它给终端用户计费,也不要据此触发财务决策;权威数据去查 Usage and Cost API 或 Console。

字段分工:Python 在每条 assistant 消息上有 message.usage 和 message.message_id,ResultMessage 上有 model_usage(按模型的成本)和 total_cost_usd(累计);TypeScript 对应 message.message.usage、modelUsage、costUSD。SDK 甚至建模了一条计费规则:当响应的 usage 报告 inference_geo: "us" 时,按列表价乘 1.1(数据驻留定价)。

部署:理解子进程模型

官方 hosting 页的起点是子进程模型:SDK 在本地启动 Claude Code 进程,所以状态默认落在本地磁盘。由此推出三种会话模式:

  • Ephemeral(短命):每个任务一个新容器,跑完就丢,简单、隔离好;
  • Long-running(长驻):容器常驻,会话在本地累积,适合交互式产品;
  • Hybrid(混合):热路径留在本地,同时把会话镜像进外部存储,兼顾延迟与可恢复。

容器化还要配好:运行时依赖、资源限制、网络策略,以及多 Agent 场景下的容器拓扑。安全侧另有 secure deployment 一页讲沙箱与权限收紧。

排障:官方把常见错误列全了

文档把错误分成三类,照着查就行:

  • CLI 启动失败:CLINotFoundError(找不到 Claude Code,装一个或指定 cli_path)、CLIConnectionError(拒绝执行批处理脚本 / 启动失败 / 未连接);
  • CLI 进程退出:ProcessError: Command failed with exit code、进程以非零码退出、返回错误结果;
  • 结构化输出异常:最典型的是"result 说 success,但 structured_output 是 None"。

另外从旧的 Claude Code SDK(< 0.1.0)升级要注意改名成本:ClaudeCodeOptions → ClaudeAgentOptions、系统提示词配置合并、settings 隔离与显式控制,以及新增的程序化子 Agent 与会话 fork。CHANGELOG 里有完整清单。

顺带提一句品牌合规:Anthropic 允许你用 "Claude Agent"、菜单已标注 Agents 时用 "Claude"、或 "{你的产品名} Powered by Claude";不允许用 "Claude Code" / "Claude Code Agent" 或仿 Claude Code 的 ASCII art——你的产品要保持自己的品牌。

参考链接

你打算把 Agent SDK 用在哪:嵌进现有产品,还是当一个自动化流水线的执行引擎?评论区聊聊你的用法,觉得这份指南有用就点个赞收藏。


作者: itech001
来源: 公众号:AI人工智能时代(the-ai-era)
网站: https://www.theaiera.top/
关注每日最新AI新闻和技术博客,主页有更多的文章的AI 技术参考:https://www.theaiera.top

本文首发于 AI人工智能时代,转载请注明出处。

posted @ 2026-10-05 17:16  iTech  阅读(7)  评论(0)    收藏  举报