尧图精选

基于Spring AI的物流智能客服:RAG+记忆+工具调用实战

🕒 发布时间:2026/10/2 10:57:02 📁 来源:尧图网络
1. 物流智能客服的整体架构为什么我选 Spring AI做物流系统的人都知道客服是所有环节里最难自动化的一块。工单密集、问题重复、客户情绪急躁而且每个问题背后几乎都要查至少两套系统订单系统管单号、运输系统管轨迹、计费系统管价格。以前我们试过用传统的多轮对话引擎去搭结果维护一套对话树比养一个客服团队还痛苦。这半年我把 Spring AI 正式引入生产项目把 RAG 检索、记忆机制和工具调用整合成了一个物流智能客服系统今天把这套方案的完整思路和踩坑过程写下来。标题里的三个关键词对应的是三个完全不同的能力模块。RAG 解决的是“知识从哪来”的问题物流行业的运费规则、禁运品清单、网点信息这些业务知识模型本身是不知道的需要从文档里检索出来再交给模型记忆解决的是“上下文怎么存”的问题客户上一句说“我家搬家了”下一句问“那新的地方能送吗”你得记得住工具调用解决的是“实时数据怎么拿”的问题运单轨迹、时效预估、运费计算这些都必须调用真实接口不能靠模型瞎编。这三个能力拼在一起才是一个合格的智能客服底座。1.1 物流客服的三类真实痛点第一个痛点是重复性问题占比太高。我们统计过一个月的客服工单大概 41% 的问题集中在运费怎么算、什么时候能送到、禁运品能不能寄这三类。这些本质上都是规则查询完全可以靠检索解决但如果每次都要人工回答不仅慢而且不同客服的回答口径还不一样。第二个痛点是查询链路太长。客户说“帮我看看单号 SF1234567890 到哪了”客服需要在物流系统里查轨迹、去订单系统确认收发件人信息、再根据当前节点预测送达时间。如果是人工处理一次完整咨询要切换三四个系统平均耗时在 3 分钟以上。这个场景天然适合让模型做意图识别然后把参数提取出来交给工具去查。第三个痛点是跨会话记忆缺失。客户今天问了一次寄件价格明天又来问“昨天问的那个价格还能用吗”人工客服有工单系统可以翻记录但传统 Chatbot 是完全没有这个能力的。这个需求对应的正是记忆模块的价值。三个痛点组合起来决定了这套智能客服系统不能是简单的“套一个 ChatAPI 壳”而必须做成 Agent 架构。1.2 Spring AI 的选型逻辑我这边团队的技术栈是 Java 系Spring Boot 是骨架已经在跑微服务。选型时对比过 LangChain4j 和 LangGraph4jLangChain4j 虽然功能全但文档和社区更新节奏不太稳定遇到问题排查耗时LangGraph4j 本身是个不错的 Agent 编排框架但对团队现有 Spring 生态的融合度还是差一点。Spring AI 是 Spring 官方主导的项目它最大的优势不是某个单一功能多强而是把模型接入、向量存储、工具调用这些东西都适配进了 Spring 的编程模型。用 Spring AI 之后存储用的是项目里现成的 Redis 和 PostgreSQL不需要额外引入一套新的存储中间件。模型层做了抽象底层模型今天接通义千问明天换 OpenAI只需改配置不用改代码。这对企业项目很重要因为大模型迭代太快绑定死一家厂商风险太高。另外 Spring AI 在 Maven 中央仓库的发布节奏很稳定版本迭代看得到不会出现你说要升级就整个崩掉的局面。1.3 系统模块划分整个系统我拆成了五个模块会话入口层WebSocket REST 双通道、Agent 编排层核心决策、RAG 检索模块、记忆管理模块、工具调用模块。会话入口负责接入微信公众号、App 内嵌客服页和网页在线客服Agent 编排层收到用户消息后先判断意图再决定走“纯知识检索”“工具调用”还是“检索工具”的复合流程RAG 模块管业务知识检索记忆管理模块负责读写会话上下文和客户画像。工具调用模块是一个独立的 Spring Service暴露了查询运单、计算运费、查询时效等函数供 Agent 编排层调用。我用一张简洁的表来说明模块职责模块核心职责技术选型会话入口多端接入、消息收发WebSocket、REST APIAgent 编排意图识别、流程决策Spring AI ChatClient、Prompt TemplateRAG 检索知识召回、重排、上下文组装PgVector、Embedding 模型记忆管理会话态与长期画像Redis、PostgreSQL工具调用实时数据查询Tool、Spring WebClient2. RAG 知识库先解决“客户问什么、我们答什么”RAG 是整个系统里投入时间最多的部分也是最容易翻车的部分。很多人以为 RAG 就是把 PDF 切碎丢进向量库就完了实际做下来不是这么回事。知识源的质量、切片长度、召回阈值、重排策略每一步都直接影响回答的准确性。这个章节我把物流场景下的实操路径完整拆开讲。2.1 知识源选择与清洗先确定检索范围。物流客服需要用到的高频知识包括运费价格表、时效标准各省市互寄时效、禁运品/限运品清单、包装规则、保价理赔说明、网点营业时间与地址。这些知识散落在多个系统里价格表在计费系统时效标准在运营部的 Excel 里禁运品清单是国家邮政法规和内部规则的结合网点信息在门店管理系统里。所以第一步不是密集做向量化而是先做数据清洗。我这里遇到几个典型问题Excel 里的运费表格式不统一有的按重量分档、有的按体积分档还有阶梯价禁运品清单有大量“含有电池的产品”这类半结构化描述网点地址有些写着“某某路与某某街交叉口西南角”这种非标表述。我的做法是统一转成 Markdown 或 JSON 结构化格式再入库。比如运费表我转成了这样的结构## 运费标准-同城寄件 - 首重 1kg 内8 元 - 续重每 1kg2 元 - 单件限重30kg这种结构比纯文本嵌入效果好很多因为向量化之后模型在检索时能更精准地定位到“首重”“续重”这种关键词。数据清洗的原则就一句话让每一段文档都围绕一个具体问题来组织问答对模式的文档优于叙述性文档。2.2 切分策略与向量化参数确定切分逻辑时我测试了固定长度切片和语义切块两类方案。固定 500 字切片是最通用的做法但对表格类内容不友好经常把一张价格表从中间切断。最终我采用的是按语义标题切块先用规则识别“##”和“###”标题然后以标题为边界切块块最小 200 字、最大 800 字超过 800 字再按段落边界二次切分。向量化模型我对比了 text-embedding-v3 和其他几个开源中文模型最终选了 text-embedding-v3特点是中文长文本效果稳定、768 维输出、适配性好。做向量化的场景里有个容易忽略的点查询语句的 embedding 和文档的 embedding 最好用同一个模型否则向量空间不一致余弦相似度就失真了。这点我们团队前期就踩过坑用不同模型分别处理查询和文档召回率一度只有 30%。向量存储用的 PgVector因为项目里 PostgreSQL 本来就有加了个 pgvector 扩展就行不用单独搭建向量数据库。建表的时候我加了一个source字段用来标记知识来源比如运费表、时效表、网点信息表这样在召回之后可以做来源过滤。2.3 召回链路与命中率优化召回不能只靠一个向量检索就完事。我的链路是先用关键词迅速过滤一遍候选集因为物流场景里单号、地名、物品名等专有名词很关键再用向量检索出 TopK 候选最后做重排。为什么需要关键词前置过滤因为向量检索对“顺丰标快”这种专有名词的识别不一定准但关键词匹配能精准命中。实际测试中加了关键词过滤后 RAG 的 hit rate 从 68% 提升到了 83%。TopK 的选择上我最终取 TopK5。太小可能遗漏关键信息太大无关内容会混进上下文干扰生成。重排环节我采用了轻量级方案用 LLM 对 5 条候选做相关性打分一对一判断而不是一次性排序虽然慢一点但准确率确实更高。实际响应耗时增加了约 300ms但对客服场景这个延迟可以接受。这个章节要强调一个容易踩的坑如果发现客户问到某些高频问题但召回结果总是差一点点别急着调向量化大概率是知识源里根本写得不够清楚。把文档好好重写一遍命中率提升比任何参数调优都明显。3. 记忆机制让客服记住“上次说到哪”记忆是区分“玩具”和“生产系统”的关键。没有记忆的聊天机器人每次对话都是“失忆”状态客户说“我刚才不是说了吗”系统完全接不上。物流客服的记忆需求格外强烈客户咨询往往持续好几天涉及寄件咨询、查件、售后反馈多个环节。这个章节我讲实现方案。3.1 短期记忆会话级上下文短期记忆的目标是承载当前会话里的上下文。比如客户说“我想寄个电脑到杭州”助手推荐了“标快”客户下一句问“那要多少钱”如果不带上下文“那要多少钱”这个问题完全无法回答。我的方案是把最近 10 轮消息连同意图识别结果存入 Redis使用sessionId作为 key过期时间设为 30 分钟。每次请求时把最近的对话摘要塞进 Prompt 里。为什么是 10 轮而不是全部因为 token 有限塞太多历史会让模型注意力分散。我也验证过把全部历史放入和截取最近 10 轮的效果差异后者回答精度更好。Redis 的 TTL 设 30 分钟是参考了客服平均会话时长数据80% 的会话在 30 分钟内结束超时后客户重新发起咨询就开新会话也合理。3.2 长期记忆客户画像与偏好长期记忆解决的是跨会话问题。客户今天咨询了寄到北京的时效明天回来说“我昨天那个件能改地址吗”系统需要知道“昨天那个件”指的是什么。我的做法是拆成两层一层是用户画像表记录客户的常用寄件地、常用收件地、偏好快递类型这些信息用结构化字段存 PostgreSQL另一层是交互记忆表记录每一次咨询服务的历史存自然语言摘要。交互记忆的记录方式我一开始设想直接用大模型把整段对话总结成摘要实践后发现成本高且不稳定。后来改成在每次会话结束时把会话标题、涉及的运单号、最终结论提取出来用模板拼成一句话比如“用户咨询 SF1234567890 运费计算得知 58 元用户未下单。”这句话作为历史记录在下次对话启动时注入 Prompt。效果很好还节省了 token。长期记忆的检索同样面临召回问题但是我对用户历史记录不做向量化因为单个用户的历史记录量很小一次会话就那么几条直接用时间倒序取最近 5 条就够了。这个设计复杂度低、稳定性高没必要用向量检索。3.3 记忆的衰减与隐私边界关于记忆的一个现实问题是不是所有信息都值得长期保存也不是所有信息都能保存。我在设计里引入了一个简单的“记忆衰减”机制参考了近期比较常讨论的“score 时间半衰期”思路不过实现得很朴素每条长期记忆有一个初始权重分重要程度同时记录最后访问时间当记忆超过 90 天未被访问时权重分按半衰期衰减衰减到阈值以下就自动归档到冷存储不再参与 Prompt 注入。这样既控制了 token 成本也保证模型不会被历史噪音干扰。隐私边界是另一个重点。客户在咨询里提到的身份证号、完整家庭住址等敏感信息必须脱敏后才允许写入记忆存储。这个在系统里是在写入前用正则加实体识别做一道过滤。同时我们关闭了跨端记忆共享比如用户在 App 里的行为不能直接暴露给微信客服端需要走客户授权流程。合规问题在物流行业尤其敏感宁可功能少一点也不能越线。4. 工具调用大模型查不了单但能“指挥”你查大模型本质上是“语言模型”不是“数据库”。你问它 SF1234567890 到哪了它不知道因为它没有实时数据。传统方案是让大模型生成 SQL 去查库但那样太危险也不可控。Spring AI 的 Tool 注解提供了一条更优雅的路径把查询能力封装成工具函数让大模型学会在合适的场景下调用它们。4.1 工具调用的逻辑工具调用本质上是把“查单号、算运费、查时效”这些确定性逻辑抽出来交给确定性的代码执行而大模型只负责“决定调用哪个函数、传什么参数”。这种分工的好处很直接数据是从真实系统里拿到的不是编出来的准确率有保障。Prompt 里我会向模型声明可用工具列表和每个工具的参数说明。Spring AI 中工具就是一个普通的 Spring Bean 方法加上 Tool 注解即可Component public class LogisticsTools { private final TrackQueryClient trackQueryClient; public LogisticsTools(TrackQueryClient trackQueryClient) { this.trackQueryClient trackQueryClient; } Tool(description 根据运单号查询物流轨迹) public String queryTrack(String trackingNumber) { TrackResult result trackQueryClient.query(trackingNumber); return result.toPromptString(); } Tool(description 计算寄件运费需要提供寄件城市、收件城市、重量(kg)) public String calculateFreight(String fromCity, String toCity, double weight) { double price freightService.calculate(fromCity, toCity, weight); return String.format(从 %s 到 %s 的重量 %.1fkg 运费为 %.1f 元, fromCity, toCity, weight, price); } Tool(description 根据寄收件城市查询预计时效天数) public String queryDeliveryTime(String fromCity, String toCity) { int days timelineService.estimate(fromCity, toCity); return String.format(从 %s 到 %s 预计时效 %d 天, fromCity, toCity, days); } }这里有个细节工具方法的返回值不是给用户看的而是给模型看的所以我在返回值里用自然语言把结构化数据描述出来这样模型读取信息更流畅。如果直接把 JSON 返回给模型模型理解起来经常有歧义。4.2 参数设计与异常兜底工具定义的参数设计涉及到“模型能不能正确提取参数”这个关键问题。比如计算运费的工具参数命名必须直观fromCity、toCity、weight描述也要写清楚单位否则模型会把“2公斤”传成 2 还是 2000 就不好说。我在实际测试中发现明确描述“重量单位kg”之后参数提取准确率有显著提升。异常兜底必须单独设计。工具调用过程中接口可能超时、查询的运单号可能不存在、参数可能缺失。我的方案是工具方法内部不抛异常而是返回一段特定格式的提示文本比如“运单号不存在请提醒用户核对单号”。这样模型把这段提示作为信息再组织成用户友好的回答。如果工具方法直接抛异常Agent 编排层就要做异常处理容易让整轮对话中断。另外我给工具调用加了一层超时熔断单个工具请求超过 5 秒就直接返回“系统繁忙请稍后重试”避免阻塞整个 Agent 流程。4.3 一次完整的工具调用实录拿一个“用户询问上门取件时间”的场景来说。用户说“我想寄个冰箱到广州什么时候能来取”Agent 先识别出这是一个寄件咨询且涉及大件物品。它先调用 RAG 模块检索“大件物品取件规则”然后根据检索结果判断要调用“queryDeliveryTime”工具查广州的时效。模型在推理后生成的调用逻辑是用户意图寄件咨询 需要信息取件时间 工具选择queryDeliveryTime(fromCity当前城市, toCity广州)实际日志输出大致是这样[AGENT] Invoking tool: queryDeliveryTime(fromCity成都, toCity广州) [TOOL] 从 成都 到 广州 预计时效 3 天 [AGENT] Compose response: 您好您咨询寄冰箱到广州的取件安排从成都到广州预计需要 3 天时间。我们的取件员会在下单后 2 小时内联系您预约上门时间。整条链路跑通后客户体验和人工客服基本一致。这里关键在 Agent 先通过 RAG 获取了“大件取件规则”再结合工具结果生成回答而不是直接拿工具结果生硬地回复。5. 整合落地Agent 编排与生产级细节难点在于把三个模块串起来。RAG、记忆、工具调用三套能力单独都能跑通但组合在一起时需要精心编排否则会出现工具调用和知识检索互相冲突、记忆信息干扰工具判断等问题。这一章专门讲编排逻辑和我在生产环境里积累的实战经验。5.1 Agent 的编排思路我用 Spring AI 的 ChatClient 作为编排核心整体流程是接收消息 - 注入记忆摘要 - 意图识别 - 决定执行路径 - 组装上下文 - 调用模型生成回答。意图识别做了简单的路由把用户问题分为知识类运费规则、禁运品、查询类单号轨迹、计算类运费计算、闲聊类。知识类走 RAG查询类和计算类走工具调用需要两类的则走复合流程。路由判断我用了模型判断加规则兜底。先让模型输出一个 JSON意图类型 关键实体再用正则校验 JSON 格式解析失败就走规则匹配比如消息中包含“单号”“轨迹”“到哪了”则强制转查询类。这样双保险避免了纯模型误判导致的多轮纠错问题。复合流程的核心是“先查后答”先调 RAG 检索业务规则再决定是否需要工具调用最后把两边的结果合并送进生成环节。5.2 生产部署的硬指标一个容易翻车的地方是并发。快递客服场景有明显的波峰波谷上午 10 点和晚上 8 点是咨询高峰QPS 能瞬间冲到平日的 5 倍。大模型接口的响应时间一般在 1-3 秒如果所有请求都串行处理高峰期必然雪崩。我的方案是加一层内存队列做削峰填谷并把模型调用线程池控制在固定大小例如 20 线程超出线程数的请求排队等待。配合超时机制保证调用方不会无限等待。这里还要注意 token 成本。每次请求注入 Prompt 模板 记忆摘要 检索结果一组对话平均消耗约 1200-1800 token。高峰期一天 5000 次咨询成本不低。我在生产上做了一套降本策略简单意图如“查运费”直接走规则工具不调用大模型生成只有复杂问题才走完整链路。这个优化让整体调用量降低了 35%。5.3 评估体系怎么证明系统真的好用没有评估体系就说不清楚系统比人工客服好了多少。我搭建了三层评估意图识别准确率判断是否转到了正确的处理路径、RAG 召回命中率检查检索出的文档是否包含关键答案、回答质量的 LLM 评测每次回答后由大模型按“准确性、完整性、礼貌度”三维打分。离线评估时我准备了 300 条历史工单作为测试集跑完之后人工抽样复核 30 条最终意图识别准确率做到了 92%RAG 命中率 83%综合回答达标率 88%。监控方面我上线了会话质检看板每天自动抽样 50 条会话对回答过长的、包含“很抱歉我不能”的、以及被用户打差评的分拣出来人工复盘。5.4 实战经验与抽坑记录最后总结几个这次项目里最有价值的坑。第一不要把模型能力神话。有的问题走规则比走模型更稳定。比如查单号直接正则提取单号再查接口比让模型提取要稳得多。第二Prompt 模板必须版本化。模型更新换代后同一个 Prompt 的效果可能变化很大。我把所有 Prompt 都放到了 Git 里管理并做了 A/B 测试机制。有一次升级了底层模型运费计算类问题的回答格式全变了就是因为 Prompt 没有跟着调。第三RAG 的效果瓶颈往往在知识管理而不是算法。我曾花两周调召回参数命中率一直在 70% 左右徘徊后来发现是知识库里有个时效表的数据没更新导致检索到的内容本身是错的。从那以后我专门建了一个知识库更新流程每次价格调整后运营部门必须在两天内把新表导入系统。第四工具调用的日志审计是硬需求。物流客服可能要面对纠纷系统必须能回溯“当时 AI 是如何回答的、调用了哪些工具、传入了什么参数”。我实现了完整的链路日志记录每轮会话存一个 traceId把所有模块的调用日志串联起来。这不仅是技术需求也是管理需求。做完整套系统后我个人的体感是Spring AI 的 Agent 方向确实值得投入但也必须承认这套体系还有不少可以继续打磨的地方。比如当前记忆模块还比较粗跨端记忆需要更多用户授权场景工具调用链路对模型能力要求不低底层模型一换参数提取的准确率就可能波动RAG 在长尾知识上依然会偶发失败。不过话说回来把这些模块真正整合到能跑生产、能扛高峰、能追溯审计比单纯研究某个算法新概念对业务的价值实在得多。后面我计划补上知识库的自动更新与版本对比能力也给 Agent 增加主动追问的流程——当工具参数缺失时不是报错而是反问用户补充信息。这些扩展都是建立在这套已完成的基础设施之上先把地基打牢再往上盖楼是这个项目最大的心得。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →