知识点思维导图
34 个知识节点
文档切分(01) - 文件上传与文档解析
读完后,你应能完成以下任务:
- 绘制“RAG(15) - 文件上传与文档解析 / 第一步:按文件类型选解析方式”的关键对象与数据流,解释“它的标题天然是文档结构。”,并用源码位置、日志或 Trace 标注证据。
- 为“RAG(15) - 文件上传与文档解析 / 第二步:切块(chunk),别太大也别太小”设计正常与异常输入,验证“所以要切成「大小适中、语义完整」的块。”,输出首个偏差位置与回归测试结果。
- 实现“RAG(15) - 文件上传与文档解析 / 第三步:每个 chunk 必须带元数据”的最小代码或配置,检验“这是最容易被忽略、却最重要的一点:切出来的每个 chunk,都要带上它从哪来。”,输出命令、结果与 Diff,并说明不适用边界。
一、与进阶篇的分工
本篇保留为文件解析入门:重点讲上传、解析、切块和来源元数据。进阶入库请读 59《知识库的 loader 和 splitter》、88《Agent 的对象存储方案》、91《企业级知识库项目》,那里会把多来源 loader、对象存储和多模态 RAG 串成完整链路。
二、文件上传与文档解析的真实应用场景
你要做一个「公司知识库问答」:员工问「报销要几天内提交」,AI 从公司制度文档里找答案回。
但模型不认识你的文档。你得先把文档「喂」进去——用户上传 员工手册.md、差旅制度.pdf,你的系统读出文字、切成小块、存起来,将来检索时才能精准捞出相关段落。这个「上传 → 解析 → 切块 → 存储」的入库流程,就是 RAG(RAG(21)《RAG 是什么》)的起点。
文档解析听起来简单(不就读个文件吗),但有两个不显然的点决定了后面问答的质量:怎么切块和留不留来源。
三、第一步:按文件类型选解析方式
不同格式的文档结构不一样,解析方式也不同。最该先支持的是 txt 和 md,纯标准库就能搞定,不用装任何库:
| 类型 | 怎么解析 | 难度 |
|---|---|---|
.txt |
直接读,按段落切 | 简单 |
.md |
顺着 #/## 标题切,保留章节结构 |
简单 |
.pdf |
要装 pdfplumber 等库,还要处理乱版式、扫描件 | 麻烦 |
.docx |
要装 python-docx | 中等 |
md 比 txt 强在哪?它的标题天然是文档结构。顺着标题切,每个块都知道「我属于哪一节」:
纯文本没有这种结构,只能按段落切,章节统一标「正文」。所以同样的内容,整理成 md 比堆成 txt,问答效果好得多。
四、第二步:切块(chunk),别太大也别太小
为什么不把整篇文档当成一块存?因为:
- 太大:检索时一捞捞出一大篇,里面大部分跟问题无关,塞进 prompt 既浪费 token 又干扰模型。
- 太小:一句话被切成两半,丢了上下文,检索到半句话答不全。
所以要切成「大小适中、语义完整」的块。一个实用策略:先按结构切(标题/段落),如果某块还是太长,就在最近的标点处再切一刀,别把句子从中间劈开:
真实项目里 chunk 大小按 token 算(常见 200-500 token),还会让相邻 chunk 有一点重叠(overlap),防止答案正好卡在切割线上。这个 demo 用字符数简化演示原理。
五、第三步:每个 chunk 必须带元数据
这是最容易被忽略、却最重要的一点:切出来的每个 chunk,都要带上它从哪来。
为什么非要留?因为 RAG 回答时要标引用来源(RAG(26)《RAG 回答生成与引用来源》):用户看到答案,还得看到「依据:差旅制度 / 住宿标准」才敢信。如果入库时把来源丢了,检索到内容也没法告诉用户出处,整个回答的可信度就垮了。除了来源,权限信息(这文档谁能看)也得在这一步带上,否则后面没法做权限隔离。
六、工程上真正会踩的坑
- 一上来就想支持所有格式。PDF、Word、网页各有各的坑(扫描件、乱版式、表格)。先把 txt/md 的解析、切块、元数据做扎实,再逐个扩格式。
- 切块时丢了来源元数据。后面 RAG 想标引用来源时发现没存,只能返工重新入库。解析阶段就得把 source、section、权限带全。
- 不校验文件类型和大小。用户传个 500MB 的文件或可执行文件,直接把服务搞挂或带来安全风险。上传入口要限类型、限大小。
- 解析失败直接吞掉。某个文件解析报错就整批中断、或者静默跳过没人知道。要返回可重试的错误,并记录哪个文件没入库成功。
七、一句话面试答法
文档怎么入库到 RAG? 流程是上传 → 校验类型大小 → 解析成纯文本 → 切块 → 带元数据存储。两个关键点:一是切块大小要适中,太大检索不精准还费 token、太小丢上下文,实战按 token 切并让相邻块有重叠;二是每个 chunk 必须带 source 和章节元数据,因为 RAG 回答要标引用来源,入库丢了来源后面就没法标。格式上先支持 txt/md(纯标准库),再扩 PDF/Word。md 因为有标题结构,顺着标题切,检索效果比纯文本好。
八、动手实践:文件上传与文档解析
这不是一个“执行命令看看输出”的空壳示例。它实现了 RAG 离线入库的第一段真实代码:读取本地 Markdown/TXT 文档,按结构解析,切成大小受控的 chunk,并给每个 chunk 补上 source 和 section 元数据。
先明确边界:python3 main.py 不会上传文件、不会调用大模型、不会生成 Embedding,也不会写入向量数据库。它只演示上传文件落盘后的离线预处理:
sample.md / sample.txt
↓
校验扩展名并读取 UTF-8 文本
↓
Markdown 按标题解析 / TXT 按段落解析
↓
长文本按标点二次切块
↓
输出 Chunk(text, metadata)
8.1 运行后到底做什么
main.py 会依次处理同目录下两个输入文件:
| 输入 | 解析策略 | 结果 |
|---|---|---|
sample.md |
识别 #、## 标题,保留“一级标题/二级标题”章节路径 |
4 个带业务章节的 chunk |
sample.txt |
按空行拆成段落,章节统一标记为“正文” | 4 个正文 chunk |
每个结果都长这样:
后续做 Embedding 时使用 text;向量入库时把 metadata 一起保存,在线检索命中后才能展示引用来源,并按租户、部门或文档权限过滤。
8.2 环境与文件
要求 Python 3.10+,只使用标准库:
# requirements.txt
# Python 3.10+ 标准库即可运行,无第三方依赖。
实验目录包含:
lab/
├── README.md # 当前说明
├── main.py # 可执行解析器
├── requirements.txt # 运行环境说明
├── sample.md # 带标题结构的制度文档
└── sample.txt # 无标题结构的班车通知
8.3 在线运行
直接使用本文“可运行源码”中的多文件沙盒执行。main.py、sample.md 和 sample.txt 会写入同一个隔离目录,无需再进入外部 Lab。
8.4 核心实现
完整实现集中在本文“可运行源码”沙盒中,避免教学片段与实际执行版本漂移。阅读时重点跟踪 parse_file -> parse_markdown/parse_text -> build_chunks -> split_long。
8.5 关键调用链
main()通过脚本目录找到sample.md和sample.txt。parse_file()校验扩展名并读取 UTF-8 文本。- Markdown 进入
parse_markdown(),TXT 进入parse_text()。 - 每段正文交给
split_long(),超过MAX_CHUNK_CHARS时优先在。、;、,后切分。 build_chunks()为每个文本块附加source、section。main()把解析结果打印出来,便于检查切块是否符合预期。
8.6 预期输出
=== sample.md 解析出 4 个 chunk ===
[1] (员工报销制度/提交时限) 员工报销需要在费用发生后的 30 天内提交,逾期需要部门经理书面说明。
[2] (员工报销制度/报销材料) 报销需要提供发票原件、审批单,以及对应的合同或采购单。电子发票需打印后粘贴。
[3] (差旅管理/交通标准) 经理级别可乘坐高铁一等座,员工乘坐二等座。机票需提前 7 天预订。
[4] (差旅管理/住宿标准) 一线城市住宿标准为每晚 500 元,其它城市 350 元,超出部分自理。
=== sample.txt 解析出 4 个 chunk ===
[1] (正文) 公司班车时刻表说明。
[2] (正文) 早班车每天 8 点从地铁站发车,晚班车 18 点 30 分从公司发车。
[3] (正文) 节假日班车停运,具体安排以行政通知为准。
[4] (正文) 雨雪天气班车可能延迟,请关注企业微信群通知。
每个 chunk 都带 source + section,可直接进入后续 Embedding 和向量入库步骤。
8.7 为什么 Markdown 比 TXT 多一层价值
两种输入都能得到文本块,但 Markdown 的标题天然提供结构。检索命中住宿标准时,系统不仅拿到正文,还能知道它属于“差旅管理/住宿标准”。TXT 没有标题,只能标记为“正文”。
这也是企业知识库入库时要尽量保留标题、页码、表格名、文档 ID、租户 ID 和权限标签的原因:纯文本只是内容,元数据决定内容能否被正确过滤、引用和追责。
8.8 生产环境还缺什么
这个最小示例故意只覆盖解析核心。真正的上传接口还必须增加:
- 文件大小、扩展名和 MIME 双重校验,不能只信用户传来的文件名。
- 隔离的临时目录或对象存储,禁止直接使用原始文件名拼接服务器路径。
- PDF、DOCX、扫描件 OCR 等独立解析器,以及解析超时和失败重试。
- 按 token 而不是字符切块,并通过评测选择 chunk size 和 overlap。
tenant_id、document_id、acl等权限元数据,检索时必须先过滤权限再返回内容。- 文档版本、内容哈希和幂等键,避免同一文件重复入库。
- 后续 Embedding、向量库写入和建库质量校验。
8.9 动手验证
- 把
MAX_CHUNK_CHARS从60改成20,观察长句如何变成更多文本块。 - 往
sample.md加一个## 费用审批章节,确认输出里出现新的章节路径。 - 新建
sample.json并传给parse_file(),确认程序明确报“不支持的文件类型”且退出码为1。 - 删除
sample.txt后运行,确认错误中包含缺失文件路径,而不是静默跳过。
8.10 可运行源码:文件上传与文档解析
下方代码与夹具就是在线沙盒实际使用的全部文件。文件标签可切换查看,点击运行会把它们写入同一个隔离目录。
main.py
requirements.txt
# Python 3.10+ 标准库即可运行,无第三方依赖。
sample.md
# 员工报销制度
## 提交时限
员工报销需要在费用发生后的 30 天内提交,逾期需要部门经理书面说明。
## 报销材料
报销需要提供发票原件、审批单,以及对应的合同或采购单。电子发票需打印后粘贴。
# 差旅管理
## 交通标准
经理级别可乘坐高铁一等座,员工乘坐二等座。机票需提前 7 天预订。
## 住宿标准
一线城市住宿标准为每晚 500 元,其它城市 350 元,超出部分自理。
sample.txt
公司班车时刻表说明。
早班车每天 8 点从地铁站发车,晚班车 18 点 30 分从公司发车。
节假日班车停运,具体安排以行政通知为准。
雨雪天气班车可能延迟,请关注企业微信群通知。
九、总结
- 第二步:切块(chunk),别太大也别太小:所以要切成「大小适中、语义完整」的块。
- 第三步:每个 chunk 必须带元数据:这是最容易被忽略、却最重要的一点:切出来的每个 chunk,都要带上它从哪来。
- 一句话面试答法:流程是上传 → 校验类型大小 → 解析成纯文本 → 切块 → 带元数据存储。
- 工程边界:用户传个 500MB 的文件或可执行文件,直接把服务搞挂或带来安全风险。
- 实现机制:因为 RAG 回答时要标引用来源(RAG(26)《RAG 回答生成与引用来源》):用户看到答案,还得看到「依据:差旅制度 / 住宿标准」才敢信。
学完自测
选择所有正确答案;提交后逐项核对判断依据。