Iris 搜索结果和原文对不上,先从切片和召回两个环节找原因

文章导读
提问后 Iris 返回的内容和原文对不上,通常不是模型本身答错,而是检索链路里某一环把正确内容弄丢了。先别急着改提示词或换模型,把链路拆成三段:切片粒度、召回命中、排序位置。用一篇短文档加一句答案就在原文里的问题做成最小样例,逐个环节观察,就能判断是切分切断了答案、压根没召回,还是召回后被排序挤出了可用范围。
📋 目录
  1. A 用单篇短文档和一句可直接回答的问题构造最小样例
  2. B 把返回结果按切片粒度还原回原文位置
  3. C 对比召回条数与答案所在切片是否在返回集合里
  4. D 检查排序或重排后答案切片的位置变化
  5. E 用同一组样例复测并记录前后差异
A A

提问后 Iris 返回的内容和原文对不上,通常不是模型本身答错,而是检索链路里某一环把正确内容弄丢了。先别急着改提示词或换模型,把链路拆成三段:切片粒度、召回命中、排序位置。用一篇短文档加一句答案就在原文里的问题做成最小样例,逐个环节观察,就能判断是切分切断了答案、压根没召回,还是召回后被排序挤出了可用范围。

先用单篇短文档、单句问题跑一次检索,把返回结果和原文逐句对齐。命中切片被切断,就看切片长度与重叠;答案所在切片不在返回集合里,就看召回条数;在集合里却排到保留范围外,就看重排与上下文拼装。三个环节不要同时改,一次只动一个参数,前后各记录一次。切片和召回的取舍要结合你环境的日志、配置与文档长度确认,不能只看最终答案像不像。

用单篇短文档和一句可直接回答的问题构造最小样例

多文档、多轮对话会掩盖真实原因:可能是相似文档互相干扰,也可能是上一轮提问改写了检索词。建议先把变量压到一个文档、一个问题。选一篇 300 到 600 字的短文档,问一句答案在文档里完整出现的问题,例如「更换电池前需要先做什么」,期望答案就是原文里的「先断开设备电源,再取下后盖」。调试点只保留这一篇文档参与检索。

拿到返回内容后,别只凭语气判断像不像,把差异落到位置:返回内容和原文各贴一行,逐句对齐,标出第一个不同点——是少了整句、少半句,还是换了措辞。少了整句多半是召回或排序问题;只少半句,往往是切片边界切断。建议用固定的记录模板,避免每次凭印象复述:

问题:更换电池前需要先做什么?
原文位置:第 2 段第 1 句
期望原文:先断开设备电源,再取下后盖。
返回内容:(原样粘贴)
首个不同点:返回内容从「先断开设备电源」开始,缺少后半句

把返回结果按切片粒度还原回原文位置

切片边界切断答案,是对不上原文里最容易确认的一种。先确认检索日志或调试返回里有没有命中切片原文和 chunk_id。有的话,把命中切片整段打印,和原文按句对照。下面是一段通用打印骨架,字段名以你环境实际返回为准:

hits = resp['hits']   # 字段名以你环境实际返回为准
for i, h in enumerate(hits):
    print(i, h.get('score'), h.get('chunk_id'))
    print(h.get('text', ''))

拿到切片文本后,按句号、问号、分号把原文切句,检查答案句是否完整落在同一个切片里。如果答案句被切成两半,或者答案句的限定条件(比如「先」「断电后」)落在前一个切片,那就是切片太长或太短、重叠为 0 导致的。调整时先只改切片长度,看命中切片是否变完整,再决定要不要加重叠。一个可替换的配置骨架如下:

chunk:
  size: 512
  overlap: 64
  # 字段名以你环境实际配置为准

overlap 让跨边界的句子有机会被完整包含,但调大 overlaps 会增加存储和检索量,需要结合文档长度和响应延迟确认,不建议一次拉到很大。

对比召回条数与答案所在切片是否在返回集合里

接下来要区分两件事:没召回,还是召回了但没被用上。前者问题在切分或检索方式,后者问题在排序或上下文拼装。做法是记录本次召回条数 top_k 和命中切片在返回列表中的序号,再看答案所在切片有没有出现在这批结果里。

把 top_k 临时调大(例如从 5 调到 20)后重跑同一个问题。如果答案切片出现了,说明之前是召回数量不够,切片粒度本身没问题;如果调大后仍然不出现,就要回头查切片是否切断答案、检索是否只做了向量匹配、问题里的关键词是否在改写时被替换掉。配置示例只作为对照起点:

Iris 搜索结果和原文对不上,先从切片和召回两个环节找原因
retrieval:
  top_k: 20          # 先临时调大做对照,再决定最终值
  # 字段名以你环境实际配置为准

需要注意的是,top_k 调大不等于答案更好。召回集合变大后,排序和上下文拼接的压力也变大,无关片段可能混进上下文。记录时把「召回条数」和「答案切片序号」分开写,不要只写最终答案。

检查排序或重排后答案切片的位置变化

如果答案切片在召回集合里,最终答案却没用上,重点看排序或重排。先保留排序前后的两份顺序:召回原始顺序,和 rerank 之后的顺序,逐条对照答案切片的位置序号是否发生变化。

可以先按这几步操作:打印 rerank 前后的切片 id 与分数;确认答案切片的序号有没有落到「保留给最终上下文」的数量之外;如果落到了外面,把重排保留数量调大,例如 top_n 从 5 调到 10,然后只改这一个参数重跑。参考配置:

rerank:
  enabled: true
  top_n: 10    # 只改这一个值做前后对照

还有一种情况是排序没问题,但拼上下文时按字符或 token 截断,排在前面的一大段无关切片把窗口占满,答案切片被截掉。这时要看拼装后的最终上下文,而不只是排序列表。调整保留数量要结合模型上下文窗口确认,窗口小的时候单纯放更多片段,反而更容易把答案挤出去。

用同一组样例复测并记录前后差异

改完一个参数,用同一篇文档、同一个问题复测,只对比这一个参数带来的变化。不要把切片长度、top_k 和重排保留数量一起改,否则无法判断是哪一步起了作用。复测时按下面几列记录,调整前后各填一行,不要凭记忆补数:

问题命中切片是否包含答案响应耗时本次调整
更换电池前需要先做什么记录 chunk_id 或序号是/否记录实际观测值调整前基线
更换电池前需要先做什么记录 chunk_id 或序号是/否记录实际观测值例如 top_k 5 改为 20

耗时只记录你自己环境的实测值,文档长度、切片数量和部署方式不同,差异会很大,不适合拿别人的数字当参照。如果复测后答案切片仍然不在命中列表里,说明前面某个环节的判断需要重做,而不是继续堆参数。可以把这组最小样例固定下来当作回归样例,之后每次调整检索配置都先跑一遍,避免改一处坏一处。