跳转到内容

JeecgBoot RAG 知识库引擎

JeecgBoot RAG 知识库引擎

学习目标

通过本章学习,你将能够:

  • 理解 JeecgBoot 的 RAG 全流程:文档摄入 → 分段 → 向量化 → 检索
  • 掌握可配置分段策略的实现方案
  • 了解多知识库 QueryRouter 的路由机制
  • 掌握 pgvector 向量存储的缓存与元数据过滤设计

前置知识

本章涉及 RAG 管线的通用原理,建议先阅读:

下文假设你已理解上述概念,直接聚焦 JeecgBoot 的具体实现。


项目实践

文档摄入流水线

JeecgBoot 支持三种文档来源:

来源类型实现方式
文件上传本地文件路径 → Apache Tika 解析
网页抓取URL → Jsoup/WebPageParser 提取正文
文本录入直接输入文本 → 跳过解析

多格式解析

文件上传
TikaDocumentParser(Apache Tika 3.2.3)
├── tika-parser-pdf-module → PDF
├── tika-parser-html-module → HTML
├── tika-parser-text-module → 纯文本
└── langchain4j-document-parser-apache-poi → Word/PPT/Excel
Document(langchain4j 统一文档模型)

可配置分段策略

这是 JeecgBoot RAG 系统的核心特色之一。分段参数不在代码中硬编码,而是存储在知识库配置中:

Knowledge 配置项
├── enableSegment # 是否开启默认分段(字符串:"enableSegment")
├── segmentStrategy # auto(自动)| custom(自定义)
├── maxSegment # 分段长度,默认 1000 字符
├── overlap # 重叠率,0-90%
├── separator # 分隔符:\n | \n\n | 。 | ! | ? | . | ! | ? | custom
├── customSeparator # 自定义分隔符
└── textRules # 文本预处理规则
├── cleanSpaces # 替换连续空格/换行/制表符
└── removeUrlsEmails # 删除 URL 和邮箱地址

分段实现

// 伪代码:CustomDocumentSplitter
DocumentSplitter createSplitter(knowledgeConfig) {
int chunkSize = config.maxSegment ?? 1000;
int overlap = config.overlap * chunkSize / 100;
String separator = resolveSeparator(config.separator, config.customSeparator);
return DocumentSplitters.recursive(chunkSize, overlap, separator);
}

分隔符解析

separator 值实际分隔符
\n换行符
\n\n双换行(段落边界)
中文句号
中文叹号
中文问号
.英文句号
custom用户自定义的 customSeparator

设计价值:不同文档类型适合不同的分段策略。技术文档按换行分段更准确,而文学性文本按句号分段能保持语义完整性。用户可根据知识库内容选择最优策略。

向量化与存储

向量存储架构

TextSegment[](分段结果)
EmbeddingModel(可配置的嵌入模型)
Embedding[]
PgVectorEmbeddingStore(PostgreSQL + pgvector)
├── 向量数据 → pgvector 列
└── 元数据 → metadata JSONB 列
├── knowledgeId # 知识库 ID(隔离不同知识库)
├── username # 创建用户(租户隔离)
├── docId # 文档 ID(溯源)
├── docName # 文档名称
└── createTime # 创建时间

向量存储缓存

// 内存中的向量存储缓存
private static final ConcurrentHashMap<String, EmbeddingStore<TextSegment>> EMBED_STORE_CACHE;
EmbeddingStore getStore(knowledgeId) {
return EMBED_STORE_CACHE.computeIfAbsent(knowledgeId, id ->
PgVectorEmbeddingStore.builder()
.host(embedStoreConfigBean.getHost())
.database(embedStoreConfigBean.getDatabase())
.table("knowledge_" + id) // 每个知识库独立的表
.dimension(embedModel.getDimension())
.build()
);
}

设计亮点:每个知识库对应 pgvector 中的独立表(knowledge_{id}),实现了数据物理隔离。缓存避免重复创建连接。

多知识库 QueryRouter 路由

当 AI 应用绑定了多个知识库时,通过 DefaultQueryRouter 统一路由:

// 伪代码:getQueryRouter
QueryRouter getQueryRouter(List<String> knowIds, int topNumber, double similarity) {
List<ContentRetriever> retrievers = new ArrayList<>();
for (String knowId : knowIds) {
EmbeddingStore store = getStore(knowId);
EmbeddingModel embedModel = getEmbeddingModel(knowId);
ContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(embedModel)
.maxResults(topNumber)
.minScore(similarity)
.build();
retrievers.add(retriever);
}
return new DefaultQueryRouter(retrievers);
}

检索参数来源:AI 应用界面可独立配置 topNumber(返回文档数)和 similarity(相似度阈值),这些参数会覆盖知识库的默认值。

知识库文档生命周期

构建流程:

  1. 上传文档 → 状态设为 building
  2. Tika 解析 → 分段 → 向量化入库
  3. 成功 → 状态设为 complete
  4. 失败 → 状态设为 failed,记录错误信息

问题与规避

陷阱表现对策
分段过长导致向量匹配不精确chunkSize=1000 但实际分段远超设置 maxSegment 上限,超出强制截断
Markdown 图片链接被当作分段边界![](url) 中包含换行分段前先提取 Markdown 图片链接,避免在图片语法中间分割
pgvector 表名冲突知识库 ID 包含特殊字符表名使用 knowledge_ 前缀 + 数字 ID
嵌入模型切换导致已有向量失效不同模型维度不同切换嵌入模型时标记已有文档为”需重建”
文档更新后旧向量未删除修改文档内容但只追加新向量删除旧 docId 对应的向量后再插入新向量

设计取舍

物理隔离 vs 逻辑隔离

JeecgBoot 选择:每个知识库独立的 pgvector 表(物理隔离)。

方案优势代价
物理隔离(独立表)数据隔离彻底,可按知识库删除/重建表数量多时管理复杂
逻辑隔离(metadata 过滤)单表管理简单删除知识库数据需要扫描全表

在多租户 SaaS 场景中,物理隔离更符合数据安全要求。

同步向量化 vs 异步向量化

JeecgBoot 选择:同步向量化(用户上传后等待构建完成)。

方案体验复杂度
同步用户知道进度,但大文件等待时间长
异步用户可继续操作,但需要状态轮询高(需要任务队列、状态通知)

对于企业级文档(通常几 MB 以内),同步方案已够用。


参考来源