Appearance
4.2.1 为什么做完清洗和切块后,才能进入索引阶段?
先给结论:因为索引不是在处理原始资料,而是在组织“已经整理好的检索对象”。如果清洗和切块还没完成,索引阶段写进去的往往就是脏对象、错对象和不可用对象。
这也是很多系统前期看起来“已经建好索引”,后面却越用越乱的根源。
为什么这三个阶段不能颠倒
可以先把这三个阶段分开看:
清洗解决的是内容干不干净切块解决的是对象粒度合不合理索引解决的是这些对象以后怎么被找回来
也就是说:
- 清洗之前,内容本身可能就不适合进入系统
- 切块之前,系统甚至还不知道应该按什么粒度建立检索对象
- 只有前两步完成后,索引阶段才知道自己到底在索引什么
如果顺序反过来,后面经常会出现一种表面上已经完成、实际上还没开始的错觉:
- 数据写进去了,但噪声也一起写进去了
- 向量算出来了,但粒度完全不适合检索
- metadata 带进去了,但字段含义并不稳定
清洗阶段到底在给索引阶段准备什么
索引阶段并不擅长替你识别“这段内容是否应该存在”,它默认你喂进去的是已经过基本处理的数据。
清洗至少要先处理这些问题:
- 模板噪声有没有去掉
- 重复内容有没有压掉
- 无效字段、页脚、导航、免责声明有没有剔除
- 文本编码、空白符、断行、特殊字符有没有基本规范化
- 明显过时、损坏、无意义的资料有没有挡在索引前面
如果这些没做好,索引阶段通常只会把问题固化下来。
比如:
- 页眉页脚被重复切成很多块,召回时反而经常冲到前面
- 同一篇内容的多个脏版本一起入库,后面模型拿到互相冲突的上下文
- 导航栏、按钮文案、页面装饰文本进入索引,导致问题一搜就先命中噪声
这时候你看到的像“检索不准”,但根因其实是前面清洗没做完。
切块阶段到底在给索引阶段准备什么
清洗解决的是“干不干净”,切块解决的是“一个索引对象应该长什么样”。
索引阶段要写进去的,不是整篇原始文档,而通常是一个个可检索对象。这个对象至少要在三个方面先站稳:
- 粒度足够合理
- 语义边界尽量完整
- 能挂上明确的 metadata
如果切块没做好,索引阶段也很难补救。
常见问题包括:
- 块太大,相关信息被淹没,召回时很难突出关键片段
- 块太小,上下文断裂,模型拿到后又无法回答完整问题
- 一个块里混了多个主题,导致向量表达模糊
- 一个主题被切得太碎,后面要靠重排和拼接硬补
所以索引阶段依赖的不是“原始文档”,而是“已经切成了合适检索单位的对象集合”。
一个更实际的流程理解
你可以把这条链路先粗略理解成:
- 原始资料进入清洗流程
- 清洗后得到相对可用的文本或结构化内容
- 再按目标检索粒度切成块
- 为每个块补上必要的 metadata
- 最后才做 embedding、写入索引、建立底层检索结构
一个最小示意可以写成这样:
python
clean_doc = {
"doc_id": "refund-policy-v3",
"title": "退款规则",
"text": "......已经去掉页眉页脚、导航和重复模板后的正文......",
"status": "active"
}
chunks = [
{
"chunk_id": "refund-policy-v3#chunk-01",
"text": "普通商品支持七天无理由退货。",
"metadata": {"doc_id": "refund-policy-v3", "section": "退款条件"}
},
{
"chunk_id": "refund-policy-v3#chunk-02",
"text": "定制类商品不支持无理由退货。",
"metadata": {"doc_id": "refund-policy-v3", "section": "退款条件"}
}
]
indexed_records = [
{
"id": chunk["chunk_id"],
"text": chunk["text"],
"vector": embed(chunk["text"]),
"metadata": chunk["metadata"],
}
for chunk in chunks
]这段代码真正想说明的是:
- 索引对象来自切块后的结果
- 切块结果又依赖清洗后的内容
- 索引阶段是在前两步基础上继续组织,而不是替代前两步
为什么很多团队会跳过这一步
因为从 Demo 视角看,直接把原始文本扔进去也可能“跑得通”。
但一旦数据规模和场景复杂度上来,问题很快就会暴露:
- 同义问题召回不稳
- 页面噪声频繁命中
- 老版本和新版本内容混在一起
- 同一主题回答前后不一致
- 调了很久 Prompt 也改善有限
这些问题往往不是因为模型不够强,而是因为索引对象从一开始就没有准备好。
从 0 开始时,比较稳的实施顺序是什么
如果你是第一次搭这条链路,一个更稳的顺序通常是:
- 先确定哪些数据可以进入知识库
- 对原始内容做基础清洗和标准化
- 选定适合该数据类型的切块方式
- 给块补齐最基本的 metadata
- 再统一做 embedding 和写入索引
- 最后用真实问题集检查召回质量
这个顺序的核心不是流程好看,而是避免把脏数据和坏粒度过早写死在索引里。
一个常见误区
很多人会觉得:
- 先把资料全部索引进去,后面边用边清洗也可以
这在少量试验里有时能凑合,但在真实系统里代价很高。
因为一旦对象已经进入索引,后面你不仅要重新清洗,还要处理:
- 旧向量怎么失效
- 旧块怎么删除
- 检索缓存怎么更新
- 新旧版本怎么切换
所以更稳的方式通常不是“先写进去再说”,而是先把进入索引的对象准备好。
一句话总结
做完清洗和切块后,才能进入索引阶段,因为索引处理的不是原始资料,而是已经整理成可检索单位的知识对象。清洗决定内容干不干净,切块决定对象长什么样,索引才决定这些对象以后怎么被稳定找回来。