Spring AI Alibaba + RAG 实战:知识库检索模块从设计到落地

Spring AI Alibaba + RAG 实战:知识库检索模块从设计到落地

混合检索 + 幂等入库 + 动态权重,这是 AI 客服知识库能跑稳的核心


与上一篇的关系

上一篇讲了 AI 客服系统的整体架构------情绪感知、意图识别、Agent 工具链。这篇是那篇的续集,专门讲 RAG 知识库模块的设计与实现。

如果你没看过上一篇,这里先补一张对比表,让你知道 RAG 模块在整个系统里处于什么位置:

上一篇已有的 本篇新增的
情绪感知(EmotionAnalyzer) 文档摄入管线(Ingest Pipeline)
意图识别(IntentClassifier) 多格式文档解析(PDF / MD / TXT / Word)
Agent 工具链(订单+退款) 向量检索 + 关键词倒排双路并行
Redis 会话缓存 RRF 混合检索融合排序
MyBatis-Plus 数据访问 searchWithScore() 相似度评分扩展
多模块 Maven 架构 动态检索权重(system_config 表控制)

一、RAG 模块在整体架构中的位置

先回顾一下整体请求链路,RAG 是其中的一个分支:

markdown 复制代码
用户问题
  → ChatController
  → ConversationService(会话管理)
  → 意图识别 → 命中 RAG 意图
  → HybridSearchService(混合检索)
      ├── PgVectorKnowledgeStore(向量检索,语义相似度)
      └── RedisKeywordIndex(关键词倒排索引,jieba 中文分词)
  → 构建 System Prompt(含检索到的 context)
  → ZhipuAI / Ollama 生成回答

核心代码在 ai-csr-rag 模块下,检索层 + 入库层 + 索引层三个组件各自独立、职责清晰,互不耦合。


二、文档摄入管线(Ingest Pipeline)

用户上传知识库文档之后,系统需要把它切块、生成向量、建索引,这一套流程就是摄入管线。

2.1 整体流程

复制代码
文档上传
  → MultiFormatDocumentLoader(支持 PDF / TXT / Markdown / Word)
  → TokenTextSplitter(按 token 分块,默认 500)
  → PgVectorKnowledgeStore(Ollama 生成 embedding,写 pgvector)
  → RedisKeywordIndex(jieba 分词,建立 Redis 倒排索引)
  → KnowledgeDocument 状态更新为 READY

2.2 几个关键的设计决策

多格式统一入口

不同格式文档的解析逻辑差异很大------PDF 要提取正文并清洗特殊字符,Markdown 要去掉语法标记,Word 要处理样式嵌套。MultiFormatDocumentLoader 统一对外暴露一个接口,内部按文件类型分派具体解析器,上层调用方不用关心格式细节。

每个 Chunk 独立持有 metadata

这里有个不起眼但很重要的细节:分块时必须给每个 chunk new HashMap<>(doc.getMetadata()),而不是直接复用父文档的 metadata 引用。

为什么?因为如果共享同一个 Map,多个 chunk 写 chunk_index 时会互相覆盖,最后所有 chunk 的 chunk_index 都变成同一个值,入库触发唯一约束冲突。这个坑我踩过,留到踩坑篇细讲。

幂等写入

PgVectorKnowledgeStore.add() 在入库前先按 document_id DELETE 旧数据,再配合 ON CONFLICT DO NOTHING 写入。这样重复 ingest 同一份文档不会报错,也不会留下重复数据。线上知识库更新文档是高频操作,幂等是必须的。


三、混合检索(Hybrid Search)

这是整个 RAG 模块最核心的部分。

3.1 为什么要混合,而不是单纯向量检索

纯向量检索在语义相似的场景很好用,但有个明显短板:用户问"退款状态是 PENDING 是什么意思",向量检索会把语义相近的内容都召回来,但"PENDING 是精确词"------这种场景关键词检索命中率反而更高。

反过来,纯关键词检索应对近义词、缩写、语义跨度大的问法就不行了。

所以两条路都要走,再融合排序,这就是混合检索的出发点。

3.2 检索流程

ini 复制代码
查询请求
  ├── 语义路:Ollama embedding → pgvector cosine 检索 → TopK 结果 + similarity score
  └── 关键词路:jieba 分词 → Redis 倒排索引命中 → TopK 结果
  → RRF 融合(k=60)→ 统一排名列表

两路并行,互不阻塞,最后用 RRF(Reciprocal Rank Fusion) 做融合排序。RRF 的公式很简单:每个文档在两路结果里分别有排名,把 1/(k + rank) 加起来就是最终得分,k=60 是经验值,能平衡两路结果的权重差异。

3.3 动态权重配置

yaml 复制代码
# 不在 yml 里写死,在 system_config 表里动态控制
rag.keyword_weight = 0.4
rag.vector_weight  = 0.6

keyword_weightvector_weightsystem_config 表里读,不重启服务就能调整检索策略。这个设计在灰度调参时非常好用------先把 keyword_weight 调高跑几天,看召回质量,不满意再往回调,不用打包发版。

配置项 说明 推荐起始值
rag.keyword_weight 关键词检索在融合中的权重 0.4
rag.vector_weight 语义检索在融合中的权重 0.6
rag.top_k 每路各取 TopK 结果 5
rag.similarity_threshold 语义相似度过滤阈值 0.5

3.4 相似度评分扩展

Spring AI 的 VectorStore.similaritySearch() 默认不返回相似度分数,只返回文档列表。但 RRF 融合需要分数来排序,所以这里做了扩展:

  • 新建 SearchHit DTO,包含 documentscore 两个字段
  • PgVectorKnowledgeStore 里新增 searchWithScore() 方法
  • 通过向量内积计算余弦相似度,回填到 SearchHit.score

扩展之后,每条检索结果都带着 similarity 字段,既能用于 RRF 融合,也能在 System Prompt 里标注置信度,让大模型知道这条知识的可靠程度。


四、Session 会话管理

多轮对话不是独立的,前几轮的上下文要带进来,大模型才能理解"你刚才说的那个订单"指的是哪一个。

SessionService 维护 USER / ASSISTANT / SYSTEM 三种角色消息记录,每轮对话结束后追加写入,下一轮检索时一并注入 System Prompt。支持历史回溯和滑动窗口截断,避免上下文过长撑爆 token 限制。


五、当前系统状态

模块 状态 备注
文档摄入(PDF / TXT / MD) ✅ 正常 12 个 chunk,embeddings 全入库
向量语义检索 ✅ 正常 相似度 0.55--0.63 区间命中
关键词倒排检索 ✅ 正常 jieba NPE 已修复,fallback 兜底
混合检索 RRF 融合 ✅ 正常 双路召回,k=60 融合
会话管理 ✅ 正常 USER / ASSISTANT 消息写入正常
Ollama 本地模型 ✅ 正常 bge-large-zh-v1.5 向量模型就绪

六、后续计划

已完成

  • Spring AI Alibaba 集成智谱 GLM-4
  • 情绪感知模块
  • 意图识别模块
  • Agent 工具链(订单查询、退款申请)
  • 订单查询服务(MyBatis-Plus)
  • 退款申请服务
  • RAG 知识库模块(文档上传 → 向量入库 → 检索问答)

正在做

  • 多轮对话记忆优化
  • 转人工功能(自动转接 + 手动转接)

后面要做

  • 知识库运营后台
  • 客服工作台(人工接待)
  • 数据统计与监控大屏

源码怎么拿

公众号 「亦暖筑序」 底部菜单 【获取源码】,完整的 Gitee 仓库直接拉。

源码里除了文章提到的这些,还有几个文章没展开的:

  • 数据库初始化脚本(5 张表,带模拟数据,跑起来就能测)
  • 完整的多模块 pom 依赖配置(不用自己一个个试版本了)
  • 情绪分析 / 意图识别的完整 Prompt 模板(文章里只是核心片段,源码里是完整可运行的)

说白了吧,看完文章理解思路,拿到源码照着跑,这才是最省时间的路径。不拿源码硬看,你会卡在"这行代码放哪个模块"这种低级问题上浪费半天。


💬 想继续聊的

你在项目里有没有遇到过"检索能命中,但回答驴唇不对马嘴"的情况?大概率是 Prompt 里的 context 注入方式有问题,或者 chunk 切分粒度没调好。评论区说一句,下篇可以专门讲这个。

RAG 知识库踩坑复盘(五个真实 Bug)已经写好了,关注等更新。


相关推荐
MeAT ITEM2 小时前
ShardingSphere-jdbc 5.5.0 + spring boot 基础配置 - 实战篇
java·spring boot·后端
ekuoleung2 小时前
Spring Boot 3.4 + Java 21 在量化平台中的架构实践
java·架构
Black蜡笔小新2 小时前
国标GB28181视频监控平台EasyCVR赋能平安乡村建设,构筑乡村治理“数字防线”
java·网络·音视频
蚰蜒螟2 小时前
从 pthread_create 到 thread_native_entry:glibc 如何唤醒 Java 线程
java·开发语言
callJJ3 小时前
JVM 类加载机制详解——从 .class 文件到对象诞生的完整旅程
java·jvm·类加载·双亲委派模型
Kiling_07043 小时前
Java Math类核心用法全解析
java·开发语言
踏着七彩祥云的小丑3 小时前
开发中用到的注解
java
小梦爱安全3 小时前
Ansible剧本1
java·网络·ansible
王莎莎-MinerU3 小时前
MinerU + LangChain 实战:从 PDF 解析到 AI 问答全流程
人工智能·langchain·pdf·开源·产品运营·团队开发·个人开发