query() 是一次性的,AI 不记得之前说过什么。但私人助理需要记住上下文:

第 5 课:多轮对话 Client


为什么需要多轮对话

query() 是一次性的,AI 不记得之前说过什么。但私人助理需要记住上下文:

code
你: 加个任务:写周报
AI: ✅ 已添加

你: 把它改成高优先级       ← AI 需要知道"它"是什么
AI: ✅ 已将"写周报"改为高优先级

你: 再加一个:整理文档
AI: ✅ 已添加

你: 看看现在有什么任务
AI: 📋 你有 2 个任务...     ← AI 记得之前加了什么

ClaudeSDKClient 就是为这种场景设计的。

基础多轮对话

code
import asyncio
from claude_agent_sdk import (
    ClaudeSDKClient, ClaudeAgentOptions,
    AssistantMessage, TextBlock, ToolUseBlock,
    create_sdk_mcp_server,
)

# 假设 tools/ 里的工具已定义
# from tools.todo import add_todo, list_todos, complete_todo, delete_todo
# from tools.notes import add_note, search_notes, list_recent_notes

SYSTEM_PROMPT = """你是私人助理 MiniClaw。

工具:
- 待办: add_todo, list_todos, complete_todo, delete_todo
- 笔记: add_note, search_notes, list_recent_notes

规则:
- 理解上下文,记住之前的对话
- 用中文,简洁友好
- 不确定就追问"""

async def main():
    options = ClaudeAgentOptions(
        system_prompt=SYSTEM_PROMPT,
        mcp_servers={"productivity": productivity_tools},
        allowed_tools=[...],  # 工具列表
    )

    print("🤖 MiniClaw 上线!(quit 退出)\n")

    async with ClaudeSDKClient(options=options) as client:
        while True:
            user_input = input("你: ").strip()
            if user_input.lower() in ['quit', 'exit', 'q']:
                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"🤖: {block.text}")
                        elif isinstance(block, ToolUseBlock):
                            print(f"  🔧 {block.name}")
            print()

asyncio.run(main)

对话示例

code
你: 加个任务:写周报
  🔧 add_todo
🤖: ✅ 已添加任务 #1「写周报」

你: 优先级调高
🤖: 你是说把刚才的「写周报」优先级调高吗?
    (AI 记得上下文,但需要确认——因为没有 edit_todo 工具)

你: 记一下:今天学了 MCP 自定义工具的用法
  🔧 add_note
🤖: 📝 已保存笔记,标签: #学习 #MCP

你: 看看今天记了什么
  🔧 list_recent_notes
🤖: 📓 最近 1 条笔记:
    [2025-03-15 14:30] 今天学了 MCP 自定义工具的用法 #学习 #MCP

中断处理

有时候 AI 处理太久,需要打断:

code
import asyncio

async def main():
    async with ClaudeSDKClient(options=options) as client:
        # 发送一个可能很久的任务
        await client.query("列出 /home 目录下所有文件的详细信息")

        # 后台接收消息
        async def receive():
            async for msg in client.receive_response():
                if isinstance(msg, AssistantMessage):
                    for block in msg.content:
                        if isinstance(block, TextBlock):
                            print(f"🤖: {block.text[:100]}...")

        task = asyncio.create_task(receive())

        # 5 秒后中断
        await asyncio.sleep(5)
        print("\n⏱️ 太久了,中断!")
        await client.interrupt()
        await task

        # 发新指令
        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"🤖: {block.text}")

动态调整

对话过程中可以改设置:

code
async with ClaudeSDKClient(options=options) as client:
    # 简单问答用 Haiku(快、便宜)
    await client.set_model("claude-haiku-4-5")
    await client.query("今天周几?")
    async for msg in client.receive_response():
        display(msg)

    # 需要深度分析时切 Sonnet
    await client.set_model("claude-sonnet-4-5")
    await client.query("帮我分析一下这周的工作效率,给出改进建议")
    async for msg in client.receive_response():
        display(msg)

    # 需要 AI 直接操作文件时放开权限
    await client.set_permission_mode("acceptEdits")
    await client.query("把笔记导出到 notes.md 文件")
    async for msg in client.receive_response():
        display(msg)

消息类型处理

code
from claude_agent_sdk import (
    AssistantMessage, UserMessage, SystemMessage, ResultMessage,
    TextBlock, ToolUseBlock, ToolResultBlock, ThinkingBlock,
)

async for msg in client.receive_response():
    if isinstance(msg, AssistantMessage):
        for block in msg.content:
            if isinstance(block, TextBlock):
                # AI 的文字回复 → 显示
                print(f"🤖: {block.text}")
            elif isinstance(block, ToolUseBlock):
                # AI 调了工具 → 显示进度
                print(f"  🔧 正在 {block.name}...")
            elif isinstance(block, ThinkingBlock):
                # AI 在思考 → 可以选择显示或隐藏
                pass

    elif isinstance(msg, UserMessage):
        for block in msg.content:
            if isinstance(block, ToolResultBlock):
                # 工具返回了结果 → 通常不需要显示
                pass

    elif isinstance(msg, ResultMessage):
        # 对话轮次结束
        if msg.total_cost_usd:
            print(f"  💰 ${msg.total_cost_usd:.4f}")

错误处理

code
from claude_agent_sdk import CLIConnectionError, ProcessError

async def safe_chat():
    try:
        async with ClaudeSDKClient(options=options) as client:
            await client.query("你好")
            async for msg in client.receive_response():
                display(msg)

    except CLIConnectionError as e:
        print(f"❌ 连接失败: {e}")
        print("请检查: 1) claude-code 是否安装  2) API Key 是否设置")

    except ProcessError as e:
        print(f"❌ 执行出错: {e}")

    except asyncio.TimeoutError:
        print("⏱️ 超时了,请重试")

也可以手动管理连接:

code
client = ClaudeSDKClient(options=options)

try:
    await client.connect()
    await client.query("你好")

    async with asyncio.timeout(30):
        async for msg in client.receive_response():
            display(msg)

except asyncio.TimeoutError:
    print("超时")
finally:
    await client.disconnect()

实用模式:命令前缀

给助理加一些快捷命令:

code
async def process_input(client, user_input: str):
    """处理用户输入,支持快捷命令"""

    # 快捷命令
    shortcuts = {
        "/todo": "列出所有待办任务",
        "/done": "列出已完成的任务",
        "/notes": "列出最近的笔记",
        "/today": "总结今天的任务和笔记",
        "/help": None,  # 特殊处理
    }

    if user_input.startswith("/"):
        cmd = user_input.split()[0].lower()

        if cmd == "/help":
            print("快捷命令:")
            print("  /todo  - 看待办")
            print("  /done  - 看已完成")
            print("  /notes - 看笔记")
            print("  /today - 今日总结")
            return

        if cmd in shortcuts:
            user_input = shortcuts[cmd]

    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"🤖: {block.text}")
                elif isinstance(block, ToolUseBlock):
                    print(f"  🔧 {block.name}")

本课小结

  • ClaudeSDKClient 提供多轮对话,有上下文记忆
  • async with 自动管理连接生命周期
  • interrupt() 中断长时间运行的任务
  • set_model() / set_permission_mode() 动态调整
  • 快捷命令让交互更高效

课后练习

  1. 实现 /export 命令,导出所有待办和笔记为文本
  2. 加一个"连续对话计数器",显示当前第几轮对话
  3. 实现超时自动中断:15 秒没收到回复就打断

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

返回课程目录