从零开始搭建RAG系统系列(三):数据准备与预处理

111 阅读4分钟

步骤一:数据准备与预处理

  • 目标:将我们的原始知识文档(例如,一组关于某个开源项目的介绍文档,可能是TXT或PDF格式)加载进来,并将其分割成适合RAG系统处理的、带有元数据的文本块(chunks)。

具体操作:

1. 数据加载(Data Loading)

我们需要从文件系统中加载文档。Lang Chain提供了多种DocumentLoader`来处理不同类型的文件。

首先,假设我们有一个名为knowledge_base`的文件夹,里面存放了我们的知识文档。例如,可以创建两个简单的.txt文件:

knowledge_base/project_intro.txt:

RAGFlow是一个基于Python的开源检索增强生成框架。

它致力于简化RAG应用的开发、评估和部署流程。

RAGFlow的核心特性包括模块化设计、多种检索策略支持以及易于扩展的接口。

该项目由ABCLab于2024年发起。

knowledge_base/features.txt:

RAGFlow的主要功能包括:

  1. 数据接⼊:⽀持PDF, TXT, Markdown等多种⽂档格式。
  2. 智能分块:提供多种⽂本分割策略,如递归分割、语义分割。
  3. 向量化与索引:集成主流Embedding模型和向量数据库。
  4. 灵活检索:⽀持向量检索、关键词检索及混合检索。
  5. LLM集成:⽅便对接OpenAI、HuggingFace等多种⼤语⾔模型。
  6. 评估套件:内置常⽤RAG评估指标和⼯具。

现在,我们使⽤LangChain的 DirectoryLoaderTextLoader 来加载这些⽂档

from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_core.documents import Document # Document type
from typing import List
# 定义知识库⽬录
knowledge_base_path = "./knowledge_base"
# 注意:请确保在Python脚本的同级⽬录下创建 knowledge_base ⽂件夹,并将上述txt⽂件放⼊其
# 使⽤DirectoryLoader加载⽬录下所有.txt⽂件,指定使⽤TextLoader
loader = DirectoryLoader(
knowledge_base_path,
glob="**/*.txt", # 匹配所有txt⽂件
loader_cls=TextLoader, # 使⽤TextLoader加载
loader_kwargs={'encoding': 'utf-8'}, # TextLoader的参数,确保UTF-8编码
use_multithreading=True, # 可以加速加载多个⽂件
show_progress=True # 显⽰加载进度
)
documents: List[Document] = loader.load()
if documents:print(f"成功加载 {len(documents)} 个⽂档.")
for i, doc in enumerate(documents):
print(f"\n--- ⽂档 {i+1}: {doc.metadata.get('source', '未知来源')} ---
print(f"内容预览 (前100字符): {doc.page_content[:100]}")
else:
print(f"未能从 '{knowledge_base_path}' 加载到任何⽂档。请检查路径和⽂件。")
# 对于PDF⽂件加载,可以使⽤PyPDFLoader (需要 pip install pypdf)
# from langchain_community.document_loaders import PyPDFLoader
# pdf_loader = PyPDFLoader("path/to/your/document.pdf")
# pdf_documents = pdf_loader.load()

说明:DirectoryLoader可以方便地加载整个目录的文件。每个加载的Document对象通常包含两部分:page_content(文档的文本内容)和metadata(一个字典,通常包含如 source等元信息,即文件名)。

2.文本分割(Text Splitting)

长文档需要被切分成更小的语义单元(chunks), 以便Embedding模型处理并提高检索的精确性。LangChain的 RecursiveCharacterTextSplitter是一个常用的选择,它会尝试按预设的一系列分隔符(如换行符、句号等)递归地分割文本,以求在满足大小限制的同时尽量保持语义的完整性。

image.png

上图展⽰了⽂本分割的过程。⻓⽂档被切分成较⼩的⽂本块 (chunks)。相邻⽂本块之间通常设置重叠(overlap)部分(⽰例中红⾊字体为上⼀块的重叠内容,绿⾊字体为与下⼀块的重叠内容),以确保语义的连续性,避免关键信息在分割点被割裂。

from langchain.text_splitter import RecursiveCharacterTextSplitter

# 初始化⽂本分割器

text_splitter = RecursiveCharacterTextSplitter(

chunk_size=200, # 每个块的最⼤字符数。应根据Embedding模型和LLM的上下⽂窗⼝进⾏调

# 对于中⽂,⼀个汉字通常算1个token,但具体取决于模型的分词器。

# BGE等模型通常有512 tokens的输⼊限制。

chunk_overlap=20, # 相邻块之间的重叠字符数,帮助保留上下⽂连贯性。

separators=["\n\n", "\n", "。", "!", "?", ",", "、", " ", ""], # 中⽂场景

length_function=len, # 使⽤字符⻓度计算chunk_size

add_start_index=True # 在metadata中添加块在原⽂中的起始位置,可选

)

if documents: # 确保前⾯加载成功

document_chunks: List[Document] = text_splitter.split_documents(document

print(f"\n⽂档被分割成 {len(document_chunks)} 个⽂本块.")

if document_chunks:

print(f"第⼀个⽂本块预览: {document_chunks[0].page_content}")

print(f"第⼀个⽂本块元数据: {document_chunks[0].metadata}")

else:

print("没有⽂档可供分割。")

document_chunks = [] # 初始化为空列表以防后续代码出错

说明::chunk_size 和 chunk_overlap 的选择对RAG性能有显著影响,需要根据具体数据、Embedding模型的能力以及LLM的上下文窗口大小进行实验调整。对于中文文本,合理设置separators尤为重要,以尽可能在自然的语义边界(如段落、句子)进行切分。