Skip to main content

RAG 多索引检索架构

核心思想

传统 RAG 中,每个 chunk(文本段)只有一个向量入口——chunk 原文的 Embedding。当用户的提问措辞与 chunk 原文差异较大时,召回率会明显下降。 多索引架构将索引内容解耦:一个 chunk 可以拥有多个索引入口,每个索引独立 Embedding、独立参与检索,但命中后统一返回原始 chunk 内容。
┌─────────────────────────────────────────────┐
│                 aigc_segment                │
│  id: seg_001                                │
│  content: "LangChat是一个企业级AI开发平台…" │
├─────────────────────────────────────────────┤
│            aigc_segment_index               │
│                                             │
│  ① DEFAULT   "LangChat是一个企业级AI开发…" │  ← chunk 原文
│  ② TITLE     "产品介绍 - LangChat企业级…"  │  ← LLM 生成标题摘要
│  ③ AUTO_QA   "LangChat是什么?"            │  ← LLM 自动生成
│  ④ AUTO_QA   "有哪些AI开发平台?"          │  ← LLM 自动生成
│  ⑤ CUSTOM    "tycoding开发的"              │  ← 用户手动添加
└─────────────────────────────────────────────┘
         ↓ 每条索引独立 Embedding
    ┌─────────────┐
    │  向量数据库   │  metadata: { segment_id, index_hash, ... }
    └─────────────┘
         ↓ 检索命中任一索引
    返回 segment.content(原始 chunk 内容)

数据模型

aigc_segment — 文本段

存储文档拆分后的原始 chunk 内容,是知识的源数据
字段说明
id主键
docs_id所属文档
knowledge_id所属知识库
contentchunk 原文(LLM 实际读到的内容)
enabled是否启用

aigc_segment_index — 索引条目

存储每个 chunk 的多个索引入口,是检索的匹配依据
字段说明
id主键
segment_id关联 aigc_segment.id
index_hash唯一标识,写入向量库元数据,用于过滤
content索引文本(被 Embedding 的内容)
type索引类型
enabled是否启用
status向量化状态

索引类型

类型来源说明
DEFAULT系统自动chunk 原文,文档拆分时自动创建,每个 segment 必有一条
TITLELLM 生成 / 回退截取文档标题 + LLM 生成的 chunk 核心摘要,知识库开启 index_title_enabled 后生效。若配置了 modelId 则调用 LLM 生成语义化标题;未配置时回退到截取 chunk 前 100 字
AUTO_QALLM 生成针对 chunk 内容自动生成的用户可能提问,知识库开启 index_auto_qa_enabled 后生效
CUSTOM用户手动用户在 chunk 详情中手动添加的自定义索引关键词
IMAGE_DESC预留图片描述索引(预留扩展)

写入流程

文档导入向量化

上传文档
  → 文档拆分(DocumentSplitterService)
  → 创建 aigc_segment 记录
  → 创建 DEFAULT 类型 aigc_segment_index
  → [可选] 生成 TITLE 索引(IndexEnhancementService)
      → 有 modelId:LLM 读取 chunk 前 500 字 → 生成 20-50 字标题摘要
      → 无 modelId:回退截取 chunk 前 100 字
      → 最终内容格式:"文档名称 - 标题摘要"
  → [可选] 调用 LLM 生成 AUTO_QA 索引(IndexEnhancementService)
  → 从 aigc_segment_index 表构建 TextSegment 列表
  → 批量 Embedding 写入向量库
关键点:TextSegment 的 text 是索引文本(用于匹配),但 metadata 中携带 segment_id(用于回查原始内容)。

手动添加自定义索引

用户在 chunk 详情中输入自定义索引文本
  → 创建 CUSTOM 类型 aigc_segment_index(携带 segment_id)
  → 单条 Embedding 写入向量库(metadata 含 segment_id)

检索模式(SearchMode)

系统支持三种检索模式,通过 RetrievalConfig.searchMode 配置:
┌──────────────────────────────────────────────────────────┐
│                    SearchMode 枚举                       │
├──────────┬───────────────────────────────────────────────┤
│ SEMANTIC │ 纯向量检索(默认)                            │
│          │ 查询文本 → Embedding → 余弦相似度匹配         │
│          │ 所有向量库均支持                               │
├──────────┼───────────────────────────────────────────────┤
│ FULLTEXT │ 纯全文检索                                    │
│          │ 基于关键词的 BM25 / 倒排索引匹配              │
│          │ 仅 Elasticsearch 原生支持                      │
│          │ PgVector / Milvus 回退到 SEMANTIC              │
├──────────┼───────────────────────────────────────────────┤
│ HYBRID   │ 混合检索(向量 + 关键词)                     │
│          │ PgVector: RRF 算法融合两路排序                 │
│          │ Elasticsearch: kNN + BM25 混合查询             │
│          │ Milvus: 不支持,回退到 SEMANTIC                │
└──────────┴───────────────────────────────────────────────┘

检索配置数据结构

RetrievalConfig:
  searchMode: "SEMANTIC"    // SEMANTIC / FULLTEXT / HYBRID
  maxResults: 5             // 召回数量
  minScore:   0.7           // 召回分数阈值
  rrfK:       80            // RRF K 值(仅 HYBRID 生效)

配置优先级

检索配置在三个场景中使用,参数来源的优先级如下:
手动召回测试(/search 接口)
  └─ 直接使用 EmbeddingSearchReq 中的 searchMode / rrfK

应用对话(Chat with RAG)
  └─ LcChatReq.retrievalConfig  ← 来自 AigcApp.retrievalConfig
  └─ 若为空,回退到知识库默认值(AigcKnowledge.maxResults / minScore)

工作流节点(KnowledgeSearchNode)
  └─ 节点参数 params.searchMode / params.rrfK

Hybrid 模式的 minScore 处理

Hybrid 模式使用 RRF 算法,分数量纲与余弦相似度完全不同:
RRF_Score = 1/(K + rank_vector) + 1/(K + rank_keyword)

K = 80 时:
  最高分 = 2/(80+1) ≈ 0.025(双路排名 #1)
  RRF 分数不会达到 1.0,与余弦相似度(0~1)不可比
因此 HYBRID 模式不使用 minScore 过滤 RRF 分数(传 minScore=0 给向量库),而是通过余弦相似度后置过滤保证结果质量:
HYBRID 检索流程:
  1. PgVector HYBRID 搜索(minScore=0)
     → RRF 融合向量 + 关键词排序,rrfK 控制排名敏感度
     → 返回 maxResults 条结果

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

向量库实例与 SearchMode

不同 SearchMode 需要不同的向量库实例配置(如 PgVector HYBRID 模式需要额外创建全文索引),因此 缓存键包含 searchMode。同一个知识库在 SEMANTIC 和 HYBRID 模式下会使用不同的 VectorStore 实例。

检索流程

向量搜索(/search 接口)

用户选择检索模式 + 输入查询文本
  → EmbeddingSearchService.search()
  → 根据 searchMode / rrfK 获取对应模式的 EmbeddingStore
  → 向量化查询文本(queryEmbedding)
  → 从 aigc_segment_index 获取启用的 index_hash 集合
  → 向量库搜索(filter: index_hash IN hashIds)
      SEMANTIC / FULLTEXT: minScore = 用户设定值
      HYBRID:              minScore = 0(RRF 分数不可比)
  → [HYBRID] 后置过滤:CosineSimilarity(queryEmbedding, match.embedding) → RelevanceScore >= 用户 minScore
  → 命中 N 条索引
  → 从 metadata 提取 segment_id
  → 批量查询 aigc_segment 获取原始 chunk 内容
  → 返回原始 chunk 内容(而非索引文本)

RAG 对话检索

用户发起对话
  → ChatServiceImpl.handleAppInfo()
      → 从 AigcApp 加载 retrievalConfig
  → KnowledgeRetrieverBuilder.build(req)
      → 从 req.retrievalConfig 解析 searchMode / maxResults / minScore / rrfK
      → per 知识库:
          → KnowledgeFactory.getEmbeddingStore(id, vectorStoreId, searchMode, rrfK)
          → EmbeddingStoreContentRetriever(dynamicFilter, maxResults, minScore)
              SEMANTIC / FULLTEXT: minScore = 用户设定值
              HYBRID:              minScore = 0(RRF 分数不可比)
          → SegmentAwareContentRetriever 包装(索引文本 → 原始 chunk)
              [HYBRID] 额外传入 embeddingModel + minScore,用于余弦相似度后置过滤
  → RagRetrieverBuilder 聚合所有检索器
  → [可选] Rerank 重排序
  → LLM 接收到的 context 是原始 chunk 内容

索引与内容分离的核心机制

向量库中存储的 TextSegment 结构:
TextSegment:
  text: "tycoding开发的"              ← 索引文本,用于向量匹配
  metadata:
    knowledge_id: "k_001"
    docs_id: "d_001"
    index_hash: "hash_xxx"            ← 用于向量过滤
    segment_id: "seg_001"             ← 用于回查原始内容
检索命中后,通过 segment_id 回查 aigc_segment 表,返回 segment.content 作为最终结果。这保证了:
  • 匹配维度丰富:同一个 chunk 可通过原文、标题、问题、自定义关键词等多种路径被召回
  • 返回内容一致:无论通过哪个索引命中,LLM 和用户看到的始终是完整的原始 chunk 内容
  • 旧数据兼容:若 metadata 中无 segment_id(迁移前的数据),自动回退到直接返回 embedded.text()

segment_id 去重

同一个 chunk 可能有多条索引(DEFAULT、TITLE、AUTO_QA、CUSTOM)同时命中,如果不去重会返回多条完全相同的内容。因此在内容回查阶段按 segment_id 去重,同一 segment 只保留得分最高的那条命中
向量库返回 5 条 match:
  match_1: index_hash=h1 (DEFAULT)  segment_id=seg_001  score=0.92
  match_2: index_hash=h2 (CUSTOM)   segment_id=seg_001  score=0.88  ← 同 segment,去重
  match_3: index_hash=h3 (CUSTOM)   segment_id=seg_001  score=0.85  ← 同 segment,去重
  match_4: index_hash=h4 (DEFAULT)  segment_id=seg_002  score=0.81
  match_5: index_hash=h5 (AUTO_QA)  segment_id=seg_002  score=0.79  ← 同 segment,去重

去重后返回 2 条结果:
  seg_001 → score=0.92(取最高分)
  seg_002 → score=0.81(取最高分)
去重在两个路径中均已实现:
  • /search 接口:EmbeddingSearchService.convertSearchResults() 中使用 LinkedHashMap<segmentId, result> 去重
  • RAG 对话:SegmentAwareContentRetriever.retrieve() 中使用 LinkedHashMap<segmentId, content> 去重

索引增强(IndexEnhancementService)

索引增强服务在文档导入阶段为每个 chunk 自动生成额外的索引入口,提升不同表述方式下的召回率。所有增强索引共用知识库配置的 modelId(增强检索 LLM 模型)。

TITLE 索引 — LLM 标题摘要

目的:为 chunk 生成一句语义化的标题描述,让用户以”主题/概念”维度提问时也能精准命中。 生成流程
开启 index_title_enabled
  → 检查知识库是否配置 modelId

  ├─ 有 modelId:
  │    → 获取 ChatModel(在循环外获取一次,所有 chunk 共用)
  │    → per chunk:
  │        → 截取 chunk 前 500 字作为 LLM 输入
  │        → System Prompt: "生成 20-50 字的标题摘要,概括核心主题"
  │        → LLM 返回标题文本
  │        → 清理引号等格式字符
  │        → 最终内容: "文档名称 - LLM生成的标题"
  │        → [失败回退] "文档名称 - chunk前100字"

  └─ 无 modelId:
       → per chunk:
           → 最终内容: "文档名称 - chunk前100字"
效果对比
chunk 原文: "LangChat是一个企业级AI开发平台,支持多种模型接入,提供聊天对话、
            RAG知识库检索、工作流编排等核心功能,并集成Coze、Dify等第三方平台..."

旧方案(截取): "产品介绍.pdf - LangChat是一个企业级AI开发平台,支持多种模型接入,提供聊天对话、RAG知识库检索、工作流编排等核心功能,并集成..."
新方案(LLM):  "产品介绍.pdf - LangChat企业级AI开发平台的核心功能与多模型接入能力介绍"
语义化标题将 chunk 的核心主题浓缩为一句话,Embedding 后在向量空间中更接近用户以概念/主题方式提出的问题。

AUTO_QA 索引 — LLM 问题生成

目的:预测用户可能针对 chunk 提出的问题,用”问题”作为索引入口,弥补 chunk 原文与用户问句之间的表述差异。 生成流程
开启 index_auto_qa_enabled + 配置 modelId
  → 获取 ChatModel
  → per chunk:
      → System Prompt: "生成用户可能提出的问题,多样化覆盖不同知识点"
      → User Prompt: "根据以下文本生成 N 个问题:\n\n{chunk全文}"
      → LLM 返回多行问题
      → 解析:按行拆分 → 移除编号前缀 → 去空行
      → 限制数量: min(解析结果数, indexAutoQaCount)
      → 每个问题创建一条 AUTO_QA 类型索引
配置参数
参数默认值说明
index_auto_qa_enabledfalse是否开启
modelId-使用的 LLM 模型(TITLE 和 AUTO_QA 共用)
index_auto_qa_count3每个 chunk 生成的问题数量

增强索引的成本考量

  • TITLE 索引:每个 chunk 调用一次 LLM(输入约 500 字,输出约 30 字),token 消耗较低
  • AUTO_QA 索引:每个 chunk 调用一次 LLM(输入为 chunk 全文,输出 N 个问题),token 消耗与 chunk 大小和问题数量成正比
  • 两者共用 modelId,建议配置成本较低的模型(如 GPT-4o-mini、Qwen-Turbo 等)
  • 增强索引在文档导入时一次性生成,不会增加检索时的 LLM 调用开销

关键类职责

所在包职责
RagRetrieverBuilderbuilderRAG 检索器总调度,聚合知识库检索器、SQL 检索器和 Rerank
KnowledgeRetrieverBuilderbuilder构建知识库 ContentRetriever,从 RetrievalConfig 读取检索参数
SegmentAwareContentRetrieverbuilder包装层,将索引文本替换为原始 chunk 内容;HYBRID 模式下通过余弦相似度后置过滤
SqlRetrieverBuilderbuilder构建数据源 Text2SQL 检索器
AiServiceBuilderbuilderAI Service 构建器,集成 RAG / Tools / Memory / SystemMessage
PromptProcessorbuilder系统提示词处理,变量替换与格式增强
EmbeddingSearchServiceembedding独立搜索服务,供 /search 接口调用,内置内容回查和 HYBRID 后置过滤逻辑
IndexEnhancementServiceservice索引增强服务,负责生成 TITLE(LLM 摘要 / 回退截取)和 AUTO_QA(LLM 问题生成)索引
VectorStoreFactoryfactory向量库实例创建与缓存,支持动态 SearchMode
KnowledgeFactoryfactory知识库门面,统一获取 EmbeddingStore 和 EmbeddingModel

检索配置使用场景

场景配置来源核心类
手动召回测试EmbeddingSearchReq.searchMode/rrfKEmbeddingSearchService
应用对话AigcApp.retrievalConfigLcChatReq.retrievalConfigKnowledgeRetrieverBuilder
工作流节点节点 params.searchMode/rrfKKnowledgeSearchNodeEmbeddingSearchService

知识库配置项

字段说明
index_title_enabled导入时自动生成标题索引(有 modelId 时调用 LLM 生成摘要标题,无 modelId 时截取前 100 字)
index_auto_qa_enabled导入时调用 LLM 为每个 chunk 生成问题索引
index_auto_qa_model_id生成问题索引使用的 LLM 模型
index_auto_qa_count每个 chunk 生成的问题数量(默认 3)
max_results检索返回的最大结果数
min_score检索相似度阈值