> 清洗步骤必须有一道"我是不是清过头了"的自检——它差点删掉一整本书
要从检索库里清掉目录页、索引页、参考文献(它们关键词密度极高, 对任何查询都有不错的余弦相似度),踩了三个坑,每一个都值一条规则。
结论先行¶
要从检索库里清掉目录页、索引页、参考文献(它们关键词密度极高, 对任何查询都有不错的余弦相似度),踩了三个坑,每一个都值一条规则。
① 过滤器一直存在,但只挂在其中一条解析路径上。
FRONT_MATTER 过滤写好了,只挂在"markdown 标题"那条 fallback 上;
90% 的书走的是内嵌书签那条路,从来没被过滤过。
② 判据"路径里任何一级命中就算前置内容",差点毁掉一本书。
一本图论教材的书签树把 "Contents" 放在 lvl1,把全部 18 章 放成它的 lvl2 子节点,于是每章的路径都是
["Contents", "1 Graphs"]。 按"任何一级"判,整本 654 页只剩 34 块,区间覆盖掉到 0.028。
修法两步:只看最深一级(小节自己的标题),再加一道安全网 —— 过滤后小节数不足 40% 就整个放弃过滤。
宁可留噪声,也不能静默丢正文。
改完全语料 0 本书覆盖低于 0.55。
③ 用删除代替重切省掉一小时 GPU,前提是一个 id 不随过滤重编号。
原来 parent_id = f"{doc_id}:{si}" 里的 si 是过滤后列表的下标,
一删就全体错位。改成用过滤前的位置之后,
"从向量库删掉这些块"与"整个重切重嵌"等价:
拿 5 本书验证:重切结果 == 库里现有 − 前置内容块 → 5/5 完全一致
于是直接 DELETE:197,075 → 183,950 块,清掉 6.7%,用时几秒而不是一小时。
为什么¶
第 ① 条是"一个修复挂在了一条路径上,而生产流量走另一条"的又一个实例 (同 把配置抽成 include 片段,会弄坏每一条 CI 没在测的组装路径)。 危险之处在于过滤器存在:代码在、测试可能也在、名字也对, 于是没有人会再问"它有没有被调用"。 多条解析路径的系统里,清洗步骤必须挂在汇流之后, 或者每条路径都显式测一遍。
第 ② 条是判据和数据结构的假设不匹配。"路径里任何一级命中"隐含 假定了书签树的层级有语义(lvl1 是部、lvl2 是章); 而实际的书签树是人做的,有人把 "Contents" 当成一个包含全书的顶层节点。 对人工产生的层级结构,不要假定层级的含义。
但真正值钱的不是"改成只看最深一级"——那只是修了这一个 bug。 值钱的是那道安全网:
过滤后剩不到 40%,就放弃过滤。
因为下一个把书签树做成怪样子的书还会来,而你不知道它会怪在哪。 一道"清掉的比例异常"的自检,不需要预见具体的失效形态。 而且它的失败方向选对了:保留噪声是可恢复的(指标略降), 静默丢掉六成正文是不可恢复的(那本书在库里等于不存在)。
第 ③ 条是一个很一般的性质:一个 id 如果依赖某个过滤的结果, 它就不能用来撤销那个过滤。 下标型 id("第 i 个")天生带这个毛病, 因为"第 i 个"的含义随集合变化。用过滤前的位置(或内容哈希、 或原始页码)做 id,才能让"删掉子集"和"重算全集"等价。 而等价性要抽样验证,不要推断 —— 这里是 5 本书 5/5 全等。
怎么用¶
任何清洗/过滤步骤配一道"清掉的比例"自检,超阈值就放弃或报警:
kept = [s for s in sections if not is_front_matter(s)]
if len(kept) < 0.4 * len(sections):
return sections # 放弃过滤,留噪声
阈值不必精确,它要挡的是量级错误,不是百分点。
log.warning 一行也比没有好 —— 但"放弃"比"报警"强,
因为报警需要有人在看。
自检要选一个比值,而不是绝对数。 "清掉 620 块"无法判断, "清掉 97% 的小节"立刻就知道不对。 同 覆盖率这类比值能抓到不抛异常的缺陷——而"每页小节数"抓不到。
过滤器要挂在汇流点,并且断言它跑过。 最便宜的做法是让过滤函数记一个计数器,流水线结束时打出 "过滤了 N 本书 / M 个小节"——如果 90% 的书从没进过这个函数, 这个数字会立刻露馅。
判据只看最深一级,除非你确认过层级有语义。 对人工维护的树形元数据(书签、标签、目录、分类), 先统计一下形态分布(lvl1 有多少种值?有没有 lvl1 直接挂全书的?), 再决定判据看哪一级。
"删除"能替代"重算"的条件写下来并抽样验证: id 不随被删集合重编号、聚合量(计数、容量、覆盖率)会被重算、 下游没有缓存旧 id 的映射。三条都成立时,省掉的可以是一小时 GPU。
出处¶
~/Tools/booksearch/docs/RAG_PLAN.md §14.1(FRONT_MATTER 只挂在
fallback 路径上、全语料类别统计)、§14.2(654 页只剩 34 块、
覆盖 0.028、40% 安全网)、§14.3(parent_id 用过滤前位置、
5/5 等价验证、197,075 → 183,950)。