人人都会AI编程

12.1 文档加载与分块实现

更新时间:2026-07-12

构建 RAG 系统的第一步,就是把散落在各处的文档(PDF、Word、网页、纯文本等)读取出来,并切分成大小合适的文本片段(chunk),以便后续向量化和检索。这一环节看似基础,却直接影响最终回答的准确性和完整性——块太大可能引入噪音,太小又可能丢失上下文。

1. 文档加载:统一入口,适配多种格式

实际业务中的文档格式五花八门,逐个编写解析逻辑效率很低。社区已有成熟工具(如 LangChain 的 Document Loader 系列)提供了统一接口,能覆盖绝大多数常见格式。

常用加载器示例:

  • PDFPyPDFLoader 按页读取,适合多数文字型 PDF;扫描版需结合 OCR(如 UnstructuredPDFLoader)。
  • WordDocx2txtLoader 直接提取 .docx 纯文本。
  • 网页WebBaseLoader 抓取网页内容并清洗标签。
  • 纯文本/MarkdownTextLoader 直接读取。

下面是一个多格式加载的实用片段:

from langchain_community.document_loaders import (
    PyPDFLoader,
    Docx2txtLoader,
    TextLoader,
)

def load_documents(file_paths):
    docs = []
    for path in file_paths:
        ext = path.suffix.lower()
        if ext == '.pdf':
            loader = PyPDFLoader(str(path))
        elif ext == '.docx':
            loader = Docx2txtLoader(str(path))
        else:
            loader = TextLoader(str(path), encoding='utf-8')
        docs.extend(loader.load())
    return docs

加载后,每个文档对象包含 page_content(文本内容)和 metadata(如来源文件名、页码),保证后续可溯源。

2. 分块策略:平衡上下文与精准度

文档加载后往往是长文本,需要切分成适合嵌入模型处理的片段。分块的核心矛盾是:块太小,可能截断完整段落,导致检索到的片段缺少必要上下文;块太大,可能包含无关内容,降低检索匹配精度,且容易超出模型输入长度限制。

常用分块方法:

  • 固定大小切分:指定块长度和重叠量,按字符数机械分割。
  • 按语义切分:基于分隔符(如 \n\n、句号)优先在自然边界处断开。
  • 递归切分:先用块级分隔符尝试,不满足时逐级降级到更小的分隔符,兼顾语义和大小一致性。

LangChain 的 RecursiveCharacterTextSplitter 是最便捷的选择,它按照优先级列表 ["\n\n", "\n", "。", ".", " ", ""] 递归切分,尽量保持段落或句子完整。

参数设定建议(实用经验值):

  • chunk_size:通常设为 500~1000 个 token(约 300~700 汉字或英文词)。知识粒度越细、回答越需精确引用的场景宜偏小。
  • chunk_overlap:设为 chunk_size 的 10%~20%(如 100 字符),避免关键信息恰好被截断在边界处。
  • 如需保留长上下文(如技术规范说明),可适当提高 chunk_size 并降低重叠。

代码示例:

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,        # 块大小(字符数)
    chunk_overlap=50,      # 重叠量
    separators=["\n\n", "\n", "。", ".", "?", "!", " ", ""],
)

chunks = text_splitter.split_documents(documents)

3. 处理特殊文档的注意事项

  • 表格、代码块:固定大小切分容易破坏表格结构或代码完整性。可先用定制解析器提取为结构化文本(如 Markdown 表格),再决定是否独立分块。
  • 标题关联:如果文档有清晰的标题层级,可在分块时将标题注入每个块的元数据或正文前缀(如“## 标题”),增强检索时的上下文关联。
  • 去噪:加载阶段应剥离页眉/页脚、水印、多余空白行等,避免污染向量相似度。

4. 验证分块效果

分块完成后,建议检查少量样本:

  • 随机抽取几个块,阅读是否语义完整。
  • 确认没有“半个句子”或明显的上下截断。
  • 检查重叠部分是否合理衔接。

一个实用的质量检查函数:

def inspect_chunks(chunks, n=5):
    for i, chunk in enumerate(chunks[:n]):
        print(f"--- Chunk {i} (length: {len(chunk.page_content)}) ---")
        print(chunk.page_content[:200])
        print(f"Source: {chunk.metadata.get('source')}")

5. 下一步衔接

分块后的文档块将进入嵌入与向量数据库环节。合理的分块不仅决定了检索的命中质量,也直接影响后续生成的上下文丰富度与精确度。在实际项目中,chunk_sizechunk_overlap 往往需要根据业务场景进行少量反复调整,才能找到最佳平衡点。