云计算百科
云计算领域专业知识百科平台

7 种检索模式 + RRF 融合 + Rerank:一个查询引擎的完整设计

在这里插入图片描述

快速阅读(30 秒速览)

  • EasyRAG 的 QueryEngine 支持 7 种查询模式:Naive / Local / Global / Hybrid / Mix / Structure / Bypass
  • 多路召回用 RRF(Reciprocal Rank Fusion)融合:只看排名不看分数,天然免疫"不同来源分数量级不同"的问题
  • Rerank 是可选增强:调用失败自动降级为原始顺序,重排挂了不影响主流程
  • 查询缓存(LRU)+ SSE 流式输出:相同问题秒回,长答案逐字吐出
  • 模式选择建议:默认 Mix,闲聊用 Bypass,结构化长文档试 Structure

前情提要:上一篇讲了护栏在生成流程中的位置。这篇看护栏上游的核心——检索引擎本身。


一个问题,七种答法

同一个问题"服务器支持哪些 RAID 级别",不同模式下走的路完全不同:

  • Naive:只做向量检索,快而糙;
  • Local:从问题实体出发做图扩展(第 3 篇);
  • Global:图的全局视角,回答总结性问题;
  • Hybrid:向量 + 关键词双路召回;
  • Mix:向量 + 关键词 + 图,三路全开;
  • Structure:走文档结构树,按章节定位;
  • Bypass:跳过一切检索直接问模型(闲聊兜底)。

为什么不做一个"最强模式"通吃?因为每种模式的成本和延迟不同,而问题的价值密度也不同。闲聊用 Mix 是浪费,多跳问题用 Naive 是自杀。把选择权交给调用方(或查询路由),才是工程化做法。


一次查询的完整旅程

客户端请求(mode, top_k, stream…)

① 缓存检查(非 bypass/非调试模式)
→ 命中 LRU 缓存 → 直接返回,全程 0 次外部调用
↓ 未命中
② (可选)QueryRouter 查询路由
→ 复杂问题分解为子问题 / 判断"无需检索"直接放行

③ 多路召回
→ 向量检索:VectorStorage.query(支持时间范围、biz_id 过滤)
→ 关键词检索:PG tsvector 或 Tantivy
→ 图扩展:实体 → 邻居 → 关系(批量接口)

④ RRF 融合(多路结果合并排序)

⑤ (可选)Rerank 精排 —— 失败则降级为 ④ 的顺序

⑥ 证据评分(护栏 1)→ 构造 Prompt → LLM 流式生成

⑦ 声明验证 + 引用校验(护栏 2/3)

⑧ 写缓存(拒答不写)→ SSE 返回

在这里插入图片描述


RRF 融合:为什么不用加权分数

多路召回后必须合并排序。直觉做法是加权:最终分 = 0.6×向量分 + 0.4×关键词分。

这是错的。 向量余弦相似度在 0~1,RRF 前的关键词 BM25 分数可能是 15.7,图扩展的权重又是另一套量纲。直接相加等于让量纲大的那路说了算,权重调到天荒地老也调不平。

RRF(Reciprocal Rank Fusion)的思路干脆利落——只看排名,不看分数:

RRF_score(doc) = Σ 1 / (k + rank_i(doc))
每一路

  • 某文档在第 i 路排第 1 名,贡献 1/(k+1);排第 10 名,贡献 1/(k+10);
  • k 是平滑常数(EasyRAG 可配置,常用 60),防止头部排名差距过大;
  • 多路都召回的文档分数累加,天然奖励"多路共识"。

优点:量纲免疫、零调参(k 不敏感)、实现五行代码。这也是 EasyRAG Hybrid/Mix 模式的标准做法。

Rerank:精排,但要可降级

RRF 解决"谁该排前面",Rerank 解决"排得够不够准"。Cross-encoder 重排把(问题, 块)配对打分,精度远高于双塔向量相似度——代价是一次网络调用。

EasyRAG 对 Rerank 的态度:锦上添花,绝不阻塞。

// easyrag-llm/src/rerank.rs 的核心语义:
// 调用成功 → 用重排分数覆盖排序,写回每块的 rerank_score
// 调用失败 → tracing::warn 记日志,返回原始顺序,主流程继续

rerank_score 写回块元数据还有个副作用红利:上一篇的证据评分快路径直接复用这些分数——一次调用,两处受益。

缓存:哪些问题值得缓存

查询级 LRU 缓存(QueryCache,手写有界实现:HashMap + VecDeque 维护访问顺序,容量 256 条),规则:

  • 缓存键 = 问题 + 模式 + 关键参数(top_k 等);
  • Bypass 模式不缓存(对话上下文每次不同);
  • 拒答不写缓存——用户补传了文档再问同样的问题,必须能拿到新答案;
  • 命中缓存的查询零外部调用,延迟从秒级降到毫秒级。

顺带区分一下:moka 缓存用在更下游的 LLM 响应缓存(easyrag-llm/src/cache.rs,Caffeine 语义的本地缓存)——查询缓存管"整个答案",LLM 缓存管"单次模型调用",两层各管各的。

流式输出:首字延迟的战争

生成阶段用 complete_stream 产出 token 流,server 层转成 SSE:

// axum SSE:每个 token 包一个事件推给前端
let events = chunks.map(move |chunk| {
Ok::<_, Infallible>(Event::default().json_data(frame).unwrap_or_default())
});
Sse::new(events.boxed())

用户体感上,"等 8 秒看到全文"和"0.8 秒开始出字"完全是两个产品。流式不是性能优化,是体验优化。


模式选型速查表

模式召回路数典型延迟适合
Naive 1(向量) 最低 简单事实问答、低延迟场景
Hybrid 2(向量+关键词) 关键词重要的场景(型号、错误码)
Local 图扩展 实体关系问题
Global 图全局 总结性问题
Mix 3(全开) 较高 生产默认
Structure 结构树 章节清晰的长文档
Bypass 0 最低 闲聊、无需检索

一个实用的决策顺序:先跑 Mix 建立基线 → 看评测报告(第 7 篇)找短板 → 按短板降配或加配。别一上来就追求全开,成本和延迟都是真金白银。


踩坑记录

坑 1:关键词检索和向量检索的结果重复占坑。 同一个块两路都召回,合并时当成两个候选。修复:融合前按块 ID 去重,保留更优的那路分数。

坑 2:Rerank 超时拖垮整体延迟。 重排服务偶尔抖动 5 秒。修复:给 rerank 设置独立超时,超时即降级——任何可选环节的故障都不允许升级为必选环节的延迟。

坑 3:缓存毒化。 调整了知识库后旧缓存还在返回过期答案。修复:文档变更时失效相关缓存;调试模式强制绕过缓存。


写在最后

查询引擎的设计哲学可以总结成三句话:

  • 融合用排名,不用分数——RRF 免疫量纲;
  • 增强要可降级——Rerank、路由、护栏全部可选,挂了不阻塞;
  • 缓存有纪律——拒答不缓存,变更必失效。
  • 下一篇回答一个灵魂拷问:你说你的方案好,证据呢?——Golden Set 评测体系。

    你的检索用了几路召回?RRF 还是加权?评论区过过招。


    本文为《手撸一个生产级 RAG:EasyRAG 实战系列》第 6 篇。上一篇:三层防幻觉护栏 | 下一篇:Golden Set 评测


    开源仓库:haibingzhao/easyrag —— 欢迎 Star、Fork、提 Issue 和 PR。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 7 种检索模式 + RRF 融合 + Rerank:一个查询引擎的完整设计
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!