很多团队做 RAG 的第一反应是“把 embedding 换成更贵的模型”,结果成本上去了,效果却不稳定。真正的问题通常不在生成,而在检索链路:召回不全、排序不准、评估缺失。
这篇给一套可直接落地的做法:先把召回做厚,再把重排做准,最后用离线 + 在线指标形成持续优化闭环。
一、先定义“准”的标准:没有指标,优化就是玄学
先建一个最小评测集(50~200 条真实问题),每条包含:
- 用户问题
- 标注答案(或关键事实)
- 应该命中的文档片段 ID(可多条)
核心离线指标建议用这 4 个:
- Recall@k:前 k 条里命中正确证据的比例(先看这个)
- MRR:第一个正确结果出现得有多靠前
- nDCG@k:相关性排序质量
- Faithfulness(生成忠实度):回答是否被检索证据支持
没有 Recall@k 的提升,后面生成调参基本白忙。
二、把召回做“厚”:多路召回比单路 embedding 更稳
实战里推荐“混合检索”:
- 稠密向量检索(semantic)
- BM25 关键词检索(lexical)
- 可选:基于标题/标签/时间的规则召回
为什么:用户问题经常带产品名、错误码、版本号,这类 token BM25 往往比向量更稳。
关键参数建议
- chunk size:300
600 中文字(或 200400 英文词) - overlap:10%~20%
- topK(召回)先拉到 30~100,再交给重排
- 多路结果做去重(按 doc_id + span hash)
三、把重排做“准”:Cross-Encoder 是性价比最高的一步
在召回后加一层 reranker(cross-encoder / bge-reranker 等),通常是最明显的质量提升点。
推荐流程:
- 召回 top 50
- 重排后取 top 5~10 给 LLM
- 强制回答引用证据(文档标题或片段 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(如版本差异问题)缺专门召回策略
六、排障清单(直接照着查)
- 回答幻觉多:先查是否给了足够证据;把 context 从 3 提到 8 试试。
- 答非所问:看重排是否把“关键词相近但语义偏题”的片段放前面。
- 新内容搜不到:检查索引增量任务延迟与失败重试。
- 命中旧版本文档:给文档加
version、updated_at,检索时加过滤。 - 长问题效果差:先做 query decomposition(子问题拆分)再召回。
七、最小可落地架构(MVP)
- 向量库:Milvus / pgvector / Weaviate(任选)
- 关键词检索:OpenSearch/Elasticsearch 或本地 BM25
- 重排:bge-reranker / cross-encoder-miniLM
- 评估:自建脚本 + 周报(Recall@k、MRR、Faithfulness)
先把这条链路跑通,再谈 agent 化和复杂工作流。
总结
RAG 提升质量最有效的顺序是:
- 先补召回(混合检索)
- 再做重排(cross-encoder)
- 最后做评估闭环(离线 + 在线)
别一上来就换更贵模型。把检索系统工程做好,效果和成本都会更像“产品”,而不是“演示”。