先交代一下背景。去年我在某电商平台的技术团队做客服系统智能化改造,老板的要求很朴素:把售后政策、退换货规则、商品使用说明这些散落在PDF文档里的知识,变成客服机器人能直接回答的问题。最初我天真地以为 Chat Completion + 提示词就够了,直到被两个现实问题打脸:一是大模型的训练数据里根本没有我们内部的手册和规则;二是哪怕结构化的商品数据,牵扯到促销叠加、阶梯运费这种多变规则时,模型会一本正经地瞎编。这两板子下来,才老老实实把 Spring AI + RAG 的全链路完整走了一遍。
这篇文章就记录这套电商智能客服从 0 到 1 的搭建过程,重点是 PDF 文档如何一步步变成精准问答的能力,以及中间那些文档里查不到、只有踩过坑才懂的经验。适合正准备做知识库问答、智能客服,或者已经在用大模型但被"幻觉"折磨的团队参考。
1. 为什么电商客服会盯上 RAG
在动手写代码之前,先想清楚一个问题:电商客服的场景有什么特殊性,为什么非得用 RAG,而不是直接调大模型接口?
1.1 传统客服系统与裸大模型的共同困境
传统客服系统大多是基于关键词匹配或者简单的规则引擎:用户说"退货",系统就去匹配"退货"相关的FAQ。这套逻辑在今天已经很难受用——用户一句话里可能有多个意图,比如"我上周买的手机壳和描述不符,能退吗?运费谁出?",这种复合问题放到旧系统里基本就转人工了。
裸上大模型也不行。电商业务的知识是高度私有的:某个大促期间的价保规则、某条产品线的保修条款、不同仓库的运费计算方式,这些信息既不在通用训练语料里,又随时在变。你让大模型自由发挥,它就会一本正经地编造一个看起来合理的退款比例,这在客服场景里就是实际的经济损失。
RAG(Retrieval-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.1""1.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-K=20,再做重排(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 串联起来就好了。