尊敬的各位技术同仁,下午好!

今天,我们将深入探讨如何利用 LangGraph 这一强大的框架,构建一个智能客服图谱驱动的闭环流程。我们的重点将放在两个核心功能上:处理进度查询投诉转接,并展示如何通过精妙的设计,实现一个能够理解用户意图、执行复杂任务并持续保持会话上下文的智能系统。

1. 智能客服图谱与LangGraph:构建智能交互的基石

在当今的客户服务领域,用户对效率和个性化的期望越来越高。传统的客服系统往往受限于预设的规则和有限的知识库,难以应对复杂多变的客户需求。智能客服图谱(Intelligent Customer Service Graph, ICSG)的出现,旨在通过结构化地表示客户、产品、服务、流程、知识点之间的关系,为智能客服系统提供深度的语义理解和推理能力。

然而,仅仅拥有一个强大的图谱是不够的。我们需要一个能够动态规划、执行和管理多步骤会话流程的引擎。LangGraph,作为LangChain的最新演进,正是为此而生。它允许我们以图形化的方式定义LLM(大语言模型)驱动的应用程序,其中每个节点可以是LLM调用、工具执行或自定义逻辑,而边则定义了状态的流转和决策逻辑。这使得构建复杂的、有状态的、闭环的智能代理成为可能。

闭环流程在这里意味着系统不仅能响应用户的请求,还能:

  1. 理解上下文: 记住之前的对话内容。
  2. 获取信息: 在需要时主动询问用户补充信息。
  3. 执行动作: 调用外部工具或API完成任务。
  4. 提供反馈: 告知用户任务的执行结果。
  5. 纠正错误: 在理解错误或工具执行失败时进行恢复。
  6. 持续交互: 保持对话的连贯性,直至用户满意或问题解决。

我们的目标是利用LangGraph的强大能力,将智能客服图谱的语义理解转化为实际的行动和连贯的对话体验。

2. 核心架构与组件

构建一个智能客服系统,尤其是要支持“处理进度查询”和“投诉转接”这类需要与外部系统交互的功能,需要以下几个核心组件:

  1. 用户输入与自然语言理解 (NLU) 层:

    • 意图识别 (Intent Recognition): 识别用户的核心意图,例如“查询进度”、“发起投诉”、“通用问答”等。
    • 实体抽取 (Entity Extraction): 从用户话语中提取关键信息,如订单号、投诉类型、联系方式等。
    • 上下文管理: 维护会话历史,以便LLM能够理解长对话的语境。
  2. 系统状态管理:

    • 一个能够清晰表示当前会话状态的数据结构,包括用户的查询、识别的意图、已抽取的实体、会话历史、工具执行结果等。这是LangGraph StateGraph 的核心。
  3. 工具与外部服务集成:

    • 进度查询工具: 模拟与订单管理系统或物流系统的API交互,查询特定订单的当前状态。
    • 投诉转接工具: 模拟与CRM系统或工单系统交互,创建新的投诉记录或将用户转接到人工客服。
    • 知识库查询工具: 处理通用问题,提供预设答案。
  4. 决策与流程编排 (LangGraph):

    • 根据NLU结果和当前系统状态,决定下一步的动作:是执行某个工具,还是向用户提问,或是生成通用回复。
    • 管理不同功能模块之间的流转,确保任务能够按序完成。
  5. 响应生成:

    • 基于任务执行结果和会话上下文,生成自然、准确、有帮助的回复给用户。

下面,我们将通过LangGraph的具体实现来详细讲解这些组件是如何协同工作的。

3. LangGraph核心概念与状态定义

LangGraph的核心是StateGraph。它允许我们定义一个状态对象,并在图中的不同节点之间传递和修改这个状态。

3.1 定义图的状态(Graph State)

首先,我们定义一个AgentState,它将作为整个LangGraph工作流的共享状态。这个状态需要包含所有可能在会话中使用的信息。

from typing import List, TypedDict, Annotated, Union
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
from langchain_core.utils.function_calling import convert_to_openai_tool
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END, START
import operator
import os

# 确保设置了OpenAI API Key
os.environ["OPENAI_API_KEY"] = "YOUR_OPENAI_API_KEY"

class AgentState(TypedDict):
    """
    LangGraph的AgentState定义,用于在各个节点之间传递和修改。
    """
    chat_history: Annotated[List[BaseMessage], operator.add] # 聊天历史,使用operator.add合并新消息
    current_intent: str # 当前识别的用户意图
    entities: dict # 从用户输入中提取的实体,如订单号、投诉详情等
    tool_output: str # 工具执行的结果
    user_query: str # 当前用户输入的原始查询
    response: str # 最终发送给用户的回复
    next_action: str # 下一步要采取的动作,例如"ask_for_order_id", "transfer_complaint"
    need_human_handoff: bool # 是否需要转接人工客服

AgentState 的字段解释:

  • chat_history: 存储所有BaseMessage(包括用户输入、AI回复、工具调用及结果),这是实现上下文管理的关键。Annotated[List[BaseMessage], operator.add] 表示当在节点中修改 chat_history 时,新的消息会被添加到现有列表的末尾。
  • current_intent: 记录系统当前识别的用户意图,例如 "progress_inquiry""complaint_transfer""general_query"
  • entities: 一个字典,用于存储从用户输入中提取的关键信息,如{"order_id": "T2023001", "complaint_type": "包裹破损"}
  • tool_output: 存储由工具函数返回的字符串结果。
  • user_query: 存储用户当前的原始输入。
  • response: 存储最终生成的,将返回给用户的回复。
  • next_action: 用于在复杂流程中指导Agent的下一步操作,例如在信息不全时提示Agent需要询问用户。
  • need_human_handoff: 布尔值,标记是否需要将请求转接给人工客服。
3.2 定义工具(Tools)

接下来,我们定义系统可以调用的外部工具。这些工具可以是与数据库、CRM、物流系统等交互的函数。

@tool
def query_order_progress(order_id: str) -> str:
    """
    查询指定订单号的当前处理进度。
    Args:
        order_id (str): 需要查询的订单编号。
    Returns:
        str: 订单的当前状态和预计完成时间。
    """
    print(f"DEBUG: Calling query_order_progress for order_id: {order_id}")
    # 模拟与外部订单系统交互
    if order_id == "T2023001":
        return "您的订单T2023001已发货,预计2天内送达。物流单号:SF123456789。"
    elif order_id == "T2023002":
        return "您的订单T2023002正在仓库打包中,预计明天出库。"
    elif order_id == "T2023003":
        return "您的订单T2023003已签收,感谢您的购买。"
    else:
        return f"未能找到订单号为 {order_id} 的信息,请检查后重试。"

@tool
def transfer_complaint(complaint_details: str, contact_info: str) -> str:
    """
    将用户投诉转接到人工客服或记录到CRM系统。
    Args:
        complaint_details (str): 用户的投诉详情。
        contact_info (str): 用户的联系方式(手机号、邮箱等)。
    Returns:
        str: 投诉转接结果或确认信息。
    """
    print(f"DEBUG: Calling transfer_complaint with details: {complaint_details}, contact: {contact_info}")
    # 模拟与CRM系统或工单系统交互
    if "紧急" in complaint_details or "无法解决" in complaint_details:
        return f"已将您的紧急投诉 '{complaint_details}' 转接到高级客服。工单号:C{hash(complaint_details + contact_info) % 100000}。客服将在15分钟内与您联系,请保持电话畅通。您的联系方式:{contact_info}。"
    else:
        return f"您的投诉 '{complaint_details}' 已记录。工单号:C{hash(complaint_details + contact_info) % 100000}。我们会在24小时内处理并回复您。您的联系方式:{contact_info}。"

# 将工具转换为OpenAI兼容格式,供LLM调用
tools = [query_order_progress, transfer_complaint]
openai_tools = [convert_to_openai_tool(t) for t in tools]

这些工具是系统与外部世界交互的接口。@tool 装饰器使得这些函数可以被LangChain的LLM以函数调用的方式识别和执行。

4. 构建LangGraph工作流:节点与边的设计

现在,我们来设计LangGraph的节点和边。我们的目标是构建一个能够处理以下流程的图:

  1. 接收用户输入。
  2. 识别用户意图和抽取实体。
  3. 根据意图路由到不同的处理分支(进度查询、投诉转接、通用问答)。
  4. 在必要时,通过LLM调用工具。
  5. 处理工具的输出。
  6. 生成回复。
  7. 如果信息不完整,能够主动询问用户。
  8. 在特殊情况下,能够转接人工客服。
4.1 初始化LLM

我们将使用OpenAI的gpt-4o模型,因为它在函数调用、多模态理解和通用推理方面表现出色。

llm = ChatOpenAI(model="gpt-4o", temperature=0)
# 绑定工具,使得LLM能够感知并调用这些工具
llm_with_tools = llm.bind_tools(openai_tools)
4.2 定义节点(Nodes)

每个节点都是一个Python函数,它接收当前的AgentState,并返回一个修改后的AgentState

4.2.1 意图识别与实体抽取节点 (process_user_input)

这是入口节点,负责接收用户的原始输入,并利用LLM进行初步的意图识别和实体抽取。

def process_user_input(state: AgentState) -> AgentState:
    """
    接收用户输入,并利用LLM进行意图识别和实体抽取。
    """
    print("---NODE: process_user_input---")
    user_query = state["user_query"]
    chat_history = state["chat_history"]

    # LLM的提示,指导其进行意图识别和实体抽取
    # 我们使用Few-shot prompting或CoT来增强其性能
    prompt = f"""
    你是一个智能客服助手。请根据以下对话历史和用户最新的查询,判断用户的主要意图,并提取所有相关实体。
    如果用户表达了查询订单进度的意图,请识别并提取订单号。
    如果用户表达了投诉或寻求帮助的意图,请识别并提取投诉详情和可能的联系方式。
    如果意图不明确或无法识别,请标记为'general_query'。

    意图类型包括:'progress_inquiry', 'complaint_transfer', 'general_query'。

    对话历史:
    {chat_history}

    用户最新查询: {user_query}

    请以JSON格式返回你的分析结果,包含'intent'(意图)和'entities'(实体,字典形式)。
    示例:
    {{
        "intent": "progress_inquiry",
        "entities": {{"order_id": "T2023001"}}
    }}
    或
    {{
        "intent": "complaint_transfer",
        "entities": {{"complaint_details": "包裹破损,物流太慢", "contact_info": "13800138000"}}
    }}
    或
    {{
        "intent": "general_query",
        "entities": {{}}
    }}
    """

    # 暂时不使用LLM Function Calling,而是直接解析JSON输出
    # 因为意图识别和实体抽取可能需要更灵活的输出结构
    response = llm.invoke(prompt)

    try:
        parsed_output = eval(response.content.strip()) # 简单的eval解析,生产环境应使用json.loads
        intent = parsed_output.get("intent", "general_query")
        entities = parsed_output.get("entities", {})
    except Exception as e:
        print(f"Error parsing LLM output: {e}. Falling back to general_query.")
        intent = "general_query"
        entities = {}

    # 更新聊天历史,将用户查询添加到历史中
    new_chat_history = chat_history + [HumanMessage(content=user_query)]

    return {
        "chat_history": new_chat_history,
        "current_intent": intent,
        "entities": entities,
        "user_query": user_query, # 保留原始查询
        "next_action": "", # 清空next_action
        "need_human_handoff": False,
        "tool_output": "", # 清空tool_output
        "response": "" # 清空response
    }
4.2.2 代理执行节点 (agent_executor)

这个节点是核心,它利用 llm_with_tools 来决定下一步是直接回答用户,还是调用某个工具。它是一个通用的代理,可以处理各种任务。

def agent_executor(state: AgentState) -> AgentState:
    """
    通用代理执行器,利用LLM(绑定了工具)来决定下一步行动。
    这可以是直接回答,也可以是调用工具。
    """
    print("---NODE: agent_executor---")
    chat_history = state["chat_history"]
    current_intent = state["current_intent"]
    entities = state["entities"]
    user_query = state["user_query"]
    next_action = state["next_action"]

    # 根据当前意图和已有的实体,构造LLM的输入
    # 引导LLM进行决策
    if current_intent == "progress_inquiry":
        if not entities.get("order_id"):
            # 如果是进度查询但缺少订单号,LLM应该被引导询问订单号
            return {"chat_history": chat_history, "response": "请问您的订单号是多少?", "next_action": "ask_for_order_id"}
    elif current_intent == "complaint_transfer":
        if not entities.get("complaint_details"):
            # 如果是投诉但缺少详情,LLM应该被引导询问投诉详情
            return {"chat_history": chat_history, "response": "请您详细描述一下您要投诉的问题。", "next_action": "ask_for_complaint_details"}
        elif not entities.get("contact_info"):
            # 如果有投诉详情但缺少联系方式
            return {"chat_history": chat_history, "response": "好的,请留下您的联系方式,比如手机号或邮箱,以便我们处理您的投诉。", "next_action": "ask_for_contact_info"}

    # 构造给LLM的完整提示,包括会话历史和当前的用户查询
    # LLM会根据提示和绑定的工具来决定是否调用工具
    messages = chat_history + [HumanMessage(content=user_query)]

    # 使用LLM with tools进行推理
    response = llm_with_tools.invoke(messages)

    # 如果LLM决定调用工具
    if response.tool_calls:
        tool_call = response.tool_calls[0] # 假设只有一个工具调用
        tool_name = tool_call["name"]
        tool_args = tool_call["args"]

        # 查找并执行对应的工具
        tool_to_call = next((t for t in tools if t.name == tool_name), None)
        if tool_to_call:
            try:
                tool_result = tool_to_call.invoke(tool_args)
                # 将工具执行结果添加到聊天历史
                new_chat_history = chat_history + [response, ToolMessage(content=tool_result, tool_call_id=tool_call["id"])]
                return {"chat_history": new_chat_history, "tool_output": tool_result, "response": tool_result, "next_action": "tool_executed"}
            except Exception as e:
                error_msg = f"工具执行失败: {tool_name},错误信息: {e}"
                print(error_msg)
                new_chat_history = chat_history + [response, ToolMessage(content=error_msg, tool_call_id=tool_call["id"])]
                return {"chat_history": new_chat_history, "tool_output": error_msg, "response": "抱歉,系统暂时无法处理您的请求,请稍后再试或联系人工客服。", "next_action": "error_occurred"}
        else:
            return {"chat_history": chat_history, "response": "抱歉,我无法识别这个工具。", "next_action": "error_occurred"}
    else:
        # 如果LLM没有调用工具,它应该生成一个直接的回复
        new_chat_history = chat_history + [response]
        return {"chat_history": new_chat_history, "response": response.content, "next_action": "responded_directly"}
4.2.3 人工转接节点 (human_handoff_node)

当系统无法处理或用户明确要求时,转接到人工客服。

def human_handoff_node(state: AgentState) -> AgentState:
    """
    当需要人工介入时,标记状态并生成转接提示。
    """
    print("---NODE: human_handoff_node---")
    return {"need_human_handoff": True, "response": "好的,我已为您转接人工客服,请稍候。"}
4.3 定义条件边(Conditional Edges)与路由逻辑

条件边是LangGraph中实现动态流程的关键。它允许我们根据AgentState中的值来决定下一个要执行的节点。

4.3.1 路由节点 (route_intent)

这个节点根据current_intent来决定将控制流路由到哪个功能模块。

def route_intent(state: AgentState) -> str:
    """
    根据当前识别的意图路由到不同的处理节点。
    """
    print("---ROUTER: route_intent---")
    current_intent = state["current_intent"]
    entities = state["entities"]
    next_action = state["next_action"]

    if next_action == "ask_for_order_id":
        return "ask_for_order_id_node" # 需要询问订单号
    elif next_action == "ask_for_complaint_details":
        return "ask_for_complaint_details_node" # 需要询问投诉详情
    elif next_action == "ask_for_contact_info":
        return "ask_for_contact_info_node" # 需要询问联系方式
    elif next_action == "tool_executed":
        return "generate_final_response" # 工具已执行,生成最终回复
    elif next_action == "responded_directly":
        return "generate_final_response" # LLM已直接回复,生成最终回复
    elif next_action == "error_occurred":
        return "generate_final_response" # 错误发生,生成错误提示

    if current_intent == "progress_inquiry":
        if entities.get("order_id"):
            return "agent_executor" # 有订单号,直接执行代理
        else:
            return "ask_for_order_id_node" # 缺少订单号,询问用户
    elif current_intent == "complaint_transfer":
        if entities.get("complaint_details") and entities.get("contact_info"):
            return "agent_executor" # 投诉详情和联系方式都有,执行代理
        elif entities.get("complaint_details") and not entities.get("contact_info"):
            return "ask_for_contact_info_node" # 有投诉详情但缺少联系方式
        else:
            return "ask_for_complaint_details_node" # 缺少投诉详情
    elif current_intent == "general_query":
        return "agent_executor" # 通用查询,直接交给代理处理
    else:
        # 无法识别的意图,也交给通用代理,它可能会询问澄清
        return "agent_executor"
4.3.2 信息补全节点 (ask_for_order_id_node, ask_for_complaint_details_node, ask_for_contact_info_node)

这些是辅助节点,用于在特定信息缺失时生成提示语。

def ask_for_order_id_node(state: AgentState) -> AgentState:
    print("---NODE: ask_for_order_id_node---")
    return {"response": "请问您的订单号是多少?", "next_action": "waiting_for_order_id"}

def ask_for_complaint_details_node(state: AgentState) -> AgentState:
    print("---NODE: ask_for_complaint_details_node---")
    return {"response": "请您详细描述一下您要投诉的问题。", "next_action": "waiting_for_complaint_details"}

def ask_for_contact_info_node(state: AgentState) -> AgentState:
    print("---NODE: ask_for_contact_info_node---")
    return {"response": "好的,请留下您的联系方式,比如手机号或邮箱,以便我们处理您的投诉。", "next_action": "waiting_for_contact_info"}
4.3.3 最终回复生成节点 (generate_final_response)

这个节点负责汇总所有信息,生成最终的回复。

def generate_final_response(state: AgentState) -> AgentState:
    print("---NODE: generate_final_response---")
    response_content = state["response"]
    chat_history = state["chat_history"]

    # 如果response为空,说明LLM在agent_executor中直接返回了结果,或者工具直接返回了结果
    if not response_content and state["tool_output"]:
        response_content = state["tool_output"]
    elif not response_content and state["next_action"] == "responded_directly" and len(chat_history) > 0:
        # 如果是LLM直接回复,且response字段为空,说明回复内容在chat_history的最后一个AIMessage中
        last_message = chat_history[-1]
        if isinstance(last_message, AIMessage):
            response_content = last_message.content

    # 更新聊天历史,添加AI的最终回复
    new_chat_history = chat_history + [AIMessage(content=response_content)]

    # 清空当前的user_query和next_action,准备迎接下一个用户输入
    return {
        "chat_history": new_chat_history,
        "response": response_content,
        "user_query": "", # 清空,等待下一个用户输入
        "next_action": "", # 清空
        "current_intent": "", # 清空,等待重新识别意图
        "entities": {} # 清空,等待重新抽取实体
    }

5. 组装LangGraph

现在,我们将所有节点和路由器组装成一个完整的StateGraph

# 构建图
workflow = StateGraph(AgentState)

# 添加节点
workflow.add_node("process_user_input", process_user_input)
workflow.add_node("agent_executor", agent_executor)
workflow.add_node("human_handoff_node", human_handoff_node)
workflow.add_node("ask_for_order_id_node", ask_for_order_id_node)
workflow.add_node("ask_for_complaint_details_node", ask_for_complaint_details_node)
workflow.add_node("ask_for_contact_info_node", ask_for_contact_info_node)
workflow.add_node("generate_final_response", generate_final_response)

# 设置入口点
workflow.set_entry_point("process_user_input")

# 添加边
# 从用户输入处理节点到意图路由
workflow.add_edge("process_user_input", "route_intent")

# 从信息补全节点(询问订单号)到路由,以便再次处理用户输入
workflow.add_edge("ask_for_order_id_node", "route_intent")
workflow.add_edge("ask_for_complaint_details_node", "route_intent")
workflow.add_edge("ask_for_contact_info_node", "route_intent")

# 从agent_executor到路由,因为agent_executor执行完可能需要根据next_action进一步判断
workflow.add_edge("agent_executor", "route_intent")

# 从路由节点根据逻辑跳转到不同的处理分支
workflow.add_conditional_edges(
    "route_intent",
    route_intent,
    {
        "agent_executor": "agent_executor", # 意图明确,且信息完整,交给agent_executor
        "ask_for_order_id_node": "ask_for_order_id_node", # 缺少订单号
        "ask_for_complaint_details_node": "ask_for_complaint_details_node", # 缺少投诉详情
        "ask_for_contact_info_node": "ask_for_contact_info_node", # 缺少联系方式
        "generate_final_response": "generate_final_response", # 工具执行完毕或直接回复,生成最终回复
    }
)

# 最终回复生成后,视为一个交互回合结束,回到起始点等待新的用户输入
# 实际上,我们可以让它回到 process_user_input,但为了演示闭环的结束,我们先指向END
# 在实际应用中,generate_final_response 应该回到一个等待用户输入的循环点
workflow.add_edge("generate_final_response", END)
# 暂时没有直接的human_handoff_node的入口,可以根据业务逻辑添加
# workflow.add_edge("human_handoff_node", END) # 简化处理,转接后结束当前流程

重要说明: 在一个真正的闭环对话系统中,generate_final_response 节点不应该直接连接到 END。它应该回溯到 process_user_input 节点,或者一个等待用户输入的节点,以实现持续的对话。但是,为了清晰地演示一个“请求-响应”的完整闭环,我们暂时将其指向 END。在实际应用中,您会通过一个外部循环来调用 graph.stream()graph.invoke(),每次调用代表一个用户请求。

# 编译图
app = workflow.compile()

6. 运行闭环流程:处理进度查询与投诉转接示例

现在,让我们通过几个具体的场景来演示这个LangGraph驱动的智能客服系统。

6.1 场景一:处理进度查询 (Progress Inquiry)

子场景 1.1: 提供完整订单号

用户输入:"我的订单T2023001怎么样了?"

print("n--- 用户查询: 我的订单T2023001怎么样了? ---")
initial_state = AgentState(chat_history=[], user_query="我的订单T2023001怎么样了?", current_intent="", entities={}, tool_output="", response="", next_action="", need_human_handoff=False)
for s in app.stream(initial_state):
    print(s)
    if "__end__" in s:
        final_state = s["__end__"]
        print(f"最终回复: {final_state['response']}")
        print(f"最终聊天历史: {final_state['chat_history']}")

预期输出流解释:

  1. process_user_input 节点识别意图为 progress_inquiry,提取 order_id: T2023001
  2. route_intent 节点根据 current_intententities 路由到 agent_executor
  3. agent_executor 节点通过 llm_with_tools 调用 query_order_progress 工具。
  4. 工具执行,返回订单进度。
  5. agent_executor 更新 tool_outputresponse,设置 next_actiontool_executed
  6. route_intent 再次被调用,根据 next_action 路由到 generate_final_response
  7. generate_final_response 节点生成最终回复,并更新 chat_history,然后流程结束(END)。

子场景 1.2: 缺少订单号,需要系统追问

用户输入:"我的订单怎么样了?"

print("n--- 用户查询: 我的订单怎么样了? ---")
initial_state = AgentState(chat_history=[], user_query="我的订单怎么样了?", current_intent="", entities={}, tool_output="", response="", next_action="", need_human_handoff=False)
for s in app.stream(initial_state):
    print(s)
    if "__end__" in s:
        final_state = s["__end__"]
        print(f"最终回复: {final_state['response']}")
        print(f"最终聊天历史: {final_state['chat_history']}")

预期输出流解释:

  1. process_user_input 识别意图为 progress_inquiry,但 entities 中缺少 order_id
  2. route_intent 节点检测到 progress_inquiry 但无 order_id,路由到 ask_for_order_id_node
  3. ask_for_order_id_node 返回 response: "请问您的订单号是多少?"next_action: "waiting_for_order_id"
  4. route_intent 再次被调用,根据 next_action 路由到 generate_final_response
  5. generate_final_response 节点将询问信息作为最终回复,流程结束。

这展示了闭环流程中“获取信息”的能力。在真实的对话系统中,用户会接着输入订单号,而系统需要能够将这个订单号与之前的“订单查询”意图关联起来。这需要更精细的会话上下文管理和状态更新逻辑。

为了演示多轮对话,我们需要稍微修改运行方式,手动模拟多轮:

print("n--- 多轮对话场景:订单查询,先问号再提供 ---")
current_chat_history = []

# 第一轮:用户询问订单,但未提供订单号
user_input_1 = "我的订单怎么样了?"
print(f"用户: {user_input_1}")
initial_state_1 = AgentState(chat_history=current_chat_history, user_query=user_input_1, current_intent="", entities={}, tool_output="", response="", next_action="", need_human_handoff=False)
for s in app.stream(initial_state_1):
    if "__end__" in s:
        final_state_1 = s["__end__"]
        current_chat_history = final_state_1["chat_history"]
        ai_response_1 = final_state_1["response"]
        print(f"AI: {ai_response_1}")
        break # 结束当前轮

# 第二轮:用户提供订单号
user_input_2 = "订单号是T2023002"
print(f"用户: {user_input_2}")
# 这里需要将上一轮的 chat_history 传递下去,并更新 user_query
initial_state_2 = AgentState(
    chat_history=current_chat_history,
    user_query=user_input_2,
    current_intent="progress_inquiry", # 假设系统能够记住之前的意图
    entities={"order_id": "T2023002"}, # 假设系统能够从新输入中提取订单号
    tool_output="", response="", next_action="", need_human_handoff=False
)
for s in app.stream(initial_state_2):
    if "__end__" in s:
        final_state_2 = s["__end__"]
        current_chat_history = final_state_2["chat_history"]
        ai_response_2 = final_state_2["response"]
        print(f"AI: {ai_response_2}")
        break # 结束当前轮

注意:initial_state_2 中,我手动设置了 current_intententities。在更完善的系统中,process_user_input 节点会更智能地利用 chat_history 来判断是否是同一意图的延续,并从最新的 user_query 中补充实体。这通常通过LLM的Few-shot提示或更复杂的NLU模型来实现。

6.2 场景二:投诉转接 (Complaint Transfer)

子场景 2.1: 提供完整投诉信息

用户输入:"我要投诉,我的包裹破损了,我的手机号是13800138000。"

print("n--- 用户查询: 我要投诉,我的包裹破损了,我的手机号是13800138000。 ---")
initial_state = AgentState(chat_history=[], user_query="我要投诉,我的包裹破损了,我的手机号是13800138000。", current_intent="", entities={}, tool_output="", response="", next_action="", need_human_handoff=False)
for s in app.stream(initial_state):
    print(s)
    if "__end__" in s:
        final_state = s["__end__"]
        print(f"最终回复: {final_state['response']}")
        print(f"最终聊天历史: {final_state['chat_history']}")

预期输出流解释:

  1. process_user_input 节点识别意图为 complaint_transfer,提取 complaint_details: "包裹破损"contact_info: "13800138000"
  2. route_intent 节点路由到 agent_executor
  3. agent_executor 节点通过 llm_with_tools 调用 transfer_complaint 工具。
  4. 工具执行,返回投诉转接结果。
  5. agent_executor 更新 tool_outputresponse,设置 next_actiontool_executed
  6. route_intent 再次被调用,路由到 generate_final_response
  7. generate_final_response 节点生成最终回复,流程结束。

子场景 2.2: 缺少联系方式,系统追问

用户输入:"我要投诉,我的包裹破损了。"

print("n--- 多轮对话场景:投诉转接,先投诉再问联系方式 ---")
current_chat_history_complaint = []

# 第一轮:用户投诉,但未提供联系方式
user_input_c1 = "我要投诉,我的包裹破损了。"
print(f"用户: {user_input_c1}")
initial_state_c1 = AgentState(chat_history=current_chat_history_complaint, user_query=user_input_c1, current_intent="", entities={}, tool_output="", response="", next_action="", need_human_handoff=False)
for s in app.stream(initial_state_c1):
    if "__end__" in s:
        final_state_c1 = s["__end__"]
        current_chat_history_complaint = final_state_c1["chat_history"]
        ai_response_c1 = final_state_c1["response"]
        print(f"AI: {ai_response_c1}")
        break

# 第二轮:用户提供联系方式
user_input_c2 = "我的手机号是13800138000。"
print(f"用户: {user_input_c2}")
initial_state_c2 = AgentState(
    chat_history=current_chat_history_complaint,
    user_query=user_input_c2,
    current_intent="complaint_transfer", # 假设系统能够记住之前的意图
    entities={"complaint_details": "包裹破损", "contact_info": "13800138000"}, # 假设能够从多轮对话中整合实体
    tool_output="", response="", next_action="", need_human_handoff=False
)
for s in app.stream(initial_state_c2):
    if "__end__" in s:
        final_state_c2 = s["__end__"]
        current_chat_history_complaint = final_state_c2["chat_history"]
        ai_response_c2 = final_state_c2["response"]
        print(f"AI: {ai_response_c2}")
        break

7. 增强闭环流程的健壮性与智能性

当前实现的闭环流程已经能够处理基本的多轮对话和工具调用,但一个生产级的智能客服系统还需要考虑更多。

7.1 完善意图识别与实体抽取
  • 更复杂的NLU模型: 使用专门训练的意图识别和实体抽取模型,或者为LLM提供更丰富的Few-shot示例和更细致的Prompt工程,使其能够更好地从多轮对话中提取和补充信息。
  • 正则表达式与槽位填充: 对于像订单号、手机号这类有固定格式的实体,可以结合正则表达式进行强约束提取,确保准确性。
  • 上下文实体融合: 当用户在后续轮次提供信息时,系统需要能够智能地将新信息与当前意图的缺失槽位进行匹配和填充。这要求 process_user_input 节点更加智能,或者在 route_intent 之后添加一个 entity_resolver 节点。
7.2 错误处理与恢复机制
  • 工具调用失败:agent_executor 中的工具调用失败时,我们目前只是返回一个通用错误。更健壮的做法是:
    • 重试机制: 尝试重新调用工具。
    • 备用方案: 如果工具持续失败,引导用户尝试其他解决途径或直接转接人工。
    • 错误日志: 详细记录错误信息,便于后期分析和改进。
  • LLM生成无效输出: 如果LLM生成了无法解析的JSON或不符合预期的回复,系统应该能够检测到并进行恢复,例如提示LLM重新生成,或者使用一个默认的回复。
7.3 上下文切换与意图重定向
  • 用户可能会在查询订单进度时突然提出一个无关的问题,例如“你们的营业时间是几点?”。当前的系统可能会将它视为 general_query 处理。更高级的系统需要:
    • 动态意图切换: 允许用户在不完成当前任务的情况下切换到新任务,并在完成后可以选择返回原任务或结束。
    • 意图优先级: 定义不同意图的优先级,例如,紧急投诉可能高于一般查询。
    • 确认机制: 在意图切换时,可以向用户确认:“您是想问营业时间吗?那订单查询的问题我们稍后再处理,可以吗?”
7.4 人工干预与监控
  • 人工转接的更精细化控制: 不仅仅是human_handoff_node,系统可以在多轮失败后、用户明确要求后、或检测到情绪负面时,主动建议转接人工。
  • 实时监控与运营: 监控客服系统的对话数据、工具调用成功率、用户满意度等指标,及时发现问题并进行优化。
  • 人机协作: 在某些复杂场景下,可以设计系统先提供初步信息,然后将上下文和初步信息一同转交给人工客服,提升人工客服的效率。
7.5 持久化与状态恢复
  • 当前的 AgentState 仅存在于单次 app.stream() 调用中。在实际应用中,需要将会话状态持久化到数据库中(例如Redis、PostgreSQL),以便用户在中断对话后可以从上次离开的地方继续。
  • LangChain提供了 RunnableWithMessageHistory,可以方便地集成消息历史的持久化。

8. 展望未来:多Agent协作与深度集成

随着LangGraph和LLM技术的发展,智能客服系统将进一步演进:

  • 多Agent协作: 针对不同的客服任务(如订单管理、技术支持、销售咨询),可以设计多个专业的LangGraph代理。当一个代理无法处理时,可以将其转交给另一个更专业的代理,形成一个Agent网络。
  • 情感识别与情绪管理: 结合情感分析技术,识别用户的情绪状态,从而调整回复的语气和策略,例如在用户情绪激动时优先转接人工。
  • 主动式服务: 基于用户行为和历史数据,系统可以主动向用户推送相关信息,如订单状态更新、促销活动等。
  • 与企业内部系统深度集成: 不仅仅是简单的API调用,而是通过LangGraph构建的代理能够更深入地理解企业内部流程和数据模型,实现更复杂的自动化任务,如自动修改订单、处理退换货申请等。

结语

今天,我们深入探讨了如何利用LangGraph构建一个支持“处理进度查询”和“投诉转接”的闭环智能客服系统。从图谱状态的定义、工具的集成,到核心节点的实现和流程的编排,我们展示了LangGraph在构建复杂、有状态、意图驱动的LLM应用方面的强大能力。通过模块化的设计和清晰的路由逻辑,我们不仅能够响应用户请求,还能主动获取信息、执行外部动作,并维持连贯的对话体验。这仅仅是开始,未来智能客服的潜力无限,期待各位能够将这些技术应用到更广阔的实践中。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐