手把手用 Spring AI 做一个智能客服:意图识别 + 工具调用 + 人工无缝切换

张开发
2026/4/13 20:40:18 15 分钟阅读

分享文章

手把手用 Spring AI 做一个智能客服:意图识别 + 工具调用 + 人工无缝切换
Spring AI 实战系列 | 进阶篇 第 1 篇智能客服系统多轮对话 工具调用 人工兜底系列说明本文为《Spring AI 实战系列 进阶篇》第 1 篇前置知识完成入门篇第 2 篇Tool Calling和第 5 篇Advisors预计阅读时间14 分钟 开发环境说明重要说明本文章是基于本地模型进行讲解不依赖任何云端 API Key真正零成本、可离线运行。Ps但是电脑配置确实带不动“高智商”的大模型后面项目源码是换了智谱的大模型去跑的项目配置电脑型号MagicBook Pro 14 202514.6 吋CPUIntel Core Ultra 5MTL Ultra5核显Intel UMA内存24GB硬盘1TB SSD本地模型Ollama qwen2.5:7b通义千问Embedding 模型qwen3-embedding:0.6b为什么选择本地模型对比项云端 APIOpenAI 等本地模型Ollama费用按 Token 计费免费网络需要联网完全离线可用数据安全数据上传到第三方数据不出本地响应速度依赖网络延迟本地推理无网络延迟适用场景生产环境、高并发开发测试、个人项目模型下载命令# 安装 OllamamacOS/Linuxcurl-fsSLhttps://ollama.ai/install.sh|sh# 下载 Chat 模型ollama pull qwen2.5:7b# 下载 Embedding 模型用于 RAGollama pull qwen3-embedding:0.6b# 验证ollama list 目录业务场景与核心挑战系统架构设计意图识别AI 路由分发多轮对话与工具调用人工接管机制一、业务场景与核心挑战1.1 场景描述搭建一个电商平台的智能客服系统用户可以通过对话完成功能示例对话订单查询“我的订单 B-12345 到哪里了”退换货“我想退货订单号是 R-98765”产品咨询“这款手机的电池容量是多少”投诉建议“你们的发货太慢了我要投诉”转人工“我要找人工客服”1.2 核心挑战挑战解决方案用户意图多样意图识别 路由分发需要查询实时数据Tool Calling 连接后端服务多轮对话上下文ChatMemory 会话状态复杂问题处理不了满意度评分 → 人工接管系统稳定性降级策略 异常处理二、系统架构设计2.1 整体架构┌─────────────────────────────────────────────────────────────────┐ │ 前端 (Vue3) │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ │ │ 聊天界面 │ │ 满意度评分 │ │ 人工客服接入WebSocket│ │ │ └─────────────┘ └─────────────┘ └─────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ Spring Boot 后端 │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ IntentRouter意图识别 路由分发 │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ 订单查询 │ │ 退换货 │ │ 产品咨询 │ │ 转人工 │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 各场景 ChatClient配置不同 System Prompt Tools │ │ │ │ - OrderQueryClient订单查询工具 │ │ │ │ - RefundClient退换货工具 │ │ │ │ - ProductClient知识库 RAG │ │ │ │ - HumanHandoffClient转人工逻辑 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ SessionManager会话状态管理 │ │ │ │ - ChatMemory 存储Redis 分布式会话 │ │ │ │ - 满意度评分 │ │ │ │ - 人工接管标记 │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌──────────────────┬──────────────────┐ ↓ ↓ ↓ ┌─────────────────┐ ┌──────────────┐ ┌─────────────────────────┐ │ Redis │ │ MySQL │ │ 外部服务 │ │ 用户登录会话 │ │ 订单数据 │ │ 库存/知识库/人工客服 │ │ ChatMemory缓存 │ │ 退换货记录 │ │ │ └─────────────────┘ └──────────────┘ └─────────────────────────┘2.2 核心组件说明组件职责技术选型IntentRouter识别用户意图路由到对应处理器结构化输出 策略模式ChatClient实例各场景独立的 AI 客户端Spring AI ChatClientSessionManager管理多轮对话上下文Redis 分布式 ChatMemoryTool方法连接后端业务系统MySQL 订单数据Tool注解HumanHandoffService监控满意度触发人工接管WebSocket MySQL 记录三、意图识别AI 路由分发3.1 为什么需要意图识别用户的一句话可能对应多种处理方式用户我的订单在哪里 → 意图ORDER_QUERY → 调用订单查询工具 用户这款手机支持快充吗 → 意图PRODUCT_QA → 查询知识库 RAG 用户我要找人工 → 意图HUMAN_HANDOFF → 转人工客服3.2 意图识别实现使用结构化输出让 AI 直接返回意图分类publicenumIntent{ORDER_QUERY,// 订单查询REFUND_REQUEST,// 退换货PRODUCT_QA,// 产品咨询COMPLAINT,// 投诉建议HUMAN_HANDOFF,// 转人工GREETING,// 问候UNKNOWN// 未知意图}publicrecordIntentResult(Intentintent,// 识别到的意图doubleconfidence,// 置信度 0.0-1.0StringextractedParams// 提取的参数如订单号){}IntentRecognizer 服务ServicepublicclassIntentRecognizer{privatefinalChatClientchatClient;publicIntentRecognizer(ChatClient.Builderbuilder){this.chatClientbuilder.defaultSystem( 你是智能客服的意图识别专家。 分析用户输入判断属于以下哪种意图 - ORDER_QUERY: 查询订单状态、物流信息 - REFUND_REQUEST: 申请退货、换货、退款 - PRODUCT_QA: 询问产品规格、功能、价格 - COMPLAINT: 投诉、抱怨、建议 - HUMAN_HANDOFF: 明确要求人工客服 - GREETING: 打招呼、问候 - UNKNOWN: 无法判断的意图 同时提取关键参数如订单号、产品名称。 confidence 是 0.0-1.0 的置信度。 ).build();}publicIntentResultrecognize(StringuserInput){returnchatClient.prompt().user(userInput).call().entity(IntentResult.class);}}3.3 路由分发器ServicepublicclassIntentRouter{privatefinalIntentRecognizerrecognizer;privatefinalMapIntent,ChatHandlerhandlers;publicIntentRouter(IntentRecognizerrecognizer,OrderQueryHandlerorderHandler,RefundHandlerrefundHandler,ProductQAHandlerproductHandler,HumanHandoffHandlerhumanHandler){this.recognizerrecognizer;this.handlersMap.of(Intent.ORDER_QUERY,orderHandler,Intent.REFUND_REQUEST,refundHandler,Intent.PRODUCT_QA,productHandler,Intent.HUMAN_HANDOFF,humanHandler);}publicChatResponsehandle(StringsessionId,StringuserInput){// 1. 识别意图IntentResultintentrecognizer.recognize(userInput);// 2. 置信度低时用通用回复if(intent.confidence()0.6){returnChatResponse.builder().reply(抱歉我不太理解您的问题。您可以问订单查询、退换货、产品咨询或输入人工转接客服。).intent(Intent.UNKNOWN).build();}// 3. 路由到对应处理器ChatHandlerhandlerhandlers.getOrDefault(intent.intent(),handlers.get(Intent.PRODUCT_QA)// 默认走产品咨询);returnhandler.handle(sessionId,userInput,intent);}}四、多轮对话与工具调用4.1 订单查询场景工具定义ComponentpublicclassOrderTools{privatefinalOrderServiceorderService;// 真实订单服务publicOrderTools(OrderServiceorderService){this.orderServiceorderService;}Tool(description根据订单号查询订单详情和物流状态)publicStringqueryOrder(ToolParam(description订单号如 B-12345)StringorderNo){OrderorderorderService.findByOrderNo(orderNo);if(ordernull){return未找到订单orderNo请检查订单号是否正确。;}returnString.format(订单号%s\n状态%s\n商品%s\n物流%s,order.getOrderNo(),order.getStatus(),order.getProductName(),order.getLogisticsInfo());}Tool(description查询用户最近的订单列表)publicStringqueryRecentOrders(ToolParam(description用户ID)StringuserId){ListOrderordersorderService.findRecentByUserId(userId,5);// 格式化返回...returnformatOrderList(orders);}}订单查询处理器ComponentpublicclassOrderQueryHandlerimplementsChatHandler{privatefinalChatClientchatClient;privatefinalOrderToolsorderTools;privatefinalChatMemoryStorememoryStore;publicOrderQueryHandler(ChatClient.Builderbuilder,OrderToolsorderTools,ChatMemoryStorememoryStore){this.orderToolsorderTools;this.memoryStorememoryStore;this.chatClientbuilder.defaultSystem( 你是订单查询助手帮助用户查询订单状态和物流信息。 如果用户没有提供订单号询问用户订单号。 回复要简洁友好关键信息状态、物流要突出显示。 ).defaultTools(orderTools).build();}OverridepublicChatResponsehandle(StringsessionId,StringuserInput,IntentResultintent){// 获取或创建该会话的 ChatMemoryChatMemorychatMemorymemoryStore.getOrCreate(sessionId);StringreplychatClient.prompt().user(userInput).advisors(newMessageChatMemoryAdvisor(chatMemory)).call().content();returnChatResponse.builder().reply(reply).intent(Intent.ORDER_QUERY).build();}}4.2 退换货场景带状态机退换货流程较长需要会话状态机管理publicenumRefundState{INIT,// 初始状态ORDER_CHECKED,// 已确认订单REASON_COLLECTED,// 已收集退货原因CONFIRMED,// 用户确认COMPLETED// 完成}ComponentpublicclassRefundHandlerimplementsChatHandler{privatefinalMapString,RefundSessionsessionsnewConcurrentHashMap();OverridepublicChatResponsehandle(StringsessionId,StringuserInput,IntentResultintent){RefundSessionsessionsessions.computeIfAbsent(sessionId,k-newRefundSession(sessionId));returnswitch(session.getState()){caseINIT-handleInit(session,userInput,intent);caseORDER_CHECKED-handleReasonCollect(session,userInput);caseREASON_COLLECTED-handleConfirm(session,userInput);caseCONFIRMED-handleSubmit(session);default-ChatResponse.builder().reply(您的退换货申请已提交客服将在 24 小时内处理。).build();};}privateChatResponsehandleInit(RefundSessionsession,Stringinput,IntentResultintent){// 提取订单号查询订单StringorderNoextractOrderNo(input,intent.extractedParams());OrderorderorderService.findByOrderNo(orderNo);if(ordernull){returnChatResponse.builder().reply(未找到订单请提供正确的订单号。).build();}session.setOrderNo(orderNo);session.setOrder(order);session.setState(RefundState.ORDER_CHECKED);returnChatResponse.builder().reply(String.format(已找到订单%s%s\n请问退货原因是什么,order.getProductName(),order.getOrderNo())).build();}// ... 其他状态处理}五、人工接管机制5.1 触发条件触发方式说明用户主动用户输入人工、找客服等关键词满意度低用户对 AI 回复评分 ≤ 2 星多次失败同一意图识别失败 3 次以上系统异常AI 服务异常或工具调用失败5.2 实现代码ServicepublicclassHumanHandoffService{privatefinalSimpMessagingTemplatemessagingTemplate;// WebSocketprivatefinalHumanAgentQueueagentQueue;/** * 触发人工接管 */publicvoidhandoffToHuman(StringsessionId,Stringreason){// 1. 标记会话状态SessionManager.markAsHumanHandoff(sessionId);// 2. 获取会话历史ListMessagehistoryChatMemoryStore.getHistory(sessionId);// 3. 加入人工客服队列HandoffRequestrequestHandoffRequest.builder().sessionId(sessionId).reason(reason).chatHistory(history).timestamp(LocalDateTime.now()).build();agentQueue.enqueue(request);// 4. 通知前端切换界面messagingTemplate.convertAndSend(/topic/handoff/sessionId,Map.of(status,HANDOFF,reason,reason));}/** * 满意度评分监控 */publicvoidrecordSatisfaction(StringsessionId,intscore){if(score2){handoffToHuman(sessionId,用户满意度低score星);}}}5.3 WebSocket 实时通信ControllerpublicclassChatWebSocketController{privatefinalIntentRouterrouter;privatefinalHumanHandoffServicehandoffService;MessageMapping(/chat/{sessionId})SendTo(/topic/chat/{sessionId})publicChatMessagehandleChat(DestinationVariableStringsessionId,ChatMessagemessage){// 检查是否已转人工if(SessionManager.isHumanHandoff(sessionId)){// 直接转发给人工客服系统returnforwardToHumanAgent(sessionId,message);}// AI 处理ChatResponseresponserouter.handle(sessionId,message.getContent());// 检查是否需要转人工如 AI 无法处理if(response.getIntent()Intent.HUMAN_HANDOFF){handoffService.handoffToHuman(sessionId,用户要求转人工);}returnChatMessage.builder().sender(AI).content(response.getReply()).timestamp(LocalDateTime.now()).build();}}六、效果展示关注公众号「AI日撰」点击菜单「获取源码」获取完整代码Gitee 仓库。

更多文章