Skip to content

14.1.5 召回到了不等于模型就能答好,为什么? ​

先给结论:召回只是“找到候选材料”,不是“形成可回答上下文”。答案质量还取决于证据组织、上下文边界和生成约束。

这是 RAG 里一个非常容易被忽略、但实际影响极大的问题。
很多人会这样理解链路:

  1. 把文档放进知识库
  2. 把相关内容召回出来
  3. 让模型基于这些内容回答

听起来完全合理。但真正的问题在第 2 步和第 3 步之间:
召回出来的东西,未必已经是模型可以稳定利用的“回答证据”。

召回解决的是“有没有”,不是“怎么用” ​

检索阶段做的事,本质上是从大量内容里找候选。
而模型生成答案时,需要的不是一堆候选,而是一组边界清楚、关系清楚、足以支持结论的证据。

所以即使“正确片段已经被召回”,仍然可能答不好。常见原因有:

  • 多段证据彼此冲突
  • 证据顺序混乱,主次不清
  • 关键信息埋在长文本里,不够突出
  • 片段虽然相关,但组合起来仍然缺关键条件

哪些场景里最容易出现这种问题 ​

1. 召回命中了正确段落,但上下文顺序混乱 ​

模型看到的材料是对的,但没有被组织成一个容易理解的结构。
结果就是它抓住了一部分信息,却漏掉了真正关键的限制条件。

2. 召回同时带回了多份互相冲突的证据 ​

例如旧版文档和新版文档同时进入上下文。
这时模型不是天然知道该信谁,它可能会混合两者,生成一个表面流畅、实则错误的答案。

3. 召回内容过长,注意力被稀释 ​

候选材料很多,但真正与问题直接相关的只占一小部分。
模型可能读到了很多背景说明,却没有抓住你最需要的那一段。

4. 多段证据需要被拼接成完整链条 ​

有些问题本来就不是一段能答完的。
例如一段说明功能定义,另一段说明权限限制,再另一段说明异常情况。
如果这些内容只是松散地放在一起,模型并不一定会自动拼成稳定的答案链。

所以,真正要做的是“证据组织” ​

把召回升级成“可回答上下文”,通常要做四件事:

1. 去重 ​

避免同类内容反复出现,浪费上下文预算。

2. 合并 ​

把同一主题、同一对象、同一流程下的证据尽量放在一起。

3. 标记 ​

为每段证据保留来源、时间、版本、权限范围等信息,方便模型区分。

4. 约束 ​

提示模型只能基于证据回答,证据不足时直接说明不足,而不是硬猜。

一个简单示意 ​

python
context = []
for doc in reranked_docs[:4]:
    context.append(
        f"source={doc.metadata.get('source')}\n"
        f"updated_at={doc.metadata.get('updated_at')}\n"
        f"{doc.page_content}"
    )

prompt = f"""
请仅基于以下证据回答。
如果证据不足,请明确说明缺少什么。

{chr(10).join(context)}
"""

这段代码最重要的不是写法,而是思路:
不要把检索结果原样裸传给模型,而要把它整理成带来源、带边界、带约束的可回答上下文。

怎么判断问题在“召回”还是在“生成” ​

一个非常实用的方法是:

  • 如果人工看检索结果都觉得证据不够,那问题在召回
  • 如果人工看检索结果已经足够,但模型仍然漏条件、乱归纳,那问题通常在上下文组织或生成约束

这一步判断非常关键。
很多所谓“模型答不好”的问题,实际上是系统把未经整理的候选材料直接丢给了模型。

一句话总结 ​

召回到了,只代表“材料到位”;答得好,取决于你有没有把这些材料组织成真正可回答的证据上下文。