代码语言

知识点思维导图

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 补上 sourcesection 元数据。

先明确边界: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.pysample.mdsample.txt 会写入同一个隔离目录,无需再进入外部 Lab。

8.4 核心实现

完整实现集中在本文“可运行源码”沙盒中,避免教学片段与实际执行版本漂移。阅读时重点跟踪 parse_file -> parse_markdown/parse_text -> build_chunks -> split_long

8.5 关键调用链

  1. main() 通过脚本目录找到 sample.mdsample.txt
  2. parse_file() 校验扩展名并读取 UTF-8 文本。
  3. Markdown 进入 parse_markdown(),TXT 进入 parse_text()
  4. 每段正文交给 split_long(),超过 MAX_CHUNK_CHARS 时优先在 后切分。
  5. build_chunks() 为每个文本块附加 sourcesection
  6. 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_iddocument_idacl 等权限元数据,检索时必须先过滤权限再返回内容。
  • 文档版本、内容哈希和幂等键,避免同一文件重复入库。
  • 后续 Embedding、向量库写入和建库质量校验。

8.9 动手验证

  1. MAX_CHUNK_CHARS60 改成 20,观察长句如何变成更多文本块。
  2. sample.md 加一个 ## 费用审批 章节,确认输出里出现新的章节路径。
  3. 新建 sample.json 并传给 parse_file(),确认程序明确报“不支持的文件类型”且退出码为 1
  4. 删除 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 回答生成与引用来源》):用户看到答案,还得看到「依据:差旅制度 / 住宿标准」才敢信。

学完自测

选择所有正确答案;提交后逐项核对判断依据。

1在“文件上传与文档解析”中,需要同时满足“与进阶篇的分工”与“文件上传与文档解析的真实应用场景”。给定正文约束“进阶入库请读 59《知识库的 loader 和 splitter》、88《Agent 的对象存储方案》、91《企业级知识库项目》,那里会把多来源 loader、对象存储和多模态 RAG 串成完整链路。”,哪些判断保持了原有处理机制?多选
2“文件上传与文档解析”出现偏差:“在“文件上传与文档解析 / 第一步:按文件类型选解析方式”中,即使不满足“纯文本没有这种结构,只能按段落切,章节统一标「正文」”,结果与副作用仍会保持不变。”已成为实际行为。围绕“第一步:按文件类型选解析方式”与“第二步:切块(chunk),别太大也别太小”,哪些判断能定位被改变的职责或边界?多选
3评审“文件上传与文档解析”方案时,验收条件包含“为什么非要留?因为 RAG 回答时要标引用来源(RAG(26)《RAG 回答生成与引用来源》):用户看到答案,还得看到「依据:差旅制度 / 住宿标准」才敢信。”。关于“第三步:每个 chunk 必须带元数据”与“一句话面试答法”的哪些决策符合正文机制?多选