朴素RAG的局限性
标准RAG流程很简单:文档分块→向量化→检索top-k→拼接到prompt→生成回答。但在实际场景中,朴素RAG面临几个核心问题:
- 检索不准:用户查询与文档语义空间不匹配
- 排序不佳:向量相似度高≠回答有用
- 上下文冗余:top-k中可能包含大量无关内容
- 无法自纠错:检索不到就硬编答案
检索层优化
混合检索
纯向量检索擅长语义匹配但弱于精确关键词。BM25等稀疏检索正好互补。混合检索的实践方案:
- 向量检索(Dense):召回语义相关段落
- 关键词检索(Sparse/BM25):召回精确匹配段落
- 融合排序:RRF(Reciprocal Rank Fusion)算法合并两路结果
def rrf_fusion(dense_results, sparse_results, k=60):
scores = {}
for rank, doc in enumerate(dense_results):
scores[doc.id] = scores.get(doc.id, 0) + 1/(k + rank)
for rank, doc in enumerate(sparse_results):
scores[doc.id] = scores.get(doc.id, 0) + 1/(k + rank)
return sorted(scores.items(), key=lambda x: -x[1])
查询改写
用户的原始query往往不是最佳检索query。通过LLM对查询进行改写:
- 查询扩展:生成多个语义变体query,分别检索后合并
- HyDE:先让LLM生成一个假设性回答,用该回答的embedding去检索(对长尾问题效果显著)
- 子问题分解:复杂问题拆成多个子问题分别检索
重排序(Reranking)
向量检索的召回质量参差不齐。在召回后加一个cross-encoder重排层:
- 向量检索召回top-50(宽召回)
- Cross-encoder(如bge-reranker-v2)对每对(query, doc)打分
- 按分数取top-5(精排序)
Cross-encoder比bi-encoder准确率高20-30%,但速度慢,所以做两阶段检索。
生成层优化
上下文压缩
检索回来的文档可能很长,需要压缩到LLM能高效处理的篇幅:
- 提取式:用小模型抽取关键句子
- 摘要式:LLM对每个文档生成精简摘要
- LLMLingua:基于困惑度删除低信息token,可压缩到原长的1/10
引用标注
高质量RAG需要可溯源。实现方式:
- 在prompt中要求模型标注引用段落编号
- 后处理时验证引用的段落确实支持该陈述
- 不支持的引用标记为"未验证"
自适应RAG:让模型决定怎么检索
Self-RAG
Self-RAG让模型学会自主决定:
- 是否需要检索(通过特殊token
[Retrieve]/[No Retrieve]) - 检索到的段落是否相关(
[Relevant]/[Irrelevant]) - 回答是否被段落支持(
[Supported]/[Partially Supported])
这种方式将检索决策内化为模型能力,需要通过强化学习训练。
CRAG(Corrective RAG)
CRAG在检索后增加一个"检索评估器":
- 评估检索结果质量:Correct / Ambiguous / Incorrect
- 如果Incorrect,触发web搜索补充
- 如果Ambiguous,将检索结果与web结果合并
- 对最终结果重排序后生成
Adaptive-RAG
根据查询复杂度自适应选择策略:
- 简单查询→单次检索
- 复杂查询→多跳检索+推理
- 分类器判断查询类型,路由到对应处理流水线
工程实践要点
评估体系
RAG系统必须有量化评估。推荐使用RAGAS框架,四个核心指标:
- Faithfulness:回答是否忠于检索内容(无幻觉)
- Answer Relevancy:回答是否切题
- Context Precision:检索内容中有用的比例
- Context Recall:回答所需信息是否被检索到
分块策略
分块大小直接影响检索粒度。经验值:
- 通用场景:256-512 tokens,10-20%重叠
- 代码文档:按函数/类分块
- 长文:按章节分块,父子分块结合(父块提供上下文,子块用于精确匹配)
总结
RAG系统正在从"拼接管道"进化为"自适应智能体"。从混合检索到重排序、从查询改写到自反思纠错,每个环节都有大量优化空间。核心原则是:不要把RAG当成固定流水线,而要设计成能根据查询难度自适应调整的动态系统。