第 1 章:第一次对话
这一章,我们用最少的代码让 Python 和 Claude 聊起来。
前提条件
- 已安装 Python 3.10+
- 已安装 Claude CLI(命令行输入
claude --version能看到版本号)- 已安装 SDK:
uv add claude-agent-sdk
1. query() — 五行代码搞定
先别管原理,直接上代码:
import anyio
from claude_agent_sdk import AssistantMessage, TextBlock, query
async def main():
async for message in query(prompt="用一句话解释什么是 Python?"):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
anyio.run(main)
运行它,你会看到 Claude 给出一句话解释。就这么简单。
现在我们逐行拆解:
| 代码 | 干了什么 |
|---|---|
from claude_agent_sdk import query |
导入 query 函数——这是 SDK 最核心的入口 |
async for message in query(prompt="...") |
把问题发给 Claude,然后逐条接收回复 |
isinstance(message, AssistantMessage) |
只取 Claude 的回复,忽略其他系统消息 |
isinstance(block, TextBlock) |
从回复里提取文字部分 |
anyio.run(main) |
启动异步事件循环,运行 main() |
你可能注意到了:一条回复里可以有多个 "内容块"(content block)。文字回复是 TextBlock,
后续章节我们还会遇到 ToolUseBlock(工具调用)和 ThinkingBlock(思考过程)。
现在只关心 TextBlock 就够了。
完整可运行代码见
examples/hello_claude.py
2. 为什么是 async?
你可能在想:为什么不能写成普通的 for 循环?为什么非要 async for?
原因很直白:Claude 的回复需要时间。
当你发一个问题给 Claude,它不会瞬间把答案全给你。它需要"想一想",然后一点一点地生成回复。 在这段等待时间里,如果用普通的同步代码,你的程序就会"卡住"——什么都干不了,只能干等着。
async 的意思就是"先去忙别的,有消息了再回来处理"。这在以下场景特别有用:
- 你的程序同时要处理多个用户的请求
- 你在 Web 服务器里调用 Claude
- 你想同时发多个问题给 Claude
如果你暂时用不到这些场景,也没关系。只要记住一个固定写法就行:
import anyio
async def main():
# 你的代码写在这里
...
anyio.run(main)
anyio 还是 asyncio?
SDK 底层用的是 anyio,所以推荐用 anyio.run() 来启动。
但如果你更熟悉标准库的 asyncio,用 asyncio.run() 也完全没问题:
import asyncio
async def main():
# 一样的代码
...
asyncio.run(main())
两者的区别:anyio.run(main) 传函数名,asyncio.run(main()) 传函数调用结果。
功能上没有差别,选你顺手的就行。
3. 给 Claude 加点配置
光发一句话有时候不够。你可能想:
- 让 Claude 扮演特定角色(比如"你是一个 Python 导师")
- 限制对话轮数,防止跑飞
- 指定用哪个模型
这些都通过 ClaudeAgentOptions 来配置:
from claude_agent_sdk import ClaudeAgentOptions, query
options = ClaudeAgentOptions(
system_prompt="你是一个耐心的 Python 导师,回答要简洁,多用例子。",
max_turns=1,
model="sonnet",
)
async for message in query(prompt="什么是列表推导式?", options=options):
...
常用配置一览
| 参数 | 类型 | 说明 |
|---|---|---|
system_prompt |
str |
系统提示词,告诉 Claude 应该扮演什么角色、用什么风格回答 |
max_turns |
int |
最大对话轮数。设成 1 表示 Claude 只回答一次就停下来 |
model |
str |
模型名称,比如 "sonnet"、"opus"、"haiku" |
max_budget_usd |
float |
预算上限(美元),超了就自动停止,防止意外烧钱 |
allowed_tools |
list[str] |
允许 Claude 使用的工具列表,后续章节会详细讲 |
关于 system_prompt
system_prompt 是你给 Claude 的"角色说明书"。它不会显示在对话里,但会深刻影响 Claude 的回答方式。
比较一下有和没有 system_prompt 的区别:
# 没有 system_prompt
query(prompt="解释一下递归")
# Claude 可能给你一段正式的、百科全书式的解释
# 有 system_prompt
query(
prompt="解释一下递归",
options=ClaudeAgentOptions(
system_prompt="你是一个喜欢用生活比喻来解释技术概念的程序员"
),
)
# Claude 可能会说"递归就像俄罗斯套娃..."
关于 max_turns
max_turns 控制 Claude 可以执行多少轮。如果 Claude 在回答你的问题时需要使用工具
(比如读取文件、运行代码),每次工具调用算一轮。
max_turns=1:Claude 只回答一次,不使用任何工具max_turns=3:Claude 最多可以用 2 次工具 + 最终回答- 不设置:Claude 会一直运行直到它认为任务完成
对于简单的问答,设成 1 就够了。
读取 ResultMessage 了解开销
每次查询结束后,SDK 会返回一个 ResultMessage,里面有这次查询的统计信息:
from claude_agent_sdk import AssistantMessage, ResultMessage, TextBlock, query
async for message in query(prompt="你好", options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
if isinstance(message, ResultMessage):
print(f"耗时: {message.duration_ms}ms")
print(f"对话轮数: {message.num_turns}")
print(f"花费: ${message.total_cost_usd}")
print(f"会话 ID: {message.session_id}")
ResultMessage 是整个消息流的最后一条消息,拿到它就意味着查询结束了。
完整可运行代码见
examples/ask_with_options.py
4. 错误处理入门
代码写好了,总要考虑"万一出错了怎么办"。SDK 里最常见的两种错误:
CLINotFoundError:Claude CLI 没装
如果用户的机器上没装 Claude CLI,SDK 在启动时就会抛出这个错误:
from claude_agent_sdk import CLINotFoundError, query
try:
async for message in query(prompt="你好"):
...
except CLINotFoundError:
print("错误:没有找到 Claude CLI!")
print("请先安装:https://docs.anthropic.com/en/docs/claude-code/overview")
这个错误是 CLIConnectionError 的子类,而 CLIConnectionError 又是 ClaudeSDKError 的子类。
你可以按需选择捕获的精细程度。
ProcessError:进程出错
如果 CLI 进程在运行过程中崩溃了,SDK 会抛出 ProcessError。
它有两个有用的属性:
from claude_agent_sdk import ProcessError, query
try:
async for message in query(prompt="你好"):
...
except ProcessError as e:
print(f"进程出错: {e}")
print(f"退出码: {e.exit_code}") # 进程退出码,比如 1
print(f"错误输出: {e.stderr}") # 标准错误输出的内容
exit_code 告诉你进程是怎么退出的,stderr 里通常有更详细的错误信息。
推荐的错误处理模板
实际项目中,建议这样写:
from claude_agent_sdk import (
CLINotFoundError,
ClaudeSDKError,
ProcessError,
query,
)
try:
async for message in query(prompt="你好"):
# 处理消息...
pass
except CLINotFoundError:
print("请先安装 Claude CLI")
except ProcessError as e:
print(f"CLI 进程出错 (退出码 {e.exit_code}): {e.stderr}")
except ClaudeSDKError as e:
# 兜底:捕获所有其他 SDK 错误
print(f"SDK 错误: {e}")
从最具体的错误开始捕获,最后用 ClaudeSDKError 兜底。这样既能针对性处理已知问题,
又不会漏掉意料之外的错误。
完整可运行代码见
examples/error_handling_basics.py
5. 小结
这一章我们学了三件事:
query()是最简单的接口 — 一个函数、一个 prompt,就能和 Claude 对话ClaudeAgentOptions控制行为 — system_prompt 定角色、max_turns 限轮数、model 选模型- 错误要分层捕获 —
CLINotFoundError>ProcessError>ClaudeSDKError
query() 适合"一问一答"的场景:你问一个问题,Claude 回答,结束。
它是无状态的——每次调用都是独立的,不会记住上一次的对话。
下一章,我们会仔细看看 query() 返回的消息到底长什么样——不光是文字,
还有工具调用、思考过程等各种内容块。
本章文件清单
01-第一次对话/
README.md # 你正在读的这个文件
examples/
hello_claude.py # 最简示例:五行代码和 Claude 说话
ask_with_options.py # 带配置的查询:system_prompt + max_turns
error_handling_basics.py # 错误处理入门