第 1 章:第一次对话

这一章,我们用最少的代码让 Python 和 Claude 聊起来。

前提条件

  1. 已安装 Python 3.10+
  2. 已安装 Claude CLI(命令行输入 claude --version 能看到版本号)
  3. 已安装 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 的意思就是"先去忙别的,有消息了再回来处理"。这在以下场景特别有用:

如果你暂时用不到这些场景,也没关系。只要记住一个固定写法就行:

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 加点配置

光发一句话有时候不够。你可能想:

这些都通过 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 在回答你的问题时需要使用工具 (比如读取文件、运行代码),每次工具调用算一轮。

对于简单的问答,设成 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. 小结

这一章我们学了三件事:

  1. query() 是最简单的接口 — 一个函数、一个 prompt,就能和 Claude 对话
  2. ClaudeAgentOptions 控制行为 — system_prompt 定角色、max_turns 限轮数、model 选模型
  3. 错误要分层捕获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         # 错误处理入门