news 2026/7/31 13:44:24

LangGraph构建智能客服审批流程的技术实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangGraph构建智能客服审批流程的技术实践

1. 项目概述:构建支持审批与恢复执行的客服Agent

去年在金融行业实施智能客服系统时,客户突然提出个需求:"当涉及交易操作时,必须插入人工审批环节,且审批后要能自动恢复之前的对话流程"。这个需求让我开始研究LangGraph这个新兴框架,最终设计出一套完整的解决方案。本文将分享如何用LangGraph构建具备审批中断与恢复能力的智能客服系统,这套方案已在银行信用卡业务中稳定运行8个月,处理了超过12万次带审批流程的客户请求。

LangGraph是LangChain团队推出的工作流编排框架,特别适合构建有复杂状态转移的AI应用。相比传统的线性对话流程,它通过图结构(Graph)实现以下关键能力:

  • 任意节点的条件跳转
  • 循环执行控制
  • 异步操作支持
  • 持久化状态管理

这些特性恰好满足客服场景中的审批需求:当用户请求触发审批条件时,系统能暂停当前对话,将请求路由至人工审批台,待审批完成后从断点恢复执行。整个过程对用户呈现为自然的等待状态,而非生硬的流程重启。

2. 核心架构设计

2.1 状态机模型设计

整个Agent的核心是一个有限状态机,包含以下关键状态节点:

状态节点触发条件输出动作
意图识别用户输入生成意图标签
路由判断意图标签分流到业务模块或审批模块
业务处理常规请求执行标准客服逻辑
审批触发敏感操作请求生成审批工单
审批等待工单状态向用户返回等待提示
结果执行审批结果执行通过/拒绝动作

在LangGraph中通过StateGraph实现这个状态机:

from langgraph.graph import StateGraph workflow = StateGraph(AgentState) # 添加所有状态节点 workflow.add_node("intent_recognizer", intent_recognizer) workflow.add_node("router", router) workflow.add_node("business_handler", business_handler) workflow.add_node("approval_trigger", approval_trigger) workflow.add_node("approval_waiting", approval_waiting) workflow.add_node("result_executor", result_executor)

2.2 审批流程集成

审批系统对接采用HTTP长轮询机制,关键设计点包括:

  1. 工单生成:当触发审批时,Agent会:

    • 冻结当前对话上下文(包括变量、历史消息等)
    • 生成唯一工单ID
    • 将审批所需信息打包发送给审批系统
  2. 状态恢复:通过Redis持久化存储以下数据:

    { "ticket_id": "APP-20240615-001", "user_id": "u12345", "frozen_state": {...}, # 序列化的对话状态 "expire_time": 3600 # 1小时TTL }
  3. 回调处理:审批系统提供两个关键接口:

    • POST /approval/request提交审批请求
    • GET /approval/status/{ticket_id}查询审批状态

重要提示:务必实现审批系统的幂等接口,防止网络重试导致重复审批

3. 关键实现细节

3.1 状态持久化方案

LangGraph的Checkpoint机制是本项目的核心技术点。我们自定义了Redis检查点存储:

from langgraph.checkpoint import BaseCheckpointSaver import pickle class RedisCheckpoint(BaseCheckpointSaver): def __init__(self, redis_client): self.redis = redis_client async def put(self, config, step, state): key = f"checkpoint:{config['configurable']['thread_id']}" await self.redis.setex( name=key, time=3600, value=pickle.dumps({"step": step, "state": state}) ) async def get(self, config): key = f"checkpoint:{config['configurable']['thread_id']}" data = await self.redis.get(key) return pickle.loads(data) if data else None

3.2 审批恢复逻辑

当收到审批通过通知时,系统执行以下恢复流程:

  1. 根据工单ID检索原始对话状态
  2. 重建LangGraph执行上下文
  3. 从断点继续执行:
async def resume_flow(ticket_id): # 从审批系统获取结果 approval_result = get_approval_result(ticket_id) # 从Redis恢复检查点 checkpoint = await redis_checkpoint.get( {"configurable": {"thread_id": ticket_id}} ) # 重建执行图 app = workflow.compile(checkpointer=redis_checkpoint) await app.update_state( checkpoint["state"], {"approval_result": approval_result} ) # 继续执行 return await app.ainvoke( inputs=None, config={"configurable": {"thread_id": ticket_id}} )

4. 实战问题与解决方案

4.1 超时处理策略

在实际运行中我们发现两个典型问题:

  1. 审批响应时间不可控(人工审批可能耗时数小时)
  2. 用户可能在等待期间发送新消息

解决方案是设计双重超时机制:

%% 注意:实际实现中应删除此mermaid图,仅保留文字描述 %% stateDiagram [*] --> 等待审批 等待审批 --> 超时提醒: 30秒无响应 超时提醒 --> 保持连接: 用户未离开 保持连接 --> 结果到达: 审批完成 超时提醒 --> 结束会话: 用户离开 结束会话 --> 邮件通知: 审批完成后

具体实现代码:

async def approval_waiting(state): start_time = time.time() while True: # 每30秒检查一次审批状态 if time.time() - start_time > 30: yield {"status": "waiting", "message": "审批处理中,请稍候..."} result = check_approval_status(state['ticket_id']) if result: return {"status": "completed", "result": result} await asyncio.sleep(5) # 非阻塞等待

4.2 上下文一致性保障

审批前后需要保持对话上下文的连贯性。我们采用以下策略:

  1. 快照对比:在审批触发时保存对话记忆快照
  2. 差异合并:恢复时对比新旧记忆,保留关键实体信息
  3. 意图保持:强制维持原始意图标签不变

关键实现代码:

def freeze_context(state): return { "intent": state["intent"], "entities": extract_entities(state["messages"]), "slots": state["slots"].copy() } def restore_context(frozen, new_state): new_state["intent"] = frozen["intent"] for entity in frozen["entities"]: if not check_entity_exists(entity, new_state["messages"]): add_entity_to_context(entity, new_state) return new_state

5. 性能优化实践

在压力测试中我们发现三个性能瓶颈及解决方案:

  1. 检查点序列化开销

    • 问题:默认pickle序列化在大型对话历史上耗时明显
    • 优化:改用orjson并压缩存储
    import orjson import zlib def serialize_state(state): return zlib.compress(orjson.dumps(state)) def deserialize_state(data): return orjson.loads(zlib.decompress(data))
  2. 审批状态查询风暴

    • 现象:大量并发请求轮询审批状态
    • 方案:实现基于Redis Pub/Sub的事件通知机制
    async def wait_approval(ticket_id): pubsub = redis.pubsub() await pubsub.subscribe(f"approval:{ticket_id}") async for message in pubsub.listen(): if message["type"] == "message": return message["data"]
  3. LangGraph执行图初始化耗时

    • 现象:每次恢复执行都重新编译图
    • 缓存:预编译常见流程的执行图
    @lru_cache(maxsize=10) def get_compiled_workflow(flow_type): if flow_type == "standard": return standard_flow.compile() elif flow_type == "approval": return approval_flow.compile()

这套优化方案使系统在200并发用户下,平均响应时间从1.8秒降至420毫秒。

6. 扩展设计:多级审批流

对于更复杂的金融场景,我们扩展支持多级审批:

  1. 条件审批路由

    def route_approval_level(request): if request["amount"] > 10000: return "manager_approval" elif request["amount"] > 5000: return "supervisor_approval" else: return "auto_approval"
  2. 并行审批处理

    async def parallel_approval(state): tasks = [] for approver in state["required_approvers"]: task = send_approval_request(approver, state) tasks.append(task) results = await asyncio.gather(*tasks) return all(r["approved"] for r in results)
  3. 审批链可视化

    def print_approval_chain(chain): print("Approval Flow:") current = chain while current: print(f"→ {current['role']} ({current['status']})") current = current.get("next")

7. 监控与调试方案

为保障生产环境稳定性,我们实施以下监控措施:

  1. 执行轨迹记录

    class ExecutionLogger: def __init__(self): self.traces = [] def log(self, node, state): self.traces.append({ "timestamp": datetime.now(), "node": node, "state": sanitize_state(state) })
  2. 关键指标埋点

    • 审批触发率
    • 平均审批耗时
    • 恢复执行成功率
    • 上下文丢失率
  3. LangSmith集成

    from langsmith import Client client = Client() def log_to_langsmith(run_id, **kwargs): client.create_feedback( run_id, key="approval_metrics", value=kwargs )

这套监控体系帮助我们及时发现并解决了多个边界条件问题,如审批超时后的状态回滚、网络中断时的补偿重试等。

8. 安全防护措施

在处理敏感业务时,我们实施了以下安全方案:

  1. 审批验证令牌

    def generate_approval_token(ticket_id): payload = { "ticket_id": ticket_id, "exp": datetime.now() + timedelta(hours=1) } return jwt.encode(payload, SECRET_KEY, algorithm="HS256") def verify_token(token): try: return jwt.decode(token, SECRET_KEY, algorithms=["HS256"]) except: return None
  2. 敏感数据脱敏

    def mask_sensitive_data(text): patterns = { r"\b\d{4}-\d{4}-\d{4}-\d{4}\b": "****-****-****-####", r"\b\d{3}-\d{2}-\d{4}\b": "***-**-####" } for pat, repl in patterns.items(): text = re.sub(pat, repl, text) return text
  3. 操作审计日志

    class AuditLogger: def log_operation(self, user, action, metadata): record = { "timestamp": datetime.utcnow(), "user": user, "action": action, "metadata": metadata } es.index(index="audit_log", body=record)

在最近一次安全审计中,这套方案成功防御了23次注入攻击尝试和5次会话劫持尝试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/31 13:43:58

MonoGame WebAssembly调试指南:浏览器控制台与源码映射实战

1. 项目概述:为什么MonoGame WebAssembly调试如此棘手?如果你正在用MonoGame开发跨平台的游戏或应用,并且最终目标是让它在浏览器里跑起来,那你大概率已经和WebAssembly(WASM)打过交道了。把C#/.NET代码编译…

作者头像 李华
网站建设 2026/7/31 13:43:49

STM32 HAL库驱动L298N电机:CubeMX配置与调试实战指南

1. 从零开始:为什么选择STM32 HAL库与L298N驱动电机如果你正在做一个机器人底盘、一个小型传送带,或者任何需要让轮子转起来的嵌入式项目,那么“STM32 L298N 直流有刷电机”这个组合大概率会出现在你的备选方案里。这个组合经典到什么程度呢…

作者头像 李华