文档切分与元数据设计:为后续检索准备可靠数据
Embedding 解决了“如何比较两段文本是否相近”,但在真实知识库中,直接把整篇文档生成一个向量通常不够用:用户的问题可能只对应其中一个小节,检索结果还需要告诉我们它来自哪份文档、哪一页或哪一段。文档切分(chunking)负责把原文变成适合检索的片段,元数据(metadata)则负责保留每个片段的身份和来源。本篇只聚焦这两个基础设计,并用 Python 标准库完成一个可运行、无需网络的最小示例。
为什么要切分文档
把整篇文档压成一个向量,会把多个主题混在一起。比如一份部署手册同时包含安装、配置、备份和故障排查,用户询问“备份文件放在哪里”时,整篇向量只能表达一个模糊的综合主题,难以精确召回相关内容。
切分后的每个片段可以单独生成 Embedding、单独参与相似度比较,并在命中后作为上下文交给模型。切分也会影响最终效果:片段太小,语义不完整;片段太大,检索结果噪声多,还可能挤占上下文窗口。因此,切分不是机械地按固定字符数截断,而是在“语义完整”和“长度可控”之间做取舍。
一个实用的片段至少应满足三点:
- 能在脱离全文后理解自己的主要意思。
- 长度适合后续的向量化和上下文预算。
- 能追溯回原文,必要时可以展示标题、页码或链接。
先设计片段和元数据
可以把一个片段看成两部分:text 是参与向量化和检索的正文,metadata 是描述它的附加信息。常见字段包括:
document_id:文档的稳定标识,不要只依赖显示名称。title:文档标题或章节标题,帮助模型理解上下文。source:文件路径、网页地址或对象存储键。section:章节名称,便于展示和过滤。chunk_index:片段在原文中的顺序。start、end:片段在规范化文本中的字符范围,方便定位。
元数据不是越多越好。应区分“用于过滤的字段”和“用于展示、引用的字段”,并给字段定义稳定类型。例如页码应保持为整数,日期应统一格式;不要今天把 page 写成数字,明天又写成“第 3 页”。如果文档更新,最好根据内容生成版本或校验值,避免旧片段与新文档混在一起。
最小可运行的切分器
下面的示例按 Markdown 二级标题识别章节,再把每章按最大字符数切开。它不调用模型,也不假装字符数等于 token 数,因此可以先在本地验证边界和元数据。每个片段都会保留文档 ID、章节名、序号和原文范围。
1 | from __future__ import annotations |
保存为 chunk_demo.py 后运行 python chunk_demo.py,即可看到每个片段的 JSON 记录。chunk_index 使用生成顺序,start 和 end 使用原始字符串的字符位置;如果后续先做了空白折叠、HTML 清理或 OCR 修正,就必须明确这些位置对应的是处理前还是处理后的文本,不能混用。
示例为了突出设计,采用了最简单的固定长度切分。实际项目可以先按标题、段落、列表和代码块等边界切分,再对超长段落做兜底截断。若相邻内容经常跨段落引用,可以加入少量 overlap,但重叠文本会增加向量数量和检索重复,应该通过测试集决定,而不是默认越多越好。
元数据如何参与检索
向量相似度只回答“哪些片段语义接近”,元数据可以补充业务约束。例如只检索 document_id 属于当前产品的片段,或只检索指定版本、语言和权限范围内的内容。权限过滤必须在返回上下文前完成,不能把所有片段交给模型后再指望模型自行隐藏敏感内容。
命中后,应用可以把 title、section、source 和 chunk_index 渲染为引用信息。若用户追问“这句话来自哪里”,程序应根据结构化字段生成链接或文件定位,而不是让模型凭记忆编造来源。保存 start、end 也有助于调试:当召回结果不对时,可以回到原文检查切分边界是否把标题、表格或关键条件截断了。
常见问题
按字符数切分是不是按 token 切分? 不是。不同语言、代码和标点的 token 密度不同。字符上限适合做离线原型;接入具体 Embedding 或生成模型时,要根据 tokenizer 和上下文窗口重新设定预算。
每个片段都要重复加入文档标题吗? 通常值得加入。标题能提供主题线索,但要避免把很长的导航、页眉和页脚重复到每个片段中。可以把标题保存在元数据,也可以在送入模型的上下文中单独拼接,二者用途不同。
文档更新后如何处理旧向量? 为文档保存版本号或内容校验值。重新切分后批量写入新版本,检索时只选择当前版本;确认新数据可用后,再清理旧版本,避免更新过程出现空窗。
元数据能不能完全替代正文? 不能。元数据用于定位、过滤和解释,语义检索仍主要依赖片段正文的向量。若字段本身包含重要语义,例如产品名或章节名,应按需求把它拼入向量化文本,同时保留原字段用于展示和过滤。
小结
文档切分的目标是让片段既足够完整,又能在检索时精确命中;元数据的目标是让片段可过滤、可追溯、可引用。本文用标准库实现了按章节和长度切分,并为每个片段保存文档 ID、章节、顺序和字符范围。接下来进入向量数据库基础时,这些片段记录会成为写入索引的输入;提前把字段和版本规则设计清楚,能显著减少后续返工。