> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeus.agentspro.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# 检索策略

> 检索策略 — 双路召回、RRF 融合排序、BM25 三层缓存

Zeus 的知识库检索采用 **向量语义搜索 + BM25 关键词匹配** 的双路召回策略，通过 **RRF（Reciprocal Rank Fusion）** 融合排序，兼顾语义理解和精确匹配。

***

## 双路召回 + RRF 融合

```mermaid theme={null}
graph TD
    query["用户查询"] --> vec["向量语义搜索<br/>(pgvector 余弦相似度)"]
    query --> bm25_search["BM25 关键词搜索<br/>(jieba 中文分词)"]

    vec -->|"top_k × 3 候选"| rrf["RRF 融合排序<br/>score = Σ weight / (rank + 60)"]
    bm25_search -->|"top_k × 3 候选"| rrf

    rrf --> result["Top K 最终结果"]

    fallback["BM25 不可用<br/>(rank-bm25/jieba 未安装)"] -.->|"自动降级"| vec_only["纯向量搜索模式"]
```

### 检索流程

| 阶段             | 说明                                                                                                    |
| -------------- | ----------------------------------------------------------------------------------------------------- |
| **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 索引采用三层缓存架构，支持懒加载和多实例部署：

```mermaid theme={null}
graph LR
    read["检索请求"] --> mem_check{"L1: 内存缓存?"}
    mem_check -->|"命中"| return["返回索引"]
    mem_check -->|"未命中"| redis_check{"L2: Redis?"}
    redis_check -->|"命中"| load_redis["解压 + 反序列化<br/>→ 写入内存"]
    redis_check -->|"未命中"| pg_check{"L3: PostgreSQL?"}
    pg_check -->|"命中"| load_pg["解压 + 反序列化<br/>→ 写入 Redis + 内存"]
    pg_check -->|"未命中"| build["构建新索引<br/>→ 写入三层缓存"]
    load_redis --> return
    load_pg --> return
    build --> return
```

### 缓存层级

| 层级     | 存储                           | 特点                       |
| ------ | ---------------------------- | ------------------------ |
| **L1** | 进程内存                         | 最快，Dict 结构，进程重启丢失        |
| **L2** | Redis                        | 多实例共享，TTL 自动过期，序列化存储     |
| **L3** | PostgreSQL（`bm25_indexes` 表） | 持久化，zlib 压缩 + pickle 序列化 |

### 索引生命周期

| 事件               | 行为                                     |
| ---------------- | -------------------------------------- |
| **首次检索**         | 懒加载 — 获取知识库所有 chunks，构建 BM25 索引，写入三层缓存 |
| **文档新增/删除**      | `invalidate(kb_id)` — 清除三层缓存中该知识库的索引   |
| **下次检索**         | 自动重建索引                                 |
| **Redis TTL 过期** | 从 PostgreSQL 恢复，或重新构建                  |

### 索引数据结构

```python theme={null}
@dataclass
class BM25IndexData:
    bm25: BM25Okapi          # BM25 索引实例
    corpus: List[List[str]]   # jieba 分词后的语料库
    documents: List[str]      # 原始文档内容
    doc_ids: List[str]        # 文档 ID 列表（用于结果映射）
    created_at: float         # 创建时间戳
    doc_count: int            # 文档数量
```

### PostgreSQL 持久化表

```sql theme={null}
CREATE TABLE IF NOT EXISTS bm25_indexes (
    kb_id VARCHAR(255) PRIMARY KEY,
    index_data BYTEA NOT NULL,        -- zlib 压缩 + pickle 序列化
    doc_count INTEGER NOT NULL DEFAULT 0,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
```

***

## 多知识库检索

当用户在对话中关联多个知识库时，系统对每个知识库独立执行双路检索，然后合并所有结果按分数排序：

```mermaid theme={null}
graph TD
    query["用户查询<br/>knowledge_base_ids: [kb_1, kb_2, kb_3]"]

    query --> kb1["KB 1: 向量 + BM25 → RRF"]
    query --> kb2["KB 2: 向量 + BM25 → RRF"]
    query --> kb3["KB 3: 向量 + BM25 → RRF"]

    kb1 --> merge["合并所有结果<br/>按融合分数排序"]
    kb2 --> merge
    kb3 --> merge

    merge --> topk["返回 Top K"]
```

每个知识库有独立的 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 |
