你问的 RAG,到底怎么从“能答”调到“答得准”

10 阅读9分钟

先给你一句最实在的:RAG 不是“给大模型塞文档”,而是把找证据组织答案拆开做。你要优化 RAG,先管检索,再管生成,别一上来就狂改提示词,真的会白忙活 ( ̄^ ̄)ゞ

先把边界画清楚

  • 截至:2026-02-10
  • 讨论范围:企业知识库问答、客服机器人、内部 Copilot、运维/流程助手
  • 默认前提:你已经有基础 LLM 调用能力,知道向量检索是啥
  • 本文不展开:预训练、SFT/RLHF 细节、具体商业平台计费

RAG 到底是什么(不用背定义,先抓直觉)

你可以把 RAG 理解成“开卷考试流水线”:

  1. 用户提问后,系统先去资料库找相关证据;
  2. 把证据片段整理后喂给大模型;
  3. 模型基于证据作答,证据不足就拒答或追问。

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 RetrievalDense + BM25 组合召回只上单路检索导致漏召回
RRF多路结果按排名做鲁棒融合把它当加权平均分数
Reranker对候选片段二次精排省掉它会让前几条质量不稳
Grounded Answer每个结论都有证据落点只要回答流畅就当正确
Abstention证据不足时拒答怕拒答影响体验,结果胡答

优化路线图:你照着这个顺序做,基本不会跑偏

阶段目标指标你该做什么反模式(别这么干)
数据治理重复率、脏数据率去重、去模板噪声、补齐版本字段原始 PDF 不清洗直接入库
切块策略Recall@k、命中跨度按语义边界切块,保留 10%~20% overlap固定字符硬切
检索召回Recall@20、MRRDense + 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. 审批链:员工提交 -> 主管审批 -> 财务复核 -> 出纳打款。

如果你的结果不稳定,优先检查三处:

  1. 文档是否有版本污染(新旧制度混在一起);
  2. 检索召回是否足够(先看 Recall,再看生成);
  3. 重排候选池是否太小(只有 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 线上观测体系(日志、指标、告警与回放)