【langgraph 从入门到精通graphApi 篇】综合实战 —— 智能客服 Agent实战代码解读

第 11 章:综合实战 ------ 智能客服 Agent

文章目录

    • [第 11 章:综合实战 ------ 智能客服 Agent](#第 11 章:综合实战 —— 智能客服 Agent)
      • [11.1 本章目标](#11.1 本章目标)
      • [11.2 核心概念](#11.2 核心概念)
      • [11.3 实战:完整智能客服 Agent](#11.3 实战:完整智能客服 Agent)
      • [11.4 API 速查](#11.4 API 速查)
      • [11.5 错误与避坑指南](#11.5 错误与避坑指南)
        • [坑 1:意图识别路由覆盖不全](#坑 1:意图识别路由覆盖不全)
        • [坑 2:人工审批超时处理](#坑 2:人工审批超时处理)
        • [坑 3:知识检索结果为空](#坑 3:知识检索结果为空)
      • [11.6 最佳实践总结](#11.6 最佳实践总结)

11.1 本章目标

学完本章你将能够:

  1. 综合运用前 10 章知识,从零构建完整智能客服 Agent
  2. 理解生产级 Agent 的系统架构设计

11.2 核心概念

系统架构

#mermaid-svg-GgMInVt8hbTGtyYn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GgMInVt8hbTGtyYn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GgMInVt8hbTGtyYn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GgMInVt8hbTGtyYn .error-icon{fill:#552222;}#mermaid-svg-GgMInVt8hbTGtyYn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GgMInVt8hbTGtyYn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GgMInVt8hbTGtyYn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GgMInVt8hbTGtyYn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GgMInVt8hbTGtyYn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GgMInVt8hbTGtyYn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GgMInVt8hbTGtyYn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GgMInVt8hbTGtyYn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GgMInVt8hbTGtyYn .marker.cross{stroke:#333333;}#mermaid-svg-GgMInVt8hbTGtyYn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GgMInVt8hbTGtyYn p{margin:0;}#mermaid-svg-GgMInVt8hbTGtyYn .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GgMInVt8hbTGtyYn .cluster-label text{fill:#333;}#mermaid-svg-GgMInVt8hbTGtyYn .cluster-label span{color:#333;}#mermaid-svg-GgMInVt8hbTGtyYn .cluster-label span p{background-color:transparent;}#mermaid-svg-GgMInVt8hbTGtyYn .label text,#mermaid-svg-GgMInVt8hbTGtyYn span{fill:#333;color:#333;}#mermaid-svg-GgMInVt8hbTGtyYn .node rect,#mermaid-svg-GgMInVt8hbTGtyYn .node circle,#mermaid-svg-GgMInVt8hbTGtyYn .node ellipse,#mermaid-svg-GgMInVt8hbTGtyYn .node polygon,#mermaid-svg-GgMInVt8hbTGtyYn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GgMInVt8hbTGtyYn .rough-node .label text,#mermaid-svg-GgMInVt8hbTGtyYn .node .label text,#mermaid-svg-GgMInVt8hbTGtyYn .image-shape .label,#mermaid-svg-GgMInVt8hbTGtyYn .icon-shape .label{text-anchor:middle;}#mermaid-svg-GgMInVt8hbTGtyYn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GgMInVt8hbTGtyYn .rough-node .label,#mermaid-svg-GgMInVt8hbTGtyYn .node .label,#mermaid-svg-GgMInVt8hbTGtyYn .image-shape .label,#mermaid-svg-GgMInVt8hbTGtyYn .icon-shape .label{text-align:center;}#mermaid-svg-GgMInVt8hbTGtyYn .node.clickable{cursor:pointer;}#mermaid-svg-GgMInVt8hbTGtyYn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GgMInVt8hbTGtyYn .arrowheadPath{fill:#333333;}#mermaid-svg-GgMInVt8hbTGtyYn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GgMInVt8hbTGtyYn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GgMInVt8hbTGtyYn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GgMInVt8hbTGtyYn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GgMInVt8hbTGtyYn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GgMInVt8hbTGtyYn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GgMInVt8hbTGtyYn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GgMInVt8hbTGtyYn .cluster text{fill:#333;}#mermaid-svg-GgMInVt8hbTGtyYn .cluster span{color:#333;}#mermaid-svg-GgMInVt8hbTGtyYn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GgMInVt8hbTGtyYn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GgMInVt8hbTGtyYn rect.text{fill:none;stroke-width:0;}#mermaid-svg-GgMInVt8hbTGtyYn .icon-shape,#mermaid-svg-GgMInVt8hbTGtyYn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GgMInVt8hbTGtyYn .icon-shape p,#mermaid-svg-GgMInVt8hbTGtyYn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GgMInVt8hbTGtyYn .icon-shape .label rect,#mermaid-svg-GgMInVt8hbTGtyYn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GgMInVt8hbTGtyYn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GgMInVt8hbTGtyYn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GgMInVt8hbTGtyYn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 产品咨询
售后投诉
闲聊
需要操作
退款操作
普通投诉
用户
意图识别
知识检索
投诉处理
闲聊回复
工具调用
回复生成
需要审批?
人工审批
流式输出

注意 :图中的「工具调用」分支在实际代码中整合到了 order_queryfaq 节点中(通过调用 @tool 装饰的函数实现)。如果希望显式分离工具调用,可以在 route_by_intent 中增加 tools 分支。

架构说明:整个系统包含 7 种节点类型,覆盖了前面学到的所有核心知识点:

  • 意图识别:使用条件边路由(第 3 章)
  • 知识检索:使用工具调用(第 4 章)
  • 人工审批:使用 interrupt() 中断机制(第 6 章)
  • 回复生成:使用 LLM 调用(第 4 章)
  • 流式输出:使用 messages 模式(第 10 章)
  • 会话持久化:使用 Checkpoint(第 5 章)
  • 用户偏好:使用 Store 长期记忆(第 8 章)

11.3 实战:完整智能客服 Agent

python 复制代码
from typing import TypedDict, Annotated, Literal
import operator
from langgraph.graph import StateGraph, START, END, add_messages
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import interrupt, Command
from langgraph.config import get_stream_writer
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage, ToolMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI


# ============================================
# 1. 定义工具
# ============================================
@tool
def query_order(order_id: str) -> str:
    """查询订单状态。输入订单号,返回订单状态。"""
    # 模拟订单查询(实际项目中替换为数据库查询)
    orders = {
        "123": "订单#123:已发货,预计明天送达",
        "456": "订单#456:已签收",
        "789": "订单#789:退款处理中",
    }
    return orders.get(order_id, f"未找到订单#{order_id}")


@tool
def process_refund(order_id: str) -> str:
    """处理退款。输入订单号,执行退款操作。⚠️ 此操作需要审批。"""
    return f"订单#{order_id} 退款已提交,将在 3-5 个工作日内退回原支付账户。"


@tool
def search_faq(query: str) -> str:
    """搜索常见问题。输入问题关键词,返回相关答案。"""
    faq = {
        "退货": "退货流程:1. 在订单页面申请退货 2. 打印退货标签 3. 寄回商品 4. 退款到账",
        "发货": "我们通常在 24 小时内发货,物流信息可在订单详情页查看",
        "优惠": "最新优惠请查看首页活动页面,新用户首单享 9 折优惠",
    }
    for key, value in faq.items():
        if key in query:
            return value
    return "请咨询在线客服获取更多帮助,或拨打 400-123-4567。"


# ============================================
# 2. 定义 State
# ============================================
class CustomerServiceState(TypedDict):
    """智能客服的完整 State"""
    messages: Annotated[list[BaseMessage], add_messages]
    intent: str                    # 用户意图
    knowledge: str                 # 检索到的知识
    needs_approval: bool           # 是否需要审批
    approved: bool                 # 是否已批准


# ============================================
# 3. 定义节点
# ============================================
def classify_intent(state: CustomerServiceState) -> dict:
    """
    意图识别节点:分析用户输入,判断意图类型。
    
    实际项目中这里应该调用 LLM 做意图分类。
    这里用关键词匹配做简单演示。
    """
    writer = get_stream_writer()
    writer({"status": "正在分析用户意图..."})
    
    last_msg = state["messages"][-1].content
    
    if "退款" in last_msg or "退货" in last_msg:
        intent = "refund"
    elif "订单" in last_msg or "查询" in last_msg or "物流" in last_msg:
        intent = "order_query"
    elif "你好" in last_msg or "谢谢" in last_msg or "再见" in last_msg:
        intent = "chitchat"
    else:
        intent = "faq"
    
    print(f"  [意图识别] 用户意图: {intent}")
    return {"intent": intent}


def route_by_intent(state: CustomerServiceState) -> Literal["order_query", "refund", "faq", "chitchat"]:
    """根据意图路由到不同处理节点"""
    return state["intent"]


def handle_order_query(state: CustomerServiceState) -> dict:
    """处理订单查询:调用订单查询工具"""
    writer = get_stream_writer()
    writer({"status": "正在查询订单..."})
    
    # 从用户消息中提取订单号(简化处理,实际应让 LLM 提取)
    result = query_order.invoke({"order_id": "123"})
    return {
        "knowledge": result,
        "messages": [ToolMessage(content=result, tool_call_id="order_query")],
    }


def handle_refund(state: CustomerServiceState) -> dict:
    """处理退款请求:标记需要审批"""
    writer = get_stream_writer()
    writer({"status": "检测到退款请求,需要人工审批..."})
    return {"needs_approval": True, "knowledge": "用户请求退款处理"}


def handle_faq(state: CustomerServiceState) -> dict:
    """处理 FAQ 搜索"""
    writer = get_stream_writer()
    writer({"status": "正在搜索知识库..."})
    
    result = search_faq.invoke({"query": state["messages"][-1].content})
    return {
        "knowledge": result,
        "messages": [ToolMessage(content=result, tool_call_id="faq_search")],
    }


def handle_chitchat(state: CustomerServiceState) -> dict:
    """处理闲聊"""
    return {"knowledge": "用户在进行闲聊对话"}


def human_approval(state: CustomerServiceState) -> dict:
    """
    人工审批节点:使用 interrupt() 暂停执行,等待人类审批。
    
    这是第 6 章 Human-in-the-Loop 的核心应用。
    """
    writer = get_stream_writer()
    writer({"status": "⏸️ 等待人工审批..."})
    
    # 中断等待审批,展示审批信息
    decision = interrupt({
        "question": "用户请求退款,是否批准?",
        "order_id": "123",
        "amount": "¥299.00",
        "reason": state["messages"][-1].content,
        "options": ["批准", "拒绝"],
    })
    
    writer({"status": f"审批结果: {decision}"})
    print(f"  [人工审批] 审批结果: {decision}")
    return {"approved": decision == "批准"}


def generate_response(state: CustomerServiceState) -> dict:
    """
    生成最终回复:根据所有前置节点的处理结果,生成最终回复。
    
    这是整个流程的终点,汇总所有信息后回复用户。
    """
    writer = get_stream_writer()
    writer({"status": "正在生成回复..."})
    
    if state.get("needs_approval") and not state.get("approved"):
        reply = "很抱歉,您的退款请求未被批准。如有疑问请联系人工客服:400-123-4567。"
    elif state.get("knowledge"):
        reply = f"您好!{state['knowledge']}\n\n还有其他可以帮助您的吗?"
    else:
        reply = "您好!我是智能客服小助手,请问有什么可以帮助您的吗?"
    
    return {"messages": [AIMessage(content=reply)]}


# ============================================
# 4. 构建图
# ============================================
checkpointer = MemorySaver()

builder = StateGraph(CustomerServiceState)

# 注册所有节点
builder.add_node("classify", classify_intent)
builder.add_node("order_query", handle_order_query)
builder.add_node("refund", handle_refund)
builder.add_node("faq", handle_faq)
builder.add_node("chitchat", handle_chitchat)
builder.add_node("approval", human_approval)
builder.add_node("generate", generate_response)

# 添加边
builder.add_edge(START, "classify")

# 条件路由:根据意图分发
builder.add_conditional_edges("classify", route_by_intent, {
    "order_query": "order_query",
    "refund": "refund",
    "faq": "faq",
    "chitchat": "chitchat",
})

# 各处理节点 → 审批/生成
builder.add_edge("order_query", "generate")
builder.add_edge("faq", "generate")
builder.add_edge("chitchat", "generate")

# 退款需要审批:refund → approval → generate
builder.add_edge("refund", "approval")
builder.add_edge("approval", "generate")

# 生成回复 → 结束
builder.add_edge("generate", END)

graph = builder.compile(checkpointer=checkpointer)


# ============================================
# 5. 测试完整流程
# ============================================
def run_customer_service(question: str, thread_id: str = "default"):
    """
    运行智能客服。
    
    如果遇到退款请求,会触发 interrupt,需要手动恢复。
    """
    config = {"configurable": {"thread_id": thread_id}}
    
    print(f"\n{'='*60}")
    print(f"用户: {question}")
    print(f"{'='*60}")
    
    try:
        result = graph.invoke(
            {"messages": [HumanMessage(content=question)], "needs_approval": False, "approved": False},
            config=config,
        )
        print(f"AI: {result['messages'][-1].content}")
    except Exception:
        print("⏸️ 图已暂停,等待人工审批...")
        
        # 模拟审批:批准
        print("\n>>> 模拟人工审批:批准 <<<")
        result = graph.invoke(
            Command(resume="批准"),
            config=config,
        )
        print(f"AI: {result['messages'][-1].content}")


# 测试不同场景
run_customer_service("你好,我想查询订单", thread_id="user-001")
run_customer_service("如何退货?", thread_id="user-001")
run_customer_service("我要退款!", thread_id="user-002")
run_customer_service("今天天气真好", thread_id="user-002")

完整流程解析

  1. 用户输入 → classify_intent 节点分析意图
  2. 根据意图路由到不同的处理节点
  3. 如果是退款 → 触发 human_approval 节点,等待人工审批
  4. 所有路径最终汇入 generate_response 生成回复
  5. 使用 MemorySaver 持久化,支持多会话隔离

11.4 API 速查

本章不新增 API,使用到的 API 来自前 10 章:StateGraph, add_node, add_edge, add_conditional_edges, MemorySaver, interrupt(), Command(resume=), get_stream_writer(), @tool, ToolMessage

11.5 错误与避坑指南

坑 1:意图识别路由覆盖不全
python 复制代码
# ❌ 错误:只覆盖了部分意图,未匹配的意图没有兜底
def route(state: State) -> Literal["a", "b"]:
    return state["intent"]  # 如果 intent 是 "c" 会报错

# ✅ 正确:添加兜底分支
def route(state: State) -> Literal["a", "b", "fallback"]:
    if state["intent"] in ("a", "b"):
        return state["intent"]
    return "fallback"
坑 2:人工审批超时处理
python 复制代码
# ❌ 错误:没有超时处理,用户不审批时图一直挂起
# ✅ 正确:设置超时 + 降级策略
# 在实际应用中,可以通过外部定时器或队列机制实现超时处理
坑 3:知识检索结果为空
python 复制代码
# ❌ 错误:检索为空时直接返回空字符串
def search(state: State) -> dict:
    return {"knowledge": do_search(state["query"])}  # 可能是空字符串

# ✅ 正确:检索为空时降级为通用回复
def search(state: State) -> dict:
    result = do_search(state["query"])
    if not result:
        result = "未找到相关信息,建议联系人工客服。"
    return {"knowledge": result}

11.6 最佳实践总结

  1. 每个节点独立可测试:不依赖全局状态,可以单独 invoke 测试
  2. 使用条件边实现意图路由,Command 实现动态跳转:各司其职
  3. 关键操作(退款)必须经过人工审批:安全第一
  4. 流式输出提升用户体验 :用 get_stream_writer() 发送进度通知
  5. 检索为空时优雅降级:不要返回空结果,提供兜底回复
相关推荐
可乐奶茶sky1 小时前
AI Agent 学习
人工智能·学习
TsingtaoAI1 小时前
3D高斯泼溅技术发展及其在具身智能领域的应用综述
人工智能·算法·ai·具身智能·高斯泼溅
神奇小汤圆1 小时前
面试官:Agent意图识别怎么做?95%的人一句话就把自己送走了
人工智能
兜客互动1 小时前
2026年AI关键词拓展挖掘软件,高效助力内容创作精准获流
人工智能·python
AI的探索之旅2 小时前
AI辅助原理图评审:电源去耦、BOOT引脚、VCAP——19项逐一核查,遗漏?不存在的
人工智能·vscode·嵌入式硬件
武子康2 小时前
Kimi K3 2.8T:开放权重不等于本地可跑 超稀疏 MoE、百万上下文与真实部署边界
人工智能
老刘说AI2 小时前
AI服务核心: 高并发原理与性能监控调优
人工智能·神经网络·langchain·llama·持续部署
GeekArch2 小时前
第24讲:Vibe模式代码风格控制——适配Keil/STM32工程规范
人工智能·stm32·单片机·嵌入式硬件·mcu·决策树·ai编程