资讯动态

Spring AI企业智能客服实战:RAG、工具调用、记忆、流式与兜底五模块落地

发布时间:2026/10/20 3:39:32 来源:尧图企业网站定制
1. 从能聊天到能办事企业客服系统的能力断层在哪里很多团队做智能客服第一版往往是个能对话的Demo接个大模型接口塞一段系统提示词用户问什么就答什么。上线之后才发现问题一大堆——问产品参数模型开始编问订单状态模型说我无法查询用户说了三句话模型忘了第一句回答一次性吐出来用户盯着空白界面等十秒遇到完全超纲的问题模型硬答一通答得离谱还没人兜底。这五个问题恰好对应五个技术能力RAG检索增强生成解决知识从哪来工具调用解决能不能动手做事记忆机制解决记不记得住上下文流式输出解决等得急不急兜底策略解决答不了怎么办。这五块拼在一起才是一个企业级智能客服该有的样子而不是一个套壳聊天框。这篇内容面向的是已经了解Spring Boot、想用Spring AI把客服系统真正落地到生产环境的开发者。我会按十课收官的思路把每个模块的设计动机、核心实现、踩过的坑和实测经验拆开讲。你不需要事先精通RAG或向量数据库但需要对Java后端开发有基本认知。读完之后你应该能独立搭出一套具备知识检索、工具调用、多轮记忆、流式响应和降级兜底的客服系统骨架。先说一个反直觉的结论这五个能力里最难的不是RAG而是兜底。RAG有成熟的向量库和检索方案工具调用有标准的Function Calling协议流式输出Spring AI直接支持记忆无非是存和取。但兜底考验的是你对模型什么时候会出错的理解深度——你得预判它会在哪些场景下胡说、哪些场景下该拒答、哪些场景下该转人工这背后是对业务边界的清晰认知而不是技术堆叠。下面按模块逐个拆。2. RAG检索增强让客服答得出企业自己的知识2.1 为什么纯靠模型参数知识必然翻车大模型的参数知识来自公开训练语料它知道一般退货政策长什么样但不知道你们公司退货政策是7天还是15天。企业客服场景里80%以上的问题都跟企业私有知识相关产品规格、价格政策、售后流程、活动规则。这些内容模型没见过你硬问它它只有两个选择——要么说我不知道要么编一个听起来合理的答案。后者更危险因为用户分不清真假。RAG的思路很直接把企业知识库切成片段存进向量数据库用户提问时先检索出最相关的几个片段把它们作为上下文塞进提示词让模型基于这些片段回答。这样模型的回答就有了事实依据而不是凭空生成。这里有个关键认知RAG不是让模型变聪明而是给模型开卷考试。模型本身的能力没变但它手里多了参考资料。所以RAG的效果上限取决于你的知识库质量和检索精度而不是模型有多强。2.2 文档切分最容易被低估的环节我见过太多团队在RAG上翻车问题不出在模型出在切分。一份产品手册PDF直接按固定字数切切出来的片段可能前半段讲A产品、后半段讲B产品检索时两个都不准。切分策略直接决定检索质量。实操中我推荐按语义结构切分而不是按字数切分。具体做法优先按文档的标题层级切。一级标题下的内容作为一个大块二级标题下作为子块。这样每个块有明确的主题边界。块大小控制在300到800字之间。太小则信息不完整太大则检索时引入噪声。这个区间是实测下来比较稳的。块之间保留10%到20%的重叠。比如上一块结尾是退货需在签收后7天内下一块开头也带上这句避免关键信息正好卡在切分边界上被割裂。每个块要带上元数据来源文档名、章节标题、更新时间。检索出来之后这些元数据可以一起塞给模型让它知道信息出处回答时也能引用来源。// 基于标题层级切分的简化逻辑示意 public ListDocumentChunk splitByHeading(String rawText) { ListDocumentChunk chunks new ArrayList(); String[] lines rawText.split(\n); StringBuilder current new StringBuilder(); String currentHeading 概述; for (String line : lines) { if (line.startsWith(#)) { // 遇到新标题先把上一块存起来 if (current.length() 0) { chunks.add(buildChunk(currentHeading, current.toString())); current.setLength(0); } currentHeading line.replaceAll(^#\\s*, ); } else { current.append(line).append(\n); } } if (current.length() 0) { chunks.add(buildChunk(currentHeading, current.toString())); } return chunks; }注意切分粒度没有万能参数必须拿真实用户问题去测。我的做法是准备50条真实客服问题跑一遍检索看Top3片段里有没有正确答案。命中率低于80%就回去调切分策略。2.3 向量化与检索相似度阈值是道安全阀文档切好之后要转成向量存进向量库。Spring AI支持多种向量存储选型时考虑三点是否支持元数据过滤、是否支持相似度阈值、运维成本。中小规模知识库几万条以内用内存型或轻量级方案就够没必要上重型集群。检索环节有个参数必须重视相似度阈值。默认情况下向量库总会返回Top-K个最相似的片段哪怕这些片段跟问题八竿子打不着。如果你不加阈值过滤用户问今天天气怎么样系统也会硬塞三段退货政策给模型模型就可能被带偏。我的经验是设一个相似度下限比如余弦相似度0.7低于这个值的片段直接丢弃。如果过滤后一个片段都不剩说明知识库里没有相关内容这时候应该走兜底逻辑而不是硬答。这个阈值同样需要拿真实问题去校准设太高会漏掉正确答案设太低会引入噪声。// 带阈值过滤的检索示意 public ListDocument retrieve(String query, double threshold, int topK) { ListDocument candidates vectorStore.similaritySearch( SearchRequest.builder().query(query).topK(topK).build() ); return candidates.stream() .filter(doc - doc.getScore() ! null doc.getScore() threshold) .collect(Collectors.toList()); }2.4 提示词组装把检索结果用对地方检索出来的片段怎么塞进提示词也有讲究。我见过两种错误做法一是把所有片段无脑拼接导致上下文过长、模型注意力分散二是只给片段不给来源模型回答时无法引用。比较稳的做法是按相似度排序取Top3到Top5每个片段标注来源明确告诉模型只基于以下资料回答资料中没有的内容不要编。这句约束非常关键它把模型的回答范围框死在检索结果内。你是一名企业客服助手。请严格基于以下参考资料回答用户问题。 如果参考资料中没有相关信息请明确告知用户这个问题我需要为您转接人工客服不要自行推测。 参考资料 [来源售后政策文档 / 退货章节] 退货需在签收后7天内发起商品需保持完好... [来源售后政策文档 / 换货章节] 换货申请需在签收后15天内提交... 用户问题我买的东西想退最晚什么时候申请实测下来加了资料中没有就不要编这句约束之后模型胡编的概率明显下降。但要注意约束不是万能的模型偶尔还是会越界所以兜底机制必须配合上这个后面讲。3. 工具调用让客服从动嘴变成动手3.1 哪些问题RAG答不了RAG能解决知识问答但有一类问题它天生答不了需要实时数据或需要执行动作的问题。比如我的订单到哪了——订单状态在数据库里不在知识库里帮我改一下收货地址——这需要调用修改接口不是回答能解决的。这类问题的共同点是答案不在静态文档里而在动态系统里。你不可能把每个用户的订单状态都提前塞进知识库只能让模型在对话过程中临时去查。这就是工具调用Function Calling要干的事。3.2 工具定义描述写得好模型才调得准工具调用的原理是你把可用的工具函数定义告诉模型模型根据用户问题判断该调哪个工具、传什么参数然后你执行这个函数把结果返回给模型模型再组织成自然语言回答。这里最关键的不是代码而是工具的描述文本。模型靠描述来判断什么时候该用这个工具。描述写得含糊模型要么该调不调要么乱调。举个例子定义一个查询订单工具Bean Description(根据订单号查询订单的当前状态包括物流进度、是否已发货、预计送达时间。当用户询问订单进度、物流信息时使用此工具。) public FunctionOrderQueryRequest, OrderQueryResponse queryOrder() { return request - orderService.query(request.orderId()); }描述里要包含三样东西这个工具能做什么、需要什么参数、什么场景下该用。第三点最容易被忽略但它直接决定模型的调用准确率。我实测过加上当用户询问订单进度时使用这句场景说明后误调用率下降了一半以上。3.3 参数校验别信模型传的参数模型传过来的参数不能直接信。它可能传个不存在的订单号可能把日期格式搞错可能漏传必填字段。所以工具函数内部必须做校验校验失败要返回明确的错误信息给模型让它知道这次调用失败了原因是参数不对模型会据此调整或者告知用户。public OrderQueryResponse query(String orderId) { if (orderId null || !orderId.matches(^ORD\\d{10}$)) { return OrderQueryResponse.error(订单号格式不正确请提供以ORD开头的10位订单号); } Order order orderRepository.findById(orderId); if (order null) { return OrderQueryResponse.error(未找到该订单请确认订单号是否正确); } return OrderQueryResponse.success(order); }提示工具返回的错误信息要写成给模型看的话而不是给程序员看的异常堆栈。模型看到订单号格式不正确会去引导用户重新提供看到一堆Java异常它只会懵。3.4 多工具编排一次对话可能调多个工具复杂问题往往需要多个工具配合。比如用户说我上周买的那个东西还没到帮我看看顺便问下能不能改地址——这需要先查订单再判断是否可改地址可能还要调改地址工具。Spring AI支持在一次对话中多轮工具调用模型会自己决定调用顺序。但这里有个坑工具调用是有次数上限的。如果不加限制模型可能在某个问题上反复调用同一个工具陷入循环。我的做法是设一个最大调用轮数比如5轮超过就强制中断走兜底话术。这个上限要根据业务复杂度调太低了复杂问题处理不完太高了浪费token还可能死循环。4. 记忆机制多轮对话里记得住比答得对更难4.1 无记忆的客服有多尴尬用户我想退那个蓝色的 客服请问您要退哪个订单 用户就刚才说的那个 客服抱歉我没有理解您指的是哪个订单这种对话在无记忆系统里天天发生。用户以为系统记得上下文系统其实每轮都是失忆状态。多轮对话的核心不是单轮答得多好而是能不能把前几轮的信息串起来。4.2 短期记忆滑动窗口不是简单截断最直接的记忆方案是把历史对话全部带上。但上下文长度有限全带上会超限而且太长的历史会稀释当前问题的权重。所以需要滑动窗口——只保留最近N轮对话。但简单截断有个问题如果用户在第一轮说了订单号后面聊了十轮才说帮我退了这个截断后订单号丢了系统就不知道退哪个。我的做法是滑动窗口 关键信息提取窗口保留最近N轮原文同时对更早的对话做一次摘要把订单号、产品名、用户诉求这类关键实体抽出来作为会话状态单独维护。public class ConversationMemory { private DequeMessage recentWindow new ArrayDeque(); private MapString, String sessionState new HashMap(); private static final int MAX_WINDOW 10; public void add(Message message) { recentWindow.addLast(message); if (recentWindow.size() MAX_WINDOW) { Message evicted recentWindow.pollFirst(); extractEntities(evicted); // 把关键实体存进sessionState } } private void extractEntities(Message message) { // 用正则或轻量模型抽取订单号、产品名等 Matcher m Pattern.compile(ORD\\d{10}).matcher(message.getText()); if (m.find()) { sessionState.put(lastOrderId, m.group()); } } }这样即使原文被截断关键信息还在sessionState里组装提示词时把它带上系统就不会忘事。4.3 长期记忆跨会话的用户偏好短期记忆管一次会话长期记忆管跨会话。比如用户上次咨询过某产品的保修问题这次又来问系统如果能记得这位用户之前关注过保修体验会好很多。长期记忆的实现通常是按用户ID存一份画像记录历史咨询主题、偏好、常见问题。每次新会话开始时把画像摘要加载进上下文。但要注意隐私边界只存业务相关的信息不要存敏感个人数据。4.4 记忆的清理与过期记忆不能无限增长。短期记忆随会话结束清理长期记忆要设过期时间。我的经验是长期记忆保留90天超过就归档或删除。另外用户明确说忘了之前的或者开启新会话时要能主动清空记忆。这个控制权要交给用户而不是系统自己决定记多久。5. 流式输出首字延迟决定用户会不会关掉页面5.1 为什么必须流式大模型生成一段200字的回答如果等全部生成完再返回用户要盯着空白界面等3到8秒。这个等待时间足以让相当一部分用户直接关掉。流式输出让文字一个字一个字往外蹦用户看到第一个字可能只要几百毫秒感知上的等待时间大幅缩短。这不是体验优化是留存问题。实测数据上首字延迟从3秒降到500毫秒用户中途放弃率能降一半以上。5.2 Spring AI的流式实现Spring AI对流的支持比较直接返回Flux类型即可。核心是把模型的流式响应透传到前端中间不要做缓冲。GetMapping(value /chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString chatStream(RequestParam String sessionId, RequestParam String message) { return chatService.streamChat(sessionId, message) .map(chunk - chunk.getOutput().getText()); }前端用EventSource或fetch的流式读取来接。这里要注意SSE的格式每个数据块要以data:开头以空行结尾否则前端解析会出问题。5.3 流式与工具调用的冲突处理流式和工具调用放一起会打架。工具调用需要模型先输出一个结构化的调用请求你执行完再把结果喂回去模型再生成最终回答。这个过程中模型的第一段输出是我要调工具而不是给用户看的文字。如果你直接流式透传用户会看到一堆JSON。处理办法是在工具调用阶段不流式等最终回答阶段再流式。具体来说检测到模型输出的是工具调用请求时先缓冲不发送工具执行完、模型开始生成自然语言回答时再开启流式透传。Spring AI的流式响应里可以通过判断chunk类型来区分。5.4 流式下的兜底怎么插流式输出一旦开始中途出错就很尴尬——用户已经看到半句话了。所以兜底逻辑要在流式开始前就判断好检索没命中、工具调用失败、模型返回异常这些情况在流式开始前就能识别直接走兜底话术。如果流式过程中才出错只能发一个中断信号前端补一句抱歉回答中断了请重试。6. 兜底策略决定系统下限的关键一环6.1 兜底不是答不上来就说不知道很多团队把兜底理解成一句抱歉我不太明白。这太粗糙了。兜底要分场景场景触发条件兜底动作知识未命中检索相似度全部低于阈值引导用户换个问法或转人工工具调用失败接口超时、参数错误告知具体原因建议重试或转人工模型拒答触发安全策略礼貌说明无法回答引导合规提问超出业务范围问题与客服无关明确告知服务范围不硬答连续多轮未解决同一问题追问超过3轮主动转人工不同场景的兜底话术和动作都不一样。统一用一句我不知道会让用户觉得系统很蠢分场景处理才能让用户觉得它至少知道自己不知道什么。6.2 转人工的触发时机转人工是兜底的终极手段但触发时机很关键。触发太早用户觉得系统没用触发太晚用户已经烦了。我的经验是设几个触发点用户明确说转人工找客服——立即转同一问题连续追问3轮仍未解决——主动询问是否转人工检索连续2次未命中——建议转人工用户情绪明显负面检测到抱怨词汇——优先转人工转人工时要把上下文一起带过去包括用户问题、系统已尝试的回答、会话状态。人工客服接手时不用让用户重复一遍体验会好很多。6.3 兜底话术的设计原则兜底话术要满足三点承认不知道、给出下一步、不甩锅。差的兜底对不起我不明白你的意思。——用户不知道接下来该干嘛。好的兜底这个问题我暂时没有找到准确的答案。您可以换个说法再问一次或者我帮您转接人工客服您看可以吗——承认了局限给了两个选项把决定权交给用户。6.4 兜底效果的监控兜底触发率是个重要指标。如果兜底触发率超过20%说明知识库覆盖不够或者检索有问题要回去优化RAG。如果转人工率突然升高可能是某类问题集中爆发要排查是不是知识库更新了但向量库没同步。这些指标要接监控不能等用户投诉了才发现。7. 十课收官模块串联与上线前的自检清单7.1 五个模块的调用顺序把五个模块串起来一次完整的对话流程是这样的用户发消息先查记忆加载会话状态和历史上下文用当前问题去向量库检索拿到相关知识片段组装提示词系统指令 检索结果 会话状态 历史对话 当前问题把可用工具的定义一起传给模型模型判断直接回答还是调用工具如果调工具执行工具把结果喂回模型回到第5步模型生成最终回答流式透传给前端全程监控检索是否命中、工具是否成功、是否触发兜底对话结束更新记忆记录指标这个流程里任何一步失败都要能优雅降级而不是整个链路崩掉。7.2 上线前的自检清单在把系统推给真实用户之前我会跑一遍这个清单知识库覆盖度拿100条真实历史客服问题测检索命中率低于85%不上线工具调用准确率构造50条需要调工具的问题看模型调用是否正确低于90%要调描述记忆连续性构造多轮对话验证关键信息在10轮后是否还在流式首字延迟P95首字延迟超过1.5秒要排查兜底触发率正常问题不应触发兜底触发率超过15%要查原因转人工链路手动触发转人工验证上下文是否正确传递异常注入手动让向量库、工具接口、模型接口分别超时看系统是否优雅降级7.3 几个我踩过的坑坑一检索阈值设太高导致大量误兜底。一开始我把相似度阈值设到0.8结果很多正常问题检索不到全走了兜底。后来降到0.65命中率上来了但噪声也多了。最后是分场景设阈值——产品参数类问题阈值高一点政策咨询类低一点。坑二工具描述写得太技术化。一开始工具描述写的是调用orderService.query方法查询订单模型根本不知道什么时候该用。改成当用户询问订单进度、物流信息时使用之后调用准确率立马上来了。模型不懂你的代码结构它只懂自然语言描述。坑三流式输出把工具调用的JSON也透传了。用户看到一堆{tool:queryOrder,args:...}体验极差。后来加了chunk类型判断工具调用阶段缓冲最终回答阶段才流式。坑四记忆无限增长导致token爆炸。早期没做窗口限制一个长会话聊了50轮上下文塞了几万token成本和延迟都上去了。加了滑动窗口和实体提取之后才稳住。坑五兜底话术太生硬。最初的兜底是系统无法回答用户反馈像机器人。改成这个问题我暂时没找到准确答案您可以换个说法或者我帮您转人工之后用户接受度高了很多。兜底话术要有人味。7.4 后续可以继续深化的方向这套骨架跑通之后还有几个方向可以继续打磨检索重排序用交叉编码器对初筛结果精排提升Top片段质量、多路召回向量检索 关键词检索融合覆盖向量检索不擅长的精确匹配场景、回答质量自动评估用另一个模型给回答打分低分自动触发兜底、A/B测试框架对比不同提示词、不同检索策略的效果。但这些都是锦上添花。先把RAG、工具、记忆、流式、兜底这五块跑稳让系统在真实流量下不崩、不胡说、不卡顿比什么都重要。我见过太多团队在检索重排序上花大力气结果兜底没做好用户问个超纲问题系统就胡编前面所有优化都白费。企业智能客服的本质不是让AI显得聪明而是让用户的问题被可靠地解决。可靠比聪明重要得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑