在第4章中,我们从全局视角解析了RAG系统的完整工作流程,明确了离线索引阶段在整个RAG流水线中的核心地位。离线索引阶段的质量直接决定了在线推理阶段的上限——如果索引阶段产生了低质量的文本块或向量表示,在线阶段无论怎样优化检索和生成策略,都无法弥补这一缺陷。本章将深入离线索引的四大核心环节:数据采集、数据清洗、文本分块和向量化,并详细介绍向量数据库这一RAG系统的“记忆中枢”。
这四个环节构成了一个精密的数据处理管道:原始数据经过采集进入系统,通过清洗去除噪声,经过分块被切分为语义单元,再通过向量化转化为数字表示,最终存储在向量数据库中等待检索。每个环节都有其独特的技术挑战和优化空间,任何一个环节的疏忽都可能导致最终RAG系统效果的显著下降。本章将结合最新的学术研究和工程实践,为读者提供一份详尽的技术指南。
5.1 数据采集:多源数据的统一处理
数据采集是离线索引管道的第一个环节,也是整个RAG系统的基础。在实际的企业应用中,知识往往分散在各种不同的数据源中——从结构化的数据库到半结构化的网页,从非结构化的PDF文档到实时产生的日志数据。如何高效、统一地从这些异构数据源中采集数据,并将其转换为RAG系统可处理的标准化格式,是数据采集阶段的核心挑战。
5.1.1 常见数据源类型
RAG系统需要处理的数据源类型极其多样。Gao等人在RAG综述论文中将知识源分为结构化知识(如知识图谱、数据库)、非结构化知识(如文本文档、网页)和半结构化知识(如带有标题和表格的文档)三大类。在实际工程中,LangChain和LlamaIndex两大主流框架都提供了丰富的数据连接器来支持不同类型的数据源接入。
以下表格对常见的数据源类型进行了系统对比:
| 数据源类型 | 典型格式 | 结构化程度 | LangChain加载器 | LlamaIndex连接器 | 处理复杂度 |
|---|---|---|---|---|---|
| 文本文档 | TXT, MD, DOCX | 非结构化 | TextLoader, Docx2txtLoader | SimpleDirectoryReader | 低 |
| 网页内容 | HTML, Markdown | 半结构化 | WebBaseLoader, AsyncHtmlLoader | SimpleWebPageReader | 中等 |
| PDF文档 | 非结构化 | PyPDFLoader, PDFMinerLoader | PDFReader | 高 | |
| 数据库 | SQL, NoSQL | 结构化 | SQLDatabaseLoader | DatabaseReader | 中等 |
| API数据 | JSON, XML | 半结构化 | JSONLoader | APIReader | 中等 |
| 知识图谱 | RDF, SPARQL | 结构化 | GraphCypherQAChain | KnowledgeGraphIndex | 高 |
| 表格数据 | CSV, Excel | 结构化 | CSVLoader, UnstructuredExcelLoader | PandasReader | 低 |
| 音视频 | MP3, MP4 | 非结构化 | WhisperParser, YoutubeLoader | AudioTranscriber | 高 |
[1] Gao et al. Retrieval-Augmented Generation for Large Language Models: A Survey. 2024. arXiv:2312.10997
[2] LangChain Document Loaders. python.langchain.com/docs/integr…
[3] LlamaIndex Data Connectors. docs.llamaindex.ai/en/stable/m…
5.1.2 文档加载器的工作原理
文档加载器(Document Loader)是数据采集阶段的核心组件,其职责是将各种格式的原始数据转换为统一的Document对象。无论是LangChain还是LlamaIndex,都采用了相似的Document抽象:每个Document对象包含两个核心属性——page_content(页面内容)和metadata(元数据)。
page_content属性存储从原始文档中提取的纯文本内容。对于不同格式的文档,文本提取的方式各不相同:对于TXT和Markdown文件,直接读取文件内容即可;对于PDF文件,需要使用PDF解析库(如PyPDF2、PDFMiner、pdfplumber等)提取文本层;对于HTML网页,需要解析DOM树并去除标签和脚本;对于DOCX文件,需要解析OpenXML格式提取段落文本。
metadata属性存储文档的元数据信息,通常包括文档来源(source)、文档标题(title)、页码(page)、创建时间(created_at)等。元数据在RAG系统中发挥着重要作用:在检索阶段,元数据过滤可以大幅缩小搜索范围,提升检索精度;在生成阶段,元数据为答案提供了来源追溯能力,增强了系统的可信度和可审计性。
LangChain Document对象示例
from langchain_core.documents import Document
doc = Document(
page_content="RAG技术通过外部知识库增强大语言模型...",
metadata={
"source": "https://example.com/rag-intro",
"title": "RAG技术介绍",
"page": 1,
"author": "张三",
"created_at": "2024-01-15"
}
)
访问内容: doc.page_content
访问元数据: doc.metadata
LangChain官方文档指出,一个良好的文档加载器应具备以下特性:支持流式加载(处理大文件时避免内存溢出)、支持异步加载(提升批量处理效率)、自动提取元数据、以及支持懒加载模式。在实际工程中,开发者应根据数据源的具体特征选择合适的加载器,并在必要时进行自定义开发。
[2] LangChain Document Loaders. python.langchain.com/docs/concep…
5.1.3 多源数据采集的挑战与对策
在实际的企业级RAG系统中,多源数据采集面临三大核心挑战:格式异构性、元数据一致性和增量更新。
格式异构性挑战:企业知识库中的数据格式千差万别——同一份技术文档可能同时存在PDF、Word和网页三种版本,不同系统的数据库表结构各异,API返回的数据格式也不统一。这种格式异构性导致数据采集代码高度碎片化,维护成本极高。对策是建立统一的数据抽象层:所有数据源经过加载后都转换为标准Document对象格式,上层处理逻辑只需面对统一的接口。LangChain和LlamaIndex的Document抽象正是为此设计。
元数据一致性挑战:不同数据源提供的元数据字段名称、格式和语义各不相同。例如,一个系统的“创建时间”字段名为created_at,另一个系统可能用create_time或date_created;时间格式可能是ISO 8601、Unix时间戳或自定义格式。对策是设计统一的元数据Schema,并在加载阶段进行元数据标准化映射。建议至少保留以下核心元数据字段:source(来源URL或路径)、title(文档标题)、doc_type(文档类型)、created_at(创建时间)、updated_at(更新时间)、chunk_id(分块标识)。
增量更新挑战:企业知识库中的数据会不断更新——新文档被添加、旧文档被修改或删除。如果每次数据变更都重新索引全部数据,将造成巨大的计算资源浪费。对策是实现增量索引机制:通过监控数据源的变化(如文件修改时间、数据库变更日志、API的last_modified字段等),仅对发生变更的文档进行重新加载、分块和向量化。LlamaIndex的IngestionPipeline和LangChain的文档加载器都支持通过自定义逻辑实现增量更新。
[1] Gao et al. RAG Survey. 2024. arXiv:2312.10997
[2] LangChain Document Loaders.
[3] LlamaIndex Data Connectors.
5.2 数据清洗:去除噪声,提升数据质量
数据清洗是离线索引管道中经常被忽视但极其重要的环节。Gao等人在RAG综述中将数据清洗归类为“预处理优化”的核心技术之一,指出“垃圾进,垃圾出”(Garbage In, Garbage Out)的原则同样适用于RAG系统。如果输入数据中充斥着噪声和低质量内容,无论后续的分块策略多么精巧、嵌入模型多么先进、检索算法多么高效,都无法产出高质量的检索结果。
5.2.1 常见数据噪声类型
在RAG系统的数据处理管道中,数据噪声可以分为以下几类:
| 噪声类型 | 描述 | 典型示例 | 影响程度 | 处理难度 |
|---|---|---|---|---|
| 格式噪声 | 文档中的格式标记和控制字符残留 | HTML标签、LaTeX公式标记、CSS样式代码 | 高 | 低 |
| 导航噪声 | 网页中的导航栏、侧边栏、页脚等内容 | 菜单链接、版权声明、广告文本 | 高 | 中等 |
| 重复噪声 | 内容相同或高度相似的文档片段 | 模板页眉页脚、免责声明、重复段落 | 中等 | 中等 |
| 编码噪声 | 字符编码问题导致的乱码 | UTF-8与GBK混用、特殊字符转义错误 | 中等 | 低 |
| 结构噪声 | 文档结构信息丢失或错乱 | 表格数据扁平化、列表层级丢失 | 高 | 高 |
| 语义噪声 | 内容本身质量低下或与主题无关 | 空段落、占位符文本、无关内容 | 中等 | 高 |
格式噪声是最常见也最容易处理的噪声类型。从PDF或HTML中提取文本时,经常会产生大量的格式残留。例如,从网页中提取的文本可能包含HTML标签(如
导航噪声在处理网页数据时尤为突出。一个典型的网页可能只有20%到30%的内容是真正的正文,其余部分都是导航栏、侧边栏、广告、版权声明等非核心内容。如果这些导航噪声被原样送入RAG系统,将严重污染知识库的质量。
5.2.2 数据清洗的核心技术
针对上述噪声类型,数据清洗管道通常包含以下核心技术:
文本提取规范化:这是数据清洗的第一步,目标是尽可能干净地从原始文档中提取纯文本。对于HTML文档,推荐使用BeautifulSoup或trafilatura等库进行智能内容提取,trafilatura能够自动识别网页正文区域并过滤导航和广告内容。对于PDF文档,推荐使用Unstructured.io或Marker等专门的PDF解析工具,它们能够更好地处理PDF中的表格、多栏布局和嵌入图片中的文字。对于Word文档,python-docx库可以直接提取段落文本。
去重去噪:去重包括精确去重和模糊去重两个层次。精确去重通过计算文本的哈希值(如MD5、SHA-256)来识别完全相同的文档片段,实现简单且效率高。模糊去重则需要计算文本之间的相似度(如Jaccard相似度、余弦相似度或MinHash),用于识别内容高度相似但不完全相同的文档片段。在实际工程中,建议先进行精确去重,再对剩余文档进行模糊去噪。此外,还应过滤掉过短的文档片段(如少于50个字符的片段通常不携带有效语义信息)和过长的文档片段(如超过10000个字符的片段可能包含多个不相关的主题)。
语言规范化:对于多语言环境下的RAG系统,语言规范化是必要的步骤。这包括:统一全角半角字符(如将全角逗号“,”替换为半角逗号“,”)、统一引号格式、去除多余的空白字符和空行、规范化Unicode编码(如使用NFKC规范化)。对于中英混合文本,还需要考虑分词边界的一致性问题。
元数据标准化:在数据清洗阶段,还应对元数据进行标准化处理。这包括:统一时间格式为ISO 8601标准、规范化文档类型标签、补充缺失的元数据字段、验证元数据的合法性等。标准化的元数据在后续的检索过滤和结果展示中发挥着重要作用。
[1] Gao et al. RAG Survey. 2024. arXiv:2312.10997
5.2.3 数据质量的衡量指标
为了系统化地评估数据清洗的效果,需要建立一套可量化的数据质量指标体系。以下表格总结了RAG系统中常用的数据质量衡量指标:
| 质量维度 | 衡量指标 | 计算方法 | 目标值 | 说明 |
|---|---|---|---|---|
| 完整性 | 文本提取率 | 提取文本长度 / 原始文档长度 | > 90% | 衡量文本提取的完整性 |
| 纯净度 | 噪声残留率 | 噪声字符数 / 总字符数 | < 5% | 衡量格式噪声的去除效果 |
| 唯一性 | 重复率 | 重复文档数 / 总文档数 | < 10% | 衡量去重效果 |
| 规范性 | 元数据覆盖率 | 含完整元数据的文档数 / 总文档数 | > 95% | 衡量元数据标准化程度 |
| 有效性 | 有效文档比例 | 有效文档数 / 总文档数 | > 85% | 过滤空文档和过短文档后的比例 |
| 语言一致性 | 语言检测一致性 | 语言标签一致的文档比例 | > 98% | 多语言场景下的语言标注准确率 |
在实际工程中,建议在数据清洗管道的每个关键节点设置质量检查点,实时监控上述指标。如果某个指标低于预设阈值,应触发告警并暂停后续处理,避免低质量数据流入知识库。这种“质量门禁”机制是保障知识库长期质量的重要手段。
5.3 文本分块:RAG效果的第一关键
文本分块(Text Chunking)是RAG离线索引管道中最为关键的环节,也是对最终检索效果影响最大的单一因素。Gao等人在RAG综述中将分块策略归类为“索引优化”的核心技术之一。分块的质量直接决定了检索的精度和召回率——分块过大,会引入大量无关信息,降低检索精度并增加“Lost in the Middle”风险;分块过小,会丢失关键上下文,导致检索到的片段缺乏足够的语义信息来支撑准确回答。
5.3.1 分块的核心原则
高质量的文本分块应遵循以下四项核心原则:
语义完整性原则:每个文本块应尽可能包含完整的语义单元。一个理想的文本块应该表达一个完整的思想或主题,而不是被生硬截断的句子片段。例如,一个关于“机器学习”的定义和解释应该完整地保留在一个文本块中,而不是被拆分到两个不同的块中。语义完整性是分块质量最基本也是最重要的要求。
上下文保留原则:文本块虽然是从长文档中切分出来的片段,但它应该保留足够的上下文信息,使得读者(或嵌入模型)能够在不参考原文的情况下理解其含义。这意味着在切分时需要保留必要的上下文信息,如所在章节的标题、前文的过渡句等。上下文保留的程度需要在信息完整性和块大小之间进行权衡。
大小适中原则:文本块的大小应在检索精度和上下文完整性之间取得平衡。块太大会引入噪声信息,降低检索精度;块太小会丢失上下文,降低检索结果的可用性。根据实践经验,对于大多数应用场景,256到1024个token的块大小是一个合理的起点,具体取值需要根据应用场景和数据特征进行调整。
场景适配原则:不同的应用场景对分块策略有不同的要求。FAQ问答场景适合使用较小的块(200-300 token),因为每个问答对本身就是自包含的语义单元;技术文档场景适合使用中等大小的块(500-1000 token),以保留足够的上下文;法律文书场景可能需要更大的块(1000-2000 token),因为法律条款通常需要完整的上下文才能准确理解。
[1] Gao et al. RAG Survey. 2024. arXiv:2312.10997
[4] LangChain Text Splitters. python.langchain.com/docs/how_to…
5.3.2 常见分块策略对比
LangChain官方文档详细介绍了多种文本分块策略,每种策略都有其适用的场景和局限性。以下表格对五种主流分块策略进行了系统对比:
| 分块策略 | 核心原理 | 优点 | 缺点 | 适用场景 | LangChain实现 |
|---|---|---|---|---|---|
| 固定字符分块 | 按固定字符数切分 | 实现简单,可预测 | 可能截断句子,破坏语义 | 对语义要求不高的场景 | CharacterTextSplitter |
| 递归字符分块 | 按分隔符层级递归切分 | 保持句子/段落完整性 | 对结构化文本效果一般 | 通用文本分块(推荐默认) | RecursiveCharacterTextSplitter |
| 按文档结构分块 | 按Markdown标题/HTML标签切分 | 语义边界清晰 | 依赖文档结构质量 | Markdown/HTML文档 | MarkdownHeaderTextSplitter, HTMLHeaderTextSplitter |
| 按Token分块 | 按Tokenizer的token数切分 | 与模型token限制对齐 | 实现复杂,依赖分词器 | 需要精确控制token数的场景 | TokenTextSplitter |
| 语义分块 | 基于嵌入相似度切分 | 语义边界最准确 | 计算成本高,速度慢 | 对检索精度要求高的场景 | SemanticChunker |
在上述策略中,RecursiveCharacterTextSplitter是LangChain官方推荐的默认分块器。它的工作原理是按照分隔符的优先级列表递归地进行切分。默认的分隔符优先级为:["\n\n", "\n", " ", ""],即优先在双换行符(段落边界)处切分,其次在单换行符处切分,再次在空格处切分,最后在字符级别切分。这种递归策略能够在保持语义完整性的同时,将块大小控制在目标范围内。
RecursiveCharacterTextSplitter 使用示例
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每个块的目标大小
chunk_overlap=200, # 相邻块之间的重叠大小
length_function=len, # 长度计算函数
separators=["\n\n", "\n", " ", ""] # 分隔符优先级
)
chunks = splitter.split_documents(documents)
[4] LangChain Text Splitters. python.langchain.com/docs/how_to…
5.3.3 语义分块技术详解
语义分块(Semantic Chunking)是一种基于文本语义相似度进行切分的先进分块技术。与传统的基于固定大小或分隔符的分块方法不同,语义分块通过分析文本中相邻句子的语义相似度变化来识别语义边界,从而在语义转折处进行切分。这种方法能够最大程度地保持每个文本块的语义完整性。
Greg Kamradt在其广受关注的视频和文章“5 Levels of Text Splitting”中,将文本分块技术分为五个层次,其中语义分块位于第四和第五层次。第四层次是基于嵌入的语义分块:首先将文本按句子切分,然后计算相邻句子嵌入向量的余弦相似度,当相似度低于预设阈值时在该位置进行切分。第五层次是基于命题(Proposition)的分块:使用LLM将每个段落分解为独立的命题(即最小的语义断言单元),然后基于命题进行分块。
语义分块的核心算法流程如下:第一步,将文档按句子切分为句子列表;第二步,使用嵌入模型将每个句子编码为向量;第三步,计算相邻句子向量之间的余弦相似度;第四步,识别相似度的“谷值”(即相似度显著下降的位置),这些位置即为语义边界;第五步,在语义边界处进行切分,形成语义完整的文本块。这种方法的关键参数是相似度阈值和百分位数阈值——阈值设置过高会导致块过大,设置过低会导致块过碎。
语义分块的主要优势在于能够准确识别主题转换的边界。例如,在一篇讨论多个主题的文章中,当作者从“机器学习”转向“深度学习”时,相邻句子的语义相似度会出现明显的下降,语义分块算法能够捕捉到这一变化并在该位置进行切分。然而,语义分块的缺点也很明显:需要对每个句子进行嵌入计算,计算成本显著高于传统方法;对于长文档,处理时间可能较长。
[5] Greg Kamradt. 5 Levels of Text Splitting. 2024. github.com/gregkamradt…
5.3.4 Late Chunking
Late Chunking(延迟分块)是由Gunther等人在2024年提出的一种创新的分块范式,其核心思想是“先嵌入,再切分”——与传统方法先切分再嵌入的流程恰好相反。该技术的论文标题为“Late Chunking in Long-Context Embedding Models”,发表在arXiv:2409.04701上。
传统分块流程存在一个根本性的问题:当文本被切分为小块后再分别嵌入时,每个块的嵌入向量仅反映该块内部的语义信息,而无法感知到更广泛的上下文。例如,一个讨论“苹果公司”的文本块,如果缺乏上下文,其嵌入向量可能更接近“水果”而非“科技公司”。Late Chunking通过反转处理顺序解决了这一问题:首先将整个文档(或大段文本)输入嵌入模型,利用Transformer的自注意力机制让模型充分理解全局上下文,然后再从模型的中间层输出中切分出各个文本块的向量表示。
具体实现上,Late Chunking利用了Jina Embeddings v2等支持长上下文的嵌入模型。模型首先处理整个长文档,生成每个token的上下文化表示(contextualized token embeddings),然后根据预定义的分块边界(如按句子或段落),将对应token的嵌入向量通过池化操作(如均值池化)聚合为块级别的向量。这样得到的块向量不仅包含了块内部的语义信息,还融入了全局上下文的理解。
实验结果表明,Late Chunking在多个检索基准数据集上显著优于传统的“先切分再嵌入”方法,特别是在需要理解跨块上下文的场景中。然而,Late Chunking也有其局限性:需要支持长上下文的嵌入模型(通常需要8K以上的上下文窗口),计算成本较高,且目前支持的模型选择有限。随着长上下文嵌入模型的不断发展,Late Chunking有望成为RAG系统中文本分块的主流方法之一。
[6] Gunther et al. Late Chunking in Long-Context Embedding Models. 2024. arXiv:2409.04701
5.3.5 Contextual Retrieval
Contextual Retrieval(上下文检索)是Anthropic公司在2024年提出的一种为文本块注入上下文信息的技术方案。该技术的核心洞察是:当文档被分块后,每个块脱离了原始文档的上下文环境,导致嵌入模型无法准确理解其含义。例如,一个包含“该方案将使收入增长20%”的文本块,如果脱离了上下文,嵌入模型无法知道“该方案”指的是什么。
Anthropic的解决方案是:在分块之后、嵌入之前,使用LLM为每个文本块生成一段简短的上下文描述,并将该描述添加到文本块的开头。具体做法是,将文档标题和文本块一起输入LLM,让LLM生成一段能够帮助理解该文本块的上下文前缀。例如,对于上述文本块,LLM可能生成“该段落讨论的是ACME公司2024年Q3的收入增长预测方案”作为上下文前缀。
Anthropic的实验结果令人印象深刻:在单跳问答任务上,Contextual Retrieval将检索失败率从49%降低到6%,实现了约8倍的提升。在多跳问答任务上,检索失败率从60%降低到27%。这一巨大提升的根本原因是:通过注入上下文信息,每个文本块都变成了一个自包含的语义单元,嵌入模型能够更准确地捕捉其真实含义。
Contextual Retrieval的实现成本需要考虑两个方面:一是LLM调用的成本——为每个文本块生成上下文描述需要一次LLM推理;二是存储成本——添加上下文描述会增加每个文本块的大小。Anthropic建议使用较小且快速的模型(如Claude 3 Haiku)来生成上下文描述,以控制成本。对于包含百万级文本块的知识库,可以采用批处理和异步调用的方式来提高效率。
[7] Anthropic. Building Effective Agents. Anthropic Documentation, 2024. docs.anthropic.com/en/docs/bui…
5.3.6 TreeRAG / 层次化分块
层次化分块(Hierarchical Chunking)是一种利用文档的层次结构进行多粒度分块的技术。其核心思想是:不是将文档切分为单一粒度的文本块,而是构建一个从粗到细的层次化索引结构,使得检索时可以根据查询的具体程度在不同粒度上进行匹配。
Sarthi等人在2024年发表的RAPTOR论文(arXiv:2401.18059)提出了一种创新的层次化检索增强方法。RAPTOR(Recursive Abstractive Processing for Tree-Organized Retrieval)通过递归地对文本块进行聚类和摘要,构建一棵“摘要树”。树的叶子节点是原始的细粒度文本块,中间节点是对下层节点的摘要,根节点是整篇文档的摘要。在检索时,系统可以同时在多个层次上进行匹配:对于具体的问题,在叶子节点层检索;对于概括性的问题,在中间节点或根节点层检索。
RAPTOR的具体构建流程如下:第一步,使用传统的分块方法将文档切分为细粒度的文本块(叶子节点);第二步,使用嵌入模型对所有文本块进行编码;第三步,基于嵌入向量对文本块进行聚类(使用高斯混合模型GMM);第四步,对每个聚类使用LLM生成摘要,形成上一层节点;第五步,重复步骤二到四,直到达到预设的层次深度或聚类质量不再提升。
实验结果表明,RAPTOR在需要多步推理的问答任务上显著优于传统的平面检索方法。其优势在于:对于需要综合多个信息源的问题,层次化的摘要节点提供了更宏观的视角;对于需要具体细节的问题,叶子节点提供了精确的信息。这种“自上而下”和“自下而上”相结合的检索方式,使得系统能够更灵活地应对不同类型的查询。
[8] Sarthi et al. RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval. 2024. arXiv:2401.18059
5.3.7 分块大小的最佳实践
分块大小是RAG系统中最常被调整的超参数之一,也是最直接影响系统效果的因素之一。以下表格总结了不同应用场景下的推荐分块大小:
| 应用场景 | 推荐块大小(Token) | 推荐重叠(Token) | 分块策略 | 说明 |
|---|---|---|---|---|
| FAQ问答 | 200-300 | 0-50 | 按问答对分块 | 每个问答对是独立语义单元 |
| 客服对话 | 300-500 | 50-100 | 按对话轮次分块 | 保留对话上下文 |
| 技术文档 | 500-1000 | 100-200 | 递归字符分块 | 平衡精度与上下文 |
| 新闻文章 | 500-800 | 100-150 | 按段落分块 | 每段通常包含完整主题 |
| 法律文书 | 1000-2000 | 200-400 | 按条款分块 | 法律条款需要完整上下文 |
| 学术论文 | 800-1500 | 150-300 | 按章节分块 | 保留学术论证的完整性 |
| 代码文档 | 300-600 | 50-100 | 按函数/类分块 | 代码单元是天然语义边界 |
| 通用场景 | 512-1024 | 100-200 | 递归字符分块 | 适合大多数场景的起点 |
需要特别注意的是,分块重叠(chunk_overlap)是一个容易被忽视但非常重要的参数。设置适当的重叠可以确保在切分边界处的信息不会丢失——如果一个关键信息恰好位于两个块的边界处,重叠区域确保它至少被完整地包含在一个块中。LangChain推荐将重叠大小设置为块大小的10%到25%。过大的重叠会增加存储和计算成本,且可能导致检索结果中出现大量重复内容。
在实际工程中,建议通过A/B测试来确定最优的分块参数。可以准备一个包含典型查询的评测数据集,在不同的块大小和重叠设置下评估检索的召回率和精度,选择综合指标最优的配置。此外,不同的文档类型可能需要不同的分块策略——一个生产级的RAG系统通常会为不同类型的文档配置不同的分块参数。
[4] LangChain Text Splitters.
[5] Greg Kamradt. 5 Levels of Text Splitting.
5.4 向量化技术:文本的数字表示
向量化(Embedding)是将文本转换为稠密向量表示的过程,是连接“人类语言”和“机器计算”的桥梁。在RAG系统中,向量化技术使得语义相似度搜索成为可能——通过将文本映射到高维向量空间,使得语义相似的文本在空间中距离更近。Gao等人在RAG综述中将嵌入优化列为RAG系统的核心技术之一,指出嵌入模型的选择和优化对检索质量有着决定性的影响。
5.4.1 嵌入模型的工作原理
现代文本嵌入模型的工作流程可以分解为四个关键步骤:分词编码、上下文编码、池化输出和对比学习训练。
分词编码(Tokenization):嵌入模型首先使用分词器(Tokenizer)将输入文本转换为一组整数ID序列。不同的模型使用不同的分词方案:BERT系列模型使用WordPiece分词器,OpenAI模型使用tiktoken分词器(基于BPE算法),BGE和E5系列模型使用与BERT兼容的分词器。分词器的词表大小通常在30000到100000个token之间。分词过程还会自动添加特殊标记,如[CLS](分类标记)、[SEP](分隔标记)等。
上下文编码(Contextual Encoding):分词后的ID序列被输入到Transformer编码器中。Transformer的多层自注意力机制使得每个token的表示不仅包含其自身的语义信息,还融合了上下文中其他token的信息。经过N层Transformer块(通常为6到12层)的处理后,每个token都获得了一个上下文化的向量表示。这些向量是高维的浮点数数组,典型维度为768、1024或1536维。
池化输出(Pooling):由于输入文本的token数量不固定,而输出需要是一个固定维度的向量,因此需要通过池化操作将变长的token向量序列聚合为固定长度的文本向量。常见的池化策略包括:均值池化(Mean Pooling)——对所有token向量取平均值;[CLS]池化——直接使用[CLS]标记的最终层向量作为文本表示;最大池化(Max Pooling)——取每个维度上的最大值。研究表明,均值池化在大多数场景下表现最为稳定。
对比学习训练(Contrastive Learning):嵌入模型通常使用对比学习方法进行训练。其核心思想是:将语义相似的文本对(正样本对)在向量空间中拉近,将语义不相似的文本对(负样本对)在向量空间中推远。InfoNCE损失函数是对比学习中最常用的目标函数。通过在大规模文本对数据上进行对比学习训练,模型学会了将语义相似的文本映射到相近的向量位置,从而支持基于向量距离的语义搜索。
[1] Gao et al. RAG Survey. 2024. arXiv:2312.10997
5.4.2 主流嵌入模型评测与选择
嵌入模型的选择是RAG系统设计中的关键决策。MTEB(Massive Text Embedding Benchmark)是目前最权威的嵌入模型评测基准,涵盖了多个语言和多种任务类型。以下表格对当前主流的嵌入模型进行了系统对比:
| 模型 | 开发者 | 维度 | 最大上下文 | MTEB平均分 | 多语言支持 | 许可协议 |
|---|---|---|---|---|---|---|
| text-embedding-3-large | OpenAI | 3072 | 8191 | 64.6 | 支持 | 商业 |
| text-embedding-3-small | OpenAI | 1536 | 8191 | 62.0 | 支持 | 商业 |
| BGE-M3 | BAAI | 1024 | 8192 | 64.2 | 支持100+语言 | 开源MIT |
| E5-Mistral-7B | Microsoft | 4096 | 32768 | 65.0 | 有限 | 开源Apache |
| GTE-Qwen2-7B | Alibaba | 3584 | 32768 | 66.2 | 支持 | 开源Apache |
| Nomic-Embed-v1.5 | Nomic AI | 768 | 8192 | 62.4 | 有限 | 开源Apache |
| Cohere Embed v3 | Cohere | 1024 | 512 | 65.1 | 支持100+语言 | 商业CC-BY |
| Voyage-3 | Voyage AI | 1024 | 32000 | 66.0 | 有限 | 商业 |
在选择嵌入模型时,需要综合考虑以下因素:模型性能(MTEB分数)、上下文长度(是否满足文档块大小需求)、多语言支持(是否需要处理中文等非英语文本)、部署方式(API调用或本地部署)、许可协议(商业使用是否受限)以及成本(API调用费用或本地部署的硬件需求)。
对于中文场景,BGE-M3是目前最推荐的开源选择。它由北京智源人工智能研究院(BAAI)开发,支持超过100种语言,最大上下文长度为8192个token,在MTEB基准上表现优异。BGE-M3的独特之处在于它同时支持稠密检索、稀疏检索和多向量检索三种模式,可以根据场景灵活选择。对于追求极致性能的场景,GTE-Qwen2-7B和Voyage-3是目前MTEB排行榜上的领先模型。
[9] MTEB Leaderboard. huggingface.co/spaces/mteb…
[11] Xiao et al. BGE M3: Empowering Representation Fusion with Multi-Lingual, Multi-Functionality, and Multi-Granularity Text Embeddings. 2024. arXiv:2402.03216
5.4.3 向量维度与效果的关系
向量维度是嵌入模型的一个重要参数,它直接影响模型的表示能力、存储成本和检索速度。传统的嵌入模型通常使用固定的向量维度(如768维或1024维),但Kusupati等人在2022年提出的Matryoshka Representation Learning(MRL)技术打破了这一限制。
MRL(arXiv:2205.13147)的灵感来源于俄罗斯套娃——通过在训练时对嵌入向量的不同维度子集施加约束,使得模型能够学习到一种“嵌套式”的向量表示。具体而言,MRL在训练时同时优化多个维度级别的表示(如768维、512维、256维、128维、64维),使得低维表示是高维表示的“前缀”——即前64维的表示已经包含了最核心的语义信息,前128维在此基础上进一步丰富,以此类推。
MRL技术的实用价值在于:它允许在推理时灵活地选择向量维度,以在存储成本和检索精度之间进行权衡。例如,使用OpenAI的text-embedding-3模型时,可以通过dimensions参数指定输出维度。实验表明,将维度从3072降低到1024,检索精度仅下降约1%到2%,但存储成本和检索速度分别提升了约3倍。对于存储预算有限或需要极低延迟的场景,甚至可以将维度降低到256或512,此时检索精度的下降仍然在可接受范围内。
向量维度与存储成本的关系是线性的——维度减半,存储成本也减半。对于一个包含100万条文本的知识库,使用1536维的float32向量需要约5.7GB的存储空间;如果将维度降低到512维,存储空间仅需约1.9GB。在大规模知识库场景中,这种存储节省是非常可观的。
[10] Kusupati et al. Matryoshka Representation Learning. NeurIPS 2022. arXiv:2205.13147
5.4.4 嵌入模型的微调
虽然通用嵌入模型在广泛的数据集上进行了预训练,但在特定的领域或任务上,通过微调可以进一步提升其表现。Xiao等人在BGE-M3论文(arXiv:2402.03216)中详细讨论了嵌入模型微调的方法和效果。以下表格对比了四种主流的嵌入模型微调方法:
| 微调方法 | 核心原理 | 训练数据需求 | 计算成本 | 效果提升 | 适用场景 |
|---|---|---|---|---|---|
| 有监督微调 | 使用标注的文本对数据进行训练 | 需要大量标注数据(万级) | 高 | 显著(5-15%) | 有标注数据的特定领域 |
| 对比学习微调 | 使用正负样本对进行对比学习 | 需要正负样本对(千级) | 中等 | 显著(5-10%) | 有相似度标注的场景 |
| 知识蒸馏 | 用大模型指导小模型学习 | 需要大模型的推理结果 | 低 | 中等(3-8%) | 模型压缩和加速 |
| 合成数据微调 | 使用LLM生成的合成数据训练 | 仅需少量种子数据 | 低 | 中等(3-7%) | 缺乏标注数据的场景 |
有监督微调是最传统的微调方法,需要准备大量标注的文本对数据(如(查询, 正文档, 负文档)三元组)。这种方法在数据充足的情况下效果最好,但标注成本高昂。对比学习微调通过构造正样本对(语义相似的文本)和负样本对(语义不相似的文本),使用对比学习损失函数进行训练。BGE系列模型就是使用对比学习在大规模数据上训练的典型代表。
知识蒸馏是一种高效的微调方法,它使用一个强大的“教师模型”(如GPT-4或大型嵌入模型)来指导一个较小的“学生模型”进行学习。教师模型为训练数据生成高质量的嵌入向量或相似度标注,学生模型通过模仿教师模型的输出来提升自己的表现。这种方法的优势在于不需要人工标注数据,且训练成本较低。
合成数据微调是近年来兴起的一种实用方法。它利用LLM(如GPT-4)根据少量种子数据自动生成大量的训练样本。例如,给定一个产品文档,可以让LLM生成多个相关的查询-文档对作为训练数据。BGE-M3论文中就使用了合成数据来增强训练集。这种方法特别适合缺乏标注数据的领域,但需要注意合成数据的质量控制——低质量的合成数据可能反而降低模型表现。
[11] Xiao et al. BGE M3. 2024. arXiv:2402.03216
5.5 向量数据库:RAG的“记忆中枢”
向量数据库是RAG系统的“记忆中枢”,负责存储、索引和检索文本块的向量表示。如果说嵌入模型赋予了文本“可计算”的语义表示,那么向量数据库则提供了在百万乃至亿级向量中快速找到最相似向量的能力。Gao等人在RAG综述中将向量数据库列为RAG系统的核心基础设施组件。
5.5.1 向量数据库的核心原理
向量数据库的核心功能是支持高效的近似最近邻搜索(Approximate Nearest Neighbor Search, ANN)。与精确的暴力搜索(遍历所有向量计算相似度)不同,ANN算法通过构建特殊的索引结构,在可接受的精度损失下,将搜索时间从O(n)降低到O(log n)甚至更低。
索引构建是向量数据库的基础操作。当新的向量被插入数据库时,系统会根据所选的索引算法将向量组织成特定的数据结构。常见的索引结构包括:基于量化的索引(如PQ乘积量化、IVF倒排文件)、基于图的索引(如HNSW层级可导航小世界图)、基于树的索引(如Annoy随机投影树)等。不同的索引结构在构建时间、内存占用、查询速度和召回率之间有不同的权衡。
相似度搜索是向量数据库的核心操作。给定一个查询向量,系统需要在索引中快速找到与其最相似的K个向量。相似度的计算通常使用余弦相似度(Cosine Similarity)或内积(Inner Product)。余弦相似度衡量两个向量方向的一致性,取值范围为[-1, 1],值越大表示越相似;内积同时考虑方向和大小,在某些场景下更为合适。
HNSW(Hierarchical Navigable Small World)是目前最广泛使用的ANN算法之一,由Malkov等人在2016年提出(arXiv:1603.09320)。HNSW构建了一个多层级的图结构:顶层是稀疏的长距离连接,用于快速定位到目标区域;底层是密集的短距离连接,用于精确搜索。搜索时从顶层开始,逐层向下导航,最终在底层找到最近邻。HNSW的核心优势在于其出色的召回率-速度权衡——在大多数数据集上,HNSW能够在保持95%以上召回率的同时,实现毫秒级的查询延迟。
[12] Malkov et al. Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs. IEEE TPAMI 2018. arXiv:1603.09320
[1] Gao et al. RAG Survey. 2024. arXiv:2312.10997
5.5.2 主流向量数据库对比
当前市面上有众多向量数据库产品可供选择,从开源的轻量级方案到企业级的分布式系统,覆盖了各种规模和需求。以下表格对八个主流向量数据库进行了系统对比:
| 向量数据库 | 类型 | 核心索引算法 | 最大支持规模 | 分布式支持 | 元数据过滤 | 语言/生态 |
|---|---|---|---|---|---|---|
| Milvus | 开源/云服务 | HNSW, IVF_PQ, DiskANN | 十亿级+ | 原生支持 | 支持标量过滤 | Go/Python/C++ |
| Pinecone | 全托管云服务 | 专有算法 | 十亿级 | 原生支持 | 支持 | REST API |
| Qdrant | 开源/云服务 | HNSW | 十亿级 | 支持分片 | 支持JSON过滤 | Rust/Python |
| Weaviate | 开源/云服务 | HNSW | 十亿级 | 支持复制 | 支持GraphQL过滤 | Go/Python |
| Chroma | 开源轻量级 | HNSW | 百万级 | 不支持 | 支持基础过滤 | Python/JS |
| pgvector | PostgreSQL扩展 | HNSW, IVF_FLAT | 百万级 | 依赖PG集群 | SQL过滤 | SQL/C/Python |
| FAISS | 开源库 | IVF, HNSW, PQ | 十亿级 | 不支持 | 不支持 | C++/Python |
| Elasticsearch | 开源/云服务 | HNSW (8.x+) | 十亿级 | 原生支持 | 丰富查询DSL | Java/REST |
在选择向量数据库时,需要考虑以下关键因素:数据规模(百万级还是十亿级)、部署方式(本地部署还是云服务)、查询延迟要求、是否需要分布式扩展、元数据过滤的复杂度、与现有技术栈的集成程度以及运维成本。对于初创团队和快速原型验证,Chroma或FAISS是轻量级的好选择;对于中等规模的生产系统,Qdrant或Weaviate提供了良好的平衡;对于大规模企业级应用,Milvus或Pinecone提供了更强的扩展性和可靠性保障。
5.5.3 索引类型与检索算法
向量数据库的检索算法可以分为两大类:精确检索(Exact Search)和近似检索(Approximate Search)。精确检索通过暴力计算查询向量与所有存储向量的相似度来找到真正的最近邻,结果100%准确但速度慢;近似检索通过索引结构加速搜索,在牺牲少量精度的前提下大幅提升速度。
以下表格对比了五种主流的检索算法:
| 算法 | 类型 | 召回率 | 查询速度 | 内存占用 | 构建速度 | 适用场景 |
|---|---|---|---|---|---|---|
| 暴力搜索(Flat) | 精确 | 100% | 慢(O(n)) | 低 | 无需构建 | 小数据集,追求精度 |
| IVF_FLAT | 近似 | 高(95%+) | 快 | 中等 | 快 | 中等规模数据集 |
| IVF_PQ | 近似 | 中等(90%+) | 很快 | 低 | 中等 | 大规模数据集,内存受限 |
| HNSW | 近似 | 很高(97%+) | 很快 | 高 | 慢 | 通用场景,推荐默认选择 |
| DiskANN | 近似 | 高(95%+) | 快 | 极低 | 中等 | 超大规模,内存受限 |
IVF(Inverted File)算法将向量空间划分为若干个聚类(Voronoi单元格),搜索时只在与查询向量最近的几个聚类中查找。IVF_FLAT在每个聚类内使用暴力搜索,精度较高但内存占用大;IVF_PQ在每个聚类内使用乘积量化压缩向量,大幅降低内存占用但会损失一些精度。IVF算法的关键参数是nlist(聚类数量)和nprobe(搜索时探测的聚类数量)。
HNSW算法是当前综合表现最好的ANN算法。它构建一个多层级的可导航小世界图,每层图中的节点通过短距离和长距离边连接。搜索过程从顶层开始,通过贪心导航逐层向下,最终在底层找到最近邻。HNSW的关键参数包括M(每个节点的最大连接数)和efConstruction(构建时的搜索宽度)。增大M和efConstruction可以提高召回率,但会增加内存占用和构建时间。
DiskANN是微软研究院提出的面向磁盘的ANN算法,其核心思想是将大部分向量数据存储在磁盘上,仅将索引结构和少量热点数据保存在内存中。这使得DiskANN能够在单机上处理十亿甚至百亿级的向量数据,且内存占用极低。DiskANN特别适合超大规模场景下内存预算有限的情况。
[12] Malkov et al. HNSW. 2018. arXiv:1603.09320
5.5.4 向量数据库的性能优化
在生产环境中,向量数据库的性能优化是保障RAG系统响应速度和可扩展性的关键。以下是五个主要的优化方向:
索引参数调优:索引参数的选择直接影响查询性能和召回率的权衡。以HNSW为例,M参数控制每个节点的连接数,推荐值为16到64;efConstruction参数控制构建时的搜索宽度,推荐值为200到500;efSearch参数控制查询时的搜索宽度,推荐值为50到200。增大这些参数可以提高召回率,但会增加内存占用和查询延迟。建议通过实验找到特定数据集和业务场景下的最优参数组合。
内存优化:对于大规模向量数据集,内存是主要的资源瓶颈。优化策略包括:使用量化技术(如标量量化SQ或乘积量化PQ)压缩向量,可将内存占用降低4到8倍,精度损失通常在1%到3%之间;使用DiskANN等支持磁盘存储的算法,将冷数据转移到磁盘上;对于使用Matryoshka嵌入的向量,可以截断到较低维度以减少内存占用。
分片与分布:当数据规模超过单节点处理能力时,需要采用分片策略将数据分布到多个节点上。常见的分片策略包括:随机分片(简单但可能导致负载不均)、基于距离的分片(将相近的向量分配到同一分片,提高局部性)和基于哈希的分片(均匀分布,负载均衡)。Milvus、Qdrant等分布式向量数据库都内置了分片和复制机制,能够自动处理数据的分布和容错。
元数据过滤优化:在实际应用中,检索通常需要结合元数据过滤条件(如时间范围、文档类型、作者等)。元数据过滤可以在向量搜索之前或之后执行。预过滤(Pre-filtering)先根据元数据条件筛选候选集,再在候选集上进行向量搜索,结果精确但可能过滤掉过多候选导致召回率下降;后过滤(Post-filtering)先进行向量搜索获取Top-K结果,再根据元数据条件过滤,速度快但可能返回不足K个结果。建议根据过滤条件的选择性来选择策略:选择性高(过滤后剩余少)用预过滤,选择性低用后过滤。
缓存策略:在RAG系统中,用户的查询往往存在重复或相似性。通过引入查询结果缓存,可以避免对重复或相似查询的重复计算,大幅降低系统的平均响应延迟。缓存策略包括:精确匹配缓存(完全相同的查询直接返回缓存结果)、语义缓存(对语义相似的查询返回缓存结果)和批量预计算缓存(对高频查询主题预先计算检索结果)。缓存的有效性取决于查询的重复率和时效性要求。
[12] Malkov et al. HNSW. 2018. arXiv:1603.09320
[1] Gao et al. RAG Survey. 2024. arXiv:2312.10997
本章深入探讨了RAG离线索引阶段的四大核心环节——数据采集、数据清洗、文本分块和向量化,以及向量数据库这一关键基础设施。我们系统分析了每个环节的技术原理、主流方案和最佳实践,并结合最新的学术研究成果(如Late Chunking、Contextual Retrieval、RAPTOR、Matryoshka MRL等)介绍了前沿的技术进展。
总结而言,构建高质量知识库需要关注以下关键要点:数据采集阶段应建立统一的数据抽象层,支持多源异构数据的标准化接入;数据清洗阶段应建立质量门禁机制,确保流入知识库的数据质量;文本分块阶段应根据场景选择合适的分块策略和参数,并关注Contextual Retrieval等前沿技术;向量化阶段应选择适合业务需求的嵌入模型,并考虑维度优化和领域微调;向量数据库阶段应根据数据规模和性能需求选择合适的数据库和索引算法,并进行系统化的性能调优。
在下一章中,我们将从离线索引阶段进入在线推理阶段,详细讨论RAG系统的检索技术——包括基础的向量检索、高级的多路召回策略、查询优化技术以及重排序(Reranking)方法。检索是连接离线知识库和在线用户查询的桥梁,其质量直接决定了RAG系统的最终效果。