先给你一句最实在的:RAG 不是“给大模型塞文档”,而是把找证据和组织答案拆开做。你要优化 RAG,先管检索,再管生成,别一上来就狂改提示词,真的会白忙活 ( ̄^ ̄)ゞ
先把边界画清楚
- 截至:2026-02-10
- 讨论范围:企业知识库问答、客服机器人、内部 Copilot、运维/流程助手
- 默认前提:你已经有基础 LLM 调用能力,知道向量检索是啥
- 本文不展开:预训练、SFT/RLHF 细节、具体商业平台计费
RAG 到底是什么(不用背定义,先抓直觉)
你可以把 RAG 理解成“开卷考试流水线”:
- 用户提问后,系统先去资料库找相关证据;
- 把证据片段整理后喂给大模型;
- 模型基于证据作答,证据不足就拒答或追问。
2020 年的经典 RAG 论文把知识分成“参数内记忆 + 外部可检索记忆”,核心价值很朴素:减少瞎编,增强时效性和可追溯性。
为什么很多 RAG 看起来很聪明,实际上很脆
我见过太多系统是这么翻车的:
- 文档都导入了,但召回总是命中旧版本;
top_k越调越大,答案反而更飘;- 模型话说得很满,却找不到任何可对应证据。
这不是模型“突然变笨”,通常是检索链路没打磨好。你先记住一个优先级:
召回率 -> 排序精度 -> 上下文组织 -> 生成约束 -> 文案润色
整体流程,咱们先看全景
flowchart TD
A[用户问题] --> B[查询理解: 改写/补全]
B --> C1[稠密检索 Dense]
B --> C2[稀疏检索 BM25]
C1 --> D[RRF 融合]
C2 --> D
D --> E[重排 Reranker]
E --> F[上下文打包: 去重/截断/排序]
F --> G[LLM 生成]
G --> H[证据一致性与置信度检查]
H --> I[回答 + 引用]
H --> J[拒答/追问]
如果你的平台不支持 Mermaid,就按这条线理解:
问题 -> 双路检索 -> 融合 -> 重排 -> 上下文打包 -> 生成 -> 置信度检查 -> 回答/拒答
这个流程里,最值钱的是中间三步:融合、重排、打包。很多项目恰恰跳过它们,然后怪模型不行,冤枉人家了 😼
关键交互时序(哪里最容易掉链子)
sequenceDiagram
autonumber
participant U as 用户
participant O as 编排器
participant R as 检索服务
participant X as 重排模型
participant L as 大模型
U->>O: 提问
O->>R: 检索(dense + sparse)
R-->>O: 候选片段
O->>X: 精排候选
X-->>O: Top-N 证据
O->>L: 问题 + 证据 + 回答约束
L-->>O: 答案草稿
alt 证据足够且一致
O-->>U: 最终答案(含证据引用)
else 证据不足或冲突
O-->>U: 拒答/追问澄清
end
这一段的核心不是“怎么调用模型”,而是“谁来拍板是否该回答”。 建议由编排器负责判定,别把所有责任甩给 LLM,不然它只能硬着头皮编。
术语别怕,5 分钟能吃透
| 术语 | 你可以这么理解 | 常见误区 |
|---|---|---|
| Chunk | 切出来用于检索的文档片段 | 切太碎丢语义,切太大噪声多 |
| Dense Retrieval | 语义向量检索 | 以为能替代关键词检索 |
| BM25 | 关键词检索强基线 | 以为“老算法就没用” |
| Hybrid Retrieval | Dense + BM25 组合召回 | 只上单路检索导致漏召回 |
| RRF | 多路结果按排名做鲁棒融合 | 把它当加权平均分数 |
| Reranker | 对候选片段二次精排 | 省掉它会让前几条质量不稳 |
| Grounded Answer | 每个结论都有证据落点 | 只要回答流畅就当正确 |
| Abstention | 证据不足时拒答 | 怕拒答影响体验,结果胡答 |
优化路线图:你照着这个顺序做,基本不会跑偏
| 阶段 | 目标指标 | 你该做什么 | 反模式(别这么干) |
|---|---|---|---|
| 数据治理 | 重复率、脏数据率 | 去重、去模板噪声、补齐版本字段 | 原始 PDF 不清洗直接入库 |
| 切块策略 | Recall@k、命中跨度 | 按语义边界切块,保留 10%~20% overlap | 固定字符硬切 |
| 检索召回 | Recall@20、MRR | Dense + BM25 混合召回 | 只保留向量检索 |
| 结果融合 | nDCG@k、稳定性 | 用 RRF 融合多路排名 | 盲目按原始分数相加 |
| 二次精排 | Precision@5、正确率 | 对候选做 Cross-Encoder 重排 | 召回后直接喂 LLM |
| 上下文装配 | 证据覆盖率、冲突率 | 去重、按相关度排序、保留来源标识 | 把 top_k 全塞进 prompt |
| 生成约束 | 幻觉率、拒答准确率 | 明确“无证据不回答”与引用格式 | 给模型完全自由发挥 |
| 评测闭环 | 回归通过率 | 离线集 + 线上抽检 + A/B 对照 | 只看主观体验不看指标 |
为啥这些优化有效(对应公开研究结论)
- RAG 的方向是对的:经典论文验证了“参数记忆 + 外部检索记忆”能提升知识密集任务表现。
- Dense 很强,但 BM25 仍然能打:DPR 证明 Dense 在开放域问答里能显著拉高 top-20 召回;BEIR 又显示 BM25 在跨域零样本场景依旧是强基线。
- RRF 很适合工程落地:在信息检索研究中,RRF 以低调参成本获得稳定融合效果。
- 重排不是可选项:BERT 系重排在排序任务上有明确收益,尤其是“候选很多但你只展示前几条”时。
- 上下文不是越长越好:长上下文研究显示,信息放在中间位置时模型利用率可能明显下降,所以需要精排和打包策略。
最小可复现示例(可本地跑)
这个示例做三件事:混合召回、RRF 融合、重排截断。你跑完就能看到“证据前移”的效果。
pip install -U sentence-transformers faiss-cpu rank-bm25 numpy
import numpy as np
import faiss
from rank_bm25 import BM25Okapi
from sentence_transformers import SentenceTransformer, CrossEncoder
def tokenize_zh(text: str):
return [ch for ch in text if ch.strip()]
docs = [
"差旅报销上限:交通+住宿合计不超过3000元,需提供发票。",
"审批链:员工提交 -> 主管审批 -> 财务复核 -> 出纳打款。",
"2025年旧制度:差旅报销上限为2000元。",
"发票抬头必须与公司全称一致,否则退回。",
]
query = "差旅报销最多报多少?"
# 1) BM25 稀疏检索
bm25 = BM25Okapi([tokenize_zh(d) for d in docs])
sparse_scores = bm25.get_scores(tokenize_zh(query))
sparse_rank = np.argsort(sparse_scores)[::-1][:3].tolist()
# 2) Dense 向量检索
embedder = SentenceTransformer("BAAI/bge-small-zh-v1.5")
doc_vecs = embedder.encode(docs, normalize_embeddings=True).astype("float32")
q_vec = embedder.encode([query], normalize_embeddings=True).astype("float32")
index = faiss.IndexHNSWFlat(doc_vecs.shape[1], 32)
index.hnsw.efConstruction = 200
index.add(doc_vecs)
_, dense_ids = index.search(q_vec, 3)
dense_rank = dense_ids[0].tolist()
# 3) RRF 融合
def rrf(rank_lists, k=60):
score = {}
for rank_list in rank_lists:
for rank, doc_id in enumerate(rank_list, start=1):
score[doc_id] = score.get(doc_id, 0.0) + 1.0 / (k + rank)
return sorted(score.keys(), key=lambda x: score[x], reverse=True)
fused = rrf([dense_rank, sparse_rank])
# 4) 重排,拿最终前2条证据
reranker = CrossEncoder("BAAI/bge-reranker-base")
pairs = [[query, docs[i]] for i in fused[:6]]
scores = reranker.predict(pairs)
best_ids = [x for _, x in sorted(zip(scores, fused[:6]), reverse=True)][:2]
print("最终证据:")
for i, idx in enumerate(best_ids, 1):
print(f"{i}. {docs[idx]}")
预期现象(示意)
最终证据:
1. 差旅报销上限:交通+住宿合计不超过3000元,需提供发票。
2. 审批链:员工提交 -> 主管审批 -> 财务复核 -> 出纳打款。
如果你的结果不稳定,优先检查三处:
- 文档是否有版本污染(新旧制度混在一起);
- 检索召回是否足够(先看 Recall,再看生成);
- 重排候选池是否太小(只有 3 条时,重排空间不够)。
常见故障排查表
| 现象 | 最可能原因 | 你该先看哪里 | 建议修复 |
|---|---|---|---|
| 答案流畅但错得离谱 | 未做证据约束 | 输出是否能映射到证据片段 | 强制引用片段 ID;无证据拒答 |
| 老政策总被答出来 | 版本字段缺失 | 命中文档的时间戳/版本号 | 入库时加版本与生效期过滤 |
top_k 越大越差 | 噪声注入 + 中间遗忘 | 不同 top_k 的正确率对比 | 先重排再截断,控制 token 预算 |
| 专有名词总漏掉 | 仅语义检索 | query 的词面匹配覆盖率 | 加 BM25、同义词词典、别名扩展 |
| 延迟超时 | 串行链路太长 | 分段耗时日志(检索/重排/生成) | 并行化、缓存、减候选池 |
| 被恶意文档带偏 | 检索注入与语料污染 | 命中片段是否含指令性污染 | 来源白名单 + 入库安全清洗 |
线上实战的 6 条硬建议
- 给每条回答落审计日志:query、命中 chunk、重排分、最终 prompt、输出文本,一条都别省。
- 指标一定分层看:检索指标和生成指标分开,不然你会错判瓶颈。
- 先做小流量灰度,再做全量切换,RAG 改动经常是“看似小,影响大”。
- 拒答不是失败,是质量控制;乱答才是。别害羞,能拒就拒 🤏
- 高价值问答做回归集,每次改索引/模型都跑一遍,别凭感觉上线。
- 安全要前置:提示注入、越权检索、敏感信息外泄,要在设计阶段就拦,不是事故后补。
你可能会问(我替你先问了)
-
Q:我是不是只要换更大的模型就行?
- A:不行。检索链路烂,再大的模型也只会“更优雅地答错”。
-
Q:Dense 和 BM25 二选一怎么选?
- A:生产上优先 Hybrid。语义和关键词是互补关系,不是替代关系。
-
Q:为什么我 prompt 写得很精致,效果还是一般?
- A:因为证据没召回对。RAG 里“检索正确”永远先于“表述优美”。
-
Q:拒答会不会影响用户体验?
- A:短期可能会,但长期体验更好。用户最烦的是自信胡说,不是诚实说“我现在不确定”。
-
Q:什么时候该上重排模型?
- A:只要你关心前几条结果质量(几乎所有业务都关心),就该上。
-
Q:怎么判断优化有没有真提升?
- A:离线看 Recall@k / nDCG / 准确率,线上看命中质量、拒答正确率、延迟和投诉率,一起看才准。
最后帮你压成三句话
- RAG 的本质是“证据优先”,不是“模型自由发挥”。
- 想要答案稳,先把检索链路打磨好,再谈生成文风。
- 评测闭环要常态化,不然系统会悄悄退化,等你发现已经晚了 (。•́︿•̀。)
你下一步可以继续挖
- GraphRAG 与传统 Chunk-RAG 的取舍
- 多跳检索与查询分解(复杂问题拆问)
- 长上下文下的证据重排与位置优化
- RAG 安全红队方法(注入、越权、数据投毒)
- RAG 线上观测体系(日志、指标、告警与回放)