很多团队做 RAG 的第一反应是“把 embedding 换成更贵的模型”,结果成本上去了,效果却不稳定。真正的问题通常不在生成,而在检索链路:召回不全、排序不准、评估缺失。

这篇给一套可直接落地的做法:先把召回做厚,再把重排做准,最后用离线 + 在线指标形成持续优化闭环。

一、先定义“准”的标准:没有指标,优化就是玄学

先建一个最小评测集(50~200 条真实问题),每条包含:

  • 用户问题
  • 标注答案(或关键事实)
  • 应该命中的文档片段 ID(可多条)

核心离线指标建议用这 4 个:

  • Recall@k:前 k 条里命中正确证据的比例(先看这个)
  • MRR:第一个正确结果出现得有多靠前
  • nDCG@k:相关性排序质量
  • Faithfulness(生成忠实度):回答是否被检索证据支持

没有 Recall@k 的提升,后面生成调参基本白忙。

二、把召回做“厚”:多路召回比单路 embedding 更稳

实战里推荐“混合检索”:

  • 稠密向量检索(semantic)
  • BM25 关键词检索(lexical)
  • 可选:基于标题/标签/时间的规则召回

为什么:用户问题经常带产品名、错误码、版本号,这类 token BM25 往往比向量更稳。

关键参数建议

  • chunk size:300600 中文字(或 200400 英文词)
  • overlap:10%~20%
  • topK(召回)先拉到 30~100,再交给重排
  • 多路结果做去重(按 doc_id + span hash)

三、把重排做“准”:Cross-Encoder 是性价比最高的一步

在召回后加一层 reranker(cross-encoder / bge-reranker 等),通常是最明显的质量提升点。

推荐流程:

  1. 召回 top 50
  2. 重排后取 top 5~10 给 LLM
  3. 强制回答引用证据(文档标题或片段 ID)

伪代码示例:

candidates = hybrid_retrieve(query, topk=50)
ranked = rerank_cross_encoder(query, candidates)
context = ranked[:8]
answer = llm_generate(query, context, require_citations=True)

四、查询改写别贪心:先做“保守重写”

Query Rewrite 不是越激进越好。建议默认策略:

  • 保留原 query
  • 只补充同义词与缩写展开
  • 不改用户意图,不“脑补”约束

可以并行跑:原 query 检索 + 改写 query 检索,最后合并去重。

五、评估闭环:周更评测 + 线上反馈回流

离线评测每周至少跑一次,线上要补两类信号:

  • 显式反馈:👍/👎、是否解决
  • 隐式反馈:追问率、复制率、停留时长

当你看到“线上追问率升高但离线指标正常”,通常是:

  • 评测集过时
  • 新文档未及时入库
  • 某类 query(如版本差异问题)缺专门召回策略

六、排障清单(直接照着查)

  1. 回答幻觉多:先查是否给了足够证据;把 context 从 3 提到 8 试试。
  2. 答非所问:看重排是否把“关键词相近但语义偏题”的片段放前面。
  3. 新内容搜不到:检查索引增量任务延迟与失败重试。
  4. 命中旧版本文档:给文档加 versionupdated_at,检索时加过滤。
  5. 长问题效果差:先做 query decomposition(子问题拆分)再召回。

七、最小可落地架构(MVP)

  • 向量库:Milvus / pgvector / Weaviate(任选)
  • 关键词检索:OpenSearch/Elasticsearch 或本地 BM25
  • 重排:bge-reranker / cross-encoder-miniLM
  • 评估:自建脚本 + 周报(Recall@k、MRR、Faithfulness)

先把这条链路跑通,再谈 agent 化和复杂工作流。

总结

RAG 提升质量最有效的顺序是:

  1. 先补召回(混合检索)
  2. 再做重排(cross-encoder)
  3. 最后做评估闭环(离线 + 在线)

别一上来就换更贵模型。把检索系统工程做好,效果和成本都会更像“产品”,而不是“演示”。