用 LangGraph 构建智能客服系统(零基础自学agent日记)

6 阅读8分钟

用 LangGraph 构建智能客服系统:我的状态图编程学习笔记

💡 这是一篇学习经验分享,记录我在 smart-customer-service 项目中学习 LangGraph、Memory、MCP、多 Agent 协作的过程和收获。

前言

在掌握了 LangChain 基础组件和 RAG 之后,我开始探索更复杂的场景:如何让 AI 处理多步骤、有分支、需要记忆的复杂任务?

答案是 LangGraph——LangChain 团队推出的状态图编排框架。通过 smart-customer-service 项目,我系统学习了 LangGraph 的核心概念,并延伸学习了 MCP 和多 Agent 协作。

这篇文章重点分享我的学习路径和知识总结。

一、从 Chain 到 Agent 到 LangGraph

1.1 三种范式的演进

┌─────────────────────────────────────────────────────────┐
│                  三种编程范式对比                         │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Chain (链)                                             │
│  ┌───┐   ┌───┐   ┌───┐   ┌───┐                        │
│  │ A │ → │ B │ → │ C │ → │ D │  固定流程               │
│  └───┘   └───┘   └───┘   └───┘                        │
│                                                         │
│  Agent (代理)                                           │
│           ┌───┐                                         │
│      ┌──→ │ A │ ──┐                                    │
│  ┌───┐    └───┘   │   ┌───┐                            │
│  │ LLM│ ←─────────┘ → │回答│  动态决策                 │
│  └───┘    ┌───┐   │                                    │
│      └──→ │ B │ ──┘                                    │
│           └───┘                                         │
│                                                         │
│  LangGraph (状态图)                                     │
│           ┌───┐                                         │
│      ┌──→ │ A │ ──┬──→ ┌───┐                           │
│  ┌───┐    └───┘   │    │ C │ ──→ ┌───┐                 │
│  │开始│ ←─────────┤    └───┘     │结束│                 │
│  └───┘    ┌───┐   │              └───┘                  │
│      └──→ │ B │ ──┘                                     │
│           └───┘     条件路由 + 循环 + 状态管理           │
│                                                         │
└─────────────────────────────────────────────────────────┘

我的理解

范式特点适用场景
Chain线性流程,简单直接简单的 RAG、固定流程任务
Agent动态决策,自主选择工具需要调用工具的问答任务
LangGraph状态图,支持循环/分支/记忆复杂业务流程、多步骤任务

1.2 为什么需要 LangGraph?

Agent 的局限

  • 无法处理复杂的条件分支
  • 难以实现循环和重试
  • 状态管理不方便
  • 无法持久化对话状态

LangGraph 的解决方案

  • 状态图描述工作流
  • 条件边实现分支
  • 循环边实现重试
  • State统一管理状态

二、LangGraph 核心概念

2.1 四大核心概念

┌─────────────────────────────────────────────────────────┐
│                LangGraph 四大核心概念                     │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. State (状态)                                        │
│     └─ 全局数据容器,在节点间传递                        │
│                                                         │
│  2. Node (节点)                                         │
│     └─ 处理单元,执行具体逻辑                           │
│                                                         │
│  3. Edge (边)                                           │
│     └─ 连接节点,定义执行流向                           │
│                                                         │
│  4. Conditional Edge (条件边)                           │
│     └─ 根据状态决定走向,实现分支                       │
│                                                         │
└─────────────────────────────────────────────────────────┘

2.2 State:统一的状态管理

from typing import TypedDict

class State(TypedDict):
    message: str          # 用户输入
    emotion: str          # 情感分析结果
    need_human: bool      # 是否需要转人工
    intention: str        # 意图分类
    info: str             # 检索到的信息
    result: str           # 最终答案
    rounds: int           # 生成轮次
    quality: bool         # 质量检查结果
    memory: list          # 对话历史

学习心得

  • State 是整个图的"共享内存"
  • 所有节点通过读写 State 通信
  • TypedDict 让状态结构清晰可预测

2.3 Node:处理节点

def analyze_emotion(state: State) -> dict:
    """节点函数:接收 State,返回更新的 State"""
    message = state["message"]
    # ... 处理逻辑 ...
    return {"emotion": emotion, "need_human": need_human}

关键点

  • 节点函数接收完整 State
  • 返回一个字典,只包含需要更新的字段
  • 框架会自动合并到全局 State

2.4 Edge 和 Conditional Edge

# 普通边:固定连接
graph.add_edge("node_a", "node_b")

# 条件边:根据状态决定
def route_after_emotion(state: State) -> str:
    if state["need_human"]:
        return "to_human"      # 返回的是边的标签
    else:
        return "to_intent"

graph.add_conditional_edges(
    "analyze_emotion",                    # 源节点
    route_after_emotion,                   # 路由函数
    {"to_human": "human_intervention",     # 标签 → 目标节点
     "to_intent": "intent_recognition"},
)

学习心得

  • 路由函数返回的是边的标签,不是节点名
  • 标签和节点名可以不同,增加了灵活性
  • 条件边是实现分支逻辑的关键

三、智能客服系统的状态图设计

3.1 工作流程

                    ┌─────────────────┐
                    │     START       │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  情感分析        │
                    │  analyze_emotion│
                    └────────┬────────┘
                             │
              ┌──────────────┴──────────────┐
              │                             │
              ▼                             ▼
     ┌─────────────────┐          ┌─────────────────┐
     │  转人工客服      │          │  意图识别        │
     │  human_         │          │  intent_        │
     │  intervention   │          │  recognition    │
     └────────┬────────┘          └────────┬────────┘
              │                             │
              ▼                             │
     ┌─────────────────┐                    │
     │      END        │                    │
     └─────────────────┘                    │
                                            │
                    ┌───────────────────────┼───────────────────────┐
                    │                       │                       │
                    ▼                       ▼                       ▼
           ┌─────────────┐        ┌─────────────┐        ┌─────────────┐
           │ 知识库检索    │        │   计算      │        │   闲聊      │
           │ knowledge_   │        │ calculator  │        │ direct_     │
           │ retrieval    │        │             │        │ answer      │
           └──────┬──────┘        └──────┬──────┘        └──────┬──────┘
                  │                       │                       │
                  └───────────────────────┼───────────────────────┘
                                          │
                                          ▼
                                 ┌─────────────────┐
                                 │   生成答案       │
                                 │  generate_answer│
                                 └────────┬────────┘
                                          │
                                          ▼
                                 ┌─────────────────┐
                                 │   质量检查       │
                                 │  check_quality  │
                                 └────────┬────────┘
                                          │
                            ┌─────────────┴─────────────┐
                            │                           │
                            ▼                           ▼
                   ┌─────────────────┐        ┌─────────────────┐
                   │     END         │        │  重新生成       │
                   │   (质量合格)     │        │  (质量不合格)   │
                   └─────────────────┘        └─────────────────┘
                                                    │
                                                    └──→ generate_answer

3.2 关键设计点

设计点实现方式作用
情感转人工Conditional Edge负面情绪直接转人工
意图路由Conditional Edge根据意图选择处理路径
质量重试Conditional Edge + 循环质量不合格重新生成
最大轮次路由函数判断防止无限循环

3.3 质量检查的循环设计

def route_after_quality(state: State) -> str:
    """质量检查后的路由"""
    if state["quality"] or state["rounds"] >= 3:
        return "end"        # 质量合格或超过3次,结束
    else:
        return "retry"      # 质量不合格,重试

graph.add_conditional_edges(
    "check_quality",
    route_after_quality,
    {"end": END, "retry": "generate_answer"},  # retry 指向 generate_answer,形成循环
)

关键点

  • 循环边实现了重试机制
  • rounds 计数防止无限循环
  • 这是 LangGraph 相比普通 Agent 的重要优势

四、Memory:让 AI 记住上下文

4.1 为什么需要 Memory?

没有 Memory

用户:公司的年假有几天?
助手:工作满1年享有5天年假...
用户:那病假呢?          ← AI 不知道"那"指的是什么
助手:请问您想了解什么?

有 Memory

用户:公司的年假有几天?
助手:工作满1年享有5天年假...
用户:那病假呢?          ← AI 知道在问公司的病假制度
助手:病假规定如下...

4.2 Memory 的实现方式

在 smart-customer-service 中,我用了最简单的方式——手动管理对话历史:

# State 中定义 memory
class State(TypedDict):
    ...
    memory: list  # 对话历史

# 在节点中使用 memory
def generate_answer(state: State) -> dict:
    memory = state["memory"]
    
    messages = [SystemMessage(content="...")]
    
    # 加入最近的对话历史(最多2轮)
    recent = memory[-4:] if len(memory) > 4 else memory
    for msg in recent:
        if msg["role"] == "user":
            messages.append(HumanMessage(content=msg["content"]))
        else:
            messages.append(HumanMessage(content=f"[助手之前说]: {msg['content'][:100]}"))
    
    messages.append(HumanMessage(content=state["message"]))
    result = llm.invoke(messages)
    return {"result": result.content.strip()}

# 在主程序中更新 memory
chat_history.append({"role": "user", "content": message})
chat_history.append({"role": "assistant", "content": result["result"]})

4.3 Memory 的优化方向

方案优点缺点
手动管理简单可控需要自己管理
ConversationBufferMemory开箱即用占用 token 多
ConversationSummaryMemory节省 token可能丢失细节
ConversationEntityMemory记住实体实现复杂

我的建议:简单场景用手动管理,复杂场景用 LangChain 内置的 Memory 类。

五、MCP:标准化的工具协议

5.1 MCP 是什么?

MCP (Model Context Protocol) 是一个开放协议,用于标准化 AI 应用如何连接外部工具。

核心思想

  • 无论工具用什么语言编写,都通过统一协议暴露给 AI
  • 客户端-服务器架构,工具可独立部署
  • 支持多种传输方式(stdio、HTTP、WebSocket)

5.2 MCP 的架构

┌─────────────────────────────────────────────────────────┐
│                    MCP 架构                              │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │              AI Agent                           │   │
│  │         (LangChain / LangGraph)                 │   │
│  └─────────────────────────────────────────────────┘   │
│                        ↑                                │
│                        │ 调用工具                       │
│  ┌─────────────────────────────────────────────────┐   │
│  │              MCP Client                         │   │
│  │         (MultiServerMCPClient)                  │   │
│  └─────────────────────────────────────────────────┘   │
│                        ↑                                │
│                        │ MCP 协议                       │
│  ┌─────────────────────────────────────────────────┐   │
│  │              MCP Server                         │   │
│  │         (FastMCP)                               │   │
│  │  ┌─────────┐ ┌─────────┐ ┌─────────┐           │   │
│  │  │ Tool A  │ │ Tool B  │ │ Tool C  │           │   │
│  │  └─────────┘ └─────────┘ └─────────┘           │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

5.3 MCP Server 实现

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("我的工具箱")

@mcp.tool()
def calculate(expression: str) -> str:
    """计算数学表达式,例如 2 + 3 * 4"""
    result = eval(expression)
    return f"{expression} = {result}"

@mcp.tool()
def get_weather(city: str) -> str:
    """查询城市天气,例如 杭州"""
    data = {"杭州": "晴,28°C", "北京": "多云,25°C"}
    return data.get(city, f"{city}:暂无数据")

if __name__ == "__main__":
    mcp.run(transport="stdio")

关键点

  • @mcp.tool() 装饰器注册工具
  • 函数名即工具名
  • docstring 是工具描述
  • 支持 stdio、HTTP 等传输方式

5.4 MCP Client 使用

from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent

async def main():
    # 创建客户端,连接 MCP 服务器
    client = MultiServerMCPClient({
        "my_tools": {
            "command": "python",
            "args": ["my_mcp_server.py"],
            "transport": "stdio",
        }
    })
    
    # 获取工具
    tools = await client.get_tools()
    print(f"获取了 {len(tools)} 个工具")
    
    # 创建 Agent
    agent = create_react_agent(model=llm, tools=tools)
    
    # 使用
    result = await agent.ainvoke({"messages": [HumanMessage(content="帮我算 123 + 456")]})

asyncio.run(main())

5.5 MCP vs 直接定义工具

对比点直接定义工具MCP
复杂度简单中等
复用性低(绑定项目)高(独立部署)
跨语言不支持支持
适用场景个人项目团队协作、生产环境

六、多 Agent 协作

6.1 为什么需要多 Agent?

单 Agent 的局限

  • 所有职责集中在一个 Agent
  • System Prompt 越来越长
  • 难以优化不同场景的表现

多 Agent 的思路

用户 → 主管 Agent(分配任务)
            ├→ 技术专家 Agent
            ├→ HR 专家 Agent
            └→ 财务专家 Agent
            ↓
     主管 Agent(整合结果)→ 回答

6.2 多 Agent 实现

from langchain_core.tools import tool
from langgraph.prebuilt import create_react_agent

# 专家 Agent 定义为工具
@tool
def tech_expert(question: str) -> str:
    """当用户问技术、编程相关问题时,调用这个专家。
    Args:
        question: 技术相关的问题
    """
    result = llm.invoke([
        {"role": "system", "content": "你是一个资深技术专家。"},
        {"role": "user", "content": question},
    ])
    return result.content

@tool
def hr_expert(question: str) -> str:
    """当用户问公司制度、人事相关问题时,调用这个专家。
    Args:
        question: HR相关的问题
    """
    result = llm.invoke([
        {"role": "system", "content": "你是一个公司HR专家。"},
        {"role": "user", "content": question},
    ])
    return result.content

# 主管 Agent 负责调度
supervisor = create_react_agent(
    model=llm,
    tools=[tech_expert, hr_expert, finance_expert],
)

# 使用
result = supervisor.invoke({"messages": [{"role": "user", "content": "Python 的装饰器是什么?"]]})

6.3 多 Agent 的设计模式

模式描述适用场景
路由模式主管分配给专家任务可明确分类
流水线模式Agent 依次处理任务有先后顺序
辩论模式多个 Agent 讨论需要多角度分析
投票模式多个 Agent 投票需要高可靠性的决策

七、知识体系总结

通过这个项目,我形成了完整的 LangGraph 知识体系:

┌─────────────────────────────────────────────────────────┐
│              LangGraph + Memory + MCP + Multi-Agent      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │              应用层                              │   │
│  │    智能客服 / 智能助手 / 多专家系统             │   │
│  └─────────────────────────────────────────────────┘   │
│                        ↑                                │
│  ┌─────────────────────────────────────────────────┐   │
│  │              编排层 (LangGraph)                  │   │
│  │    State / Node / Edge / Conditional Edge       │   │
│  └─────────────────────────────────────────────────┘   │
│                        ↑                                │
│  ┌─────────────────────────────────────────────────┐   │
│  │              能力层                              │   │
│  │    Memory (记忆) / MCP (工具协议) / Multi-Agent  │   │
│  └─────────────────────────────────────────────────┘   │
│                        ↑                                │
│  ┌─────────────────────────────────────────────────┐   │
│  │              基础层                              │   │
│  │    LLM / RAG / Embedding / VectorDB             │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

八、学习路径建议

LangChain 基础
    │
    ├── Prompt Template
    ├── LLM 调用
    ├── Chain 链式调用
    └── Tools 工具定义
           │
           ▼
       Agent 入门
           │
           ├── ReAct 模式
           ├── create_react_agent
           └── 工具调用机制
                  │
                  ▼
              RAG 应用
                  │
                  ├── 文档加载切分
                  ├── 向量化存储
                  └── 检索生成
                         │
                         ▼
                     LangGraph
                         │
                         ├── State 状态管理
                         ├── Node 节点定义
                         ├── Edge 边连接
                         └── Conditional Edge 条件路由
                                │
                                ▼
                          进阶主题
                                │
                                ├── Memory 记忆系统
                                ├── MCP 工具协议
                                └── Multi-Agent 协作

九、项目地址


上一篇:从零搭建RAG知识库问答系统(零基础自学agent日记)