Skip to main content

向量存储与 RAG 检索架构设计

一、整体架构概览

LangChat 的 RAG 系统分为两条核心链路:文档入库(Ingestion)检索增强生成(Retrieval),共享同一套工厂层基础设施。
┌─────────────────────────────────────────────────────────────────┐
│                        工厂层(Factory)                          │
│  ┌──────────────────┐  ┌──────────────────┐  ┌───────────────┐  │
│  │ KnowledgeFactory │──│ VectorStoreFactory│  │EmbeddingModel │  │
│  │   (门面入口)    │  │  (向量库实例)   │  │   Factory     │  │
│  └──────────────────┘  └──────────────────┘  └───────────────┘  │
│         │                      │                     │          │
│         │              ┌───────┴───────┐             │          │
│         │              │   Caffeine    │             │          │
│         │              │   本地缓存    │             │          │
│         │              └───────────────┘             │          │
└─────────┼──────────────────────┼─────────────────────┼──────────┘
          │                      │                     │
    ┌─────┴──────┐         ┌─────┴──────┐        ┌────┴─────┐
    │  PgVector  │         │Elasticsearch│       │  Milvus  │
    └────────────┘         └─────────────┘       └──────────┘

二、向量存储设计

2.1 多后端支持

后端SEMANTICFULLTEXTHYBRID特点
PgVector✗(回退 SEMANTIC)✓(RRF 融合)向量 + PostgreSQL 全文检索,开箱即用
Elasticsearch✓(kNN + BM25)向量 + 全文检索,需企业版许可
Milvus✗(回退 SEMANTIC)✗(回退 SEMANTIC)纯向量检索,适合大规模数据集

2.2 检索模式(SearchMode)

系统通过 SearchMode 枚举定义三种检索模式,在向量库构建阶段即生效:
  • SEMANTIC(默认):纯向量检索,通过 Embedding 余弦相似度匹配,所有向量库均支持
  • FULLTEXT:纯关键词检索,基于 BM25 / 倒排索引匹配,仅 Elasticsearch 原生支持
  • HYBRID:混合检索,同时使用向量和关键词两路搜索后融合排序
SearchMode 影响向量库实例的构建方式。以 PgVector 为例:
buildPgVectorStore(knowledgeId, config, searchMode, rrfK):
  builder = PgVectorEmbeddingStore.builder()
      .host(...).port(...).table(knowledgeId)...

  if searchMode == HYBRID:
      builder.searchMode(HYBRID).rrfK(rrfK)   // 启用全文索引 + RRF 融合
  else if searchMode == FULLTEXT:
      log.warn("PgVector不支持纯全文检索,回退到SEMANTIC")

  return builder.build()

2.3 实例缓存策略

向量存储实例通过 Caffeine 本地缓存管理,缓存键由连接参数(provider、host、port、username、password、database、dimension、tableName)和 searchMode 的 MD5 哈希生成。
  • 相同配置参数 + 相同 searchMode 的请求共享同一个连接实例
  • 不同 searchMode 使用不同实例(因为 HYBRID 模式需额外全文索引配置)
  • 每个知识库使用 knowledgeId 作为表名/集合名,实现数据隔离
  • 配置变更时通过 clearCache() 失效所有 searchMode 的缓存
缓存键 = MD5(provider|host|port|user|pass|db|dim|table|searchMode)

2.4 KnowledgeFactory — 门面层

KnowledgeFactory 是获取向量库和 Embedding 模型的统一入口,屏蔽了 VectorStore 配置查询细节:
getEmbeddingStore(knowledgeId)
    → 查询 AigcKnowledge → 获取 vectorStoreId
    → 查询 AigcVectorStore 配置
    → VectorStoreFactory.getVectorStore(knowledgeId, vectorStoreId, config)

getEmbeddingStore(knowledgeId, vectorStoreId, searchMode, rrfK)
    → 查询 AigcVectorStore 配置
    → VectorStoreFactory.getVectorStore(knowledgeId, vectorStoreId, config, searchMode, rrfK)

2.5 Hybrid 搜索与 minScore 适配

PgVector Hybrid 模式采用 RRF(Reciprocal Rank Fusion)算法融合两路排序:
RRF_Score = 1/(K + rank_vector) + 1/(K + rank_keyword)

K = 80 时:
  双路最高分 = 2/81 ≈ 0.0247(两路都排名 #1)
  单路最高分 = 1/81 ≈ 0.0123(只被一路搜索命中)
核心问题:用户配置的 minScore(如 0.69)是基于余弦相似度(0~1)的语义,直接用于 RRF 会过滤掉全部结果。 解决方案:HYBRID 模式不使用 minScore 过滤 RRF 分数(传 minScore=0 给向量库),而是通过余弦相似度后置过滤保证结果质量:
HYBRID 检索流程:
  1. 向量库搜索(minScore=0)
     → RRF 融合向量 + 关键词排序,rrfK 控制排名敏感度
     → 返回 maxResults 条结果

  2. 后置过滤(CosineSimilarity)
     → 计算每条结果与查询的余弦相似度
     → 余弦相似度 < 用户设的 minScore → 过滤掉
     → 和 SEMANTIC 模式用同样的质量标准
实现位置:
  • /search 接口:EmbeddingSearchService.search() 中直接后置过滤
  • RAG 对话:SegmentAwareContentRetriever 中通过 embeddingModel 计算余弦相似度后置过滤

三、检索配置系统(RetrievalConfig)

3.1 配置数据结构

RetrievalConfig:
  searchMode: "SEMANTIC"    // SEMANTIC / FULLTEXT / HYBRID
  maxResults: 5             // 召回数量
  minScore:   0.7           // 召回分数阈值(0~1,用户语义)
  rrfK:       80            // RRF K 值(仅 HYBRID 生效)

3.2 三个使用场景

┌───────────────────────────────────────────────────────────────────┐
│                    检索配置的三条路径                               │
│                                                                   │
│  ① 手动召回测试                                                   │
│     SegmentSearch UI → EmbeddingSearchReq.searchMode/rrfK         │
│                      → EmbeddingSearchService                     │
│                                                                   │
│  ② 应用对话                                                       │
│     AigcApp.retrievalConfig → ChatServiceImpl.handleAppInfo()     │
│                              → LcChatReq.retrievalConfig          │
│                              → KnowledgeRetrieverBuilder          │
│                                                                   │
│  ③ 工作流节点                                                     │
│     KnowledgeSearchNode.params.searchMode/rrfK                    │
│                              → EmbeddingSearchReq                 │
│                              → EmbeddingSearchService             │
└───────────────────────────────────────────────────────────────────┘

3.3 参数优先级

  • 应用对话RetrievalConfig 的值优先于知识库默认配置。若 retrievalConfig 为 null,回退到 AigcKnowledge.maxResults / minScore,searchMode 回退到 SEMANTIC
  • 手动测试 / 工作流:直接使用请求参数,null 值回退到默认值(SEMANTIC, rrfK=80)

四、文档入库链路(Ingestion)

AigcDocsController                    ← HTTP 入口


EmbeddingServiceImpl                  ← 业务编排层

  ├─ DocumentSplitterService          ← 文档分割
  │    ├─ 纯文本 → SplitterUtil(LangChain4j 分割器)
  │    └─ 文件   → DocumentParseService
  │         ├─ SMART/CUSTOM 模式 → ParserFactory(PDF/Word/Excel/CSV/图片解析器)
  │         ├─ MINERU 模式     → 外部 OCR 服务 → Markdown 分割
  │         └─ QA 模式         → 逐行解析为问答对

  ├─ KnowledgeFactory                 ← 获取模型和存储
  │    ├─ EmbeddingModelFactory       → EmbeddingModel 实例
  │    └─ VectorStoreFactory          → EmbeddingStore 实例

  └─ EmbeddingExecutor                ← 批量向量化
       ├─ 分批(每批 60 条)
       ├─ embeddingModel.embedAll()   → 生成向量
       ├─ embeddingStore.addAll()     → 写入向量库
       └─ ConsumptionRecorder        → 记录 Token 消耗

关键数据流

原始文档 → 解析/分割 → TextSegment[] → 向量化 → 向量库

                          ├─ metadata.knowledgeId  知识库归属
                          ├─ metadata.docsId       文档归属
                          ├─ metadata.segmentId    原始chunk关联
                          └─ metadata.indexHash    索引唯一标识(UUID)

                                 └─ 同步写入 MySQL
                                    aigc_segment(原始内容)
                                    aigc_segment_index(索引条目)

五、检索链路(Retrieval)

系统提供两条检索路径:

5.1 直接搜索(知识库搜索 UI)

搜索请求(含 searchMode / rrfK)


EmbeddingSearchService
  ├─ 1. 获取 EmbeddingModel
  ├─ 2. 根据 searchMode/rrfK 获取 EmbeddingStore
  ├─ 3. 查询 MySQL 获取已启用的索引 hashId 集合
  ├─ 4. 向量化查询文本(queryEmbedding)
  ├─ 5. 设置 searchMinScore
  │      SEMANTIC / FULLTEXT: minScore = 用户设定值
  │      HYBRID:              minScore = 0(RRF 分数不可比)
  └─ 6. embeddingStore.search(EmbeddingSearchRequest)
         ├─ queryEmbedding  → 向量相似度检索
         ├─ query           → 关键词检索(Hybrid 模式)
         ├─ filter          → INDEX_HASH.isIn(hashIds)
         ├─ maxResults       → 召回数量
         └─ minScore         → 适配后的阈值
  → 7. [HYBRID] 后置过滤
         ├─ CosineSimilarity(queryEmbedding, match.embedding)
         └─ RelevanceScore >= 用户 minScore → 保留,否则过滤
  → 8. convertSearchResults()
         ├─ 从 metadata 提取 segment_id
         ├─ 批量查询 aigc_segment 获取原始 chunk 内容
         └─ 返回原始 chunk 内容(而非索引文本)

5.2 RAG 对话检索(Chat with RAG)

POST /aigc/chat/completions


ChatEndpoint → ChatServiceImpl
  ├─ handleAppInfo()     ← 从 App 配置加载 knowledgeIds + retrievalConfig
  └─ LcChatServiceImpl.streamingChat()

       ├─ ChatModelFactory       → StreamingChatModel

       └─ AiServiceBuilder.build()

            ├─ ChatMemory         → Redis 支持的消息窗口
            ├─ SystemMessage       → PromptProcessor 处理
            ├─ Tools               → DynamicToolFactory(插件 + MCP)

            └─ RAG(DefaultRetrievalAugmentor)

                 ├─ QueryRouter(DefaultQueryRouter)
                 │    将查询路由到所有注册的检索器

                 ├─ KnowledgeRetrieverBuilder ─────────────────┐
                 │    从 req.retrievalConfig 解析:              │
                 │    searchMode / maxResults / minScore / rrfK│
                 │                                              │
                 │    per 知识库:                               │
                 │    ├─ EmbeddingModel + EmbeddingStore        │
                 │    │     (searchMode/rrfK 传入工厂)          │
                 │    ├─ 查询已启用索引 → hashIds              │
                 │    ├─ 设置 searchMinScore                   │
                 │    │     SEMANTIC: minScore = 用户设定值     │
                 │    │     HYBRID:   minScore = 0             │
                 │    └─ EmbeddingStoreContentRetriever        │
                 │         → SegmentAwareContentRetriever 包装 │
                 │           [HYBRID] 传入 embeddingModel +    │
                 │           minScore 用于余弦相似度后置过滤    │
                 │                                              │
                 ├─ SqlRetrieverBuilder ────────────────────────┤
                 │    per 数据源:                              │
                 │    └─ Text2SQL 检索器                       ├→ 合并
                 │                                              │
                 └─ ContentAggregator ─────────────────────────┘
                      ├─ 无 Rerank → 默认按分数排序
                      └─ 有 Rerank → ReRankingContentAggregator
                           └─ ScoringModel(RerankModelFactory)

六、段落启用/禁用机制

向量库中的数据不会被删除。运行时通过 MySQL 的 aigc_segment_index 表控制可见性:
aigc_segment_index 表
  ├─ status = true     向量化成功
  ├─ enabled = true    用户启用
  └─ indexHash         对应向量库中的 metadata.INDEX_HASH
检索时查询 status=true AND enabled=true 的索引条目,收集 indexHash 集合,注入 MetadataFilter 过滤。这样用户禁用某个段落时,无需操作向量库,只需修改 MySQL 字段即可生效。

七、多路检索融合

当一次对话关联多个知识库时,系统会创建多个检索器实例。LangChain4j 的 DefaultQueryRouter 将同一查询发送给所有检索器,各自独立返回结果,最终由 ContentAggregator 合并:
场景聚合策略
未启用 Rerank按各检索器返回的原始分数排序
启用 Rerank所有结果送入 ScoringModel 重新打分排序
Rerank 模型支持通义、GiteeAI、SiliconFlow、Xinference 等多个提供商。

八、可观测性

组件职责
ConsumptionRecorder记录 Embedding Token 消耗(入库 + 检索)
ChatListener / ChatTrace记录对话生命周期(推理、RAG 来源、Tool 调用、Token 用量)
日志各层关键节点均有结构化日志,包含 knowledgeId、searchMode、resultCount、score 等字段

九、核心类索引

所在包职责
VectorStoreFactoryfactory.vectorstore向量库实例创建与缓存,支持动态 SearchMode
KnowledgeFactoryfactory.knowledge知识库门面,统一获取 EmbeddingStore 和 EmbeddingModel
EmbeddingModelFactoryfactory.modelEmbedding 模型实例创建与缓存
RerankModelFactoryfactory.modelRerank 模型实例创建与缓存
RagRetrieverBuilderbuilderRAG 检索器总调度,聚合知识库 + SQL 检索器 + Rerank
KnowledgeRetrieverBuilderbuilder知识库检索器构建,从 RetrievalConfig 读取参数
SegmentAwareContentRetrieverbuilder包装层,索引文本 → 原始 chunk 内容替换;HYBRID 模式下通过余弦相似度后置过滤
SqlRetrieverBuilderbuilderText2SQL 检索器构建
AiServiceBuilderbuilderAI Service 总构建器(RAG + Tools + Memory)
EmbeddingSearchServiceembedding/search 接口的独立搜索服务
SearchModecommon.ai.consts检索模式枚举(SEMANTIC / FULLTEXT / HYBRID)
RetrievalConfigcommon.ai.dto检索配置 DTO