Files
ailine/rag_indexer/README.md
root 933d418d77
Some checks failed
构建并部署 AI Agent 服务 / deploy (push) Failing after 17m12s
检索器重构
2026-04-19 22:01:55 +08:00

281 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 离线 RAG 索引构建系统 (Offline RAG Indexer)
该模块负责 RAG 系统的阶段一:**离线索引构建**。它将外部的非结构化数据如文档、PDF、网页等清洗、切分并转化为向量最终存入向量数据库中。
## 🎯 演进路线与核心算法 (Roadmap)
### Level 1: 基础暴力切分 (Basic Recursive Splitting)
- **核心算法**: 递归字符切分。它按照预定义的分隔符列表(如 `["\n\n", "\n", "。", "", "", " ", ""]`)从大到小尝试切分文本,直到每块的大小满足最大长度限制。
- **优缺点**: 实现极简单,速度快。但非常容易将一句话拦腰截断,导致上下文语义丢失。
- **实现指南**:
-`langchain_text_splitters` 导入 `RecursiveCharacterTextSplitter`
- 实例化时设置 `chunk_size`(如 500`chunk_overlap`(如 50直接调用 `.split_documents(raw_docs)` 方法。
### Level 2: 语义动态切分 (Semantic Chunking)
- **核心算法**: 句子级相似度阈值算法。
1. 将文章按标点符号按句子拆分。
2. 使用轻量级 Embedding 模型将每一句向量化。
3. 计算相邻两句之间的余弦相似度 (Cosine Similarity)。
4. 当相似度低于设定阈值时(说明两句话讲的不是同一件事,语义发生了转折),在此处"切断"形成一个新的块。
- **优缺点**: 极大程度保留了段落内语义的连贯性,对 LLM 回答非常友好。但由于在切分阶段就需要调用向量模型,耗时略长。
- **实现指南**:
-`langchain_text_splitters` 导入 `TextSplitter` 作为基类。
-`langchain_experimental.text_splitter` 导入 `SemanticChunker`
- 实现 `SemanticChunkerAdapter` 继承 `TextSplitter`,解决类型不兼容问题。
- 实例化时需要传入你已经配置好的 Embedding 模型实例(如基于 `LlamaCppEmbedder` 封装的本地模型)。
### Level 3: 高级父子块策略 (Parent-Child / Auto-merging)
- **核心算法**: 层次化双重存储与映射。
- **切分机制**: 首先将文档粗切为较大的"父块 (Parent Chunk, 约 1000 字符)",随后将父块细切为较小的"子块 (Child Chunk, 约 200 字符)"。
- **存储机制**: 仅仅将**子块**的向量存入 Qdrant 用于精准计算距离;将**父块**的原始内容存在 PostgreSQL DocStore 中,通过 UUID 相互映射。
- **核心思路**: 解决 RAG 领域经典的矛盾——检索时块越小越容易精确命中(去除噪声);但生成回答时,块越大越能给大模型提供充足的上下文背景。
- **实现指南**:
- 使用 `langchain_classic.retrievers` 中的 `ParentDocumentRetriever` 模块。
- 在写入时,你需要同时准备一个底层的 `VectorStore` (即 Qdrant) 和一个 `BaseStore`
- **推荐方案**: 使用 `PostgresDocStore` 作为 docstore支持持久化存储。
- 将两种不同的 `TextSplitter` 分别赋值给检索器的 `child_splitter``parent_splitter`,然后调用 `.add_documents()` 即可让系统自动完成映射。
### Level 3.1: PostgreSQL DocStore 集成
- **核心优势**: 利用 PostgreSQL 作为持久化存储,适合生产环境。使用异步连接池,支持高并发。
- **实现步骤**:
1. **配置连接**: 设置 `DB_URI` 环境变量或通过 `docstore_conn_string` 参数指定
2. **创建 docstore**: 使用 `rag_indexer.store.create_docstore()` 工厂函数
3. **注入到 IndexBuilder**: 通过构造函数参数注入
- **使用示例**:
```python
from rag_indexer.builder import IndexBuilder, SplitterType
# 创建 IndexBuilder
builder = IndexBuilder(
collection_name="rag_documents",
splitter_type=SplitterType.PARENT_CHILD,
parent_chunk_size=1000,
child_chunk_size=200,
docstore_conn_string="postgresql://user:pass@host:5432/db",
)
```
### Level 3.2: 语义切分与父子块策略结合
- **核心优势**: 结合语义切分的连贯性和父子块策略的层次化存储优势,实现更精准的检索和更丰富的上下文。
- **实现原理**:
- **父块切分**: 使用 `RecursiveCharacterTextSplitter` 创建大块约1000字符提供完整的上下文背景
- **子块切分**: 使用 `SemanticChunkerAdapter` 创建小块,根据语义连贯性动态切分,提高检索精度
- **存储机制**: 子块向量存入 Qdrant 用于精准检索,父块内容存入 PostgreSQL 提供完整上下文
- **使用示例**:
```python
from rag_indexer.builder import IndexBuilder, SplitterType
# 创建 IndexBuilder结合语义切分与父子块策略
builder = IndexBuilder(
collection_name="rag_documents",
splitter_type=SplitterType.PARENT_CHILD,
# 父子块配置
parent_chunk_size=1000,
child_chunk_size=200,
# 子块使用语义切分
child_splitter_type=SplitterType.SEMANTIC,
# PostgreSQL 存储配置
docstore_conn_string="postgresql://user:pass@host:5432/db",
)
```
- **配置参数**:
- `child_splitter_type`: 子块切分器类型,可选 `SplitterType.RECURSIVE`(默认)或 `SplitterType.SEMANTIC`
- 当使用语义切分时,系统会自动使用已配置的 Embedding 模型进行句子级相似度计算
### Level 4: GraphRAG基于图和关系的 RAG
- **核心算法**: LLM 实体关系抽取 (NER & Relation Extraction)。
- **核心思路**: 解决传统纯向量检索难以处理"跨文档复杂关系推理"的痛点A公司的CEO是谁他名下的B公司主要业务是什么这种需要横跨多页 PDF 的跳跃性问题)。
- **实现原理**:
1. **实体提取**: 利用 LLM 从文档中提取实体(如人物、组织、地点、事件等)
2. **关系抽取**: 识别实体之间的关系(如"CEO of"、"founded by"、"located in"等)
3. **图构建**: 将实体作为节点,关系作为边,构建知识图谱
4. **混合检索**: 结合向量检索和图查询,同时利用语义相似性和结构关系
- **技术栈**:
- **图数据库**: Neo4j 或 RedisGraph
- **LLM 工具**: `LLMGraphTransformer` 或自定义 Prompt
- **集成方式**: 与向量存储并行,形成混合检索系统
- **实现指南**:
- 使用 `langchain_community.graphs` 模块
- 配置本地大模型(如 `Gemma-4-E2B`)用于实体关系抽取
- 构建包含实体和关系的图结构,存储到图数据库
- 实现混合检索逻辑,结合向量相似度和图路径分析
### Level 5: 多模态 RAG (Multi-modal RAG)
- **核心算法**: 跨模态嵌入和多模态融合。
- **核心思路**: 突破纯文本限制,支持图像、表格、音频等多种数据类型的理解和检索。
- **实现原理**:
1. **多模态嵌入**: 使用 CLIP 等模型将不同模态数据映射到统一向量空间
2. **多模态索引**: 为不同类型的内容创建专用索引
3. **跨模态检索**: 支持以文搜图、以图搜文等跨模态查询
- **技术栈**:
- **多模态模型**: CLIP、BLIP 等
- **存储**: 向量数据库 + 对象存储
- **检索**: 混合向量检索
---
## 📂 架构与文件结构设计
```
rag_indexer/
├── __init__.py
├── loaders.py # 负责调用 unstructured 解析不同类型文件
├── splitters.py # 负责实现 Recursive、Semantic 切分逻辑及适配器
├── embedders.py # 封装本地 llama.cpp 交互的 Embedding 接口
├── vector_store.py # 封装 Qdrant 写入、Upsert、Collection 初始化操作
├── builder.py # 核心编排文件,将上述模块串联成 Pipeline
├── cli.py # 命令行入口
└── store/
├── __init__.py
├── factory.py # docstore 工厂函数
└── postgres.py # PostgreSQL DocStore 实现
```
---
## 🔄 工作流程详解
### 数据流向总览
```
┌─────────────────────────────────────────┐
│ builder.py │
│ IndexBuilder 入口 │
└─────────────────┬───────────────────────┘
┌─────────────────▼───────────────────────┐
│ loaders.py │
│ DocumentLoader.load_file() │
│ → 返回 List[Document] │
└─────────────────┬───────────────────────┘
┌─────────────────▼───────────────────────┐
│ ParentDocumentRetriever │
│ ┌─────────────────────────────────┐ │
│ │ parent_splitter (粗切) │ │
│ │ 父块 ~1000 字符 │ │
│ └────────────┬────────────────────┘ │
│ │ │
│ ┌────────────▼────────────────────┐ │
│ │ child_splitter (细切) │ │
│ │ 子块 ~200 字符 │ │
│ └────────────┬────────────────────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ 子块向量 父块原始内容 │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────┐ ┌─────────────────┐ │
│ │vector_store│ │ store/ │ │
│ │ (Qdrant) │ │ (PostgreSQL) │ │
│ └────────────┘ └─────────────────┘ │
└─────────────────────────────────────────┘
```
### 文件职责详解
| 文件 | 职责 | 关键类/函数 |
|------|------|------------|
| **builder.py** | 核心编排,负责串联整个流程 | `IndexBuilder` |
| **loaders.py** | 解析各种文档格式PDF、Word、TXT等 | `DocumentLoader` |
| **splitters.py** | 文本切分策略Recursive/Semantic及适配器 | `SplitterType`, `get_splitter()`, `SemanticChunkerAdapter` |
| **embedders.py** | 向量化(封装 llama.cpp embedding 接口) | `LlamaCppEmbedder` |
| **vector_store.py** | Qdrant 向量数据库操作 | `QdrantVectorStore` |
| **store/postgres.py** | PostgreSQL DocStore 实现 | `PostgresDocStore` |
| **store/factory.py** | docstore 工厂函数 | `create_docstore()` |
### 核心实现细节
#### 1. 文本切分
- **递归切分**: 使用 `langchain_text_splitters.RecursiveCharacterTextSplitter`,支持中文分隔符
- **语义切分**: 使用 `langchain_experimental.text_splitter.SemanticChunker`,通过 `SemanticChunkerAdapter` 适配 `TextSplitter` 接口
- **父子块策略**: 父块使用递归切分1000字符子块可选择递归或语义切分200字符
#### 2. 向量化
- **Embedding API**: 使用 `LlamaCppEmbedder` 封装本地 llama.cpp 服务,支持 `embed_documents` 和 `embed_query` 方法
- **向量维度**: 自动检测模型维度(默认 2560创建对应大小的 Qdrant 集合
#### 3. 向量存储
- **Qdrant 集成**: 使用 `langchain_qdrant.QdrantVectorStore` 作为底层存储
- **集合管理**: 自动创建/复用集合,支持 `force_recreate` 参数
- **批量写入**: 支持 `batch_size` 参数,避免单次请求过大
#### 4. 文档存储
- **PostgreSQL**: 使用 `PostgresDocStore` 持久化存储父块,支持异步连接池
- **数据映射**: 通过 UUID 将子块与父块关联,检索时返回完整父块
### 调用顺序
#### 1. 创建 IndexBuilder入口
```python
from rag_indexer.builder import IndexBuilder, SplitterType
builder = IndexBuilder(
collection_name="my_docs",
splitter_type=SplitterType.PARENT_CHILD,
parent_chunk_size=1000,
child_chunk_size=200,
docstore_conn_string="postgresql://user:pass@host:5432/db",
)
```
#### 2. 构建索引
```python
import asyncio
# 方式A从单个文件构建
async def main():
count = await builder.build_from_file("/path/to/document.pdf")
print(f"已索引 {count} 个块")
# 方式B从目录批量构建
async def main():
count = await builder.build_from_directory("/path/to/docs/")
print(f"已索引 {count} 个块")
asyncio.run(main())
```
#### 3. 检索(获取完整父块上下文)
```python
import asyncio
async def main():
# 检索时返回完整父块
results = await builder.search_with_parent_context("查询内容", k=5)
for doc in results:
print(doc.page_content)
asyncio.run(main())
```
### 检索流程
```
1. vector_store.similarity_search() → 从 Qdrant 找到相关子块
2. retriever.get_relevant_documents() → 根据子块 ID 获取对应父块
3. 返回完整父块给用户
```
---
### 串联与触发方式
使用 `cli.py` 入口脚本:
```bash
# 设置环境变量
export QDRANT_URL="http://115.190.121.151:6333"
export QDRANT_API_KEY="your-api-key"
export DB_URI="postgresql://postgres:password@host:5432/langgraph_db?sslmode=disable"
# 执行索引构建
python -m rag_indexer.cli --path data/user_docs/tech_manual.pdf
```
这相当于系统后台的**"离线学习阶段"**,你可以随时挂载定时任务去扫描文件夹,增量更新知识库。