Files
ailine/rag_indexer/README.md
2026-04-19 15:01:40 +08:00

310 lines
15 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、网页等清洗、切分并转化为向量最终存入向量数据库中。
## 📊 系统工作流示意图
```mermaid
graph TD
A[原始文档集合 <br> PDF / Word / Markdown] --> B(文档加载器 DocumentLoader)
B --> C{文本切分策略 Splitter}
C -->|基础策略| D1[固定字符长度切分 <br> Recursive Split]
C -->|进阶策略| D2[语义边界切分 <br> Semantic Chunking]
C -->|高级策略| D3[父子文档切分 <br> Parent-Child / Auto-merging]
D1 & D2 & D3 --> E[向量化 Embedder <br> llama.cpp: embeddinggemma]
E --> F[(Qdrant 向量数据库)]
subgraph "元数据管理"
G[提取作者、日期、页码等元数据 Metadata] -.附加.-> E
end
```
---
## 🎯 演进路线与核心算法 (Roadmap)
### Level 1: 基础暴力切分 (Basic Recursive Splitting)
- **核心算法**: 递归字符切分。它按照预定义的分隔符列表(如 `["\n\n", "\n", " ", ""]`)从大到小尝试切分文本,直到每块的大小满足最大长度限制。
- **优缺点**: 实现极简单,速度快。但非常容易将一句话拦腰截断,导致上下文语义丢失。
- **实现指南**:
-`langchain.text_splitter` 导入 `RecursiveCharacterTextSplitter`
- 实例化时设置 `chunk_size`(如 500`chunk_overlap`(如 50直接调用 `.split_documents(raw_docs)` 方法。
### Level 2: 语义动态切分 (Semantic Chunking)
- **核心算法**: 句子级相似度阈值算法。
1. 将文章按标点符号按句子拆分。
2. 使用轻量级 Embedding 模型将每一句向量化。
3. 计算相邻两句之间的余弦相似度 (Cosine Similarity)。
4. 当相似度低于设定阈值时(说明两句话讲的不是同一件事,语义发生了转折),在此处“切断”形成一个新的块。
- **优缺点**: 极大程度保留了段落内语义的连贯性,对 LLM 回答非常友好。但由于在切分阶段就需要调用向量模型,耗时略长。
- **实现指南**:
-`langchain_experimental.text_splitter` 导入 `SemanticChunker`
- 实例化时需要传入你已经配置好的 Embedding 模型实例(如基于 `OpenAIEmbeddings` 封装的 llama.cpp 本地模型),并设置 `breakpoint_threshold_type="percentile"` 等阈值参数。
### Level 3: 高级父子块策略 (Parent-Child / Auto-merging)
- **核心算法**: 层次化双重存储与映射。
- **切分机制**: 首先将文档粗切为较大的“父块 (Parent Chunk, 约 1000 词)”,随后将父块细切为较小的“子块 (Child Chunk, 约 200 词)”。
- **存储机制**: 仅仅将**子块**的向量存入 Qdrant 用于精准计算距离;将**父块**的原始内容存在内存或 Document Store (如 KV 数据库) 中,通过 UUID 相互映射。
- **核心思路**: 解决 RAG 领域经典的矛盾——检索时块越小越容易精确命中(去除噪声);但生成回答时,块越大越能给大模型提供充足的上下文背景。
- **实现指南**:
- 使用 `langchain.retrievers` 中的 `ParentDocumentRetriever` 模块。
- 在写入时,你需要同时准备一个底层的 `VectorStore` (即 Qdrant) 和一个 `BaseStore`
- **推荐方案**: 使用 `LocalFileStore` (默认) 或 `PostgresDocStore` 作为 docstore。
- 将两种不同的 `TextSplitter` 分别赋值给检索器的 `child_splitter``parent_splitter`,然后调用 `.add_documents()` 即可让系统自动完成映射。
### Level 3.1: PostgreSQL DocStore 集成
- **核心优势**: 利用 PostgreSQL 作为持久化存储,适合生产环境。使用同步连接池,避免异步复杂度。
- **实现步骤**:
1. **安装依赖**: `pip install psycopg2-binary`
2. **配置连接**: 设置 `DB_URI` 环境变量或直接在代码中指定 PostgreSQL 连接字符串
3. **创建 docstore**: 使用 `PostgresDocStore` 类直接创建
4. **注入到 IndexBuilder**: 在创建 `IndexBuilder` 时通过 `docstore` 参数注入
- **使用示例**:
```python
from rag_indexer.docstore_manager import PostgresDocStore
from rag_indexer.builder import IndexBuilder, SplitterType
# 创建 PostgreSQL docstore
docstore = PostgresDocStore(
connection_string="postgresql://user:pass@host:5432/db",
table_name="parent_documents"
)
# 创建 IndexBuilder 并注入 docstore
builder = IndexBuilder(
collection_name="rag_documents",
splitter_type=SplitterType.PARENT_CHILD,
docstore=docstore,
parent_chunk_size=1000,
child_chunk_size=200,
)
```
### Level 3.2: 语义切分与父子块策略结合
- **核心优势**: 结合语义切分的连贯性和父子块策略的层次化存储优势,实现更精准的检索和更丰富的上下文。
- **实现原理**:
- **父块切分**: 使用递归字符切分创建大块约1000词提供完整的上下文背景
- **子块切分**: 使用语义动态切分创建小块约200词根据语义连贯性动态切分提高检索精度
- **存储机制**: 子块向量存入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: RAG-Fusion (多路改写与倒数排名融合)
- **核心优势**: 通过大模型发散思维,将单一问题改写为多个相似问题,扩大搜索面,再利用数学统计算法合并结果,提高检索的全面性和准确性。
- **实现原理**:
1. **多路查询改写**: 利用LLM将原始查询改写成3-5个不同表述的查询从不同角度表达相同意图
2. **倒数排名融合 (RRF)**: 对每个改写查询的结果进行RRF融合公式为 $RRF\_score(d) = \sum_{q \in Q} \frac{1}{k + rank_q(d)}$,避免单一检索结果主导
3. **结果去重**: 对融合后的结果进行去重,确保返回的文档唯一
- **使用示例**:
```python
from rag_indexer.builder import IndexBuilder, SplitterType
from langchain_openai import OpenAI
# 创建 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",
)
# 创建语言模型用于查询改写
llm = OpenAI(
openai_api_base="http://localhost:8000/v1",
openai_api_key="no-key-needed",
model_name="Qwen2.5-7B-Instruct",
temperature=0.3,
)
# 使用 RAG-Fusion 检索
query = "如何申请项目资金?"
results = builder.retrieve_with_fusion(
query=query,
llm=llm,
num_queries=3,
k=5,
return_parent=True
)
```
- **配置参数**:
- `llm`: 语言模型实例,用于查询改写
- `num_queries`: 生成的查询数量建议3-5个
- `k`: 返回的文档数量
- `return_parent`: 是否返回父块上下文
### Level 5: GraphRAG 与 多模态 (Graph & Multi-modal)
- **核心算法**: LLM 实体关系抽取 (NER & Relation Extraction)。
- **核心思路**: 解决传统纯向量检索难以处理“跨文档复杂关系推理”的痛点A公司的CEO是谁他名下的B公司主要业务是什么这种需要横跨多页 PDF 的跳跃性问题)。
- **实现指南**:
- 使用本地的大模型(如 `Gemma-4-E2B`)配合 `langchain_community.graphs` 模块。
- 利用 `LLMGraphTransformer` 组件,在读取文档时,通过预设的 Prompt 强制大模型提取出实体Node和关系Edge直接写入诸如 Neo4j 这样的图数据库中,而非传统的 Qdrant 向量库。
---
## 所需依赖与安装
为了支持完整的文档解析和 Qdrant 写入,需要安装以下 Python 包:
```bash
# 基础核心库
pip install langchain langchain-core langchain-openai langchain-qdrant
# 用于复杂文档解析 (PDF, Word, Excel 等)
pip install unstructured pdf2image pdfminer.six
# 用于语义分块 (可选)
pip install langchain-experimental
# 用于 PostgreSQL 存储 (可选,用于 Parent-Child 策略)
pip install psycopg2-binary
# 用于 RAG-Fusion (可选,需要语言模型)
pip install langchain-openai
```
---
## 📂 架构与文件结构设计
在 `rag_indexer/` 目录下,需创建以下核心文件:
```text
rag_indexer/
├── __init__.py
├── loaders.py # 负责调用 unstructured 解析不同类型文件
├── splitters.py # 负责实现 Recursive、Semantic、Parent-Child 切分逻辑
├── embedders.py # 封装本地 llama.cpp 交互的 Embedding 接口
├── vector_store.py # 封装 Qdrant 写入、Upsert、Collection 初始化操作
├── docstore_manager.py # 文档存储管理器,支持 LocalFileStore 和 PostgreSQL
└── builder.py # 核心编排文件,将上述模块串联成 Pipeline
```
---
## 🔄 工作流程详解
### 数据流向总览
```
┌─────────────────────────────────────────┐
│ builder.py │
│ IndexBuilder 入口 │
└─────────────────┬───────────────────────┘
┌─────────────────▼───────────────────────┐
│ loaders.py │
│ DocumentLoader.load_file() │
│ → 返回 List[Document] │
└─────────────────┬───────────────────────┘
┌─────────────────▼───────────────────────┐
│ ParentDocumentRetriever.add_documents()│
│ ┌─────────────────────────────────┐ │
│ │ parent_splitter (粗切) │ │
│ │ 父块 ~1000 词 │ │
│ └────────────┬────────────────────┘ │
│ │ │
│ ┌────────────▼────────────────────┐ │
│ │ child_splitter (细切) │ │
│ │ 子块 ~200 词 │ │
│ └────────────┬────────────────────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ ▼ ▼ │
│ 子块向量 父块原始内容 │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────┐ ┌─────────────────┐ │
│ │vector_store│ │ docstore_manager│ │
│ │ (Qdrant) │ │ (PostgreSQL) │ │
│ └────────────┘ └─────────────────┘ │
└─────────────────────────────────────────┘
```
### 文件职责详解
| 文件 | 职责 | 关键类/函数 |
|------|------|------------|
| **builder.py** | 核心编排,负责串联整个流程 | `IndexBuilder` |
| **loaders.py** | 解析各种文档格式PDF、Word、TXT等 | `DocumentLoader` |
| **splitters.py** | 文本切分策略Recursive/Semantic/Parent-Child | `SplitterType`, `get_splitter()` |
| **embedders.py** | 向量化(封装 llama.cpp embedding 接口) | `LlamaCppEmbedder` |
| **vector_store.py** | Qdrant 向量数据库操作 | `QdrantVectorStore` |
| **docstore_manager.py** | 父文档存储PostgreSQL/本地文件) | `PostgresDocStore`, `get_docstore()` |
### 调用顺序
#### 1. 创建 IndexBuilder入口
```python
from rag_indexer.builder import IndexBuilder, SplitterType
builder = IndexBuilder(
collection_name="my_docs",
splitter_type=SplitterType.PARENT_CHILD,
qdrant_url="http://localhost:6333",
parent_chunk_size=1000,
child_chunk_size=200,
)
```
#### 2. 构建索引
```python
# 方式A从单个文件构建
builder.build_from_file("/path/to/document.pdf")
# 方式B从目录批量构建
builder.build_from_directory("/path/to/docs/")
```
#### 3. 检索(获取完整父块上下文)
```python
# 检索时返回完整父块
results = builder.search_with_parent_context("查询内容")
```
### 检索流程
```
1. vector_store.similarity_search() → 从 Qdrant 找到相关子块
2. retriever.get_relevant_documents() → 根据子块 ID 获取对应父块
3. 返回完整父块给用户
```
---
### 串联与触发方式
在你的 LangGraph 系统外,创建一个执行脚本 `scripts/run_indexer.py`
```bash
# 终端执行,将本地的 PDF 手册刷入向量数据库
export QDRANT_URL="http://115.190.121.151:6333"
python scripts/run_indexer.py --file data/user_docs/tech_manual.pdf
```
这相当于系统后台的**“离线学习阶段”**,你可以随时挂载定时任务去扫描文件夹,增量更新知识库。