双路召回 + RRF 融合
检索流程
| 阶段 | 说明 |
|---|---|
| 1. 向量检索 | pgvector 余弦相似度搜索,按 user_id + knowledge_base_id 元数据过滤,获取 top_k × 3 候选 |
| 2. BM25 检索 | jieba 中文分词 + BM25Okapi 关键词匹配,同样获取 top_k × 3 候选 |
| 3. RRF 融合 | Reciprocal Rank Fusion 合并两路结果,公式:score = vector_weight / (rank + 60) + keyword_weight / (rank + 60) |
| 4. 排序截取 | 按融合分数降序,返回 Top K 结果 |
参数配置
| 参数 | 默认值 | 说明 |
|---|---|---|
top_k | 5 | 返回的最大结果数 |
vector_weight | 0.6 | 向量搜索在 RRF 中的权重 |
keyword_weight | 0.4 | 关键词搜索在 RRF 中的权重 |
use_rerank | true | 启用时获取 3 倍候选再融合排序;关闭则直接返回向量结果 |
降级策略
当rank-bm25 或 jieba 未安装时,系统自动降级为纯向量搜索模式,只使用 pgvector 进行语义检索。日志会输出 ⚠️ BM25 依赖不可用,使用纯向量搜索模式。
BM25Store 三层缓存
BM25 索引采用三层缓存架构,支持懒加载和多实例部署:缓存层级
| 层级 | 存储 | 特点 |
|---|---|---|
| L1 | 进程内存 | 最快,Dict 结构,进程重启丢失 |
| L2 | Redis | 多实例共享,TTL 自动过期,序列化存储 |
| L3 | PostgreSQL(bm25_indexes 表) | 持久化,zlib 压缩 + pickle 序列化 |
索引生命周期
| 事件 | 行为 |
|---|---|
| 首次检索 | 懒加载 — 获取知识库所有 chunks,构建 BM25 索引,写入三层缓存 |
| 文档新增/删除 | invalidate(kb_id) — 清除三层缓存中该知识库的索引 |
| 下次检索 | 自动重建索引 |
| Redis TTL 过期 | 从 PostgreSQL 恢复,或重新构建 |
索引数据结构
PostgreSQL 持久化表
多知识库检索
当用户在对话中关联多个知识库时,系统对每个知识库独立执行双路检索,然后合并所有结果按分数排序: 每个知识库有独立的 BM25 索引,互不干扰。RAGService
RAGService 是检索的服务层封装,提供:
| 方法 | 说明 |
|---|---|
search(query, knowledge_base_id, user_id, top_k, score_threshold) | 单知识库检索 |
search_multiple_knowledge_bases(query, knowledge_base_ids, user_id, top_k) | 多知识库检索 + 合并排序 |
format_for_context(chunks, max_chars) | 格式化为 LLM 上下文(带来源标注) |
format_for_tool_output(chunks, max_chunks) | 格式化为 Agent 工具输出 |
get_retriever(user_id, knowledge_base_id, k) | 获取 LangChain VectorStoreRetriever |