RAG 检索层实战:Redis 缓存 + ES 混合检索 + BGE-Rerank 重排全链路落地与踩坑

前言

近期基于 FastAPI + LangGraph 自研智能客服 RAG 平台,初期仅使用 Chroma 向量检索存在两大痛点:

  1. 单纯向量检索关键词召回能力弱,大量业务相关文档漏召,回答准确率偏低;
  2. 高频重复问答、会话历史频繁查询数据库与向量引擎,接口响应慢、频繁触发大模型 API 限流。

为此搭建一套分层优化架构:ES和Chroma 做粗粒度多路召回、BGE-Rerank 做语义精排、Redis 多层缓存减负,完整覆盖检索、排序、性能优化全流程。本文结合项目源码,拆解整套方案设计、实现细节与线上踩坑经验,所有代码均已开源。

一、整体架构链路

用户问答请求完整执行流程:

markdown 复制代码
用户提问 → Redis缓存判断
    ├─ 缓存命中:直接返回历史回答,终止流程
    └─ 缓存未命中 → ES 关键词+向量混合粗召回 → 多路文档合并去重
        → BGE-Rerank 相关性打分、过滤低相关分片
        → 截取高分片段拼接上下文送入LLM
        → 生成回答存入Redis缓存,返回前端

各组件分工:

表格

组件 核心职责 数据结构
Redis 会话缓存、问答结果缓存、热门分片缓存 String、Set,差异化过期时间
Elasticsearch 全文关键词 + 向量混合粗召回,存储全部知识库分片 双路召回+IK分词
BGE-Rerank 对召回候选文档做语义精细打分、过滤无关内容 本地离线推理,限制候选数量控制耗时

二、Elasticsearch 混合检索实现

2.1 设计思路

单一向量检索缺少关键词匹配能力,纯文本检索缺少语义理解,采用双路召回逻辑进行检索

  1. 同时检索文本相似度与向量相似度,两路结果融合,扩大候选文档覆盖范围;
  2. 限制单次召回 20 条分片,平衡召回完整性与后续重排推理耗时。

2.2 核心简化代码

ini 复制代码
# 异步ES混合检索示例
async def adual_retrieve(self, query: str, user_id: int, top_k_es=20) -> list[Document]:
    skip_filter = (user_id == 0)
    retriever = self.get_retriever(user_id)
    vector_docs = await retriever.ainvoke(query)
    # 异步ES
    es_hits = await es_client.abm25_search(
        query=query,
        user_id=user_id,
        top_k=top_k_es,
    )
    es_docs = []
    for hit in es_hits:
        doc = Document(
            page_content=hit["content"],
            metadata={
                "source": hit["source"],
                "file_md5": hit["file_md5"],
                "user_id": hit["user_id"],
                "chunk_idx": hit["chunk_idx"],
                "_score": hit["_score"]
            }
        )
        es_docs.append(doc)
    seen_keys = set()
    merged = []
    for d in vector_docs + es_docs:
        md5 = d.metadata.get("file_md5")
        idx = d.metadata.get("chunk_idx")
        if md5 and idx is not None:
            key = f"{md5}_{idx}"
            if key in seen_keys:
                continue
            seen_keys.add(key)
        merged.append(d)
    return merged

2.3 踩坑总结

  1. 无分词器:原生 ES 对中文切割混乱,必须安装 IK 分词;
  2. 频繁重复检索:高频相同 query 反复请求 ES,造成接口延迟升高,上层依赖 Redis 缓存拦截。

三、BGE-Rerank 重排精排优化

3.1 为什么需要重排

ES 粗召回会返回 20 条候选文档,其中包含大量语义无关、弱相关分片,直接送入大模型会产生两个问题:

  1. 无关文本占用 Token,增加调用成本;
  2. 干扰 LLM 判断,容易生成幻觉、答非所问。

重排模型输入「用户问题 + 单条文档」,计算精准相关性分数,过滤低分内容,保留高价值上下文。

3.2 实现逻辑

  1. ES 召回 20 条文档,提取纯文本列表;
  2. 批量送入 BGE-rerank-base-v2 模型打分;
  3. 文本与分数绑定排序,按自定义阈值剔除低相关内容;
  4. 截取前 5 条高分文档还原完整 Document 对象,拼接上下文。

3.3 核心代码

python 复制代码
from FlagEmbedding import FlagReranker

class RerankClient:
    def __init__(self):
        self.ENABLE = True
        self.reranker = FlagReranker("BAAI/bge-reranker-base-v2")

    def rerank(self, query: str, text_list: list[str], top_k=5, threshold=-5.0):
        # 降级兜底
        if not self.ENABLE or self.reranker is None or len(text_list) == 0:
            return text_list[:top_k]
        
        # 批量打分
        pairs = [[query, text] for text in text_list]
        scores = self.reranker.compute_score(pairs)
        text_score = list(zip(text_list, scores))
        # 分数降序
        text_score.sort(key=lambda x: x[1], reverse=True)
        # 阈值过滤
        filter_text = [t for t, s in text_score if s >= threshold]
        return filter_text[:top_k]

3.4 关键工程处理

  1. 异步线程隔离:重排是 CPU 密集计算,使用 asyncio.to_thread 执行,不阻塞 FastAPI 异步事件循环;
  2. 自动降级:模型加载失败、内存不足时直接跳过重排,使用原始召回结果,保证服务不中断;
  3. 候选数量控制:召回上限 20 条,数量过多会大幅增加推理耗时。

四、Redis 多层缓存架构设计

4.1 三级缓存策略

  1. 对话会话缓存 以会话 ID 为 key,缓存历史对话记录,减少 SQLite 频繁读写,消除数据库锁报错;过期 2 小时。
  2. 问答结果缓存 对完全一致的用户问题缓存完整回答,规避大模型 API 重复调用、缓解 429 限流;过期 12 小时。
  3. 热门知识库分片缓存 高频检索文档存入 Redis,避免重复请求 ES,降低检索耗时。

4.2 核心特性

  1. 连接池复用:全局单例 Redis 客户端,复用连接减少握手开销;
  2. 差异化过期时间:会话短期失效、知识库分片长期缓存;
  3. 容错重试:Redis 连接失败自动降级,走原始 ES 检索链路;
  4. Set 集合去重:缓存文档 ID,避免重复加载相同分片。

五、通用落地经验总结

  1. RAG 检索分层标准范式:粗召回 (ES) → 精排 (Rerank) → 缓存削峰 (Redis) ,兼顾召回精度与服务性能;
  2. 分层容错设计是生产必备:向量引擎、重排模型、缓存任一组件异常均有兜底逻辑,不会导致服务不可用;
  3. 重排不要无限增加候选数量,20 条以内是速度与效果平衡点;
  4. 缓存核心解决两大问题:数据库压力、大模型 API 限流,是低成本性能优化方案。

六、后续优化方向

  1. 数据库升级:SQLite 替换为 PostgreSQL,支持高并发多用户访问;
  2. Redis 集群部署:单机 Redis 扩容为集群,支持分布式多实例项目;
  3. Rerank 模型轻量化 / 微调:针对业务领域语料微调,进一步提升行业文档匹配精度;
  4. 接入 LangSmith 全链路监控,可视化缓存命中率、重排耗时、检索召回指标。

结尾

整套检索优化方案完整开源在 Gitee, 在企业级知识库场景中,ES 混合检索 + BGE 重排是提升回答质量的标准方案,搭配 Redis 缓存可低成本解决并发性能瓶颈。 欢迎评论区交流你们在 RAG 落地过程中遇到的检索、缓存相关问题!

相关推荐
Elasticsearch4 小时前
ES 存日志很贵?我用 ES 9.5 把日志从 4.5G 压到 412M 压缩比11.2倍,日志硬扫每秒128万行!
elasticsearch
Elasticsearch7 小时前
Elasticsearch 作为统一平台:引入第二套数据系统究竟要付出什么代价
elasticsearch
Elastic 中国社区官方博客8 小时前
Elasticsearch:列式索引模式 - Columnar index mode
大数据·数据库·elasticsearch·搜索引擎·全文检索
Elasticsearch8 小时前
Elasticsearch 的批量查询阶段如何在大规模场景下提升搜索性能
elasticsearch
Ramboooooooo9 小时前
SkyWalking-10.4.0 Docker + Nacos + Elasticsearch 生产级部署手册
elasticsearch·docker·skywalking
互联网中的一颗神经元19 小时前
04 — 安全撤销:改错了怎么退回去
大数据·安全·elasticsearch
Elasticsearch1 天前
Elasticsearch:列式索引模式 - Columnar index mode
elasticsearch
Elasticsearch1 天前
从 CrashLoopBackOff 到根本原因,只需几秒:使用 Elastic Observability 自动化 20 分钟的 Kubernetes 调查流
elasticsearch
互联网中的一颗神经元2 天前
09 — .git 地图:打开那个隐藏文件夹
大数据·git·elasticsearch