第4课:来回聊天 —— ClaudeSDKClient
本课目标
上节课的 query() 是"问完就走"。但做研究平台需要持续对话——你说一句,AI 回一句,你再接着说。这就需要 ClaudeSDKClient。
query() vs ClaudeSDKClient
| query() | ClaudeSDKClient | |
|---|---|---|
| 比喻 | 发短信 | 打电话 |
| 对话轮次 | 通常一轮 | 想聊多久聊多久 |
| 连接方式 | 一次性 | 持续保持 |
| 能力 | 基础问答 | 自定义工具、Hooks、子 Agent |
| 适合场景 | 简单任务 | 复杂交互式应用 |
研究平台用的就是 ClaudeSDKClient。
最简多轮对话
"""
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() 做不到的
做成交互式聊天
更实际一点,做一个终端里的聊天程序:
"""
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)
运行它:
=== 研究助手已启动 ===
输入问题开始聊天,输入 q 退出
你: 新能源汽车市场怎么样?
Claude: 新能源汽车市场正处于高速增长阶段...
你: 哪些公司是龙头?
Claude: 基于我们刚才讨论的新能源汽车市场,主要龙头包括...
你: q
再见!
看到没?Claude 记得你之前问的是新能源汽车,所以第二个问题它知道你说的"龙头"是指新能源汽车领域的公司。这就是会话状态保持的好处。
理解消息流
当你调 client.receive_response() 时,消息是一条一条流过来的,不是一次性给你的。这很重要,因为在研究平台里,你需要根据不同类型的消息做不同的处理。
"""
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 展开。
本课小结
ClaudeSDKClient是"打电话"模式,支持持续对话async with+client.query()+client.receive_response()是三板斧- Claude 会记住上下文,后续问题可以省略前置信息
- 消息是一条一条流过来的,你可以分别处理不同类型
课后练习
- 在聊天循环里加一个功能:输入
clear时重新创建 client,清空对话历史 - 统计每轮对话收到了几条消息、分别是什么类型
- 思考一下:如果你是"组长 Agent",你需要用 ClaudeSDKClient 的哪些能力?