RAG 翻车的第一责任人,往往是数据清洗

大多数人把 RAG 效果不好归咎于选错向量库或 Embedding 模型,但脏数据一旦进索引,检索精度会断崖式下跌。这篇拆一个四层预处理 Pipeline,以及几个最容易被忽略的坑。

RAG 翻车的第一责任人,往往是数据清洗

做 RAG 的人有个通病:效果不好就换向量库、调 Top-K、换 Embedding 模型。但这些都排在很后面。

最先决定检索质量的,是进索引之前那一步——数据清洗与预处理。 脏数据(乱码、页眉页脚、重复块、OCR 错误)一旦向量化入库,会在语义空间里形成一片噪声,检索时不断把不相关的内容顶上来。这种损伤,后面怎么调都救不回来。

一个四层 Pipeline

我把预处理拆成四层,从原始文件到高质量分块:

原始 PDF/Word/网页
   ↓
第一层 文档解析:OCR 修正 → 布局分析 → 表格识别 → 图片转文字
   ↓
第二层 文本清洗:编码修复 → 特殊字符 → 乱码处理 → 格式规范化
   ↓
第三层 内容增强:去重 → 信息补全 → 上下文扩充 → 术语标准化
   ↓
第四层 质量过滤:噪声检测 → 低质过滤 → 敏感内容处理
   ↓
高质量分块 → 向量化 → 入库

前两层是”别把垃圾放进去”,后两层是”让有用的更好用”。

最容易踩的坑

页眉页脚混进正文。 很多报告每页都有公司名、页码、打印时间。这些东西不清理,会污染 embedding——而且因为每一页都有,它们在向量空间里的”存在感”反而特别强,检索时老是被命中。识别特征很简单:短文本 + 含”第 N 页”或日期模式。

private boolean isHeaderOrFooter(String text) {
    if (text.length() > 200) return false;
    if (text.matches(".*第\\s*\\d+\\s*页.*")) return true;
    if (text.matches(".*\\d{4}[-年]\\d{1,2}[-月]\\d{1,2}日.*")) return true;
    return false;
}

OCR 错误一路向下传染。 扫描件提取出来如果是乱码,得先判断文本层是否缺失,是的话走 OCR,而且 OCR 的错误会一路传到检索结果,所以识别质量要单独抽检。一个实用的小技巧是建一张常见错字映射表,比如把”高血厈”纠回”高血压”——这种错误在大模型眼里是”合理的词”,不会自己发现。

文本清洗要按顺序。 不是简单 replace,而是一连串:修编码 → 去控制字符 → 归一化 Unicode → 去零宽字符 → 归一化空白。其中”中文间的多余空格”很容易被忽略,但嵌进 embedding 后会轻微干扰相似度计算。

public String clean(String text) {
    if (text == null || text.isEmpty()) return text;
    String cleaned = fixEncoding(text);
    cleaned = removeControlChars(cleaned);
    cleaned = normalizeUnicode(cleaned);
    cleaned = removeZeroWidthChars(cleaned);
    cleaned = normalizeWhitespace(cleaned);
    cleaned = fixOCRCommonErrors(cleaned);  // 医疗等场景的专有错字
    return cleaned;
}

去重要分三层

重复内容会浪费向量空间、还会让检索结果出现”同一段话出现三次”。我按强度分了三道:

  1. 精确去重:完全相同的,直接丢。
  2. 近似去重:MinHash / LSH 算 Jaccard 相似度,超过 0.85 的算重复,保留质量更高的版本。
  3. 语义去重:用 Embedding 算余弦相似度,超过 0.95 的算重复。

逐层递进,成本低的前面跑,成本高的(要调 Embedding 模型)最后跑。

最后用质量分兜底

清洗完不意味着都该进索引。我用一个综合质量分给每个块打分,低于阈值的直接过滤掉:

score.setOverall(
    score.getCompleteness()      * 0.25 +  // 内容完整性
    score.getInformationDensity()* 0.15 +  // 信息密度(非空比例)
    score.getFluency()           * 0.15 +  // 语言流畅度
    score.getMedicalAccuracy()   * 0.25 +  // 领域准确性
    score.getTimeliness()        * 0.10 +  // 时效性
    score.getAuthority()         * 0.10    // 来源权威性
);

阈值设 0.6 是经验值,太低过滤不住噪声,太高会把”短但关键”的内容误杀。

小结

RAG 的检索质量,七分在入库前,三分在检索时。把预处理 Pipeline 搭好,比换十个向量库都管用。上面这套我是在医疗文档上打磨出来的,但编码修复、页眉页脚、三层去重、质量分这几个坑,在任何领域的 RAG 里都通用。

奇妙感 本文采用署名-非商业性使用-相同方式共享协议,转载请注明出处与作者。

目录