Appearance
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 前,把这些候选整理成更适合模型稳定使用的证据组合。