Skip to content

4.2.1 为什么做完清洗和切块后,才能进入索引阶段? ​

先给结论:因为索引不是在处理原始资料,而是在组织“已经整理好的检索对象”。如果清洗和切块还没完成,索引阶段写进去的往往就是脏对象、错对象和不可用对象。

这也是很多系统前期看起来“已经建好索引”,后面却越用越乱的根源。

为什么这三个阶段不能颠倒 ​

可以先把这三个阶段分开看:

  • 清洗 解决的是内容干不干净
  • 切块 解决的是对象粒度合不合理
  • 索引 解决的是这些对象以后怎么被找回来

也就是说:

  • 清洗之前,内容本身可能就不适合进入系统
  • 切块之前,系统甚至还不知道应该按什么粒度建立检索对象
  • 只有前两步完成后,索引阶段才知道自己到底在索引什么

如果顺序反过来,后面经常会出现一种表面上已经完成、实际上还没开始的错觉:

  • 数据写进去了,但噪声也一起写进去了
  • 向量算出来了,但粒度完全不适合检索
  • metadata 带进去了,但字段含义并不稳定

清洗阶段到底在给索引阶段准备什么 ​

索引阶段并不擅长替你识别“这段内容是否应该存在”,它默认你喂进去的是已经过基本处理的数据。

清洗至少要先处理这些问题:

  • 模板噪声有没有去掉
  • 重复内容有没有压掉
  • 无效字段、页脚、导航、免责声明有没有剔除
  • 文本编码、空白符、断行、特殊字符有没有基本规范化
  • 明显过时、损坏、无意义的资料有没有挡在索引前面

如果这些没做好,索引阶段通常只会把问题固化下来。

比如:

  • 页眉页脚被重复切成很多块,召回时反而经常冲到前面
  • 同一篇内容的多个脏版本一起入库,后面模型拿到互相冲突的上下文
  • 导航栏、按钮文案、页面装饰文本进入索引,导致问题一搜就先命中噪声

这时候你看到的像“检索不准”,但根因其实是前面清洗没做完。

切块阶段到底在给索引阶段准备什么 ​

清洗解决的是“干不干净”,切块解决的是“一个索引对象应该长什么样”。

索引阶段要写进去的,不是整篇原始文档,而通常是一个个可检索对象。这个对象至少要在三个方面先站稳:

  • 粒度足够合理
  • 语义边界尽量完整
  • 能挂上明确的 metadata

如果切块没做好,索引阶段也很难补救。

常见问题包括:

  • 块太大,相关信息被淹没,召回时很难突出关键片段
  • 块太小,上下文断裂,模型拿到后又无法回答完整问题
  • 一个块里混了多个主题,导致向量表达模糊
  • 一个主题被切得太碎,后面要靠重排和拼接硬补

所以索引阶段依赖的不是“原始文档”,而是“已经切成了合适检索单位的对象集合”。

一个更实际的流程理解 ​

你可以把这条链路先粗略理解成:

  1. 原始资料进入清洗流程
  2. 清洗后得到相对可用的文本或结构化内容
  3. 再按目标检索粒度切成块
  4. 为每个块补上必要的 metadata
  5. 最后才做 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 开始时,比较稳的实施顺序是什么 ​

如果你是第一次搭这条链路,一个更稳的顺序通常是:

  1. 先确定哪些数据可以进入知识库
  2. 对原始内容做基础清洗和标准化
  3. 选定适合该数据类型的切块方式
  4. 给块补齐最基本的 metadata
  5. 再统一做 embedding 和写入索引
  6. 最后用真实问题集检查召回质量

这个顺序的核心不是流程好看,而是避免把脏数据和坏粒度过早写死在索引里。

一个常见误区 ​

很多人会觉得:

  • 先把资料全部索引进去,后面边用边清洗也可以

这在少量试验里有时能凑合,但在真实系统里代价很高。

因为一旦对象已经进入索引,后面你不仅要重新清洗,还要处理:

  • 旧向量怎么失效
  • 旧块怎么删除
  • 检索缓存怎么更新
  • 新旧版本怎么切换

所以更稳的方式通常不是“先写进去再说”,而是先把进入索引的对象准备好。

一句话总结 ​

做完清洗和切块后,才能进入索引阶段,因为索引处理的不是原始资料,而是已经整理成可检索单位的知识对象。清洗决定内容干不干净,切块决定对象长什么样,索引才决定这些对象以后怎么被稳定找回来。