
痛点分析传统智能客服的“健忘症”与“选择困难症”在动手之前我们得先搞清楚传统方案到底“痛”在哪里。很多基于简单规则或早期框架的智能客服常常被两个核心问题困扰对话状态管理混乱“健忘症”用户说“我想订一张明天去北京的机票”客服理解了。用户接着问“那天气怎么样”很多系统就懵了因为它可能丢失了“北京”和“明天”这两个关键的上下文槽位Slot要么回答不了天气要么得让用户再重复一遍地点和时间。这种多轮会话中上下文信息的丢失和混乱是体验差的首要原因。多意图嵌套与冲突处理不佳“选择困难症”用户的对话往往是跳跃和混合的。比如在查询订单物流intent: query_logistics的过程中突然问“能不能改地址”intent: modify_address。传统线性流程很难优雅地处理这种意图切换、返回和嵌套容易导致流程中断或给出错误答复。技术对比为什么是LangGraph市面上做对话系统的工具不少比如老牌的Rasa和云服务DialogFlow (Google)。它们各有优劣但今天我们聚焦在对话流的编排能力上。Rasa功能强大但对话管理Dialogue Management主要依靠规则Rules和故事Stories来训练策略Policy其流程的可视化和动态调整相对复杂更像是在写剧本对于复杂、多分支的对话流维护成本较高。DialogFlow上手快但对话流CX本质上是一种预定义的、树状的状态机虽然提供了可视化编辑器但在处理灵活跳转、动态路径选择以及复杂的业务逻辑集成时会显得比较笨重和受限。LangGraph 的闪光点在于它用有向无环图DAG来建模对话流程。每个节点Node是一个清晰定义的操作如意图识别、调用API、回复用户边Edge定义了状态流转的条件。这带来了几个核心优势可视化与可调试性整个对话流程就是一张图一目了然哪里卡住了很容易定位。模块化每个节点功能单一易于复用和测试。灵活编排基于状态的判断可以轻松实现循环、条件分支、并行处理甚至子图嵌套完美应对多轮、多意图的复杂场景。核心实现用LangGraph搭建对话骨架理论说再多不如一行代码。我们开始用 LangGraph 构建一个具备基础能力的智能客服对话系统。假设我们的客服需要处理“查询订单”和“投诉建议”两个主要意图。1. 定义状态与构建带Fallback的状态机首先我们需要定义一个全局的对话状态。这个状态会在各个节点间流转。from typing import TypedDict, Annotated, Union from langgraph.graph import StateGraph, END from langgraph.graph.message import add_messages import operator # 1. 定义对话状态结构 class AgentState(TypedDict): # 消息历史LangGraph 内置支持 messages: Annotated[list, add_messages] # 当前识别出的用户意图 current_intent: str # 从对话中提取的槽位信息如订单号、问题描述 slots: dict # 标记对话是否应该结束 should_end: bool # 记录上一个执行的节点用于调试和fallback last_node: str # 2. 初始化状态图 workflow StateGraph(AgentState)2. 创建对话节点与路由逻辑我们用node装饰器来创建图节点。这里实现几个关键节点路由判断意图、处理查询订单、处理投诉、以及一个万能的Fallback节点。from langgraph.graph import Node # 节点A意图识别与路由 Node async def route_intent(state: AgentState) - AgentState: 根据用户最新消息判断意图 last_message state[“messages”][-1].content if state[“messages”] else “” user_input last_message.lower() # 简单的关键字匹配生产环境应替换为NLU模型如Rasa NLU、BERT分类器 if “订单” in user_input and (“查” in user_input or “在哪” in user_input): intent “query_order” elif “投诉” in user_input or “建议” in user_input: intent “complaint” elif “结束” in user_input or “再见” in user_input: intent “goodbye” else: intent “unknown” # 更新状态 new_state state.copy() new_state[“current_intent”] intent new_state[“last_node”] “route_intent” return new_state # 节点B处理订单查询 Node async def handle_order_query(state: AgentState) - AgentState: 查询订单逻辑可能需要向数据库或API请求数据 # 这里模拟从状态slots中获取订单号实际应从消息中通过NER提取 order_id state[“slots”].get(“order_id”, “未提供”) # 模拟业务调用 try: # 假设这里调用了一个外部服务 # order_info await order_service.query(order_id) order_info f“订单 {order_id} 状态已发货” response f“为您查询到{order_info}。还有什么可以帮您” except Exception as e: # 异常捕获与友好提示 response f“查询订单时出现系统错误{e}请稍后再试或联系人工客服。” new_state state.copy() new_state[“should_end”] True # 严重错误建议结束会话 # 将助手的回复添加到消息历史 new_state state.copy() new_state[“messages”].append({“role”: “assistant”, “content”: response}) new_state[“last_node”] “handle_order_query” return new_state # 节点C处理投诉建议 Node async def handle_complaint(state: AgentState) - AgentState: 收集投诉信息 # 通常这里会涉及多轮槽位填充例如询问投诉类型、具体内容等 # 为简化我们假设用户一次性说清了 complaint_text state[“messages”][-1].content # 模拟保存到工单系统 # ticket_id await ticket_system.create(complaint_text) ticket_id “T20241010001” response f“您的投诉建议已记录工单号{ticket_id}客服将在24小时内联系您。感谢您的反馈” new_state state.copy() new_state[“messages”].append({“role”: “assistant”, “content”: response}) new_state[“last_node”] “handle_complaint” return new_state # 节点FFallback 处理未知意图 Node async def handle_fallback(state: AgentState) - AgentState: 处理无法识别的意图引导用户或转人工 response “抱歉我没有理解您的意思。您可以尝试说‘查询订单’或‘我要投诉’。需要我为您转接人工客服吗” new_state state.copy() new_state[“messages”].append({“role”: “assistant”, “content”: response}) new_state[“current_intent”] “unknown” # 保持未知状态下次继续路由 new_state[“last_node”] “handle_fallback” return new_state # 节点E结束对话 Node async def handle_goodbye(state: AgentState) - AgentState: 礼貌结束对话 response “感谢您的使用再见祝您有美好的一天” new_state state.copy() new_state[“messages”].append({“role”: “assistant”, “content”: response}) new_state[“should_end”] True new_state[“last_node”] “handle_goodbye” return new_state3. 组装节点并定义流转条件将节点添加到图中并定义它们之间的流转逻辑边。# 将所有节点添加到图中 workflow.add_node(“router”, route_intent) workflow.add_node(“query_order”, handle_order_query) workflow.add_node(“complaint”, handle_complaint) workflow.add_node(“fallback”, handle_fallback) workflow.add_node(“goodbye”, handle_goodbye) # 设置入口点 workflow.set_entry_point(“router”) # 定义路由逻辑根据 current_intent 决定下一个节点 def decide_next_node(state: AgentState) - str: intent state[“current_intent”] if intent “query_order”: return “query_order” elif intent “complaint”: return “complaint” elif intent “goodbye”: return “goodbye” else: # unknown 或其他未处理意图 return “fallback” # 从router节点出发根据条件指向不同节点 workflow.add_conditional_edges( “router”, decide_next_node, { “query_order”: “query_order”, “complaint”: “complaint”, “goodbye”: “goodbye”, “fallback”: “fallback”, } ) # 对于处理节点和fallback节点执行完后都回到router去判断用户下一句话 # 这样就形成了一个循环直到should_end为True workflow.add_edge(“query_order”, “router”) workflow.add_edge(“complaint”, “router”) workflow.add_edge(“fallback”, “router”) # 结束节点直接指向END workflow.add_edge(“goodbye”, END) # 编译图 app workflow.compile()现在我们已经有了一个可以运行的基础对话图。你可以用app.invoke()传入初始状态来运行它。graph TD A[router: 意图识别] --|query_order| B[query_order: 处理订单查询] A --|complaint| C[complaint: 处理投诉建议] A --|goodbye| D[goodbye: 结束对话] A --|unknown/fallback| E[fallback: 未知意图处理] B -- A C -- A E -- A D -- F((END))生产考量让系统健壮且可靠一个玩具Demo和线上可用的服务之间隔着无数个生产级别的细节。这里讲三个关键点。1. 对话超时机制用户可能说着说着就离开了。我们需要一个机制来清理长时间无响应的会话以释放资源。import asyncio import time from collections import defaultdict class SessionManager: def __init__(self, timeout_seconds300): # 默认5分钟超时 self.sessions defaultdict(dict) # session_id - {“state”: AgentState, “last_active”: timestamp} self.timeout timeout_seconds async def get_or_create_session(self, session_id: str) - AgentState: now time.time() session self.sessions.get(session_id) # 检查会话是否存在且是否超时 if session and (now - session[“last_active”] self.timeout): session[“last_active”] now return session[“state”] else: # 创建新会话或重置超时会话 new_state AgentState( messages[], current_intent“”, slots{}, should_endFalse, last_node“” ) self.sessions[session_id] {“state”: new_state, “last_active”: now} return new_state async def cleanup(self): 定期清理超时会话 now time.time() expired_ids [sid for sid, data in self.sessions.items() if now - data[“last_active”] self.timeout] for sid in expired_ids: del self.sessions[sid]2. 敏感词过滤中间件在回复用户或处理用户输入前进行内容安全检查是必须的。class SensitiveWordFilter: def __init__(self, sensitive_words_path: str): # 从文件或数据库加载敏感词库 with open(sensitive_words_path, ‘r’, encoding‘utf-8’) as f: self.sensitive_words set(line.strip() for line in f if line.strip()) def filter_input(self, text: str) - tuple[str, bool]: 过滤用户输入返回过滤后的文本和是否包含敏感词标志 found False filtered_text text for word in self.sensitive_words: if word in filtered_text: filtered_text filtered_text.replace(word, “***”) found True return filtered_text, found async def filter_middleware(self, state: AgentState) - AgentState: 作为LangGraph的预处理中间件 last_msg state[“messages”][-1] if last_msg[“role”] “user”: filtered_text, is_sensitive self.filter_input(last_msg.content) if is_sensitive: # 可以记录日志或触发警告 new_state state.copy() new_state[“messages”][-1].content filtered_text new_state[“slots”][“_sensitive_flag”] True return new_state return state # 使用方式在调用app.invoke之前先通过中间件处理state # filtered_state await filter_middleware(initial_state) # result await app.ainvoke(filtered_state)3. 压测数据参考我们对上述架构的原型进行了简单的压力测试使用Locust模拟在4核8G的云服务器上得到以下数据供参考场景模拟1000个并发用户持续发起“查询订单”和“投诉”的混合请求。平均响应延迟~120msP95延迟95%的请求快于该值~250msP99延迟~450ms系统吞吐量约 800 requests/second说明此数据严重依赖于NLU模型的复杂度、外部API的响应时间以及业务逻辑的复杂度。上述数据基于简单的关键词匹配和模拟业务逻辑得出引入深度学习NLU模型后延迟可能会增加。避坑指南前人踩过的坑避免循环依赖的DAG设计LangGraph虽然支持循环比如我们的例子中处理节点后回到router但要小心死循环。确保每个循环路径都有“出口”条件例如设置最大轮数、通过should_end状态判断或者在decide_next_node逻辑中在特定条件下指向END或一个终止节点。对话节点幂等性保障网络可能超时用户可能重复发送相同指令。要确保你的节点尤其是那些调用外部API或修改数据库的节点是幂等的。例如handle_complaint节点在收到相同内容时应该先检查是否已生成过工单而不是重复创建。可以在状态slots中保存一个has_created_ticket的标志或者在调用外部服务时使用唯一ID如session_id message_hash来保证。互动与思考最后留一个开放性问题给大家这也是复杂对话系统中的经典难题“当用户在一个意图的对话流中比如正在填写投诉表单突然毫无征兆地切换到一个完全无关的话题比如问‘今天天气怎么样’你的LangGraph对话流应该如何设计才能既准确回答新问题又能在用户想回来时无缝接上之前的流程”是设计一个全局的、高优先级的“中断与恢复”子图还是在状态中维护一个“对话栈”或者有更巧妙的conditional_edge设计欢迎在评论区分享你的思路或者直接提交PR到本文的示例代码仓库假设的我们一起完善这个高可用的对话系统搭建的过程就像拼乐高LangGraph提供了清晰的图纸DAG和标准的接口Node让我们能专注于每个业务模块积木的实现。从痛点到原型再到生产级别的优化每一步的思考和实践都让系统更稳固。希望这篇笔记能帮你少走弯路快速上手LangGraph构建出体验流畅的智能客服。