检索回来三条,每条都跟问题沾点边,但拼起来答不出正确答案。这时候换嵌入模型通常不是第一步要做的事——大多数「检索不相关」的根因在向量化之前的文档处理环节。

先分清是召不回,还是排不准

在动任何参数之前,做一次分层验证:

  1. 把含答案的原文片段直接拼进 prompt,看模型能否答对。答对,说明生成侧没问题,问题在检索侧。
  2. 用原文档里含答案的那句话本身作为 query 去检索,看目标块是否出现在 top-k。
  3. 对照三种结果分别归因:原句也召不回,是分块或向量化的问题;原句能召回、用户问法召不回,是查询与文档表述风格不一致的问题;都能召回但排不到前面,是排序问题,对应 top-k、阈值或 rerank。

这一步的价值在于把「检索不准」拆成互斥的几类,否则很容易在错误的层面反复调参。

用距离分布判断分块是否合理

对一批真实 query,记录每次返回的 top-k 相似度分数,观察分布形态,比单看某一条结果更有信息量:

  • 候选分数全部挤在一个很窄的区间里,彼此差异极小,说明向量区分度不足。最常见的原因不是模型不行,而是每个 chunk 里都混进了相同的模板文字(页眉页脚、导航、免责声明、重复的表头),这些东西把不同文档的向量拉到了一起。
  • 目标块的分数很高却排在后面,属于排序问题,处理方式与分块无关。
  • 分数整体偏低但相对排序是对的,可能是查询与文档的表述差异,先考虑查询改写而不是重切文档。

还可以做一次反向检索:拿每个 chunk 去检索 query 集合,看它命中哪些问题。如果某个 chunk 对所有 query 都排在中游,它很可能是一个语义混杂块——里面装了两三个主题,向量落在了这些主题的「平均位置」,谁都不像。

清洗和分块,收益通常大于调参数

优先级上,先把这些处理掉:模板化的页眉页脚、版权声明、重复表头、以及与正文无关的导航文本。它们不会单独造成错误,但会系统性地压缩向量之间的可分性。

分块层面有几个容易踩的点:

  • 固定长度切分是最容易做错的做法。 优先按结构切:标题层级、段落、列表项、代码块边界。硬切会把一个完整论述拦腰截断,前半句在 A 块、后半句在 B 块,两块都答不出问题。
  • 标题路径要跟着块走。做法有两种:把「一级标题 > 二级标题」拼成前缀后一起嵌入;或者作为结构化元数据字段,用于过滤和结果展示。只把标题留在文档结构里、不进入向量,正文块就会「不知道自己在讲什么主题」。
  • 小块匹配、大块投喂。 用小粒度块做向量相似度匹配,命中后把其所属的父块或相邻块送给模型。这样匹配精度和上下文完整性可以同时拿到,不必在 chunk 大小上做非此即彼的取舍。
  • overlap 不是越大越好。 它的作用是防止答案正好落在切分边界上,代价是重复内容增多、top-k 里挤进大段相同文字。常见起点是块长的 10%–20%,但这个区间只是探索起点,收益是否为正必须在自己的评估集上验证。
  • 一个 chunk 只讲一件事。 混合主题的块是最难被检索到的,因为它的向量没有明确指向。

标题和正文要不要分开嵌入

这在工程上是一个折中,不是标准答案。先明确要解决什么:如果正文块缺少标题信息,正文向量就丢失了主题线索;如果标题和正文长度差异很大,混在一起确实可能被标题主导或被正文稀释。

一个成本较低的做法是:把标题路径作为前缀拼进正文再嵌入,同时另存一份结构化元数据供过滤使用。这样只维护一份向量,检索链路简单。只有当评估显示前缀拼接会明显改变正文语义时,才值得考虑双向量或按字段分别检索再合并分数——那会带来分数归一化和合并策略的额外复杂度,需要相应验证收益。

元数据过滤:把硬条件从语义里拿出来

文档类型、时间范围、权限、产品线、租户这类条件,应该用过滤表达式表达,而不是指望相似度去理解。「最近一次修订的政策」这种限定交给向量去猜,失败率很高。前提是入库时就把这些字段抽好,事后补抽往往要全量重建。

调整顺序

  1. 先修清洗和分块,用同一套评估集看 recall@k 的变化。
  2. 再调 top-k 和相似度阈值。阈值不要拍脑袋,用前面的正负样本分布来找分界点。
  3. 再考虑查询改写,把用户的口语化问法映射到文档的表述风格。
  4. 最后才考虑换嵌入模型或引入 rerank。

顺序反了,就会出现「换了模型、指标没动」的情况。

没有评估集就不要调参

最小可行做法:挑 30–100 条真实或构造的问题,为每条标注对应的正确原文位置,固定下来。每次只改一个变量,记录 recall@k 和 MRR。

有一个细节容易被忽略:分块方案一变,chunk 编号就全变了。评估集应该按原文定位(文档 + 字符区间或段落锚点)标注,而不是按 chunk id 标注,否则每次重新切分都要重标一遍,评估成本会高到没人愿意做。

还需要注意的边界

上面给出的参数区间都是探索起点,不是推荐值。语料长度分布、语言、专业术语密度都会改变最优区间,唯一可靠的判断依据是自己在评估集上的召回结果。

另外,多语言内容、代码、表格和公式,通用的文本切分与嵌入方式未必适用,需要单独评估,不能沿用正文的结论。

最后是一个一致性风险:更换嵌入模型会改变向量维度和语义空间,历史向量必须全量重建,不能只对新文档重新向量化。新旧向量混在同一个索引里,检索结果会变得不可预测,而且很难从单条 bad case 上看出原因。

从工程角度看,处理「检索不相关」的合理路径是先定位问题层,再动对应的旋钮:分块决定向量表达什么,嵌入决定怎么表达,检索参数决定取哪些。把这三层的职责分清,调试过程才可复现。