到目前为止我们一直用 query()。它的特点是一进一出,像发邮件:你发一个问题,AI 回一个答案,完事。

第 5 课:多轮对话客服


query() 和 Client 的区别

到目前为止我们一直用 query()。它的特点是一进一出,像发邮件:你发一个问题,AI 回一个答案,完事。

但客服对话不是这样的:

code
客户: 我的订单有问题
AI: 请问您的订单号是多少?    ← 需要追问
客户: ORD-001
AI: [查订单] 您好,这个订单...  ← 需要记住上文
客户: 那我要退货
AI: [查退货政策] 根据政策...    ← 还是要记住之前的对话

这种场景需要 ClaudeSDKClient——它能维持一个持续的对话会话,有上下文记忆。

code
query():
  用户 ──消息──→ AI ──回复──→ 结束
  (每次都是全新的,不记得之前说了什么)

ClaudeSDKClient:
  用户 ←──→ AI ←──→ 用户 ←──→ AI ...
  (一个持续的会话,记得所有对话历史)

基础多轮对话

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

SYSTEM_PROMPT = """你是智能客服助手"小智"。规则:
1. 用中文、友好地和客户交流
2. 如果信息不够,主动追问
3. 每次回复不超过 3 句话
4. 遇到解决不了的问题说"我帮您转人工客服"
"""

async def main():
    options = ClaudeAgentOptions(
        system_prompt=SYSTEM_PROMPT,
    )

    print("🤖 客服小智上线(输入 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}")
            print()

asyncio.run(main)

试试这个对话:

code
客户: 我想退款
🤖 小智: 请问您的订单号是多少?

客户: ORD-001
🤖 小智: 请问您退款的原因是什么?

客户: 产品质量有问题
🤖 小智: 很抱歉给您带来不好的体验。我这边帮您...

AI 记得之前说了什么——这就是 Client 的多轮对话能力。

带工具的多轮对话

结合第 4 课的工具,做一个有真实数据的客服:

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

# === 模拟数据 ===
ORDERS = {
    "ORD-001": {"id": "ORD-001", "customer": "张三", "product": "基础版年付",
                "amount": 1200, "status": "active", "date": "2025-01-15"},
    "ORD-002": {"id": "ORD-002", "customer": "李四", "product": "企业版月付",
                "amount": 500, "status": "active", "date": "2025-03-01"},
}

TICKETS = {}  # 新创建的工单存这里
ticket_counter = 100

# === 工具 ===
@tool("get_order", "查询订单详情", {"order_id": {"type": "string", "description": "订单号"}})
async def get_order(order_id: str) -> str:
    order = ORDERS.get(order_id.upper())
    if not order:
        return f"未找到订单 {order_id}。请核实订单号格式(如 ORD-001)。"
    return json.dumps(order, ensure_ascii=False, indent=2)

@tool("create_ticket", "创建新的客服工单",
      {"subject": {"type": "string", "description": "工单标题"},
       "description": {"type": "string", "description": "问题描述"},
       "priority": {"type": "string", "description": "优先级: high/medium/low"}})
async def create_ticket(subject: str, description: str, priority: str = "medium") -> str:
    global ticket_counter
    ticket_counter += 1
    ticket_id = f"TK-{ticket_counter}"
    TICKETS[ticket_id] = {
        "id": ticket_id, "subject": subject,
        "description": description, "priority": priority,
        "status": "open",
    }
    return f"✅ 工单已创建: {ticket_id} | 标题: {subject} | 优先级: {priority}"

@tool("check_refund_eligibility", "检查订单是否符合退款条件",
      {"order_id": {"type": "string", "description": "订单号"}})
async def check_refund_eligibility(order_id: str) -> str:
    order = ORDERS.get(order_id.upper())
    if not order:
        return f"未找到订单 {order_id}"
    # 简单模拟退款规则
    from datetime import datetime, timedelta
    order_date = datetime.strptime(order["date"], "%Y-%m-%d")
    days = (datetime.now() - order_date).days
    if days <= 7:
        return f"✅ 订单 {order_id} 在 7 天内,可全额退款 ¥{order['amount']}"
    elif days <= 30:
        refund = order["amount"] * (30 - days) / 30
        return f"⚠️ 订单 {order_id} 超过 7 天,可部分退款约 ¥{refund:.0f}"
    else:
        return f"❌ 订单 {order_id} 超过 30 天,不符合退款条件"

# === MCP 服务器 ===
support_server = create_sdk_mcp_server(
    name="support",
    version="1.0.0",
    tools=[get_order, create_ticket, check_refund_eligibility],
)

# === 主程序 ===
SYSTEM_PROMPT = """你是客服助手"小智"。你有以下工具:

1. get_order - 查询订单信息
2. create_ticket - 为客户创建工单
3. check_refund_eligibility - 检查退款资格

工作规则:
- 先了解客户问题,信息不够就追问
- 需要查数据时主动使用工具
- 回复要友好、专业、简洁
- 退款/退货必须先检查资格
- 解决不了的转人工
- 用中文回复"""


async def main():
    options = ClaudeAgentOptions(
        system_prompt=SYSTEM_PROMPT,
        mcp_servers={"support": support_server},
        allowed_tools=[
            "mcp__support__get_order",
            "mcp__support__create_ticket",
            "mcp__support__check_refund_eligibility",
        ],
    )

    print("=" * 40)
    print("🤖 客服小智 v2.0(带工具版)")
    print("=" * 40)
    print("输入 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
客户: 我想退款
🤖 小智: 请问您的订单号是多少?

客户: ORD-001
  🔧 [正在 get_order...]
  🔧 [正在 check_refund_eligibility...]
🤖 小智: 您的订单 ORD-001 是基础版年付 ¥1200。
         由于已超过 7 天,可以部分退款约 ¥XXX。需要帮您提交退款申请吗?

客户: 好的,帮我提交
  🔧 [正在 create_ticket...]
🤖 小智: 已为您创建退款工单 TK-101,我们会在 1-3 个工作日内处理。

AI 自动完成了:查订单 → 检查退款资格 → 创建工单,全程多轮对话。

对话管理进阶

中断处理

客户可能突然改变话题,用 interrupt() 处理:

code
async with ClaudeSDKClient(options=options) as client:
    await client.query("帮我查一下所有订单的情况")  # 可能很久

    # 如果 AI 处理太久,可以中断
    async def watch_response():
        async for msg in client.receive_response():
            if isinstance(msg, AssistantMessage):
                for block in msg.content:
                    if isinstance(block, TextBlock):
                        print(f"🤖 {block.text}")

    task = asyncio.create_task(watch_response())

    # 超时中断
    await asyncio.sleep(10)
    print("\n⏱️ 处理时间过长,中断...")
    await client.interrupt()
    await task

    # 发新指令
    await client.query("算了,只帮我查 ORD-001 就行")
    async for msg in client.receive_response():
        # 处理新回复...
        pass

动态调整模型

对话进行中可以切换模型——简单问题用便宜模型,复杂问题用强模型:

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)

动态调整权限

某些操作需要更高权限:

code
async with ClaudeSDKClient(options=options) as client:
    # 默认模式:需要确认
    await client.query("查一下 ORD-001")
    async for msg in client.receive_response():
        display(msg)

    # 客户确认后,放开权限让 AI 直接操作
    await client.set_permission_mode("acceptEdits")
    await client.query("帮我提交退款申请")
    async for msg in client.receive_response():
        display(msg)

消息类型详解

receive_response() 中你会收到不同类型的消息:

code
async for msg in client.receive_response():
    if isinstance(msg, AssistantMessage):
        # AI 的回复,可能包含多种内容
        for block in msg.content:
            if isinstance(block, TextBlock):
                # 文字回复 → 展示给客户
                print(block.text)
            elif isinstance(block, ToolUseBlock):
                # AI 调用了工具 → 可以显示"正在处理..."
                print(f"正在 {block.name}...")

    elif isinstance(msg, UserMessage):
        # 工具调用的结果(SDK 自动处理)
        for block in msg.content:
            if isinstance(block, ToolResultBlock):
                # 工具返回的数据
                pass

    elif isinstance(msg, ResultMessage):
        # 对话结束
        print(f"费用: ${msg.total_cost_usd:.4f}")
        print(f"耗时: {msg.duration_ms}ms")

本课小结

  • ClaudeSDKClient 提供多轮对话能力,有上下文记忆
  • 结合 MCP 工具,AI 能在对话中随时查询数据和执行操作
  • interrupt() 处理超时和话题切换
  • set_model() 动态切换模型平衡成本
  • set_permission_mode() 动态调整权限

课后练习

  1. 给客服加一个"满意度评价"功能:对话结束前问客户打几分
  2. 实现对话历史保存:每轮对话记录到文件中
  3. 加一个超时机制:如果 AI 处理超过 15 秒就自动中断并道歉

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

返回课程目录