RAG 为什么效果不稳定:从切片、召回到重排,拆解知识库问答最常见的 8 个问题
做过一轮 RAG 的团队,基本都会碰到同一个阶段性困惑:
- Demo 看起来不错,正式一用就开始飘;
- 同样一类问题,这次答得很好,下次却漏重点;
- 文档明明在库里,系统却像没看到;
- 明明已经接了向量检索,回答还是不够稳;
- 用户反馈不是“完全不能用”,而是“有时候很好,有时候不敢信”。
这类问题最麻烦的地方在于:
它不是彻底坏掉,而是间歇性不稳定。
而一套“偶尔很好、偶尔翻车”的系统,往往比一套一直普通的系统更难调。因为你很难第一时间判断,问题到底出在:
- 文档切片;
- Embedding 模型;
- 召回策略;
- 重排逻辑;
- Prompt 设计;
- 上下文拼接;
- 还是知识源本身。
所以这篇文章不讲空泛概念,也不做趋势评论,只解决一个非常具体的问题:
RAG 为什么效果不稳定?
我会从工程视角,把知识库问答里最常见的 8 类问题拆开说,并给出对应的排查顺序、可落地的配置思路和一个最小可用的调试框架。
如果你正在做企业知识库、文档问答、客服知识助手,或者已经接了向量库但效果总是不稳,这篇文章应该能帮你少走很多弯路。
先说结论:RAG 的“不稳定”,大多数不是模型问题,而是链路问题
很多人一看到效果波动,第一反应是:
- 模型不够强;
- 上下文不够长;
- Embedding 模型不够新;
- 换个更贵的模型可能就好了。
这些当然可能有影响,但在大多数实际项目里,RAG 不稳定的根因通常更靠前,主要集中在这几层:
- 知识源本身不干净
- 切片策略不合理
- 召回质量不稳定
- 重排缺失或重排不准
- 上下文拼接方式有问题
- Prompt 让模型“发挥过头”
- 评估方式太主观
- 系统根本没按问题类型分流
也就是说,RAG 不是一个“接上向量库就会稳定”的系统。
它本质上是一条多环节链路,只要上游某一环处理不好,下游模型就会“认真地基于错误上下文回答”。
这也是为什么很多团队会产生错觉:
模型看起来很聪明,但系统整体并不可靠。
先建立一个最小排查框架:别一上来就换模型
在深入 8 个问题之前,先给一个很实用的建议:
如果你的 RAG 表现不稳,不要第一步就换模型。
先把问题拆成这四步看:
用户问题
↓
召回了什么内容
↓
最终送进模型的上下文是什么
↓
模型是怎么基于这些上下文回答的
你真正要看的,不是“最终答案不对”,而是:
- 没召回到正确文档?
- 召回到了,但排序不对?
- 排序对了,但切片有缺口?
- 上下文给对了,但 Prompt 让模型过度总结?
建议先给自己的 RAG 链路补一层最基本的可观察日志。
例如,至少记录:
{
"query": "跨月报销是否需要额外审批?",
"retrieved_chunks": [
{
"doc_id": "expense_policy_v3",
"chunk_id": "expense_policy_v3#12",
"score": 0.83,
"text_preview": "报销提交时间一般应在费用发生后的当月内完成……"
},
{
"doc_id": "expense_policy_v2",
"chunk_id": "expense_policy_v2#09",
"score": 0.79,
"text_preview": "超过当月未报销的情况需说明原因……"
}
],
"reranked_chunks": [
"expense_policy_v2#09",
"expense_policy_v3#12"
],
"final_context_ids": [
"expense_policy_v2#09",
"expense_policy_v3#12"
],
"answer": "跨月报销需要说明原因,是否额外审批需以制度为准……"
}
只要你没有这类日志,后面很多优化基本都只能靠猜。
问题一:知识源本身混乱,RAG 只是把混乱放大了
这是最常见,也最容易被低估的问题。
很多团队一上来就开始做:
- 文档切片;
- 向量化;
- 检索;
- Prompt 优化;
但真正的问题可能根本不在技术层,而在知识层。
常见表现
- 同一问题有多个版本答案;
- 系统有时引用旧制度,有时引用新制度;
- 召回结果看起来都“相关”,但互相冲突;
- 模型会试图把冲突资料综合成一个模糊回答。
典型根因
你的知识源里可能同时存在:
- 正式文档;
- 讨论稿;
- 临时通知;
- 历史版本;
- 非权威总结;
- 复制粘贴的二次整理稿。
而系统没有给它们做优先级区分。
该怎么处理
先不要着急调检索,先做知识治理:
docs/
├── official/
│ ├── expense_policy_v3.md
│ └── travel_policy_v2.md
├── archived/
│ ├── expense_policy_v2.md
│ └── old_notice_2024.md
├── draft/
│ └── expense_policy_discussion.md
└── faq/
└── reimbursement_faq.md
给文档打上最基本的元数据:
{
"doc_id": "expense_policy_v3",
"title": "费用报销制度 V3",
"status": "official",
"version": "3.0",
"updated_at": "2026-03-01",
"priority": 100
}
然后在召回或重排阶段把 status、priority 带进去。
不然你再好的 Embedding,也只能更高效地检索混乱。
问题二:切片策略不合理,导致“找到了,但没找到完整意思”
很多 RAG 效果不稳定,根本问题不是没召回,而是切片切坏了。
常见表现
- 系统明明召回了相关内容,但回答漏条件;
- 结论有了,限制条件没了;
- 示例被召回了,规则正文没被召回;
- 一段看起来相关,但实际上关键信息在前后片段里。
为什么会这样
因为切片不是纯技术参数,它决定了“知识以什么粒度被理解”。
比如一条规则可能是这样写的:
### 跨月报销规则
员工应在费用发生当月提交报销。
如遇出差、节假日或系统故障等特殊情况,可跨月提交。
跨月提交需补充说明,超过两个月的情形需部门负责人审批。
如果你按固定长度粗暴切片,可能变成:
chunk A:
员工应在费用发生当月提交报销。如遇出差、节假日或系统故障等特殊情况,可跨月提交。
chunk B:
跨月提交需补充说明,超过两个月的情形需部门负责人审批。
于是用户问“跨月报销是否需要审批”,模型如果只拿到 chunk A,就容易答偏。
更合理的做法
优先做语义结构切片,而不是只按字数切。
比如:
- 按标题切;
- 按段落切;
- 按规则块切;
- 再做适度 overlap。
示例伪代码:
def split_by_sections(doc):
sections = parse_markdown_sections(doc)
chunks = []
for section in sections:
if len(section.text) < 800:
chunks.append(section.text)
else:
chunks.extend(sliding_window(section.text, size=600, overlap=120))
return chunks
一个经验原则
- 规则类文档:按“条款块”切
- FAQ 类文档:按“问题-回答对”切
- 技术文档:按“功能说明 / 步骤 / 参数块”切
- 太长的段落再做二次滑窗
不要指望一个统一切片规则适合所有文档类型。
问题三:只做向量召回,不做混合检索,导致某些问题天然召不准
纯向量检索在很多场景里好用,但不是所有场景都稳。
常见表现
- 包含精确术语、版本号、接口名的问题召回不准;
- 包含短关键词的问题表现很差;
- 语义很像,但业务上不对的内容被召回来;
- 用户明明问的是特定字段,系统却给了相似背景说明。
为什么会这样
向量检索擅长“语义相似”,不擅长所有“精确匹配”需求。
比如这些问题:
- “报销单字段
expense_type有哪些枚举值?” - “接口
/v2/auth/token和/v3/auth/token的区别是什么?” - “错误码 E4032 是什么原因?”
这些问题里,关键词本身很关键。
只靠向量检索,容易把“语义类似但关键字不对”的内容召回来。
更稳的做法:混合检索
把关键词检索和向量检索结合起来。
一个简单思路:
def hybrid_retrieve(query):
bm25_results = bm25.search(query, top_k=10)
vector_results = vectordb.search(query, top_k=10)
return reciprocal_rank_fusion(bm25_results, vector_results)
RRF 示例:
def reciprocal_rank_fusion(*ranked_lists, k=60):
scores = {}
for ranked in ranked_lists:
for rank, item in enumerate(ranked):
doc_id = item["id"]
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
什么时候一定要考虑混合检索
如果你的知识库里经常出现:
- 版本号
- API 路径
- 错误码
- 枚举字段
- 产品型号
- 术语缩写
那纯向量检索很容易不稳定,混合检索通常更靠谱。
问题四:召回对了,但没重排,导致“有用内容被埋了”
很多团队做 RAG 到召回这一步就结束了。
但实际上,召回不是终点,重排才是把结果变稳的关键一层。
常见表现
- 正确文档明明在 top10,但没进最终上下文;
- 相似但不关键的内容排在前面;
- 真正回答问题的片段被背景介绍挤掉了;
- 最终拼给模型的上下文里,最重要的内容反而不突出。
为什么会这样
初始召回追求的是“别漏掉”,不是“最懂你现在这个问题”。
而生成模型的上下文窗口又有限,你最终只能塞进去一部分内容。
如果不做重排,系统就会经常出现这种情况:
- 召回结果“看起来还行”
- 最终答案却“不太对劲”
更稳的做法
引入一层 reranker。
伪代码:
retrieved = hybrid_retrieve(query)
reranked = reranker.rank(query=query, documents=[x["text"] for x in retrieved])
final_chunks = reranked[:5]
重排时要看什么
比起只看向量分数,重排更关注:
- 这个片段是不是直接回答了问题;
- 它是不是主规则,不是背景噪声;
- 它是不是当前版本的有效内容;
- 它的“回答性”强不强。
实战建议
如果你的 RAG 还没做重排,而你已经在折腾 Prompt、模型和参数,大概率顺序反了。
先把“给模型什么材料”这件事做好,再谈“让模型怎么说”。
问题五:上下文拼接太粗暴,把模型喂“撑”了
这也是非常常见的问题。
很多系统的做法是:
召回 top-k,全部拼进去,交给模型。
结果往往是:
- 上下文太长,噪声太多;
- 多个片段互相冲突;
- 模型抓不住重点;
- 越想“多给一点保险”,结果越不稳。
常见错误拼接方式
以下是检索到的内容:
[chunk1 全文]
[chunk2 全文]
[chunk3 全文]
[chunk4 全文]
[chunk5 全文]
请回答用户问题。
这种方式的问题是:
系统没有告诉模型“哪些更重要、哪些是补充、哪些可能冲突”。
更合理的做法
把上下文按角色组织,而不是简单堆文本。
例如:
你将根据下面资料回答问题。
优先依据“官方正式资料”。
如果资料之间有冲突,优先采用版本更新更近、优先级更高的内容。
如果资料不足以支持明确结论,请直接说明“资料不足”。
[资料1 - 官方制度 V3 - 优先级100]
...
[资料2 - FAQ 补充说明 - 优先级80]
...
[资料3 - 历史版本,仅供参考 - 优先级20]
...
一个很实用的原则
不要一味追求“多塞一点”。
很多时候:
- top3 精准上下文 > top10 杂乱上下文
如果你发现系统越来越不稳,先试着减少上下文数量,而不是继续加。
问题六:Prompt 设计在鼓励模型“补全常识”,而不是“忠于资料”
这是很多团队容易忽视的一层。
常见表现
- 文档没明确说,模型却给了肯定结论;
- 检索结果不完整,模型开始自己脑补;
- 系统回答看起来很通顺,但依据不够扎实。
常见错误 Prompt
请根据以下资料和你的知识,尽可能完整地回答用户问题。
问题就出在“和你的知识”“尽可能完整”这类表达上。
它会鼓励模型在资料不足时补全常识。
更稳的 Prompt 写法
你是知识库问答助手。
请只根据提供的资料回答问题,不要使用资料之外的常识补充结论。
如果资料无法支持明确答案,请直接回答“根据当前资料无法确认”。
如果存在条件、例外或审批要求,必须优先写出。
回答后附上引用的资料编号。
推荐的输出格式
结论:
...
依据:
- 资料1
- 资料2
待确认:
...
结构化输出能显著降低“说得很像,但逻辑不清”的问题。
问题七:没有做问题分流,把所有问题都硬塞进同一条 RAG 链路
很多团队把所有知识问题都统一走一套 RAG 管道。
这通常会带来稳定性问题。
为什么
因为不同问题类型,本来就应该有不同处理方式。
比如:
- 高频标准问答 → FAQ
- 精确术语查询 → 搜索 / 关键词检索
- 开放式文档问答 → RAG
- 状态类问题 → API / 数据库查询
如果不分流,系统就会出现两种典型症状:
- 本该用固定答案的题,被模型自由生成了;
- 本该做精确查询的题,被语义检索绕弯了。
一个最小路由思路
def route_query(query):
if is_structured_query(query):
return "db"
if is_faq_query(query):
return "faq"
if has_strong_keyword_pattern(query):
return "search"
return "rag"
哪怕这个路由一开始不够聪明,也比“所有问题都统一扔进 RAG”更稳。
问题八:没有自己的评测集,优化全靠感觉
这是很多 RAG 项目到后期最痛苦的点。
团队常常这样讨论:
- “我感觉这次好一点了”
- “这类问题好像稳定了”
- “但另一些又变差了”
问题在于,没有评测集,你就无法稳定判断优化到底有没有效果。
最低限度也该有一个样本集
例如建一个最小 eval.jsonl:
{"query":"跨月报销是否需要审批?","expected_docs":["expense_policy_v3#12"],"expected_keywords":["跨月","审批","两个月"]}
{"query":"错误码 E4032 是什么意思?","expected_docs":["api_error_code#44"],"expected_keywords":["E4032","权限","token"]}
{"query":"/v2/auth/token 和 /v3/auth/token 有什么区别?","expected_docs":["auth_api_v2#3","auth_api_v3#2"],"expected_keywords":["v2","v3","token"]}
评测至少看三层
- 召回是否命中
- 最终上下文是否包含关键片段
- 回答是否覆盖关键条件
一个简单的调试脚本可以先这么写:
def evaluate_case(case):
retrieved = hybrid_retrieve(case["query"])
retrieved_ids = [x["id"] for x in retrieved[:10]]
hit = any(doc in retrieved_ids for doc in case["expected_docs"])
return {
"query": case["query"],
"hit_top10": hit,
"retrieved_ids": retrieved_ids
}
没有评测集,所有优化都容易变成“玄学调参”。
一个更稳的 RAG 最小工程结构,可以长什么样
如果你现在要把一个 RAG 系统做得更可维护,我建议最少把结构拆成下面这样:
rag-system/
├── data/
│ ├── raw/
│ ├── cleaned/
│ └── metadata/
├── ingest/
│ ├── parser.py
│ ├── cleaner.py
│ ├── splitter.py
│ └── embed.py
├── retrieval/
│ ├── bm25.py
│ ├── vector.py
│ ├── fusion.py
│ └── reranker.py
├── prompt/
│ └── qa_prompt.txt
├── eval/
│ ├── eval.jsonl
│ └── run_eval.py
└── app/
└── api.py
你不一定非要照这个目录来,但核心思路要有:
- 数据清洗独立
- 切片独立
- 检索独立
- 重排独立
- Prompt 独立
- 评测独立
只有这样,系统出问题时你才知道该改哪一层。
一个排查顺序建议:别再盲目按模型价格往上堆
如果你今天就要开始排查 RAG 效果不稳定的问题,我建议按这个顺序来:
第一步:先查知识源
- 有没有旧版本混入
- 有没有冲突资料
- 有没有权威优先级
第二步:再查切片
- 规则有没有被切散
- 标题和正文有没有分开
- 文本块是不是太碎或太大
第三步:再查召回
- 纯向量是否适合你的问题类型
- 是否需要混合检索
- top-k 是否太小或太大
第四步:再查重排
- 正确结果是否被埋在后面
- 最终送模型的片段是不是最该看的
第五步:再查 Prompt
- 有没有鼓励模型脑补
- 有没有要求依据优先
- 有没有让模型承认“不确定”
第六步:最后再查模型
- 模型是否能更稳地压上下文
- 模型是否擅长长文本整合
- 是否真的有必要升级
这个顺序的核心逻辑很简单:
先修“给模型什么”,再修“模型怎么说”。
结语:RAG 不稳定,不是因为它不行,而是因为它不是一个单点功能
很多人第一次做 RAG,会把它想成一个功能:
- 接文档
- 接向量库
- 接模型
- 就能问答
但真正做过之后就会发现,RAG 更像一条系统链路。
它不是只有一个“模型质量”变量,而是多个环节叠加后的结果。
所以,当你感觉系统“时好时坏”时,最该做的不是立刻换更贵的模型,而是把整条链路拆开看:
- 文档干净吗?
- 切片合理吗?
- 召回稳吗?
- 重排做了吗?
- 上下文拼得好吗?
- Prompt 有没有鼓励乱发挥?
- 评测是否可重复?
把这些问题理顺之后,你会发现很多所谓的“不稳定”,其实并不神秘。
它只是系统在用一种不太友好的方式提醒你:
RAG 的难点,从来不只是生成,而是检索链路的工程化。
关键词建议
- RAG
- 知识库问答
- 向量检索
- 重排模型
- 检索增强生成
- AI 知识库
摘要建议
RAG 项目最常见的问题不是完全不能用,而是“时好时坏”。本文从工程视角拆解了 RAG 效果不稳定的 8 个常见原因,覆盖知识源治理、切片策略、混合检索、重排、上下文拼接、Prompt 设计、问题分流和评测方法,并给出可落地的代码片段与排查顺序。对于正在做知识库问答、文档助手或企业 AI 应用的团队,这篇文章更适合作为一次系统性体检,而不是单点调参指南。