文档解析与切分
文档解析与切分 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
读完本课你能把一份文档变成可检索的片段:先解析出纯文本,再按标题和窗口切分,并且说清为什么切分粒度直接决定检索质量。
问题场景
你把一份 2 万字的接口文档导入资料库,问「登录失败锁定多久」,检索器返回了整份文档——上下文塞进两万字,答案就在其中某一行。要么把上下文撑爆(A02 讲过上下文是稀缺资源),要么模型在长文里看走眼。整篇存不行,那就得切:把文档切成一段段带来源信息的小块,检索的最小单位从「文档」变成「片段」。
核心解释
本站的处理分两步,都在 lib/documents/parse.ts:
第一步:解析(extractText)——把各种格式变成纯文本。txt/md 直接读(顺手处理 BOM 头);PDF 用 pdf.js 提取文字层;扫描件没有文字层,明确报错「需要 OCR 能力,本站暂不具备」,而不是默默索引出一份空文档。解析失败的文档要给出人能看懂的原因。
第二步:切分(chunkText)——本站采用「标题分节 + 滑动窗口」:
- 按 Markdown 标题(
#到######)把文本分成节,每个片段记录它所属的heading——这是后续引用定位的关键元数据(A22 会用到)。 - 节内容不超过约 800 字的 1.4 倍时整节成一块,避免小节被硬切。
- 超长节按 800 字窗口、100 字重叠 滑动切块,最后整体上限 500 块防失控。
// 切分核心逻辑(节选自 lib/documents/parse.ts 的 chunkText)
const WINDOW = 800;
const OVERLAP = 100;
for (const sec of sections) {
if (sec.body.length <= WINDOW * 1.4) {
chunks.push({ orderIndex: chunks.length, heading: sec.heading, text: sec.body });
continue;
}
let start = 0;
while (start < sec.body.length) {
const end = Math.min(start + WINDOW, sec.body.length);
chunks.push({
orderIndex: chunks.length,
heading: sec.heading,
text: (start > 0 ? "…" : "") + sec.body.slice(start, end),
});
if (end >= sec.body.length) break;
start = end - OVERLAP; // 回退 100 字,让跨界句子在两块里都完整出现
}
}
为什么是这两个参数:窗口太小(如 200 字),一句话的上下文被切掉,检索到了也对不上问题;窗口太大(如 5000 字),又回到「命中即整篇」的老问题——检索粒度粗、上下文浪费。重叠解决的是句子被拦腰切断:关键句恰好跨在边界上时,因为重叠它在相邻两块里都完整存在,检索至少能命中一块。
如何验证:切完不是结束,要拿真实问题去检索,看命中片段是否「正好包含答案及其上下文」。
示例拆解
上传后落到库里的数据形状(真实表结构):
-- 切分结果存两处:doc_chunk 供 LIKE 兜底与展示,fts_doc 供全文索引
CREATE TABLE doc_chunk (
document_id TEXT NOT NULL, -- 来源文档(引用定位用)
collection_slug TEXT NOT NULL, -- 所属资料集(限定检索范围用)
order_index INTEGER NOT NULL, -- 片段顺序(可打开原文片段)
heading TEXT, -- 所属标题(展示与定位)
text TEXT NOT NULL -- 片段正文
);
关键点:
- 每个片段都带着「我从哪来」:
document_id+order_index+heading。没有这些,检索命中后引用无从谈起。 - 上传走
/api/documents的 PUT(multipart 表单),服务端同步解析索引,document.status从pending走到indexed或failed,失败原因存在error字段里可查。 - 同一文档重复导入会先删旧块再写新块(
DELETE FROM doc_chunk WHERE document_id = ?),避免新旧片段混在一起。
动手任务
- 打开本课实验区,对样例文本分别用窗口 60 和 120 跑切分,数一数块数,想想为什么本站取 800 字(对照片段要「装得下一个完整问答上下文」)。
- 把重叠去掉(
start = end),在输出里找一条恰好跨在边界上被切断的句子,说出它会带来什么检索后果。 - 把样例文本的标题行全删掉再切分,观察
heading全为 null——想一下检索结果少了什么信息,引用还能定位到哪一级。
常见错误
- 扫描件 PDF 静默导入成功:文字层提取出来是空的,索引了一堆空片段还不报错。必须检测「提取文字过短」并明确报错。
- 按固定字数硬切、不留重叠:关键句被切断,检索命中了却答不对——症状是「片段明明相关但只差半句」。
- 切分时丢掉标题等元数据:片段检索命中了,但引用只能给一串正文,用户无法定位来源。
- 重复导入不清理旧块:同一文档出现两份片段,检索结果重复且引用错乱。
检查与复盘
自查清单:
- 能说出解析阶段的边界(什么格式能处理、什么情况必须报错)
- 能默写切分的两个关键参数与各自的作用(窗口长度、重叠)
- 能解释「片段带 document_id / order_index / heading」分别服务于什么
参考资料
- MDN:File API —— 上传文件的读取与类型判断
- Mozilla pdf.js —— 本站 PDF 文字层提取所用库
- 下一课 A20 讲另一条检索路线:把片段变成向量做语义检索。
读完这一课了?
标记完成后,工作台会安排接下来的学习。