检索增强后模型反而更敢胡说八道了
上个月帮一个项目做内部知识库的问答系统,用的是 LangChain 搭的 RAG 流程:用户提问 → 向量检索 → 拼接 prompt → 大模型生成回答。看起来是一个标准范式,但上线后出了个问题——模型回答的"自信程度"明显超过了它实际掌握的信息。
更诡异的是,同样的问题,不带检索直接问模型,它反而会说不确定、需要更多信息;加了检索之后,它不仅给出了答案,还附上了详细的推理过程,但答案里的细节是错的。
这就是 RAG 系统里一个经常被忽略的坑:检索增强之后,幻觉不仅没减少,反而变得更难被发现了。
检索增强到底增强了什么

先说 RAG 的基本流程。LangChain 里的标准做法是用 RetrievalQA 或者组合 VectorStoreRetriever + StuffDocumentsChain。核心思路是把用户的 query 转成向量,在向量数据库里找 Top-K 相似的文档片段,然后把这些片段拼进 prompt 里让模型参考。
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.llms import OpenAI
from langchain.chains import RetrievalQA
embeddings = OpenAIEmbeddings()
vectorstore = Chroma(persist_directory="./db", embedding_function=embeddings)
qa = RetrievalQA.from_chain_type(
llm=OpenAI(),
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 4})
)
result = qa.run("我们的退款政策是什么?")
这个流程看起来没有问题。但问题出在三个地方:检索质量、prompt 构造方式、以及模型本身的特性。
第一个问题是检索回来的内容不一定相关。向量检索依赖 embedding 的语义相似度,但 embedding 模型对某些关键词的区分能力很弱。比如用户问"退款政策",检索回来的可能是"售后服务流程"或者"客户投诉处理",这些文档里确实提到了退款两个字,但具体内容并不准确对应。
第二个问题是 prompt 的构造方式。LangChain 默认的 stuff 模式会把所有检索到的文档直接拼接进 prompt,格式通常是:
Context information is below.
---------------------
{page_content}
---------------------
Given this context, answer the question.
Question: {question}
Answer:
这种构造方式有一个隐性的信号:我把这些文档放在这里了,说明它们应该有用。模型接收到这个信号后,会倾向于在这些文档里"找答案",哪怕文档本身并不完整或者不完全相关。
第三个问题,也是最重要的,是大模型自身的特性。现在的 LLM 经过大量指令微调,对"给定上下文回答"这类指令有很强的服从倾向。它被训练成在 provided context 的基础上生成回答,而不是自己去判断 context 是否充分。这意味着,当检索结果质量一般时,模型不会像纯生成那样坦诚地说"我不知道",而是会基于上下文"合理推断"——而这个推断,往往是幻觉。
为什么检索后幻觉反而更严重
这里需要区分两种幻觉:一种是模型纯靠训练数据生成的错误内容,另一种是模型在检索内容基础上"合理编造"的错误内容。
纯生成时的幻觉,模型其实有一定概率识别出自己的不确定性。尤其是当问题超出它的知识范围时,GPT-4 或 Claude 这类模型会倾向于说"抱歉,我没有相关信息"。这是一种相对安全的幻觉——至少不会被误认为是事实。
但加了检索之后,情况变了。检索结果给模型提供了一个"有据可查"的假象。模型看到了文档片段,认为信息已经提供了,于是它开始基于这些片段进行推理和补充。问题在于,文档片段可能只是部分相关,或者缺少关键限定条件,模型在填补这些空白时会引入自己的"常识"——而这些常识在特定业务场景下很可能是错的。
举个实际的例子。某项目的知识库里有这样一段关于接口限流的文档:
核心接口 QPS 限制为 100,超过后返回 429 错误。熔断阈值默认 50%,超过阈值时熔断器打开 30 秒。
这段文档是对的,但它说的是核心接口的通用规则。用户的实际问题是:"订单查询接口的限流策略是什么?"向量检索把这段文档召回了,因为语义相似度高。但实际项目里,订单查询接口有独立的限流配置,QPS 上限是 50,熔断阈值是 30%。
模型拿到这段通用文档后,没有质疑为什么问的是"订单查询"但返回的是"核心接口"的规则,而是直接给出了基于这段文档的答案:"QPS 限制为 100,熔断阈值 50%"。这就是一个典型的检索增强幻觉——答案看起来合理、有依据,但事实上是错的。
更麻烦的是,这样的错误回答比直接说"我不知道"更难发现。因为它有逻辑、有细节、有推理过程,审核的人容易基于"内容看起来很完整"就放过了。
设计上的取舍
理解了问题的成因,再看 RAG 系统的设计,就会发现几个明显的取舍点。
检索数量与噪声的平衡。 很多教程建议 k=4 或 k=10,但检索越多并不等于质量越好。多余的文档片段会带来更多噪声,模型有更多材料可以"合理编造"。实践中发现,对于准确性要求高的场景,k=2 反而比 k=4 效果更好——召回率低了,但准确率提高了。
prompt 里要不要强制模型声明不确定。 一个有效的做法是在 prompt 里明确要求:如果检索内容不足以回答问题,直接说"根据现有资料无法回答",不要自行推断。这相当于给模型一个"退出机制"。
prompt_template = """Use the following pieces of context to answer the question at the end.
If you don't know the answer, say "I don't have enough information to answer this based on the provided context."
Do not make up answers or infer information not present in the context.
Context:
{context}
Question: {question}
Answer:"""
这个改动成本很低,但能显著减少检索增强幻觉——代价是回答率会降低,会有更多"我不知道"的情况。这是一个明确的取舍:宁可多报错,不要多误导。
chunk 策略的影响。 文档切分的方式直接决定检索质量。NaiveTextSplitter 按字符数切分很容易把一个完整的概念拆到两个 chunk 里,导致检索回来的片段信息不完整。实践中用 RecursiveCharacterTextSplitter 并按语义段落切分,效果会好很多。对于结构化文档(如 API 文档、配置手册),按代码块或配置项切分比按字符数切分更有效。
源文档的可追溯性。 一个好的 RAG 系统应该在回答里附带引用来源,让审核者能回溯到原始文档。LangChain 的 RetrievalQA 默认不返回 source,需要自定义 chain 来追加这部分信息。否则即使发现答案有问题,也很难定位是哪个检索片段导致了错误。
排查方向
如果你的 RAG 系统也出现了"检索后幻觉更严重"的问题,可以按以下顺序排查:
-
确认检索质量。 打印每次检索返回的文档片段,人工评估它们与问题的相关性。很多时候问题出在检索本身,而不是模型。可以用简单的关键词匹配作为 baseline,对比向量检索的效果。
-
检查 prompt 构造。 确认 prompt 里有没有给模型"必须基于上下文回答"的强暗示。如果有,尝试加入不确定性声明的要求。
-
测试纯生成 vs 检索增强的一致性。 对同一批问题,分别跑纯生成模式和检索增强模式,对比回答的差异。如果发现检索增强后的回答明显更"自信"但内容偏离更大,说明问题出在模型对上下文的过度利用。
-
调整检索参数。 降低 k 值、提高相似度阈值、尝试不同的 embedding 模型。不同的 embedding 模型对特定领域的语义区分能力差异很大,换一个更适合业务场景的 embedding 模型有时能从根本上改善检索质量。
-
增加答案校验环节。 对于准确性要求高的场景,可以在模型回答后加一个校验步骤——比如用另一个 prompt 让模型自我检查答案是否真的能从上下文推导出来。这会增加延迟和成本,但能显著提升可靠性。
写在最后
RAG 的核心价值确实是"让模型基于事实回答",但这个价值成立的前提是检索结果的质量足够好、prompt 的设计足够克制。现实中,检索结果很少是完美相关的,而模型又有很强的服从倾向,这两个因素叠加就会产生"检索增强幻觉"——模型不是在胡说八道,而是在"合理地胡说八道"。
处理这个问题的核心思路不是追求更完美的检索,而是接受检索结果的不确定性,通过 prompt 设计给模型明确的边界:不知道就说不知道,不要自己补全。这个取舍在准确性要求高的场景里是必须的——一个会认错的答案,远好于一个看起来很对的答案。

浙公网安备 33010602011771号