RAG 落地常见陷阱与优化策略
RAG 不是银弹:从原型到生产的差距
Demo 阶段的 RAG 通常表现完美,但上线后用户反馈"搜不准""搜不全""幻觉更严重了"。原因在于:原型用干净数据 + 简单查询,生产环境面临脏数据、模糊查询、多文档融合等真实问题。
陷阱一:分块策略不当
默认按固定长度分块(chunk_size=500)是最常见的错误。
| 文档类型 | 推荐分块策略 | 原因 |
|---|---|---|
| 产品文档/手册 | 按章节/标题分块(语义分块) | 保持语义完整性 |
| 代码 | 按函数/类分块 | 代码上下文至关重要 |
| FAQ/对话 | 按 Q&A 对分块 | 保持问答映射 |
| 长文章/论文 | 重叠分块 (overlap=100) | 避免切断关键信息 |
核心原则:分块不是技术问题,是业务问题——理解文档结构后再决定如何切。
陷阱二:只用向量检索
纯向量检索的两个致命缺陷:(1) 对专有名词/代号/编号不敏感,(2) 短查询缺乏语义上下文。
解决方案:混合检索
# BM25(关键词)+ 向量(语义)双路召回
bm25_results = bm25_index.search(query, k=20)
vector_results = vector_db.search(embed(query), k=20)
# RRF (Reciprocal Rank Fusion) 融合
def rrf(results_a, results_b, k=60):
scores = {}
for rank, doc in enumerate(results_a):
scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1)
for rank, doc in enumerate(results_b):
scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)[:10]
陷阱三:检索了但没检索对(查询-文档不匹配)
用户问"怎么退款",文档里写的是"退货流程"——纯关键词或向量都匹配不上。
解决方案
- 查询重写:用 LLM 把用户口语化问题转为接近文档风格的查询
- HyDE:先让 LLM 生成假设答案,用假设答案的 embedding 去检索
- 多轮改写:对话场景下将"那这个呢"改写为完整查询"那XX产品的退款流程呢"
# 查询重写示例
user_query = "怎么退款"
rewritten = llm.generate(
"将用户问题改写为适合检索文档的查询语句:",
user_query
)
# → "退款流程 退货申请 退款 操作步骤"
陷阱四:检索到但 LLM 用错了
检索返回了 5 个文档片段,LLM 可能:忽略关键信息、混淆多个文档、使用过时内容。
解决方案
- 引用标注:在 Prompt 中要求 LLM 引用来源:"请引用具体的文档编号[1][2]"
- Re-rank + 截断:只送 Top-3 给 LLM,避免信息过载
- 时间衰减:旧文档降低权重,优先使用最新内容
- 来源校验:LLM 回答后,用检索结果做事实核查
陷阱五:增量更新导致数据不一致
文档更新后,vector DB 中的旧 embedding 仍然存在,用户搜到过期内容。
解决方案
- 文档版本管理:每条 chunk 带 doc_version,检索时过滤旧版本
- 双写策略:先写新版本,验证无误后再删旧版本
- 定时全量重建:定期重建索引(业务低峰期),清理脏数据
RAG 效果评估
不能只看"像不像",要量化评估:
| 指标 | 含义 | 如何计算 |
|---|---|---|
| Hit Rate | Top-K 结果中包含正确答案的比例 | 人工标注测试集 |
| MRR | 正确答案的平均倒数排名 | 正确答案排名越靠前 MRR 越高 |
| Faithfulness | LLM 回答是否忠于检索到的文档 | LLM-as-Judge 逐句核对 |
| Answer Relevance | 回答是否与用户问题相关 | Embedding 相似度 |
优化路线图
- Level 1 — 基础可用:语义分块 + 向量检索 + 基础 Prompt
- Level 2 — 召回优化:混合检索 + RRF + 查询重写
- Level 3 — 精度优化:Re-rank + HyDE + 引用标注
- Level 4 — 生产可靠:增量更新 + 效果评估 + A/B 实验
- Level 5 — 持续改进:用户反馈闭环 + 主动学习 + 自动评估