先交代一下背景。去年我在某电商平台的技术团队做客服系统智能化改造老板的要求很朴素把售后政策、退换货规则、商品使用说明这些散落在PDF文档里的知识变成客服机器人能直接回答的问题。最初我天真地以为 Chat Completion 提示词就够了直到被两个现实问题打脸一是大模型的训练数据里根本没有我们内部的手册和规则二是哪怕结构化的商品数据牵扯到促销叠加、阶梯运费这种多变规则时模型会一本正经地瞎编。这两板子下来才老老实实把 Spring AI RAG 的全链路完整走了一遍。这篇文章就记录这套电商智能客服从 0 到 1 的搭建过程重点是 PDF 文档如何一步步变成精准问答的能力以及中间那些文档里查不到、只有踩过坑才懂的经验。适合正准备做知识库问答、智能客服或者已经在用大模型但被幻觉折磨的团队参考。1. 为什么电商客服会盯上 RAG在动手写代码之前先想清楚一个问题电商客服的场景有什么特殊性为什么非得用 RAG而不是直接调大模型接口1.1 传统客服系统与裸大模型的共同困境传统客服系统大多是基于关键词匹配或者简单的规则引擎用户说退货系统就去匹配退货相关的FAQ。这套逻辑在今天已经很难受用——用户一句话里可能有多个意图比如我上周买的手机壳和描述不符能退吗运费谁出这种复合问题放到旧系统里基本就转人工了。裸上大模型也不行。电商业务的知识是高度私有的某个大促期间的价保规则、某条产品线的保修条款、不同仓库的运费计算方式这些信息既不在通用训练语料里又随时在变。你让大模型自由发挥它就会一本正经地编造一个看起来合理的退款比例这在客服场景里就是实际的经济损失。RAGRetrieval-Augmented Generation检索增强生成解决的核心问题是把模型自己记答案变成先从自己的知识库里检索证据再基于证据生成答案。知识库里的内容是你能控制的模型只是组织语言的工具。这在电商客服这种对准确性要求高、知识更新快的场景里是当前最务实的技术路线。1.2 RAG 和微调的分工与取舍很多团队一上来就纠结要不上微调吧我的建议是除非你的知识形态非常固定、表达方式高度统一比如某种标准合同条款否则不要优先做微调。原因有三第一电商知识变更太频繁。大促规则一个月改一次微调一次数据的准备、训练、评测周期至少以周计等你训完规则又变了。第二微调不适合做事实记忆。微调擅长的是改变模型的风格、格式、行为模式而不是往里塞事实。硬塞也行但会不稳定而且训练数据里的噪声会被一并记住。第三可解释性要求。客服场景一旦产生纠纷你需要告诉运营这个答案是依据哪一条规则生成的。RAG天然有检索来源可以直接反查证据链微调做不到这一点。所以在这个项目里我的分工策略是模型用现成的通用大模型纯粹负责语义理解和语言组织知识全部走 RAG 检索模型不新增任何事实记忆。1.3 Spring AI 在链路中的生态位置为什么选 Spring AI因为我们的公司技术栈是 Java / Spring Boot不可能为了一个客服模块把核心服务换 Python。Spring AI 是 Spring 官方出的 AI 框架它做的事情和 LangChain 类似统一了 PDF 解析、文本分割、向量化、向量存储、增强生成这一条链路的 API并且提供了一套 Spring Boot 风格的配置方式可以非常自然地融入现有工程。对我来说最大的价值是两点一是它把文档加载-转换-向量化-检索-问答的每个环节都能用 Java 直接操作不用混布 Python 服务二是有统一的抽象层底层的模型和向量库可以随时切换这对后续降本或替换模型非常关键。2. PDF 文档解析最不起眼但最致命的一环我们常说 RAG 的瓶颈在召回召回的瓶颈又往往出在解析这一步。PDF 解析不是把文字抠出来那么简单一个小细节没处理好后续整个检索质量都会受到影响。2.1 电商 PDF 文档的真实形态先盘点一下我们当时要处理的几种典型 PDF售后政策文件图文混排条款分章节经常带层级序号1.1、1.2、2.3.1。商品使用说明书表格密集比如故障代码表和对应处理方式还有大量示意图。价格与运费说明数字密集涉及阶梯价格、区域差异、时间区间。少量扫描件门店提供的纸质盖章规则的扫描版纯图片。每一种都有各自的坑。图文混排的主要问题是表格和文字在提取时的顺序错乱数字密集型文档的问题是数字容易被 OCR 或文本提取搞错扫描件则是整体上就多一层 OCR 识别成本。这个阶段我给你的第一个实战建议是先人工盘一遍知识库把 PDF 按上面的类型分类再决定用哪种解析策略。不要指望一种解析方案通吃所有文件。2.2 解析引擎选型与落地配置Spring AI 内置了PagePdfDocumentReader底层用的是 Apache PDFBox适合处理文本型 PDF。但实际用下来你会发现PDFBox 对复杂版面、表格、多栏布局的还原能力偏弱尤其遇到横向文本时经常乱序。所以我在项目里做了分层处理纯文本型 PDF直接用 Spring AI 的PagePdfDocumentReader按页读取简单快速。复杂版面的 PDF使用 PDFBox 做了二次处理抽取文本的同时按坐标块进行重组。这段逻辑如果不想自己写可以直接引入 Apache Tika 作为补充。扫描件先接 OCR 服务转成带坐标的文本层再走文档读取流程。如果你遇到的是扫描件较多的情况建议引入独立的 OCR 服务。我用的是 PaddleOCR 的 Docker 部署版本通过 HTTP 接口集成到 Java 侧。注意这里有个经验OCR 的模型要针对电商文档做微调才行否则优惠券价保退换货这类词经常被识别成同音字后面检索时全变噪声。我们初期因为 OCR 识别错误导致一大批关于价保的问题找不到答案排查了很久才发现是价保被识别成了嫁保。2.3 文档分割块大小、重叠度怎么定把 PDF 解析出的文本进行合理切割是决定检索召回质量的关键一步。切大了每个块携带太多无关信息向量化后主题被稀释检索相关性下降切小了单块信息不完整模型拿到的上下文不足以回答问题。Spring AI 里最常用的是TokenTextSplitter。我最终确定的一套参数可以直接抄块大小600 token重叠150 token分割策略优先按标题层级、段落边界切分为什么是 600因为电商的售后条款往往一段就能说明一个完整规则600 token 大约能覆盖 300-500 个汉字刚好装下条件 操作 例外这样的完整逻辑链。重叠 150 token 是为了避免检索时正好落在切割缝隙上让两个块之间有一个缓冲带。但这里有一个容易走极端的坑分割规则完全按字数硬切。如果某个条款标题下有一段很长的解释和一个举例表格硬切会把表格拆到另外一个块里导致检索的文章看不懂表格。所以我在分割器外层加了一层规则识别到标题节点时强制结束当前块新起一个块。这是基于常见实践做的补充效果非常明显——带章节结构的制度类文档检索命中率会有明显提升。2.4 清洗与元数据注入容易被忽略的细节解析出来的文本不能直接用还得经过一道清洗管线。我按下图逻辑处理这里不画图描述一下去掉页眉页脚电商文档页眉常有品牌名、文档编号这些信息在分割后容易被误当成正文内容。合并断行PDF 里很多地方是硬换行段落实际是连续的合并后才能正确分块。保留标题层级把1.11.2这类标题提取出来作为元数据存下来后续能辅助检索。元数据注入是我强烈建议做的一步。每块文本除了正文还要附加 documentId、category、title、pageNumber 等字段。这个信息的价值在检索阶段才会显现同一个问题命中多个块时可以按来源文档分组去重避免重复计算同一来源的多个片段在系统运营阶段运营可以按 category 快速定位哪一类文档的检索质量出了问题。3. 向量化与向量存储构建可检索的知识地基解析和分割做完了下一步就是把每块文本变成向量存进向量数据库。这一步的核心不是会调用 API而是选型和参数层面的权衡。3.1 Embedding 模型怎么选才划算Embedding 模型的选择直接影响检索效果。但这里我想先破一个迷信不是模型越贵、维度越高越好。在实际项目里检索效果和业务场景的相关性要高于模型本身的公认排名。我的选型逻辑是三条中文效果优先客服语料以中文为主模型在中文语义上的表现必须实测过。维度适中维度越高占用的存储和检索资源越大。比如把 5 万块文本转成 1536 维向量对比 768 维存储差距接近一倍检索耗时的差距也很显著。稳定性模型服务如果是第三方提供要注意 API 稳定性如果自建部署要看显存占用和推理速度。我们上线第一批用的模型维度是 1536后来在调优阶段换成了同语义能力但维度更低的模型向量库的存储成本直接下降检索耗时也缩短了。这里你需要注意一点切换 Embedding 模型后已存在的向量必须重新生成不能混用。我在项目里是写了一个离线任务在低峰期全量重新向量化上线时直接切库。3.2 向量数据库选型与 Spring AI 的抽象Spring AI 提供了统一的VectorStore接口这意味着你在选型上不用一开始就锁死。实际项目里我从SimpleVectorStore起步做 Demo上线前换成了独立的向量数据库。到底选哪个我的建议框架如下数据量少于 10 万块、维度适中用 Redis 的 Vector Set 即可够用且不需要额外维护组件。数据量大、检索逻辑复杂优先考虑专用的向量数据库原因在于索引结构的效率、并发控制、混合检索能力都会好很多。有重排序和混合检索需求需要支持向量相似度 关键词权重的联合检索能力这一点不是所有向量库都能优雅支持。我用的是基于 Docker 部署的一个开源向量库Spring AI 的依赖里提供了对应的实现配置一个连接地址就能直接注入VectorStore接口。这个抽象层带来的最大收益是开发阶段用的是本地临时库上线架构变化后只要改依赖和配置业务代码不需要动。3.3 插入策略与相似度计算的参数细节向量化的写入阶段就容易踩坑的是批量插入的阈值。最初我用每批次 100 条插入5 万条数据跑了很久而且完整写完后才发现有几批失败导致部分数据缺失。后来调整成每批次 50 条、失败自动重试三次并且在结束后做了一次总数对账确保入库块数和分割出的块数一致。检索阶段的 Top-K 取值也有讲究。K 太小容易漏掉关键证据K 太大噪声会稀释答案质量。在项目早期我把 Top-K 设为 5对应 3000 token 左右。后来为了覆盖更多候选改成了先取 Top-K20再做重排Rerank效果要好得多——先说粗检索保证召回再用重排模型精排。重排模型虽然多一层调用但在售后规则这种强上下文场景下非常值。此外相似度阈值一定要设。设一个相似度下限低于下限的检索结果直接丢弃不要让模型硬答。我们在调试中发现用户提问保修期多久这种泛化问题检索出来的片段相似度普遍只有 0.5 左右直接追问会导致模型拿不相关内容去编。设了 0.65 的阈值之后这类问题会落到兜底回复准确率反而上来了。4. Spring AI 问答链路把检索结果变成可靠的回答文档解析、向量化、检索这些环节做得再好最终用户体验还是要落到问答这一步。Spring AI 在这一层的核心能力是QuestionAnswerAdvisor——它把检索增强的流程封装成了对模型调用的一个顾问。4.1 核心调用流程问答链路的调用本质上只有几步接收用户问题用同一个 Embedding 模型把问题向量化到向量库检索相似片段把片段拼进 Prompt再交给大模型生成答案。Spring AI 把这套流程串得很顺ChatClient负责管理与大模型的对话。QuestionAnswerAdvisor在调用模型之前先去向量库检索把检索结果以参考文档形式塞进 Prompt 的上下文里。我强烈建议你用 Advisor 机制而不是手写拼接。手写拼接的麻烦在于Prompt 模板、检索结果格式、多轮历史的处理都散落在业务代码里改一处就要全链路回归。Advisor 机制把这些都统一在了一条链路中可以链式叠加多个 Advisor比如一个做敏感词过滤一个做检索增强一个做回答格式约束。4.2 Prompt 编排从角色到回答边界即便有检索Prompt 的编排仍然直接影响回答质量。我前后迭代了多个版本最终沉淀了几个关键原则。首先是系统提示词里的角色与边界定义。我用的中文模板大致包含角色设定你是电商客服助手只能依据提供的参考文档回答。约束如果参考文档中没有明确信息必须回答对不起我暂时无法从现有资料中找到准确答案建议转人工客服进一步查询。格式要求回答时优先给出结论再给出依据摘要。敏感词策略涉及个人隐私、支付账号、第三方链接等一律拒绝回答。然后是用户问题的上下文增强。在把问题交给模型之前我会先把用户原问题做一次意图改写也就是在检索之前生成一个更适合检索的查询词。这个技巧在电商场景效果尤其明显。比如用户说我那个手机壳是不是能退啊直接拿去向量检索效果一般但改写为手机壳退货政策后召回准确率会好很多。实现方式不复杂用一次低成本的模型调用先让模型把问题改写为标准的检索句式再做向量化检索。4.3 引用溯源与不可答兜底客服场景还有一个硬需求回答要带来源。我们在系统提示词里要求模型在回答末尾附上【参考文档】xxx.docx第x页这种格式。因为检索出来的每个片段都带有元数据模型可以直接读取到文档名和页码。这个设计在线上效果很好既方便运营抽查回答质量也方便用户在页面上直接跳转到原始条款。另一个必须做的是不可答兜底当检索结果低于相似度阈值或者结果为空时不要进入正常的问答流程而是走一个降级策略。我在这里给了两个分支有低质量检索结果但相似度不够回复资料暂时不足并附上人工客服入口。完全没有检索结果直接转人工。不要小看这两个分支。在灰度期间我们对比了有兜底和没兜底的输出胡编乱造的问题基本清零用户对客服的满意度数据也明显回升因为用户宁可听到转人工也不愿意听到一个明显错误的答案。5. 灰度上线与全链路调优模块开发完只是万里长征走了一半。真正费力的是灰度阶段对效果数据的分析和调优。这个阶段我每天都在三点一线地发现问题和解决问题。5.1 召回效果差的排查链路线上第一个明显问题是问题召回的效果不稳定。同一个问题换个问法明明语料里有一模一样的句子检索结果却排得很靠后。排查时不要直接上重排序先把链路拆开逐步复现。我整理了一套排查顺序先确认问题向量化是否异常。用同一个 Embedding 模型把问题向量化再和语料块做相似度计算看分布是否合理。再确认语料质量。检查命中的片段是不是纯表格文本、OCR 噪声等异常块。然后确认分割策略。看看目标信息是否被切碎或和其他语义混在了一起。最后才考虑调模型、加重排。有一次问题就出在第二步某商品说明书的故障代码表被解析成了多行无序文本向量化之后语义完全错乱用户问冰箱显示E4怎么办检索到的是可能原因请检查室温怎么调 Top-K 都没用。后来单独对该类文档走了表格专线解析流程把表格按行列结构拼接成自然语句再重新向量化这个问题才解决。经验就一条不要一上来就调模型调参数先把数据源本身清洗合格。5.2 幻觉收缩的三板斧大模型编造答案的问题我在前面几个环节已经做了不少拦截但线上还是有过漏网之鱼。总结三个最有效的手段配合使用才彻底压住第一板斧是检索源头控制相似度阈值 无结果转人工。这个前面提过了不再展开。第二板斧是生成侧约束在系统提示词里强调不得超出参考文档范围如果信息在文档里找不到如实说明。不要觉得这是废话模型对指令的遵从度在参考文档里没有这种明确场景下比什么技巧都有效。第三板斧是答案校验模型生成后做一次简单的证据对齐。我把生成文本里的关键实体如退款比例、保修年限抽取出来去检索结果片段里确认是否出现不出现则判为可疑答案走兜底回复。这个校验逻辑不需要精确匹配语义级别即可可以用模型调用也可以整理出高频实体词表做快速匹配。电商场景里像30天7天150%包邮这类数字关键词用词表校验成本更低、效率更高。5.3 响应延迟与成本控制客服系统对响应时间的容忍度比聊天工具高一点但也不能让用户等太久。我们的目标是不超过 3 秒返回首字。实测下来链路耗时占比大概是问题改写检索式0.3 秒问题向量化0.1 秒向量检索0.2 秒模型生成1.5-2.5 秒不等优化方向还是老三样一是把模型服务部署在靠近应用的地域减少网络往返二是把问题和语料的向量化做离线缓存问题向量缓存命中后直接跳过这一步三是模型输出用流式Streaming用户侧首字响应的体感会比等全部生成完再展示好很多。成本控制方面我的核心策略是分级调用模型简单的售后政策咨询用轻量级模型就够复杂的纠纷判断、多轮深度引导才调用更强的大模型。这条降本逻辑运营非常认可——毕竟客服场景 80% 的问题都是重复且简单的没必要每次都上重型模型。6. 一些踩坑心得与可扩展方向最后分享几个纯属于做过才懂的体会。第一RAG 项目的质量上限几乎由数据质量决定不要指望模型或者说 Prompt 技巧能兜住脏数据。电商知识库每天都在变一定要有知识更新管线新增 PDF 自动解析入库、下线文档自动移除对应向量、变更文档需全量重跑该分类语的向量化。我们后来设计了定时任务和人工审核槽位才算把知识新鲜度这个事管起来。第二评估体系要早建。不要上线后凭感觉说效果还行。我从项目一开始就维护了一批评测用例覆盖常见问题、变体问法、疑难纠纷、异常输入四类。每次修改 Prompt、调整分割参数、切换模型都先跑一遍评测集用准确率数据来决策而不是靠印象。第三多轮对话的处理比单轮要复杂得多。用户会问那运费呢支持吗这类省略主语的问题单纯把上一轮回答拼接进 Prompt 是不够的。建议对会话里的指代做一次梳理将省略句补充完整后再进检索链路。至于后续扩展的方向如果团队把单知识库客服做顺了可以往两个角度延伸一个是用同样的链路切换到售后工单分类、商品评论摘要等场景只替换文档解析规则和 Prompt 模板其他部分完全复用另一个是接入人工客服辅助模式让 AI 在客服对话过程中实时推荐答案由人来确认发送。这两个方向都不需要重新发明轮子RAG 基础设施搭好后扩展就是改改配置的事。这套系统上线几个月里我最大的感受是做 RAG 项目最怕的不是技术复杂而是被AI两个字带偏以为主要精力都在模型调用上。真正花时间的永远是最枯燥的数据清洗、文档结构和评测打磨。把这些基本功做扎实剩下的交给 Spring AI 串联起来就好了。