AI- RAG笔记02 - Load Chunking
导读
本文学习来源 all-in-rag
个人学习笔记整理总结,有错误或者遗漏希望大家指正
Load解决“怎么把不同格式的资料读进来,并变成可处理的文本和元数据”。Chunking解决“怎么把长文档切成适合检索和生成的知识块”。
Load 和 Chunking 在 RAG 中的位置
RAG 一般分为离线知识库构建和在线问答两条链路。
离线知识库构建:
文档收集 -> 加载解析 -> 清洗过滤 -> 文本切分 -> 向量化 -> 写入向量库在线问答:
用户问题 -> 问题向量化 -> 检索相关 chunk -> 可选重排 -> 拼接上下文 -> 大模型生成答案Load和Chunking决定了向量库里到底存放什么知识、以什么粒度存放。后面的 embedding、retrieval、generation 都建立在它们的输出之上。
如果这一步做差了:
- 文本没加载全,后面永远检索不到缺失内容。
- 噪声没清掉,向量库会存入大量无意义内容。
- chunk 切得太碎,答案缺上下文。
- chunk 切得太大,语义被稀释,检索不准。
- 元数据缺失,回答无法引用来源和页码。
一句话:
Load 决定原材料质量,Chunking 决定知识入库粒度。Load
1. Load是什么
Data Load不是简单的open().read(),而是把不同格式、不同结构、不同质量的原始资料,解析成统一的文本对象和元数据对象。
它要解决三个问题:
读什么:PDF、Word、Markdown、HTML、TXT、网页、数据库、API 等。怎么读:选择合适的 loader 和解析策略。读出来怎么用:保留正文、结构、元数据,并过滤噪声。
典型输出不是一个普通字符串,而是类似这样的结构:
{"page_content":"RAG 是一种结合检索和生成的技术……","metadata":{"source":"rag.pdf","page_number":3,"category":"NarrativeText","section":"技术定义"}}其中page_content是后续要切分和向量化的正文,metadata是后续检索过滤、来源引用、错误排查的重要依据。
2. Load 做什么
2.1 解析原始格式
不同格式的资料有完全不同的解析难点。
| 格式 | 加载重点 |
|---|---|
TXT | 编码、换行、空行 |
Markdown | 标题、列表、代码块、链接 |
PDF | 阅读顺序、页眉页脚、表格、扫描件、OCR |
Word | 标题样式、段落、表格、批注 |
HTML | 正文抽取、导航栏、广告、脚本、页脚 |
Excel | 行列关系、表头、sheet 名称 |
代码文件 | 函数、类、注释、依赖结构 |
PDF 最麻烦,因为它更关心“页面长什么样”,不一定保存了正确的文本逻辑顺序。
2.2 提取文档结构
好的 loader 不只提取文字,还会尽量识别文档元素:
| 元素类型 | 含义 | RAG 中的处理建议 |
|---|---|---|
Title | 标题 | 通常保留,可作为 chunk 上下文 |
NarrativeText | 正文段落 | 主要知识来源 |
ListItem | 列表项 | 常和上级标题合并 |
Table | 表格 | 尽量转 Markdown 或 JSON |
FigureCaption | 图片说明 | 可与图片或附近正文绑定 |
Header | 页眉 | 经常是噪声,需要评估过滤 |
Footer | 页脚 | 常见噪声,如页码、水印、版权 |
PageNumber | 页码 | 更适合作为 metadata |
CodeSnippet | 代码块 | 保持格式,不要随意切碎 |
UncategorizedText | 未分类文本 | 需要抽样判断 |
注意:元素类型是加载器的判断结果,不是真理。一段正文可能被误判为Title,导航栏也可能被解析成Header。
2.3 保留元数据
元数据是“关于文本的信息”。它不一定参与正文语义,但对 RAG 非常重要。
常见元数据包括:
source:文件路径或 URL。page_number:页码。section_title:所属章节标题。filetype:文件类型。category:元素类型。created_at或last_modified:时间信息。permission:权限范围,企业知识库常用。
元数据的价值:
溯源:回答时能引用文件、页码或链接。过滤:只检索某类文档、某个时间范围、某个部门资料。调试:定位错误来自加载、切分、检索还是生成。权限:避免用户查到不该看的私有知识。
3. 常见 loader
加载器没有绝对最好,只有是否适合当前数据。
| 工具 | 适合场景 | 特点 |
|---|---|---|
TextLoader | 纯文本 | 简单、快、依赖少 |
DirectoryLoader | 批量目录文件 | 适合本地知识库批处理 |
Unstructured | PDF、Word、HTML、Markdown 等 | 统一接口,能识别文档元素 |
PyMuPDF4LLM | 文本型 PDF 转 Markdown | 对 PDF 文本抽取友好 |
Marker | PDF 转 Markdown | 适合书籍、论文类 PDF |
MinerU | 学术论文、财报、复杂 PDF | 版面、公式、表格能力更强 |
Docling | 企业报告、合同、表格 | 偏企业级文档解析 |
LlamaParse | 复杂 PDF、法律、论文 | 商业 API,解析质量较高 |
FireCrawlLoader | 网页、在线文档 | 抓取并抽取网页正文 |
选择思路:
- 简单文本用简单 loader,不要过度复杂化。
- 网页重点处理导航栏、广告、推荐内容和页脚。
- 扫描 PDF 必须考虑 OCR。
- 论文、合同、财报要重点关注标题层级、表格、公式和跨页内容。
- 技术文档要保护代码块和标题结构。
3.1 Unstructured 基本理解
Unstructured是面向非结构化文档处理的工具。
它的优点:
- 支持多种文档格式。
- 提供统一的
partition入口。 - 能把文档拆成带类型的
Element。 - 能保留元数据。
- 能在解析之后继续做元素级 chunking。
示例:
fromunstructured.partition.autoimportpartition elements=partition(filename="../../data/C2/pdf/rag.pdf",content_type="application/pdf")forelementinelements:print(element.category)print(element)常见参数:
| 参数 | 作用 |
|---|---|
filename | 本地文件路径 |
file | 文件对象 |
url | 远程文件地址 |
content_type | 指定 MIME 类型,减少自动识别误差 |
include_page_breaks | 是否保留分页符 |
strategy | 解析策略,如auto、fast、hi_res、ocr_only |
encoding | 文本编码 |
策略理解:
fast:速度快,适合文本层清晰的 PDF。hi_res:更重视版面分析,适合复杂 PDF。ocr_only:适合扫描件或图片型 PDF。auto:自动选择,适合入门,但生产环境要抽样验证。
4. Load 质量怎么判断
不要只看“代码是否跑通”,要看“解析结果是否适合作为知识入库”。
| 维度 | 检查问题 | 坏结果表现 |
|---|---|---|
| 完整性 | 正文、标题、表格、列表是否都在 | 原文有答案,解析结果里没有 |
| 准确性 | 文字是否识别正确 | OCR 错字、乱码、符号丢失 |
| 顺序性 | 阅读顺序是否正确 | 多栏 PDF 左右栏串读 |
| 结构性 | 标题、正文、列表、表格是否区分 | 全部混成一坨文本 |
| 干净程度 | 是否混入噪声 | 导航栏、广告、水印、页脚大量出现 |
| 元数据 | 是否保留来源、页码、章节 | 答案无法引用来源 |
| 可切分性 | 输出是否适合进入 chunking | 片段过碎、过长或语义断裂 |
推荐检查方式:
fromcollectionsimportCounter types=Counter(e.categoryforeinelements)print(types)fori,einenumerate(elements[:20],1):print(i,e.category,str(e)[:200])要重点抽样看文档开头、中间、结尾。开头容易有目录和导航,中间代表正文质量,结尾容易有参考文献、版权、水印和页脚。
5. Load 后的清洗与规范化
加载成功不等于可以直接入库。加载后通常还需要清洗和规范化。
常见处理:
- 删除页眉、页脚、广告、导航栏、水印、版权声明。
- 删除空文本、无意义短文本、重复菜单。
- 合并被错误拆开的相邻段落。
- 把标题挂到对应正文前面。
- 表格转成 Markdown 或 JSON。
- 保留代码块缩进和格式。
- 统一 metadata 字段名称。
- 保存中间结果,方便回溯。
推荐流程:
Load -> Inspect -> Clean -> Normalize -> Chunk -> Embedding -> 入库不要直接:
Load -> Chunk -> Embedding -> 入库6. Load、Element、Chunk 的区别
| 概念 | 含义 | 例子 |
|---|---|---|
Load | 把原始文件读入并解析 | PDF -> 一组元素 |
Element | 加载器解析出的结构单元 | 一个标题、一个段落、一个表格 |
Chunk | 为检索和向量化准备的文本块 | 一个语义完整的知识片段 |
关系:
- 一个 chunk 可以由多个 element 合并而来。
- 一个过长的 element 也可以被继续切成多个 chunk。
- element 偏文档结构,chunk 偏检索粒度。
例如:
Title: 工作流程 NarrativeText: RAG 包含三个主要过程:检索、增强和生成。 ListItem: 检索:从外部知识库获取相关信息。 ListItem: 增强:将查询和检索内容放入提示词模板。 ListItem: 生成:让大模型生成答案。这些 element 可以合并成一个 chunk:
工作流程 RAG 包含三个主要过程:检索、增强和生成。 检索:从外部知识库获取相关信息。 增强:将查询和检索内容放入提示词模板。 生成:让大模型生成答案。Chunking
1. chunk是什么
Text Chunking是把加载后的长文档切成更小、更适合处理的文本块。
它不是为了“把文本切短”这么简单,而是为了设计 RAG 系统中的知识检索单位。
一句话:
Chunk 是向量库里的基本知识单位,也是检索返回给大模型的基本上下文单位。如果用户问一个问题,检索系统并不是把整篇文档都拿出来,而是找出最相关的几个 chunk,再交给大模型生成答案。
所以 chunking 的目标是:
- 每个 chunk 足够小,能被 embedding 模型完整处理。
- 每个 chunk 足够完整,能表达一个相对清楚的语义。
- 每个 chunk 噪声少,主题集中,容易被问题召回。
- 每个 chunk 带有 metadata,方便溯源和过滤。
2. 为什么要 Chunking
2.1 适应 Embedding 模型上下文限制
Embedding 模型有输入长度上限。超过上限的内容可能被截断,导致向量不能代表完整文本。
例如一个 embedding 模型最多处理 512 token,如果传入 2000 token,后面内容可能直接丢失或被截断。
所以 chunk 大小必须适配 embedding 模型的上下文窗口。
2.2 适应 LLM 上下文窗口
生成阶段会把多个检索结果、用户问题和提示词一起放入 LLM 上下文。
如果单个 chunk 过大:
- 能放入 prompt 的 chunk 数量会减少。
- 模型看到的信息广度会下降。
- 无关内容会挤占上下文窗口。
2.3 提高检索精准度
检索的基本单位是 chunk。chunk 主题越集中,越容易和用户问题匹配。
如果一个 chunk 同时包含“技能介绍、背景故事、推荐出装”,用户问“怎么出装”时,这个 chunk 的语义会被其他主题稀释,可能召回不出来。
2.4 降低生成噪声
LLM 拿到的上下文越干净,越容易给出可靠答案。chunk 太大、太杂、噪声太多,会增加幻觉和答非所问的概率。
3. 为什么 chunk 不是越大越好
既然模型上下文很长,那 chunk 大一点不是更完整吗?
答案是否定的。
3.1 Embedding 会压缩信息
Embedding 的本质是把一段文本压缩成一个固定维度向量。
例如把一段文本压缩成 768 维向量:
很短且主题单一的文本 -> 768 维向量 很长且主题很多的文本 -> 也是 768 维向量文本越长、主题越多,一个向量要承载的信息越多,语义就越容易被稀释。
3.2 Lost in the Middle
长上下文模型也不是平均关注所有位置。大量研究和实践都发现,模型更容易关注开头和结尾,中间信息可能被忽略。
如果 chunk 很大,关键信息可能埋在中间,生成时更难被模型利用。
3.3 主题稀释
一个好的 chunk 应该围绕一个相对明确的主题。
坏 chunk:
角色背景 + 技能介绍 + 参考资料 + 页面导航 + 广告好 chunk:
技能:激素枪 激素枪可以远程治疗队友,也可以自我治疗……主题越集中,检索越精准。
4. chunk_size / chunk_overlap / separator / metadata
4.1 chunk_size
chunk_size是每个 chunk 的目标大小。
它可以按字符数、token 数或其他长度函数计算。LangChain 中很多 splitter 默认按字符数计算,也可以传入 token 计数器。
影响:
- 太小:上下文不足,语义容易断。
- 太大:主题混杂,检索不准,成本更高。
4.2 chunk_overlap
chunk_overlap是相邻 chunk 之间重复保留的内容。
作用:
- 缓解边界处语义被切断的问题。
- 保留跨段落、跨句子的上下文。
- 提高边界附近内容被召回的概率。
代价:
- overlap 越大,重复内容越多。
- 向量库体积变大。
- embedding 成本和检索噪声可能增加。
4.3 separator
separator是切分优先使用的分隔符,例如段落、换行、句号、逗号、空格。
好的分隔符顺序应该尽量从大语义单位到小语义单位:
["\n\n","\n","。",","," ",""]这表示优先按段落切,再按换行、句子、短语、空格,最后必要时按字符切。
4.4 metadata
chunk 不应该只有正文,还应该继承或补充元数据:
{"source":"蜂医.txt","section":"技能","chunk_index":12}metadata 让 chunk 可过滤、可排序、可引用、可调试。
5. 常见切分策略
5.1 基础切分策略一:CharacterTextSplitter
CharacterTextSplitter通常被理解为固定大小分块,但 LangChain 的实现并不是机械地每 N 个字符切一次。
它大致做两件事:
- 先按分隔符切分,默认常用段落分隔符。
- 再把小片段合并到接近
chunk_size的大小。
示例:
fromlangchain.text_splitterimportCharacterTextSplitter text_splitter=CharacterTextSplitter(chunk_size=200,chunk_overlap=10)chunks=text_splitter.split_documents(docs)优点:
- 简单、快、容易理解。
- 适合日志、短文本、结构不复杂的材料。
缺点:
- 语义感知弱。
- 遇到超长段落时可能无法继续细切。
- 容易在不合适的位置切断内容。
理解重点:
CharacterTextSplitter 更像“按分隔符切分后再按大小合并”,不是绝对均匀切块。5.2 基础切分策略二:RecursiveCharacterTextSplitter
RecursiveCharacterTextSplitter是 RAG 中最常用的基础切分器之一。
它的核心思想:
先用大粒度分隔符切;如果还太长,就递归使用更小粒度分隔符继续切。典型分隔符:
separators=["\n\n","\n","。",","," ",""]算法过程:
1. 优先按段落切 2. 段落仍太长,就按换行切 3. 仍太长,就按句号切 4. 仍太长,就按逗号、空格切 5. 最后必要时按字符切示例:
fromlangchain.text_splitterimportRecursiveCharacterTextSplitter text_splitter=RecursiveCharacterTextSplitter(separators=["\n\n","\n","。",","," ",""],chunk_size=200,chunk_overlap=10,)chunks=text_splitter.split_documents(docs)优点:
- 比固定字符切分更尊重语义边界。
- 能处理超长段落。
- 配置简单,适合大多数普通文档。
缺点:
- 本质仍是字符规则,不真正理解语义。
- 分隔符设计不好时,中文文本容易切得不理想。
中文文本建议加入中文标点:
separators=["\n\n","\n"," ","。","!","?",",","、",""]代码文档可以使用语言特化分隔符:
RecursiveCharacterTextSplitter.from_language(language=Language.PYTHON,chunk_size=500,chunk_overlap=50)5.3 基础切分策略三:SemanticChunker
SemanticChunker是语义分块。它不主要依赖字符数或固定分隔符,而是尝试在语义发生明显变化的位置切开。
核心思想:
相邻句子语义差异小,就放在一起;语义差异大,就在中间切开。工作流程:
1. 把文本切成句子 2. 给句子或句子窗口生成 embedding 3. 计算相邻句子的语义距离 4. 根据阈值识别语义断点 5. 按断点合并成 chunk它需要的是embedding model,不是聊天模型。DeepSeek 这类聊天模型通常用于生成,不适合直接作为SemanticChunker的 embedding 来源。可以使用本地bgeembedding,也可以使用千问、OpenAI 等 embedding API。
示例:
fromlangchain_experimental.text_splitterimportSemanticChunker text_splitter=SemanticChunker(embeddings,breakpoint_threshold_type="percentile")docs=text_splitter.split_documents(documents)断点识别方法:
| 方法 | 含义 | 适合场景 |
|---|---|---|
percentile | 超过某个百分位的语义距离就切 | 默认方法,通用 |
standard_deviation | 超过均值加 N 倍标准差就切 | 语义变化比较明显的文本 |
interquartile | 用四分位距识别异常跳跃 | 抗异常值能力较好 |
gradient | 看语义距离变化率 | 法律、医疗等语义变化较细的文本 |
优点:
- 更接近“按主题变化切分”。
- 对长文本的主题边界更敏感。
缺点:
- 依赖 embedding 模型质量。
- 成本更高,速度更慢。
- 参数不好时可能切得太少或太多。
- 语义切分不保证 chunk 大小一定适合检索。
对于 Markdown、HTML、LaTeX、代码等结构明显的文档,优先利用文档结构。
5.4 MarkdownHeaderTextSplitter
Markdown 文档天然有标题层级:
# 第一章 ## 1.1 背景 ## 1.2 方法可以用MarkdownHeaderTextSplitter按标题切分,并把标题路径写入 metadata。
好处:
- 保留章节结构。
- chunk 能知道自己属于哪个标题。
- 检索和生成时上下文更明确。
典型流程:
先按标题切成逻辑块 -> 再用 RecursiveCharacterTextSplitter 控制大小这是非常实用的组合:
结构切分负责语义边界,递归切分负责长度控制。5.5 HTML、JSON、代码
不同结构文档适合不同切分方式:
| 文档类型 | 推荐思路 |
|---|---|
| Markdown | 按标题层级切,再按长度细分 |
| HTML | 先抽正文,再按标题、段落、DOM 区块切 |
| JSON | 按对象、字段、路径切,保留 key path |
| 代码 | 按类、函数、方法切,保留文件路径和符号名 |
| 表格 | 按表、行组、语义字段切,保留表头 |
结构化文档不要一上来粗暴按字符切,否则会丢掉天然结构优势。
6. 如何选择切分策略
没有通用最优 chunking。要根据文档类型、任务目标、embedding 模型和检索方式选择。
| 场景 | 推荐策略 |
|---|---|
| 普通文章、说明文档 | RecursiveCharacterTextSplitter |
| Markdown 技术文档 | 标题切分 + 递归字符切分 |
| HTML 网页 | 正文抽取 + 标题/段落切分 |
| PDF 报告、合同 | Unstructured/MinerU 解析元素 + by_title 或递归切分 |
| 代码库 | 按文件、类、函数切分 |
| 法律、医疗长文档 | 结构切分 + 语义切分 + 严格质检 |
| 问答型短知识 | 小 chunk,低 overlap |
| 需要综合上下文的场景 | 父子 chunk 或句子窗口 |
初学和大多数普通 RAG 项目,可以先用:
Load/Clean -> RecursiveCharacterTextSplitter -> Embedding -> 检索评估如果文档结构明显,再升级为:
结构切分 -> 递归切分 -> 保留标题 metadata -> Embedding7. 切分参数怎么调
调参没有固定答案,但有经验范围。
7.1 chunk_size
常见经验:
- 中文短问答知识:
200 到 500字符。 - 普通说明文档:
500 到 1000字符。 - 英文或 token 计数场景:
200 到 800token。 - 代码块:尽量按函数或类,不只看长度。
- 表格:尽量按表格语义,不只看长度。
判断方式:
- 如果召回结果经常缺上下文,chunk 可能太小。
- 如果召回结果经常包含多个无关主题,chunk 可能太大。
7.2 chunk_overlap
常见经验:
- 普通文本:chunk_size 的
10% 到 20%。 - 结构清晰文档:overlap 可以小一些。
- 叙事连续文档:overlap 可以适当大一些。
- 知识点独立文档:overlap 不宜过大。
判断方式:
- 如果边界处信息经常丢失,增加 overlap。
- 如果重复召回严重、向量库膨胀,减少 overlap。
7.3 不要只看数量
chunk 数量多不一定好,少也不一定坏。重点看:
- 是否覆盖关键知识。
- 是否主题集中。
- 是否大小合适。
- 是否能被典型问题召回。
- 是否能支撑模型回答。
8. chunk结果怎么判断效果
好 chunk 的特征:
- 主题集中。
- 语义完整。
- 长度适中。
- 噪声少。
- 有清楚的来源和标题 metadata。
- 能独立回答某一类问题。
- 不把无关章节强行混在一起。
坏 chunk 的特征:
- 只有半句话,缺上下文。
- 太长,包含多个主题。
- 混入导航栏、页脚、水印、参考资料噪声。
- 表格行列关系丢失。
- 代码块被切断。
- 没有 source、page、section 等元数据。
- 检索时经常答非所问。
8.1 肉眼抽查
抽查每个 chunk:
- 开头是否突兀。
- 结尾是否断裂。
- 是否包含完整知识点。
- 是否混入无关主题。
- metadata 是否正确。
8.2 用典型问题测试召回
准备一些真实问题:
蜂医的技能有哪些? 蜂医在团队中的定位是什么? 激素枪有什么作用?看检索结果:
- 是否召回正确 chunk。
- 正确 chunk 是否排名靠前。
- 召回内容是否足够回答问题。
- 是否混入大量噪声。
8.3 看最终回答质量
最终还是要看 RAG 回答:
- 是否准确。
- 是否引用来源。
- 是否遗漏关键点。
- 是否因为上下文不足而幻觉。
9. Chunking 常见误区
9.1 只追求 chunk 小
chunk 太小会导致上下文不足。模型可能只看到一个局部片段,不知道它属于哪个主题。
9.2 只追求 chunk 大
chunk 太大会导致主题稀释。embedding 向量会变得笼统,检索不精准。
9.3 只用一种策略处理所有文档
PDF、Markdown、网页、代码、表格的结构不同,不应该全部用同一种切法。
9.4 忽略 metadata
只保存正文,不保存来源、标题、页码,会让系统难以溯源和调试。
9.5 认为语义切分一定最好
语义切分更智能,但也更依赖 embedding 模型和参数。它可能切得太粗,也可能切得太碎。语义切分仍然需要评估。
总结
Load 是把原始资料变成可处理的知识原料。 Chunking 是把知识原料设计成适合检索的知识单位。Load 的核心关注:
- 文件是否能读出来。
- 内容是否完整准确。
- 结构是否被识别。
- 噪声是否被清理。
- 元数据是否保留。
Chunking 的核心关注:
- chunk 是否语义完整。
- chunk 是否主题集中。
- chunk 是否长度合适。
- chunk 是否能被问题召回。
- chunk 是否带有可溯源 metadata。
