Python SDK 参考:安装、函数与 ClaudeSDKClient
Agent SDK Python 的安装、query() 与 ClaudeSDKClient 的选择,query、tool、create_sdk_mcp_server、会话函数的完整签名,以及 ClaudeSDKClient 类的方法与示例(继续对话、流式输入、中断、权限控制)。
本页是 Python Agent SDK 参考的第一部分:安装、函数和 ClaudeSDKClient 类。配置类 ClaudeAgentOptions 和各种类型见「Python SDK 参考:选项与类型」,消息、内容块和错误类型见「Python SDK 参考:消息与错误」,hook 类型、工具输入输出和沙盒配置见「Python SDK 参考:Hook、工具与沙盒」。
安装
把包安装到虚拟环境里(在较新的 Debian、Ubuntu 和 Homebrew Python 上,对系统 Python 运行 pip install 会失败并报 error: externally-managed-environment):
python3 -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdkuv、Windows PowerShell 和 API Key 设置见 Agent SDK 快速开始的 Setup 一节。
在 query() 与 ClaudeSDKClient 之间选择
Python SDK 提供两种与 Claude Code 交互的方式:
| 特性 | query() | ClaudeSDKClient |
|---|---|---|
| 会话 | 默认创建新会话 | 复用同一个会话 |
| 对话 | 单次交换 | 同一上下文里多次交换 |
| 连接 | 自动管理 | 手动控制 |
| 流式输入 | ✅ 支持 | ✅ 支持 |
| 中断 | ❌ 不支持 | ✅ 支持 |
| Hooks | ✅ 支持 | ✅ 支持 |
| 自定义工具 | ✅ 支持 | ✅ 支持 |
| 继续聊天 | 通过 continue_conversation 或 resume 手动 | ✅ 自动 |
| 用例 | 一次性任务 | 持续对话 |
对聊天界面这类交互式应用,或下一步动作取决于 Claude 的响应时,用 ClaudeSDKClient。
函数
本页的签名块和裸的 async for / async with 片段只是示意:要运行它们,把代码体包进 async def main(): ... 并调用 asyncio.run(main())。
query()
默认为与 Claude Code 的每次交互创建新会话,返回一个在消息到达时产出它们的异步迭代器。除非你在 ClaudeAgentOptions 里传 continue_conversation=True 或 resume,每次 query() 调用都从头开始,不记得先前的交互。
async def query(
*,
prompt: str | AsyncIterable[dict[str, Any]],
options: ClaudeAgentOptions | None = None,
transport: Transport | None = None
) -> AsyncIterator[Message]| 参数 | 类型 | 说明 |
|---|---|---|
prompt | str | AsyncIterable[dict] | 输入提示:字符串,或流式模式下的异步可迭代对象 |
options | ClaudeAgentOptions | None | 可选的配置对象(为 None 时默认 ClaudeAgentOptions()) |
transport | Transport | None | 与 CLI 进程通信的可选自定义传输 |
返回产出对话消息的 AsyncIterator[Message]。
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
async def main():
options = ClaudeAgentOptions(
system_prompt="You are an expert Python developer",
permission_mode="acceptEdits",
)
async for message in query(prompt="Create a Python web server", options=options):
print(message)
asyncio.run(main())tool()
用类型安全的方式定义 MCP 工具的装饰器。
def tool(
name: str,
description: str,
input_schema: type | dict[str, Any],
annotations: ToolAnnotations | None = None
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]| 参数 | 类型 | 说明 |
|---|---|---|
name | str | 工具的唯一标识 |
description | str | 工具做什么的人类可读描述 |
input_schema | type | dict[str, Any] | 定义工具输入参数的 schema,见下 |
annotations | ToolAnnotations | None | 可选的 MCP 工具注解,向客户端提供行为提示 |
输入 schema 选项:1. 简单类型映射(推荐):{"text": str, "count": int, "enabled": bool};2. JSON Schema 格式(用于复杂校验):
{
"type": "object",
"properties": {
"text": {"type": "string"},
"count": {"type": "integer", "minimum": 0},
},
"required": ["text"],
}返回一个包装工具实现并返回 SdkMcpTool 实例的装饰器函数。
from claude_agent_sdk import tool
from typing import Any
@tool("greet", "Greet a user", {"name": str})
async def greet(args: dict[str, Any]) -> dict[str, Any]:
return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}ToolAnnotations
工具的行为提示,作为 tool() 的 annotations 参数传入。ToolAnnotations 扩展 MCP SDK 的 mcp.types.ToolAnnotations,多一个 maxResultSizeChars 字段,每个提示可用 camelCase 或 snake_case 书写:ToolAnnotations(readOnlyHint=True) 与 ToolAnnotations(read_only_hint=True) 等价;SDK 接受注解的地方也可以传普通的 mcp.types.ToolAnnotations。snake_case 名字和带类型的 maxResultSizeChars 字段需要 Python Agent SDK 0.2.140 及以上;0.1.31 到 0.2.139 原样重新导出 mcp.types.ToolAnnotations,在 0.1.55 到 0.2.139 上你仍可把 maxResultSizeChars 作为关键字参数传入(MCP 类接受额外字段,SDK 把值转发给 Claude Code)。所有字段都是可选的,客户端不应依赖这些提示做安全决定。
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
title | str | None | None | 工具的人类可读标题 |
readOnlyHint | bool | None | False | 为 True 时工具不修改其环境 |
destructiveHint | bool | None | True | 为 True 时工具可能做破坏性更新(仅当 readOnlyHint 为 False 时有意义) |
idempotentHint | bool | None | False | 为 True 时用相同参数重复调用没有额外影响(仅当 readOnlyHint 为 False 时有意义) |
openWorldHint | bool | None | True | 为 True 时工具与外部实体交互(如网页搜索);为 False 时工具的领域是封闭的(如记忆工具) |
maxResultSizeChars | int | None | None | Claude Code 把该工具的文本结果保留在对话里而不是存到文件的字符数上限,最高 500,000;含图片的结果不受影响。这是 Claude Code 设置而不是 MCP 提示:SDK 把它放在工具的 _meta 里,作为 anthropic/maxResultSizeChars 发送 |
from claude_agent_sdk import tool, ToolAnnotations
from typing import Any
@tool(
"search",
"Search the web",
{"query": str},
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=True),
)
async def search(args: dict[str, Any]) -> dict[str, Any]:
return {"content": [{"type": "text", "text": f"Results for: {args['query']}"}]}create_sdk_mcp_server()
创建在你的 Python 应用内运行的进程内 MCP 服务器。
def create_sdk_mcp_server(
name: str,
version: str = "1.0.0",
tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfig| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
name | str | - | 服务器的唯一标识 |
version | str | "1.0.0" | 服务器版本字符串 |
tools | list[SdkMcpTool[Any]] | None | None | 用 @tool 装饰器创建的工具函数列表 |
返回可以传给 ClaudeAgentOptions.mcp_servers 的 McpSdkServerConfig 对象。
from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions
@tool("add", "Add two numbers", {"a": float, "b": float})
async def add(args):
return {"content": [{"type": "text", "text": f"Sum: {args['a'] + args['b']}"}]}
@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
async def multiply(args):
return {"content": [{"type": "text", "text": f"Product: {args['a'] * args['b']}"}]}
calculator = create_sdk_mcp_server(
name="calculator",
version="2.0.0",
tools=[add, multiply], # 传入被装饰的函数
)
# 与 Claude 一起使用
options = ClaudeAgentOptions(
mcp_servers={"calc": calculator},
allowed_tools=["mcp__calc__add", "mcp__calc__multiply"],
)list_sessions()
列出过去的会话及其元数据;可按项目目录过滤,也可列出所有项目的会话。同步,立即返回。
def list_sessions(
directory: str | None = None,
limit: int | None = None,
offset: int = 0,
include_worktrees: bool = True
) -> list[SDKSessionInfo]| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
directory | str | None | None | 要列出会话的目录;省略时返回所有项目的会话 |
limit | int | None | None | 最多返回的会话数 |
offset | int | 0 | 从排序结果开头跳过的会话数,配合 limit 做分页 |
include_worktrees | bool | True | directory 位于 git 仓库内时,包含所有 worktree 路径的会话 |
返回类型 SDKSessionInfo:
| 属性 | 类型 | 说明 |
|---|---|---|
session_id | str | 唯一的会话标识 |
summary | str | 显示标题:自定义标题、最近的提示、自动生成的摘要,或第一个提示 |
last_modified | int | 最后修改时间,自 epoch 起的毫秒数 |
file_size | int | None | 会话文件字节数(远程存储后端为 None) |
custom_title | str | None | 会话标题:用户设置的标题,没设置时是自动生成的标题 |
first_prompt | str | None | 会话里第一个有意义的用户提示 |
git_branch | str | None | 会话结束时的 git 分支 |
cwd | str | None | 会话的工作目录 |
tag | str | None | 用户设置的会话标签(见 tag_session()) |
created_at | int | None | 会话创建时间,自 epoch 起的毫秒数 |
打印某个项目最近的 10 个会话(结果按 last_modified 降序排序,第一项最新;省略 directory 则跨所有项目搜索):
from claude_agent_sdk import list_sessions
for session in list_sessions(directory="/path/to/project", limit=10):
print(f"{session.summary} ({session.session_id})")get_session_messages()
读取过去会话的消息。同步,立即返回。
def get_session_messages(
session_id: str,
directory: str | None = None,
limit: int | None = None,
offset: int = 0
) -> list[SessionMessage]| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
session_id | str | 必填 | 要读取消息的会话 ID |
directory | str | None | None | 在其中查找的项目目录;省略时搜索所有项目 |
limit | int | None | None | 最多返回的消息数 |
offset | int | 0 | 从开头跳过的消息数 |
返回类型 SessionMessage:
| 属性 | 类型 | 说明 |
|---|---|---|
type | Literal["user", "assistant"] | 消息角色 |
uuid | str | 唯一的消息标识 |
session_id | str | 会话标识 |
message | Any | 原始消息内容 |
parent_tool_use_id | str | None | 对子智能体消息,是派生它的 Agent 工具使用块的 id;主会话消息和较旧的会话为 None |
parent_agent_id | str | None | 对来自嵌套子智能体的消息,是父子智能体的智能体 id;主会话消息、顶层子智能体消息和较旧的会话为 None(需要 Python Agent SDK 0.2.140 及以上) |
from claude_agent_sdk import list_sessions, get_session_messages
sessions = list_sessions(limit=1)
if sessions:
messages = get_session_messages(sessions[0].session_id)
for msg in messages:
print(f"[{msg.type}] {msg.uuid}")get_session_info()
按 ID 读取单个会话的元数据,而不必扫描整个项目目录。同步,立即返回。
def get_session_info(
session_id: str,
directory: str | None = None,
) -> SDKSessionInfo | None| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
session_id | str | 必填 | 要查找的会话 UUID |
directory | str | None | None | 项目目录路径;省略时搜索所有项目目录 |
返回 SDKSessionInfo,找不到会话时返回 None。已有来自先前运行的会话 ID 时很有用:
from claude_agent_sdk import get_session_info
info = get_session_info("550e8400-e29b-41d4-a716-446655440000")
if info:
print(f"{info.summary} (branch: {info.git_branch}, tag: {info.tag})")rename_session()
通过追加一个自定义标题条目来重命名会话。重复调用是安全的,最近的标题生效。同步。
def rename_session(
session_id: str,
title: str,
directory: str | None = None,
) -> None| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
session_id | str | 必填 | 要重命名的会话 UUID |
title | str | 必填 | 新标题,去掉空白后必须非空 |
directory | str | None | None | 项目目录路径;省略时搜索所有项目目录 |
session_id 不是有效 UUID 或 title 为空时抛 ValueError;找不到会话时抛 FileNotFoundError。
from claude_agent_sdk import list_sessions, rename_session
sessions = list_sessions(directory="/path/to/project", limit=1)
if sessions:
rename_session(sessions[0].session_id, "Refactor auth module")新标题在之后的读取里出现在 SDKSessionInfo.custom_title 中。
tag_session()
给会话打标签;传 None 清除标签。重复调用是安全的,最近的标签生效。同步。
def tag_session(
session_id: str,
tag: str | None,
directory: str | None = None,
) -> None| 参数 | 类型 | 默认 | 说明 |
|---|---|---|---|
session_id | str | 必填 | 要打标签的会话 UUID |
tag | str | None | 必填 | 标签字符串,或 None 以清除;存储前做 Unicode 清理 |
directory | str | None | None | 项目目录路径;省略时搜索所有项目目录 |
session_id 不是有效 UUID 或 tag 清理后为空时抛 ValueError;找不到会话时抛 FileNotFoundError。
from claude_agent_sdk import list_sessions, tag_session
# 给最近的会话打标签
sessions = list_sessions(directory="/path/to/project", limit=1)
if sessions:
tag_session(sessions[0].session_id, "needs-review")
# 之后:找出所有带该标签的会话
for session in list_sessions(directory="/path/to/project"):
if session.tag == "needs-review":
print(session.summary)类
ClaudeSDKClient
在多次交换之间维持一个对话会话。 这相当于 TypeScript SDK 的 query() 函数在内部的工作方式:它创建一个能继续对话的客户端对象。
class ClaudeSDKClient:
def __init__(self, options: ClaudeAgentOptions | None = None, transport: Transport | None = None)
async def connect(self, prompt: str | AsyncIterable[dict] | None = None) -> None
async def query(self, prompt: str | AsyncIterable[dict], session_id: str = "default") -> None
async def receive_messages(self) -> AsyncIterator[Message]
async def receive_response(self) -> AsyncIterator[Message]
async def interrupt(self) -> None
async def set_permission_mode(self, mode: PermissionMode) -> None
async def set_model(self, model: str | None = None) -> None
async def rewind_files(self, user_message_id: str) -> None
async def get_mcp_status(self) -> McpStatusResponse
async def get_context_usage(self) -> ContextUsageResponse
async def reconnect_mcp_server(self, server_name: str) -> None
async def toggle_mcp_server(self, server_name: str, enabled: bool) -> None
async def stop_task(self, task_id: str) -> None
async def get_server_info(self) -> dict[str, Any] | None
async def disconnect(self) -> None| 方法 | 说明 |
|---|---|
__init__(options) | 用可选配置初始化客户端 |
connect(prompt) | 连接到 Claude,可带可选的初始提示或消息流 |
query(prompt, session_id) | 在流式模式下发送新请求 |
receive_messages() | 以异步迭代器接收来自 Claude 的所有消息 |
receive_response() | 接收消息直到并包括一个 ResultMessage |
interrupt() | 发送中断信号(只在流式模式下有效) |
set_permission_mode(mode) | 更改当前会话的权限模式 |
set_model(model) | 更改当前会话的模型;传 None 重置为 Claude Code 的默认模型 |
rewind_files(user_message_id) | 把文件恢复到指定用户消息时的状态,需要 enable_file_checkpointing=True |
get_mcp_status() | 获取所有已配置 MCP 服务器的状态,返回 McpStatusResponse |
get_context_usage() | 获取按类别、skill 和工具拆解的上下文窗口用量,与交互式会话里 /context 显示的数据相同,返回 ContextUsageResponse;为计算该拆解,Claude Code 会发出几个不出现在消息流里的 token 计数 API 请求 |
reconnect_mcp_server(server_name) | 重试连接失败或已断开的 MCP 服务器 |
toggle_mcp_server(server_name, enabled) | 在会话中途启用或禁用 MCP 服务器;禁用 stdio、SSE 或 HTTP 服务器会移除其工具 |
stop_task(task_id) | 停止运行中的后台任务;消息流里随后跟一条状态为 "stopped" 的 TaskNotificationMessage |
get_server_info() | 获取服务器的初始化信息,包括可用命令和输出样式 |
disconnect() | 断开与 Claude 的连接 |
上下文管理器支持
客户端可以用作异步上下文管理器来自动管理连接:
import asyncio
from claude_agent_sdk import ClaudeSDKClient
async def main():
async with ClaudeSDKClient() as client:
await client.query("Hello Claude")
async for message in client.receive_response():
print(message)
asyncio.run(main())重要:迭代消息时,避免用 break 提前退出,这会引起 asyncio 清理问题;要让迭代自然完成,或用标志位跟踪你是否已找到所需内容。
示例:继续对话
import asyncio
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, ResultMessage
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())示例:用 ClaudeSDKClient 流式输入
query() 也接受用户消息字典的异步可迭代对象,所以你可以在发送时组装提示,或包含图片这类内容块。Claude Code 一收到第一条产出的消息就开始响应,不等可迭代对象结束,receive_response() 在结束该响应的 ResultMessage 处停止。要把 Claude 在回答之前应读取的一切放进一条消息(如这个生成器所做的),并把每个 query() 调用与它自己的 receive_response() 配对。
import asyncio
from claude_agent_sdk import ClaudeSDKClient
async def message_stream():
"""在发送时组装提示,并作为一条用户消息产出。"""
readings = {"Temperature": "25°C", "Humidity": "60%"}
data = ", ".join(f"{name}: {value}" for name, value in readings.items())
yield {
"type": "user",
"message": {
"role": "user",
"content": f"Analyze the following sensor data and describe any patterns you see: {data}",
},
}
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())示例:使用中断
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: terminal_reason={message.terminal_reason!r}")
# 被中断的轮次的 terminal_reason 是 "aborted_streaming" 或 "aborted_tools"
# 发送新命令
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())中断之后的缓冲区行为:interrupt() 发送停止信号,但不清空消息缓冲区。被中断任务已经产生的消息(包括它的 ResultMessage)仍留在流里。你必须先用 receive_response() 排空它们,才能读取新查询的响应;如果在 interrupt() 之后立即发送新查询并只调用一次 receive_response(),你收到的是被中断任务的消息,而不是新查询的响应。
示例:高级权限控制
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
from claude_agent_sdk.types import (
PermissionResultAllow,
PermissionResultDeny,
ToolPermissionContext,
)
async def custom_permission_handler(
tool_name: str, input_data: dict, context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
"""工具权限的自定义逻辑。"""
# 阻止写入系统目录
if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"):
return PermissionResultDeny(
message="System directory write not allowed", interrupt=True
)
# 重定向敏感文件操作
if tool_name in ["Write", "Edit"] and "config" in input_data.get("file_path", ""):
safe_path = f"./sandbox/{input_data['file_path']}"
return PermissionResultAllow(
updated_input={**input_data, "file_path": safe_path}
)
# 允许其他一切
return PermissionResultAllow(updated_input=input_data)
async def main():
# 不要同时把被把关的工具列在 allowed_tools 里:允许规则在 can_use_tool 运行之前就批准调用
options = ClaudeAgentOptions(can_use_tool=custom_permission_handler)
async with ClaudeSDKClient(options=options) as client:
await client.query("Update the system config file")
async for message in client.receive_response():
# 会改用沙盒路径
print(message)
asyncio.run(main())