上节课的 query() 是"问完就走"。但做研究平台需要持续对话——你说一句,AI 回一句,你再接着说。这就需要 ClaudeSDKClient。

第4课:来回聊天 —— ClaudeSDKClient

本课目标

上节课的 query() 是"问完就走"。但做研究平台需要持续对话——你说一句,AI 回一句,你再接着说。这就需要 ClaudeSDKClient


query() vs ClaudeSDKClient

query() ClaudeSDKClient
比喻 发短信 打电话
对话轮次 通常一轮 想聊多久聊多久
连接方式 一次性 持续保持
能力 基础问答 自定义工具、Hooks、子 Agent
适合场景 简单任务 复杂交互式应用

研究平台用的就是 ClaudeSDKClient。


最简多轮对话

code
"""
04_basic_client.py
用 ClaudeSDKClient 进行多轮对话
"""
import anyio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock


async def main():
    options = ClaudeAgentOptions(
        system_prompt="你是一个友好的助手,回答简洁明了。",
        max_turns=1,
    )

    # async with 确保用完后自动关闭连接
    async with ClaudeSDKClient(options=options) as client:

        # 第一轮对话
        await client.query("我想学做菜,从哪里开始?")
        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")

        print("\n--- 第二轮 ---\n")

        # 第二轮对话(Claude 记得上一轮说了什么)
        await client.query("那推荐一道简单的家常菜吧")
        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")


anyio.run(main)

关键点: - async with ClaudeSDKClient(options) as client — 打开连接 - await client.query("...") — 发送消息 - async for msg in client.receive_response() — 接收回复 - 第二轮对话时,Claude 记得之前的上下文,这是 query() 做不到的


做成交互式聊天

更实际一点,做一个终端里的聊天程序:

code
"""
04_chat_loop.py
交互式终端聊天
"""
import anyio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock


async def main():
    options = ClaudeAgentOptions(
        system_prompt="你是一个行业研究助手,帮用户分析各种行业和市场。",
        max_turns=1,
    )

    async with ClaudeSDKClient(options=options) as client:
        print("=== 研究助手已启动 ===")
        print("输入问题开始聊天,输入 q 退出\n")

        while True:
            user_input = input("你: ").strip()
            if user_input.lower() in ["q", "quit", "exit"]:
                print("再见!")
                break
            if not user_input:
                continue

            await client.query(user_input)
            async for msg in client.receive_response():
                if isinstance(msg, AssistantMessage):
                    for block in msg.content:
                        if isinstance(block, TextBlock):
                            print(f"\nClaude: {block.text}\n")


anyio.run(main)

运行它:

code
=== 研究助手已启动 ===
输入问题开始聊天,输入 q 退出

你: 新能源汽车市场怎么样?
Claude: 新能源汽车市场正处于高速增长阶段...

你: 哪些公司是龙头?
Claude: 基于我们刚才讨论的新能源汽车市场,主要龙头包括...

你: q
再见!

看到没?Claude 记得你之前问的是新能源汽车,所以第二个问题它知道你说的"龙头"是指新能源汽车领域的公司。这就是会话状态保持的好处。


理解消息流

当你调 client.receive_response() 时,消息是一条一条流过来的,不是一次性给你的。这很重要,因为在研究平台里,你需要根据不同类型的消息做不同的处理。

code
"""
04_message_flow.py
观察消息流
"""
import anyio
from claude_agent_sdk import (
    ClaudeSDKClient, ClaudeAgentOptions,
    AssistantMessage, UserMessage, SystemMessage, ResultMessage,
    TextBlock, ToolUseBlock
)


async def main():
    options = ClaudeAgentOptions(max_turns=1)

    async with ClaudeSDKClient(options=options) as client:
        await client.query("你好呀")

        async for msg in client.receive_response():
            # 打印消息类型,看看到底收到了什么
            msg_type = type(msg).__name__
            print(f"[收到消息] 类型: {msg_type}")

            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    block_type = type(block).__name__
                    print(f"  └─ 内容块: {block_type}")
                    if isinstance(block, TextBlock):
                        print(f"     文字: {block.text[:50]}...")

            elif isinstance(msg, ResultMessage):
                print(f"  └─ 会话ID: {msg.session_id}")


anyio.run(main)

运行后你会看到类似这样的输出:

graph TD A["收到消息 类型: AssistantMessage"] --> B["内容块: TextBlock"] B --> C["文字: 你好! 有什么我可以帮你的吗?..."] D["收到消息 类型: ResultMessage"] --> E["会话ID: abc123..."] A --> D

为什么研究平台选 ClaudeSDKClient?

因为研究平台需要以下能力,而这些只有 ClaudeSDKClient 才支持:

能力 query() ClaudeSDKClient
多轮对话
自定义工具(MCP)
Hooks 钩子
子 Agent 协作
中断控制

所以接下来的课程都围绕 ClaudeSDKClient 展开。


本课小结

  1. ClaudeSDKClient 是"打电话"模式,支持持续对话
  2. async with + client.query() + client.receive_response() 是三板斧
  3. Claude 会记住上下文,后续问题可以省略前置信息
  4. 消息是一条一条流过来的,你可以分别处理不同类型

课后练习

  1. 在聊天循环里加一个功能:输入 clear 时重新创建 client,清空对话历史
  2. 统计每轮对话收到了几条消息、分别是什么类型
  3. 思考一下:如果你是"组长 Agent",你需要用 ClaudeSDKClient 的哪些能力?

沿着当前专题继续,或返回课程目录重新整理阅读顺序。

返回课程目录