从零到一搭建智能客服系统(LangGraph + FastAPI + 智谱AI 实战)
一、这个项目是做什么的「π域」是一个快递行业的 AI 智能客服系统。它的核心价值是用 AI Agent 替代 80% 的重复性人工客服工作实现 7×24 小时秒级响应。具体来说它能做这几件事FAQ 问答用户问“运费怎么算”、“寄到北京要多久”——系统基于知识库自动回答订单查询用户输入运单号系统调用快递鸟 API 返回真实物流轨迹投诉工单用户说“包裹破损了”系统提取信息自动生成工单编号地址修改用户说“改地址”系统引导用户提供新地址转人工用户说“转人工”系统通过 WebSocket 排队客服接单后实时对话二、技术栈选型为什么是这些组件选型选型理由后端框架FastAPI轻量、异步、自动生成 Swagger 文档开发效率极高多 Agent 编排LangGraph支持状态管理和条件路由比 LangChain 更灵活可控大模型智谱 GLM-4-Flash性价比极高响应速度快中文能力优秀向量数据库Chroma轻量级、本地持久化、零配置无需额外部署实时通信WebSocket双向实时通信天然适合排队 聊天场景前端原生 HTML CSS JS无框架依赖轻量快速酷黑主题三、系统架构一张图看懂用户输入 │ ▼ FastAPI /chat 接口 │ ▼ ───────────────────────────────────────────────────────── │ LangGraph 多 Agent 编排 │ │ │ │ ──────────── ──────────── │ │ │ 意图识别 │ → │ 条件路由 │ │ │ │ (Intent) │ │ (Router) │ │ │ ──────────── ─────────── │ │ │ │ │ ───────────────────────────────── │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ──────── ──────── ──────── │ │ │ FAQ │ │ 订单 │ │ 投诉/转人工│ │ │ │ Agent │ │ Agent │ │ Agent │ │ │ ──────── ──────── ──────── │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ Chroma检索 快递鸟API WebSocket排队 │ ───────────────────────────────────────────────────────── │ ▼ 返回最终回答四、核心功能详细实现1. 意图识别Few-shot 上下文记忆问题在零样本场景下模型对“寄到广州要几天”这类边缘问题容易误判为 other。解决方案构建 Few-shot 示例库运行时动态检索最相似的 3 个示例注入 Prompt。示例库结构data/intent_examples.json[{question:怎么算运费,intent:faq},{question:寄到北京要多久,intent:faq},{question:查一下我的快递,intent:order},{question:转人工,intent:human},{question:改地址,intent:change_address}]核心代码defintent_agent(state:AgentState):questionstate.get(user_question)examplesload_examples()similarfind_similar_examples(question,examples,top_k3)few_shot_text\n.join([f用户{ex[question]}\n输出{{\intent\: \{ex[intent]}\}}forexinsimilar])promptf 你是一个快递客服意图识别专家。判断用户问题属于以下哪一类 - faq: 咨询常见问题 - order: 查询订单/物流 - complaint: 投诉或理赔 - human: 转人工 - change_address: 修改地址 - other: 其他 以下是一些参考示例{few_shot_text}用户问题{question}多轮上下文记忆维护 context_summary 字段将最近 2 轮对话摘要传入意图识别 Prompt解决指代消解问题用户查一下我的快递 AI请提供运单号 用户YT3762892935155 ✅ 系统能理解这是在补充运单号2. RAG 知识库FAQ 问答技术方案分块策略chunk_size512重叠 50 字符Embedding 模型智谱 embedding-2向量库Chroma本地持久化检索策略Top-3 相似片段FAQ 加载代码defload_faq():chunkssplit_faq(data/faq_knowledge.md,chunk_size512,overlap50)collectionget_chroma_collection()ids[ffaq_{i}foriinrange(len(chunks))]collection.add(documentschunks,idsids)检索 生成代码deffaq_agent(state:AgentState):querystate.get(user_question)collectionget_chroma_collection()resultscollection.query(query_texts[query],n_results3)context\n\n.join(results[documents][0])promptf基于以下知识回答用户问题\n{context}\n问题{query}return{final_answer:call_llm(prompt)}3. 真实订单查询快递鸟 API对接步骤注册快递鸟账号获取 EBusinessID 和 APIKey封装签名算法MD5 Base64实现智能识别快递公司编码根据运单号前缀核心代码defquery_order(order_id:str):# 1. 智能识别快递公司shipper_coderecognize_express(order_id)# SF/YTO/ZTO...ifnotshipper_code:return{code:-1,msg:无法识别该运单号所属快递公司}# 2. 构造请求request_dataf{{LogisticCode:{order_id},ShipperCode:{shipper_code}}}params{EBusinessID:CUSTOMER_CODE,RequestType:8002,RequestData:request_data,DataSign:encrypt(request_data,APP_KEY),DataType:2}# 3. 发送请求并解析resprequests.post(https://api.kdniao.com/api/dist,dataparams)resultresp.json()ifresult.get(Success):return{code:0,data:{traces:result.get(Traces,[])}}return{code:-1,msg:result.get(Reason,查询失败)}4. 转人工闭环WebSocket架构设计用户端 ws──→ WebSocket 服务器 ←──ws── 客服端 │ ├── 排队队列 ├── 活跃会话管理 ─ 消息路由消息类型类型方向说明join_queue用户 → 服务器加入排队agent_ready客服 → 服务器客服上线agent_take客服 → 服务器接单chat双方 → 服务器聊天消息转发agent_offline客服 → 服务器客服下线end_session客服 → 服务器结束会话WebSocket 消息路由核心代码asyncdefhandle_message(ws,message):datajson.loads(message)msg_typedata.get(type)client_iddata.get(client_id)ifmsg_typejoin_queue:waiting_queue.append({user_id:client_id,ws:ws})awaitws.send(json.dumps({type:queue_status,position:len(waiting_queue)}))elifmsg_typeagent_take:user_infowaiting_queue.pop(0)active_sessions[user_info[user_id]]{user_ws:user_info[ws],agent_ws:ws}awaituser_info[ws].send(json.dumps({type:assigned}))awaitws.send(json.dumps({type:assigned}))elifmsg_typechat:targetdata.get(target)contentdata.get(content)iftargetagent:awaitactive_sessions[client_id][agent_ws].send(...)eliftargetuser:foruid,sessioninactive_sessions.items():ifsession[agent_ws]ws:awaitsession[user_ws].send(...)五、踩坑记录真实经验问题原因解决方案KeyError: ‘“intent”’Prompt 中 JSON 示例的花括号被 str.format() 误解析将 {“intent”: “faq”} 改为 {{“intent”: “faq”}}LLM 返回 json {…} 模型有时会输出 Markdown 代码块用正则 r’json\s*({.?})\s’ 提取纯 JSONKeyError: ‘user_question’LangGraph 状态传递丢失字段使用 state.get(“user_question”, “”) 安全取值快递鸟返回没有可用套餐账号未开通服务或套餐未生效切换沙箱环境测试或联系客服开通免费套餐WebSocket 客服消息用户收不到路由逻辑错误未正确映射 agent_ws 到 user_id在 active_sessions 中双向存储排队列表不刷新renderQueue 动态修改 h3 导致 DOM 引用丢失预置 refreshSpinner只更新内容不重建 DOM六、项目成果功能完成度功能状态FAQ 问答✅订单查询真实 API✅投诉工单生成✅地址修改✅转人工闭环WebSocket✅用户退出人工✅客服结束会话✅前端酷黑主题✅代码结构pisphere/ ── main.py # FastAPI 入口 ── state.py # AgentState 定义 ── graph.py # LangGraph 图构建 ── websocket_server.py # WebSocket 服务器 ── agents/ │ ├── intent.py # 意图识别 │ ├── faq.py # FAQ 检索 │ ├── order.py # 订单查询 │ ├── complaint.py # 投诉工单 │ ├── change_address.py # 地址修改 │ ├── handoff.py # 转人工 │ ─ fallback.py # 兜底 ── rag/ │ ├── chroma_client.py # Chroma 客户端 │ ─ faq_loader.py # FAQ 加载器 ── web/ │ ├── index.html # 用户端 │ ─ customer_service.html # 客服端 ── data/ ─ intent_examples.json # Few-shot 示例库七、后续优化方向优先级优化项说明高环境变量配置API Key 等敏感信息移入 .env高Docker 容器化便于部署和迁移中工单存储升级JSON → SQLite/PostgreSQL中日志结构化print → logging 模块低Embedding 模型对比测试 bge-large-zh 等模型对检索效果的影响八、总结从零到一我用 3 周时间 完成了「π域」智能客服系统的开发。这个项目让我深入理解了RAG 完整流程分块 → 向量化 → 存储 → 检索 → 生成LangGraph 多 Agent 编排状态管理、条件路由、节点协作WebSocket 实时通信排队、接单、消息转发Prompt Engineering 实战Few-shot、上下文注入、置信度阈值更重要的是这个项目验证了 “Java 后端开发者可以快速转型 AI Agent 开发” ——你不需要成为算法专家也能构建出可用的 AI 产品。