Soup's Blog

Back

RAG实战(四)高级索引与检索策略Blur image

代码开源Github地址

RAG高级索引与检索策略:提升检索质量的关键#

在前面的章节中,我们学习了查询优化和路由技术。但是,检索质量不仅取决于查询,还取决于如何组织和索引文档。本章将深入探讨高级索引和检索策略。

为什么需要高级索引?#

基础索引的局限性

问题1: 文档过长

  • 一个10000字的文档被整体嵌入
  • 语义信息过于粗糙
  • 检索不精确

问题2: 上下文丢失

  • 将文档分成小块
  • 每块独立检索
  • 丢失了块与块之间的关系
  • 无法理解完整上下文

问题3: 多语义内容

  • 一个文档包含多个主题
  • 单一向量无法表示所有语义
  • 相关内容可能被遗漏

问题4: 检索冗余

  • 检索到大量文档
  • 很多内容重复或不相关
  • 影响最终答案质量

本章技术概览#

技术核心目标适用场景复杂度
智能分块优化块大小和边界所有RAG系统
多向量索引一个文档多个向量多主题文档⭐⭐⭐
父文档检索检索小块返回大块保持上下文⭐⭐
上下文压缩压缩检索结果减少冗余⭐⭐⭐
时间衰减检索考虑文档新鲜度时效性内容⭐⭐

Part 1: 智能文档分块策略#

1.1 核心概念#

文档分块(Chunking)是将长文档切分成更小片段的过程。好的分块策略能显著提升检索质量。

1.2 分块方法对比#

方法1: 固定长度分块

def fixed_length_split(text: str, chunk_size: int = 500) -> List[str]:
    """简单但可能切断语义"""
    return [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
python

方法2: 句子级分块

方法3: 语义分块 ✅ 推荐

def semantic_split(text: str, embeddings) -> List[str]:
    """基于语义相似度分块"""
    # 将在下面实现
    pass
python

1.3 普通文本分割器#

运行结果:

文档被分成 1 块

块 1:
# Python编程基础

## 变量和数据类型

Python是一种动态类型语言,这意味着你不需要声明变量的类型。
Python支持多种数据类型,包括整数、浮点数、字符串等。

## 控制流

Py...
plaintext

1.4 语义分块#

处理流程:

输入文本

分割句子(中文优化)

计算每个句子的嵌入

计算相邻句子的相似度

在相似度低的地方切分

输出语义分块结果
plaintext

运行结果:

1.5 分块最佳实践#

流程:

输入文档

按文档结构分割(标题、段落)

检查每个章节的大小

对过长的章节进行二次分割

为每个块添加元数据

输出结构化分块结果
plaintext

运行结果:

块 1/1
大小: 166 字符
内容: 
# 机器学习基础
机器学习是人工智能的一个重要分支。它使计算机能够从数据中学习。

# 深度学习
深度学习是机器学习的一个子领域。它使用神经网络来建模复杂模式。

# Python编程
Python...
metadata: {'source': 'python_tutorial.md', 'author': '张三', 'chunk_id': 0, 'chunk_total': 1, 'chunk_size': 166}
plaintext

1.6 分块策略总结#

选择指南:

  • 固定长度分块:适合格式规整的文档,处理速度快
  • 句子级分块:适合自然语言文本,保持语义完整性
  • 语义分块:适合复杂文档,保证语义连贯性
  • 智能分块:综合最优方案,推荐生产环境使用

最佳实践建议:

  1. 根据文档类型选择合适的分块策略
  2. 设置合理的块大小和重叠区域
  3. 为每个块添加丰富的元数据
  4. 考虑文档的语义边界和结构特点
  5. 测试不同分块策略对检索效果的影响

通过智能分块策略,我们可以显著提升RAG系统的检索质量和准确性,为后续的高级检索技术奠定坚实基础。


Part 2: 多向量索引 - Multi-Vector Indexing#

2.1 核心概念#

多向量索引为单个文档生成多个向量,每个向量代表文档的不同方面或部分。这样可以更全面地表示文档的语义。

2.2 为什么需要多向量索引?#

场景:一篇包含多个主题的文档

文档内容:
"""
本文介绍Python编程基础。

第一部分:变量和数据类型
Python支持多种数据类型...

第二部分:函数和模块
函数是可重用的代码块...

第三部分:面向对象编程
类是对象的蓝图...
"""

用户查询: "Python中的类是什么?"
python

单向量索引的问题:

  • 整个文档被表示为一个向量
  • 向量混合了所有主题的语义
  • 可能无法精确匹配”类”的相关内容

多向量索引的优势:

  • 为每个部分生成独立向量
  • “面向对象编程”部分的向量更匹配查询
  • 检索更精确

2.3 实现多向量检索器#

处理流程:

输入文档

为每个文档生成唯一ID

存储完整文档到文档存储

将文档分割成小块

为每个小块添加文档ID元数据

将小块向量化并存储到向量数据库

构建多向量检索器

完成索引构建
plaintext

2.4 实际应用演示#

运行结果:

📝 索引 2 个文档,生成 2 个文档ID
✅ 存储文档: 36cf53c2-968a-4540-9f75-e2e0e02a9d04 (长度: 178 字符)
✅ 存储文档: 8e2af8dd-e458-4f04-bc14-01d02552e8c9 (长度: 88 字符)
📊 文档 36cf53c2-968a-4540-9f75-e2e0e02a9d04 分割为 2 个小块
📊 文档 8e2af8dd-e458-4f04-bc14-01d02552e8c9 分割为 1 个小块
✅ 存储 3 个小块到向量数据库
🔍 调试信息:
📊 向量数据库: 3 个小块
📚 文档存储: 2 个文档ID
✅ 文档 36cf53c2-968a-4540-9f75-e2e0e02a9d04: 存在 (178 字符)
✅ 文档 8e2af8dd-e458-4f04-bc14-01d02552e8c9: 存在 (88 字符)
plaintext

2.5 检索测试#

测试1:机器学习相关查询

print("\n🔍 检索测试:")
results = multi_indexer.retrieve_full_documents("什么是机器学习?", k=2)
for i, doc in enumerate(results):
    print(f"结果 {i+1}: {doc[:100]}...")
python

运行结果:

测试2:Python编程相关查询

print("\n🔍 检索测试:")
results = multi_indexer.retrieve_full_documents("什么是python编程?", k=2)
for i, doc in enumerate(results):
    print(f"结果 {i+1}: {doc[:100]}...")
python

运行结果:

2.6 多向量检索机制详解#

检索流程:

用户查询 "什么是机器学习?"

在向量数据库中搜索相似的小块

找到相关小块:[小块2, 小块5, 小块8...]

获取小块的 doc_id:[doc_id_1, doc_id_2, doc_id_1...]

根据 doc_id 从文档存储中检索完整文档

返回完整文档:[完整文档1, 完整文档2...]
plaintext

2.7 多向量检索优缺点分析#

优点:

  • 更精确的语义表示:每个文档部分都有独立的向量表示
  • 提高相关性得分:可以精确匹配文档的特定部分
  • 灵活的检索策略:支持多种检索模式(完整文档 vs 小块)
  • 更好的上下文理解:返回完整文档保持上下文完整性

缺点: ⚠️

  • 存储成本增加:需要存储更多向量和元数据
  • 索引时间更长:需要为每个文档生成多个向量
  • 实现复杂度高:需要管理文档存储和向量存储的同步
  • 检索延迟增加:需要额外的文档查找步骤

2.8 适用场景建议#

推荐使用多向量索引当:

  • 文档包含多个独立主题或章节
  • 需要精确匹配文档的特定部分
  • 检索质量比存储成本更重要
  • 文档结构清晰,可以自然分割

选择单向量索引当:

  • 文档内容单一,语义集中
  • 存储资源有限
  • 需要快速索引和检索
  • 文档结构不清晰,难以分割

多向量索引技术通过为文档的不同部分创建独立的向量表示,显著提升了检索的精确性和相关性,是处理复杂文档结构的有效解决方案。


Part 3: 父文档检索器 - Parent Document Retriever#

3.1 核心概念#

父文档检索器的策略是:

  • 将文档分成小块进行索引和检索(精确匹配)
  • 返回大块或完整文档给LLM(保持上下文)

3.2 工作原理#

索引阶段:

大文档
    ↓ 分块
小块1、小块2、小块3
    ↓ 向量化
存储在向量数据库
plaintext

检索阶段:

用户查询
    ↓ 检索
匹配小块2
    ↓ 查找父文档
返回:包含小块2的大块/完整文档
plaintext

3.3 实现父文档检索器#

3.4 实际应用演示#

运行结果:

📝 开始索引文档...

📄 处理文档 1/1
   🔧 父文档分割: 1 个大块
   🔍 子文档分割: 大块 1 → 2 个小块
✅ 索引完成: 所有文档已存储

📊 存储状态调试:
🔍 向量数据库: 2 个子文档
   第一个子文档的元数据: {'doc_id': 'parent_0_0', 'source': 'python_guide.md'}
📚 文档存储: 1 个父文档ID
   父文档ID列表: ['parent_0_0']
plaintext

3.5 检索测试#

print("\n" + "="*60)
results = parent_retriever.retrieve("Python中for循环怎么用?", k=1)

# 显示结果
print("\n🎯 最终结果:")
for i, doc in enumerate(results):
    print(f"文档 {i+1}:")
    print(f"内容预览: {doc.page_content[:100]}...")
    print(f"父文档ID: {doc.metadata['doc_id']}")
python

运行结果:

3.6 父文档检索的优势#

最佳场景:

  • 需要精确匹配 + 完整上下文
  • 文档有明确的层次结构
  • 答案需要周围的解释
  • 避免上下文丢失

技术优势:

  1. 检索精度高:使用小块进行相似性搜索,匹配更精确
  2. 上下文完整:返回父文档级别的完整内容
  3. 避免信息碎片化:保持相关内容的连贯性
  4. 支持复杂查询:能够处理需要多段上下文的复杂问题

适用场景:

  • 技术文档检索:如API文档、编程教程
  • 学术论文搜索:需要完整段落理解概念
  • 法律文档分析:需要完整条款上下文
  • 医疗记录查询:需要完整病历信息

3.7 与其他检索策略对比#

策略检索粒度返回粒度优势劣势
标准检索文档块文档块简单快速上下文可能不完整
多向量索引文档小块完整文档精确匹配存储成本高
父文档检索文档小块父文档块平衡精度和上下文实现复杂度中等

父文档检索器通过”小块检索,大块返回”的策略,在保持检索精度的同时提供了完整的上下文信息,是处理需要深度理解的复杂查询的理想选择。


Part 4: 上下文压缩检索 - Contextual Compression#

4.1 核心概念#

上下文压缩检索在检索后对文档进行过滤和压缩,只保留与查询最相关的内容。

4.2 为什么需要压缩?#

问题:检索冗余

问题:

  • → 只有”列表推导式”部分相关
  • → 其他部分是噪声
  • → 浪费LLM的上下文窗口
  • → 可能影响答案质量

解决方案:上下文压缩

  • → 只提取相关部分:“列表推导式是创建列表的简洁方式…”
  • → 节省tokens
  • → 提高答案精度

4.3 实现LLM过滤器#

4.4 实际应用演示#

运行结果:

print("\n✂️ 压缩检索:")
compressed_docs = compressed_retriever.retrieve(query)
print(f"长度: {len(compressed_docs[0].page_content)} 字符")
print(compressed_docs[0].page_content)
python

运行结果:

✂️ 压缩检索:
Processed prompts: 100%|██████████| 1/1 [00:00<00:00,  2.64it/s]
Processed prompts: 100%|██████████| 1/1 [00:00<00:00,  2.67it/s]
Processed prompts: 100%|██████████| 1/1 [00:00<00:00,  2.67it/s]
Processed prompts: 100%|██████████| 1/1 [00:00<00:00,  2.66it/s]
长度: 72 字符
列表推导式是Python中创建列表的简洁方式。语法:[expression for item in iterable if condition]
plaintext

4.5 实现嵌入过滤器#

操作流程:

用户查询: "Python列表推导式"

基础检索器检索相关文档

获取多个候选文档

计算查询与每个文档的嵌入相似度

过滤相似度低于阈值的文档

返回高相似度文档
plaintext

运行结果:

4.6 实现文档分割过滤器#

运行结果:

4.7 压缩策略对比#

压缩器方法速度质量成本
LLM提取器LLM提取相关内容
嵌入过滤器相似度过滤
管道压缩组合多种方法

4.8 技术优势与适用场景#

优势:

  • 显著节省tokens:减少LLM处理的无用信息
  • 提高答案质量:专注于相关内容
  • 降低计算成本:减少API调用费用
  • 提升响应速度:处理更少的内容

适用场景:

  • 长文档检索:技术文档、学术论文等
  • 多主题查询:需要精确匹配特定部分
  • 成本敏感应用:需要控制API调用成本
  • 实时系统:需要快速响应的应用

选择指南:

  • 追求质量:选择LLM提取器或管道压缩
  • 追求速度:选择嵌入过滤器
  • 平衡方案:管道压缩提供最佳平衡

上下文压缩检索技术通过智能过滤和内容提取,有效解决了检索冗余问题,是构建高效RAG系统的关键技术之一。


Part 5: 时间衰减检索 - Time-Weighted Retrieval#

5.1 核心概念#

时间衰减检索考虑文档的新鲜度,给予新文档更高的权重。这种策略特别适用于新闻、技术文档、市场报告等时效性强的场景。

5.2 为什么需要时间衰减?#

场景对比:

# 旧文档(2023年)
old_doc = Document(
    page_content="2023年1月:Python 3.11发布",
    metadata={"date": "2023-01-01"}
)

# 新文档(2024年)
recent_doc = Document(
    page_content="2024年10月:Python 3.13发布,性能提升显著",
    metadata={"date": "2024-10-01"}
)

用户查询: "Python最新版本"
python

问题:

  • 标准检索可能返回旧文档(Python 3.11)
  • 用户期望获得最新信息(Python 3.13)
  • 时间因素影响答案的准确性和价值

解决方案:时间衰减检索

  • 给予新文档更高的相关性权重
  • 自动平衡语义相关性和时效性
  • 确保返回最新、最准确的信息

5.3 实现时间加权检索器#

5.4 实际应用演示#

运行结果:

docs: [Document(page_content='2024年10月:Python 3.13发布,性能提升显著,新增了JIT编译器', metadata={'date': '2024-10-01', 'source': 'python_release_notes'}), Document(page_content='2023年1月:Python 3.11发布,引入了新的语法特性和性能改进', metadata={'date': '2023-01-01', 'source': 'python_release_notes'})]

检索结果(按时间加权):

结果 1:
日期: 2024-10-01
内容: 2024年10月:Python 3.13发布,性能提升显著,新增了JIT编译器

结果 2:
日期: 2023-01-01
内容: 2023年1月:Python 3.11发布,引入了新的语法特性和性能改进
plaintext

5.5 衰减率参数调优#

运行结果:

查询: 'Python最新特性'
============================================================

衰减率: 0.001
  1. 2024-10-01: 2024年10月:Python 3.13发布,性能提升显著...
  2. 2023-01-01: 2023年1月:Python 3.11发布,引入了新的语法...

衰减率: 0.01
  1. 2024-10-01: 2024年10月:Python 3.13发布,性能提升显著...
  2. 2023-01-01: 2023年1月:Python 3.11发布,引入了新的语法...

衰减率: 0.1
  1. 2024-10-01: 2024年10月:Python 3.13发布,性能提升显著...
  2. 2023-01-01: 2023年1月:Python 3.11发布,引入了新的语法...
plaintext

5.6 自定义时间衰减函数#

5.7 时间衰减策略对比#

衰减策略公式特点适用场景
指数衰减weight = base^(days/interval)前期衰减快,后期平缓新闻、社交媒体
线性衰减weight = 1 - (衰减率 × days)均匀衰减,易于控制技术文档、研究报告
阶梯衰减按时间段分段设置权重离散化处理,简单明了法律法规、政策文件

5.8 实际应用场景#

1. 新闻检索系统

# 新闻文档示例
news_docs = [
    Document(
        page_content="今日股市大涨,科技股领涨",
        metadata={"date": "2024-11-20", "category": "财经"}
    ),
    Document(
        page_content="上周市场回顾:整体平稳",
        metadata={"date": "2024-11-13", "category": "财经"}
    )
]

# 高衰减率确保最新新闻优先
news_retriever = TimeSensitiveRetriever(vectorstore, decay_rate=0.1)
python

2. 技术文档检索

# 技术文档示例
tech_docs = [
    Document(
        page_content="React 18新特性:并发渲染",
        metadata={"date": "2024-06-01", "framework": "React"}
    ),
    Document(
        page_content="React 17版本特性介绍",
        metadata={"date": "2023-03-01", "framework": "React"}
    )
]

# 中等衰减率平衡新旧信息
tech_retriever = TimeSensitiveRetriever(vectorstore, decay_rate=0.01)
python

3. 学术论文检索

# 学术论文示例
paper_docs = [
    Document(
        page_content="2024年最新AI研究成果",
        metadata={"date": "2024-10-01", "field": "人工智能"}
    ),
    Document(
        page_content="经典机器学习算法综述",
        metadata={"date": "2020-05-01", "field": "机器学习"}
    )
]

# 低衰减率重视经典文献
paper_retriever = TimeSensitiveRetriever(vectorstore, decay_rate=0.001)
python

时间衰减检索技术通过智能平衡信息的新鲜度和相关性,为时效性敏感的应用场景提供了重要价值。合理配置衰减参数和策略,可以显著提升检索系统的实用性和用户体验。


综合实战:构建高级RAG系统#

在前面的章节中,我们深入探讨了各种高级索引和检索技术。现在,让我们将这些技术整合到一个完整的、生产就绪的高级RAG系统中。

系统架构设计#

我们的高级RAG系统采用模块化设计,包含以下核心组件:

系统架构图:

核心实现代码#

系统部署与测试#

性能对比测试#

测试结果输出:

性能优化策略#

1. 智能缓存系统

2. 异步处理优化

async def async_batch_query(self, questions: List[str], method: str = "hybrid"):
    """异步批量查询"""
    semaphore = asyncio.Semaphore(5)  # 控制并发数
    
    async def process_question(question):
        async with semaphore:
            return await asyncio.to_thread(self.query, question, method)
    
    tasks = [process_question(q) for q in questions]
    return await asyncio.gather(*tasks)
python

3. 动态参数调优

通过这个完整的高级RAG系统实现,我们成功整合了前面讨论的所有先进技术,为构建生产级的智能问答应用提供了坚实的基础。系统具有良好的可扩展性、可配置性和监控能力,可以满足不同场景下的需求。

RAG实战(四)高级索引与检索策略
http://www.soupcola.top/blog/rag_blogs/rag_blogs-4
Author Soup Cola
Published at 2026年1月31日