Claude Agent SDK 是什么
官方描述:Claude Agent SDK 让你能够构建自主运行的 AI Agent——它们可以读取文件、执行命令、搜索网络、编辑代码等。SDK 提供了驱动 Claude Code 的相同工具、代理循环和上下文管理能力。
团队想把"会读代码、能跑命令、可调接口"的 Agent 嵌进自己的系统,又不想每次都开着 Claude Code 客户端手动操作?Claude Agent SDK 就是把 Claude Code 的能力封装成可编程接口,让你用几行 Python(也支持 TypeScript)就能驱动一个自主 Agent。这篇文章从安装、核心配置讲到自定义工具,带你把 SDK 真正跑起来。
说白了,它就是"可编程的 Claude Code",主要用来做应用集成和自定义 Agent。SDK 提供了这些能力:
| 能力 | 说明 |
|---|---|
| 文件操作 | Read、Write、Edit 文件 |
| 代码执行 | 运行 Bash 命令、执行脚本 |
| 内容搜索 | Grep、Glob 搜索 |
| 网络访问 | WebFetch、WebSearch |
| MCP 扩展 | 连接外部工具和服务 |
| 会话管理 | 保持上下文、恢复会话 |
| 权限控制 | 细粒度的工具权限管理 |
| Hooks | 工具执行前后的拦截和处理 |
一、快速上手
安装
建议在虚拟环境里安装。在较新的 Debian、Ubuntu 以及用 Homebrew 装的 Python 上,直接对系统 Python 跑 pip install 会报 error: externally-managed-environment。
pip install claude-agent-sdk
验证是否装成功:
pip show claude-agent-sdk
正常会输出类似:
Name: claude-agent-sdk
Version: 0.2.123
Summary: Python SDK for Claude Code
Home-page: https://github.com/anthropics/claude-agent-sdk-python
Author:
Author-email: Anthropic <support@anthropic.com>
License: MIT
Location: /Users/macos/.venv/lib/python3.13/site-packages
Requires: anyio, mcp, sniffio
Required-by:
版本号会随发布变化,上面只是我本地的一个快照,实际以
pip show的输出为准。
query():一次性的简单调用
query() 每次调用都会新建一个会话,最适合"一句话、跑完即走"的场景:一行代码就能调用,工具循环也帮你自动处理。多轮对话请往下看 ClaudeSDKClient。
from claude_agent_sdk import query, AssistantMessage, TextBlock
import asyncio
async def main():
# 简单查询
async for message in query("你好,请用 Java 写个冒泡排序"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
asyncio.run(main())
ClaudeSDKClient:可保持上下文的客户端
如果是多次来回的对话,query() 就不太够用了。ClaudeSDKClient 维护一个可以持续对话的客户端对象,关键特性:
- 会话连续性:多个
query()之间保留对话上下文 - 同一次对话:记得之前发过的消息
- 可中断:任务跑到一半能停下来
- 显式生命周期:你来控制会话何时开始、何时结束
- 响应驱动:根据返回结果继续发后续消息
- 自定义工具与 Hooks:支持用
@tool装饰器定义工具,也能挂 Hooks
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
AssistantMessage,
TextBlock,
)
async def main():
options = ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob"],
max_turns=10,
permission_mode="acceptEdits",
)
async with ClaudeSDKClient(options=options) as client:
await client.query("分析 src/ 目录下所有 js 文件,是否有语法错误,并修复")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
asyncio.run(main())
二、核心配置 ClaudeAgentOptions
ClaudeAgentOptions 是控制 Agent 行为的核心配置类,可以理解成 Agent 的"说明书":用什么模型、能调哪些工具、最多跑几轮、在哪个目录干活,全写在它里面。
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
tools | list[str] | ToolsPreset | None | None | 工具配置 |
allowed_tools | list[str] | [] | 无需提示即可自动批准的工具 |
system_prompt | str | SystemPromptPreset | None | None | 系统提示配置 |
mcp_servers | dict[str, McpServerConfig] | str | Path | {} | MCP 服务器配置 |
strict_mcp_config | bool | False | 仅使用指定的 MCP 服务器 |
permission_mode | PermissionMode | None | None | 工具使用的权限模式 |
model | str | None | None | Claude 模型别名或完整名称 |
fallback_model | str | None | None | 备用模型 |
max_turns | int | None | None | 最大代理轮次 |
max_budget_usd | float | None | None | 成本预算限制 |
disallowed_tools | list[str] | [] | 要拒绝的工具 |
cwd | str | Path | None | None | 工作目录 |
env | dict[str, str] | {} | 环境变量 |
hooks | dict[HookEvent, list[HookMatcher]] | None | None | 钩子配置 |
thinking | ThinkingConfig | None | None | 思考配置 |
effort | EffortLevel | None | None | 努力级别 |
enable_file_checkpointing | bool | False | 启用文件变更跟踪 |
可以看到,配置项覆盖了 model、fallback_model、max_turns、tools 等,能按业务需要灵活组合。相比在客户端手动操作,用 SDK 省了不少重复劳动。比如:
- 设一个合理的
max_turns,在质量和速度之间取舍; - 用
tools+permission_mode框住 Agent 能碰的工具和权限,防止越界。
options = ClaudeAgentOptions(
allowed_tools=["Read", "Grep", "Glob"],
max_turns=3,
permission_mode="acceptEdits",
)
三、消息类型与响应处理
LLM 返回的结果不是一次给完,而是异步、多次流式吐出来的。拿到消息后怎么处理,取决于消息类型,这是写后续逻辑的关键。
async for message in client.receive_response():
# 流事件
if isinstance(message, StreamEvent):
event = message.event
event_type = event.get("type")
if event_type == "text":
print(f"text: {event.get('text')}")
# 完整的助手响应
elif isinstance(message, AssistantMessage):
# 处理 content 里的各个 block
# 最终结果
elif isinstance(message, ResultMessage):
# 处理最终结果
# 系统消息
elif isinstance(message, SystemMessage):
# 处理系统消息
四、会话管理
实际业务里,下游任务经常要接着上游的上下文继续干。SDK 提供了几种会话玩法。
用 session_id 复用会话
执行多次会话时,如果下游依赖上游,可以通过 session_id 把上下文串起来:把 session_id 存到数据库,下次直接传进去就能续上。
async with ClaudeSDKClient() as client:
# 第一次查询
await client.query("创建一个 java 项目")
async for msg in client.receive_response():
print(msg)
# 获取会话 ID
session_id = client.session_id
print(f"Session ID: {session_id}")
# 继续对话(Agent 记得之前的上下文)
await client.query("在项目中添加一个 README.md 文件")
async for msg in client.receive_response():
print(msg)
多轮持续对话
import asyncio
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock
async def main():
async with ClaudeSDKClient() as client:
await client.query("What's the capital of France?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
# 跟进问题——上下文被保留
await client.query("What's the population of that city?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
# 再问一个,仍是同一段对话
await client.query("What are some famous landmarks there?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Claude: {block.text}")
asyncio.run(main())
流式输入
流式输入的精髓就是"边生成、边接收,不等完再拿"。相比客户端"一次性输入、一次性输出",流式让你在输入的同时就能拿到输出,不必等输入全部结束,也能根据实时信息随时调整。
import asyncio
from claude_agent_sdk import ClaudeSDKClient
async def message_stream():
"""动态生成消息。"""
yield {
"type": "user",
"message": {"role": "user", "content": "Analyze the following data:"},
}
await asyncio.sleep(0.5)
yield {
"type": "user",
"message": {"role": "user", "content": "Temperature: 25°C, Humidity: 60%"},
}
await asyncio.sleep(0.5)
yield {
"type": "user",
"message": {"role": "user", "content": "What patterns do you see?"},
}
async def main():
async with ClaudeSDKClient() as client:
# 向 Claude 流式输入
await client.query(message_stream())
async for message in client.receive_response():
print(message)
# 同一会话内继续追问
await client.query("Should we be concerned about these readings?")
async for message in client.receive_response():
print(message)
asyncio.run(main())
Plan 模式:先规划后执行
客户端的 Plan 模式(先规划、后执行)在 SDK 里同样支持,而且可以中途切换。
async with ClaudeSDKClient(options=options) as client:
# 先用只读模式规划
await client.update_options(permission_mode="planMode")
await client.query("code review")
async for msg in client.receive_response():
print(msg)
# 用户确认后,切换到可编辑模式
await client.update_options(permission_mode="acceptEdits")
await client.query("fix bug")
async for msg in client.receive_response():
print(msg)
会话中断
流式会话里,Agent 陷入死循环、跑得太久、或者你临时改了主意,都能用 interrupt() 把它掐断。
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, ResultMessage
async def interruptible_task():
options = ClaudeAgentOptions(allowed_tools=["Bash"], permission_mode="acceptEdits")
async with ClaudeSDKClient(options=options) as client:
# 启动一个长任务
await client.query("Count from 1 to 100 slowly, using the bash sleep command")
# 让它先跑一会儿
await asyncio.sleep(2)
# 中断任务
await client.interrupt()
print("Task interrupted!")
# 把中断任务的消息排空(含它的 ResultMessage)
async for message in client.receive_response():
if isinstance(message, ResultMessage):
print(f"Interrupted task finished with subtype={message.subtype!r}")
# 被中断的任务 subtype 是 "error_during_execution"
# 发一条新指令
await client.query("Just say hello instead")
async for message in client.receive_response():
if isinstance(message, ResultMessage) and message.subtype == "success":
print(f"New result: {message.result}")
asyncio.run(interruptible_task())
五、自定义工具
SDK 自带不少工具,但真实业务里常要查数据库、查文件系统、调远程服务,这些就得自己写工具了。用 @tool 装饰器即可:指定名称、描述和参数,业务逻辑写在函数体里,SDK 会自动把它注册成 Agent 可调用的工具。
常见用法:
- 创建并配置 MCP 服务端
- 调用内部接口
- 发送通知
- 执行下游逻辑
以 MCP 为例,下面用 @tool 定义两个工具:
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
tool,
create_sdk_mcp_server,
AssistantMessage,
TextBlock,
)
import asyncio
from typing import Any
# 用 @tool 装饰器定义自定义工具
@tool("calculate", "Perform mathematical calculations", {"expression": str})
async def calculate(args: dict[str, Any]) -> dict[str, Any]:
try:
result = eval(args["expression"], {"__builtins__": {}})
return {"content": [{"type": "text", "text": f"Result: {result}"}]}
except Exception as e:
return {
"content": [{"type": "text", "text": f"Error: {str(e)}"}],
"is_error": True,
}
@tool("get_time", "Get current time", {})
async def get_time(args: dict[str, Any]) -> dict[str, Any]:
from datetime import datetime
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return {"content": [{"type": "text", "text": f"Current time: {current_time}"}]}
async def main():
# 把自定义工具挂到 SDK MCP server 上
my_server = create_sdk_mcp_server(
name="utilities", version="1.0.0", tools=[calculate, get_time]
)
options = ClaudeAgentOptions(
mcp_servers={"utils": my_server},
allowed_tools=["mcp__utils__calculate", "mcp__utils__get_time"],
)
async with ClaudeSDKClient(options=options) as client:
await client.query("What's 123 * 456?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Calculation: {block.text}")
await client.query("What time is it now?")
async for message in client.receive_response():
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(f"Time: {block.text}")
asyncio.run(main())
上面这个例子里,MCP 工具就是普通的 Python 函数。由于它们和你的应用跑在同一个进程内(in-process SDK server),省掉了 IPC 开销,性能反而更好。

上图是整个自定义工具的调用流程图:LLM Agent 并不直接调用具体工具,而是发出统一的 tool_use 请求,把意图表达成标准化的工具调用(如 mcp__{server}__{tool})。中间的 MCP Server 像个"工具路由中枢",按命名规范解析请求、做权限控制和路由分发,再调对应的工具函数。一次自定义工具的触发和执行,其实走的是"注册 ➔ 匹配 ➔ 决策 ➔ 调度执行 ➔ 结果反馈"的完整链路:LLM 读懂意图后调度具体工具,再把结果回给用户。
当然,如果某些业务必须跑在独立进程、要给多个进程提供服务,那就用独立 MCP 服务,比如用 stdio 模式拉起一个独立进程:
# mcp_server.py
from datetime import datetime
from mcp.server.fastmcp import FastMCP
# 创建独立的 FastMCP 服务
mcp = FastMCP("utilities")
@mcp.tool()
async def calculate(expression: str) -> str:
"""执行数学计算"""
try:
return f"Result: {eval(expression, {'__builtins__': {}})}"
except Exception as e:
return f"Error: {str(e)}"
@mcp.tool()
async def get_time() -> str:
"""获取当前时间"""
return f"Current time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
if __name__ == "__main__":
# 以 stdio 模式启动该独立进程
mcp.run(transport="stdio")
六、为什么要用 SDK
命令行加 Headless 模式,适合做一次性操作;碰到更复杂的业务自动化,就该上 SDK 了。它本质是把 Claude Code 变成你自定义的 Runtime Harness——能按业务细粒度地调,性能和落地效果都更贴合自己的场景。说到底,它让"把 Agent 能力嵌进自有服务"这件事,从"开着客户端手点"变成了"写几行代码自动跑"。