Skip to content

8.1.1 检索结果为什么不能原样全塞给模型? ​

先给结论:因为检索结果是“候选”,不是“已经整理好的最终证据”。它们天然会带有重复、噪声、边缘相关、上下文残缺和结构混乱等问题。原样全塞给模型,通常不会让回答更稳,只会让模型更难抓住真正关键的信息。

很多系统最早都会写成这样:

python
results = retrieve(query, top_k=10)
prompt = build_prompt(query, results)
answer = llm(prompt)

这当然能跑通,但真正的问题在于:

  • results 只是“检索阶段找回来的候选”

候选并不等于:

  • 没有重复
  • 没有冲突
  • 没有旧版本
  • 每条都完整
  • 每条都值得进入最终 Prompt

一、检索结果里天然会混进噪声 ​

检索的目标通常是:

  • 尽量别漏掉可能相关的内容

这意味着它天生会保留一部分“像相关,但不一定最该用”的内容。比如:

  • 背景介绍
  • 模板块
  • 表面上同主题但回答力度不够的段落
  • 旧版本的相似说明

如果把这些结果原样全塞进去,模型首先遇到的问题就不是“没资料”,而是:

  • 资料太杂,重点不清

二、上下文预算会被快速浪费 ​

即使模型上下文窗口很大,可用预算也不是无限的。

真正应该关心的不是:

  • 能不能塞得下

而是:

  • 塞进去的每一段,是否都值得占一个位置

如果前几条候选本身高度重复,或者只是同一事实的不同切片,原样拼接只会导致:

  • 重复内容占掉预算
  • 真正新的证据进不来
  • 模型把大量注意力花在低增量材料上

三、原样拼接会把“检索的粗结果”误当成“生成的精输入” ​

这是最容易被忽略的一点。

检索阶段得到的是:

  • 可能相关的候选集合

而生成阶段真正需要的是:

  • 足够直接、足够完整、彼此不冲突、顺序合理的证据组合

这两者中间天然隔着一层“上下文构造”。

如果跳过这层,系统就会把:

  • 为召回设计的粗候选

直接当成:

  • 为生成设计的最终材料

这通常是不成立的。

四、很多块单独看是对的,但放在一起会误导模型 ​

这是另一个很现实的问题。

单条候选也许都没有明显错误,但放到一起后可能出现:

  • 新旧版本并存
  • 一般规则和例外规则并存
  • 定义和非适用范围混在一起
  • 多段相似内容反复强化某个次要观点

这时模型的问题就不再只是“会不会理解每一条”,而是:

  • 它会优先相信哪一条
  • 它会怎样综合这些彼此不完全一致的证据

所以原样全塞,本质上是在把排序、筛选和冲突处理的工作,全部丢给模型临场解决。

这很不稳。

五、很多候选本身并不完整 ​

检索出来的往往是 chunk,而不是天然完整的知识单元。

所以单条候选很可能存在这些情况:

  • 命中的句子在本块里,但解释在下一块
  • 步骤在本块里,但前置条件在上一块
  • 表格命中了,但表头或注释在别处

如果把这些不完整块原样全部塞进去,模型不一定能自动恢复正确结构。

这也是为什么上下文构造里常常还需要:

  • 相邻块扩展
  • 小范围合并
  • 文档级恢复

一个更稳的最小思路 ​

python
candidates = retrieve(query, top_k=20)
candidates = rerank(query, candidates)
candidates = deduplicate(candidates)
candidates = expand_or_merge_if_needed(candidates)
final_context = select_for_prompt(candidates, max_tokens=2500)
answer = llm(build_prompt(query, final_context))

这段代码想说明的是:

  • 检索结果只是起点,不是终点
  • 候选进入模型前,需要经过再组织

一个常见误区 ​

很多人会把“回答不完整”理解成:

  • 给模型的材料还是不够多

但真实情况很常见的是:

  • 给得已经很多了,只是里面重复太多、顺序太乱、关键证据不够突出

这时继续把更多候选原样塞进去,通常只会让问题更严重。

一句话总结 ​

检索结果不能原样全塞给模型,因为它们只是候选,不是最终证据。候选天然会带有噪声、重复、冲突和结构残缺。上下文构造的意义,就是在进入 Prompt 前,把这些候选整理成更适合模型稳定使用的证据组合。