Files
rag/docs/RAG系统完整指南.md
lacerate551 54a6815ad4 docs: 重写 RAG 系统指南并重命名(移除 AgenticRAG 内容)
- 全面重写文档,反映统一编排路径和四层缓存架构
- 添加 Query Cache 修复说明和语义缓存集成文档
- 重命名 Agentic_RAG完整指南.md → RAG系统完整指南.md
- 同步更新开发与系统模块说明.md 中的引用链接
2026-06-05 21:34:56 +08:00

853 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RAG 系统完整指南
> **版本**: v4.0(统一编排 + 四层缓存修复)
> **生产入口**: `api/chat_routes.py::rag()` → `generate()` → `core/engine.py`
> **最后更新**: 2026-06-05
>
> 本次更新:删除未使用的 AgenticRAG 备用编排路径10 个文件 ~2050 行),修复 Query Cache 键不匹配与阈值问题,将语义缓存集成至生产 `/rag` 端点。
## 一、功能概述
本系统是一个检索增强生成RAG问答系统采用**单一统一编排路径**,由 `api/chat_routes.py``generate()` 函数直接编排全流程。核心能力包括:
| 功能 | 说明 | 实现位置 |
|------|------|----------|
| **意图分析** | LLM 驱动的双层判断(是否需要检索)+ 查询改写 | `core/intent_analyzer.py` |
| **混合检索** | 向量检索 + BM25 + RRF 融合 + Rerank 重排 | `core/engine.py` |
| **四层缓存** | Query + Embedding + RerankLRU+ 语义缓存FAISS | `core/cache.py` + `core/semantic_cache.py` |
| **流式生成** | SSE 流式答案输出,逐 token 推送 | `core/engine.py::generate_answer_stream()` |
| **引用标注** | 自动标注信息来源和引用编号 | `api/chat_routes.py::_attach_citations()` |
| **富媒体** | 图片/表格的智能提取与展示 | `api/chat_routes.py` |
| **查询理解** | 查询分解、扩展、MMR 去重、自适应 TopK | `core/` 各独立模块 |
| **安全护栏** | 敏感信息过滤、Prompt 安全守卫 | `api/response_utils.py``core/prompt_guard.py` |
---
## 二、系统架构
### 2.1 整体架构图
```
┌─────────────────────────────────────────────────────────────────────┐
│ 用户输入 │
└────────────────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 语义缓存检查 (SemanticCache - FAISS) │
│ cosine ≥ 0.92 → 命中则直接返回缓存结果 │
│ 跳过检索 + 生成全流程(~100ms vs ~9s
└────────────────────────────┬────────────────────────────────────────┘
↓ 未命中
┌─────────────────────────────────────────────────────────────────────┐
│ 意图分析 (IntentAnalyzer) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 改写查询 │ │ 双层判断 │ │ 子查询拆分 │ │
│ │ (指代消解) │ │ (是否检索) │ │ (对比/推理) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
└─────────┼──────────────────┼──────────────────┼─────────────────────┘
↓ ↓ ↓
┌──────────┐ ┌──────────────────────────────────────────┐
│ 直接回答 │ │ 统一编排流程 │
│ (LLM) │ │ 1. 混合检索 (engine.search_knowledge) │
└──────────┘ │ 2. 上下文提取 + 来源去重 │
│ 3. 图片补充检索 + 打分选择 │
│ 4. 构建上下文 │
│ 5. 流式答案生成 (engine.generate_stream) │
│ 6. 答案图号对齐 + 引用标注 │
│ 7. 敏感信息过滤 │
│ 8. 语义缓存写入 + SSE finish │
└────────────────────┬─────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 检索层 (RAGEngine) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 查询缓存 │ │ 向量检索 │ │ BM25 检索 │ │
│ │ (LRU 500) │ │ (语义匹配) │ │ (关键词匹配) │ │
│ │ 命中直接返回│ └──────┬───────┘ └──────┬───────┘ │
│ └──────────────┘ └─────────────────┘ │
│ ↓ │
│ ┌──────────────┐ │
│ │ RRF 融合 │ ← 动态权重(查询类型/长度驱动)│
│ └──────┬───────┘ │
│ ↓ │
│ ┌───────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 废止过滤 │→ │ Rerank 重排 │→ │ MMR 去重 │ │
│ └───────────┘ │ (云端API) │ └──────┬───────┘ │
│ └──────┬───────┘ ↓ │
│ ┌───────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ FAQ 加权 │→ │ 黑名单过滤 │→ │ 时间衰减 │ │
│ └───────────┘ └──────────────┘ └──────┬───────┘ │
│ ↓ │
│ ┌───────────────┐ ┌──────────────┐ │
│ │ 上下文扩展 │→ │ 自适应 TopK │ │
│ └───────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ 答案生成 (LLM 流式) │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ 整合多源信息 + 标注来源 + 引用编号 + SSE 流式输出 │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
### 2.2 编排方式
系统采用**函数式编排**而非类组合模式。整个 RAG 流程由 `api/chat_routes.py``generate()` 函数直接控制,按步骤调用各独立模块:
- **意图分析**`core/intent_analyzer.py``analyze_intent()`
- **检索管线**`core/engine.py``search_knowledge()`
- **流式生成**`core/engine.py``generate_answer_stream()`
- **引用标注**`api/chat_routes.py` 内的 `_attach_citations()`
- **缓存系统**`core/cache.py``RAGCacheManager` 单例 + `core/semantic_cache.py``SemanticCache` 单例
各模块通过 `get_engine()``get_cache_manager()``get_semantic_cache()` 等工厂函数获取全局单例实例。
---
## 三、四层缓存架构
### 3.1 缓存层次概览
| 层次 | 缓存类型 | 存储结构 | 容量 | TTL | 作用 |
|------|----------|----------|------|-----|------|
| L1 | Query Cache | LRU (OrderedDict) | 500 条 | 1 小时 | 缓存完整问答结果,命中后跳过整个检索+生成 |
| L2 | Embedding Cache | LRU (OrderedDict) | 2000 条 | 24 小时 | 缓存向量化结果,避免重复调用 embedding 模型 |
| L3 | Rerank Cache | LRU (OrderedDict) | 1000 条 | 1 小时 | 缓存 Rerank 分数,避免重复调用 Reranker |
| L4 | Semantic Cache | FAISS IndexFlatIP | 10000 条 | 无过期 | 语义级缓存,相似查询也能命中 |
### 3.2 Query Cache
Query Cache 是最外层的完整问答结果缓存。命中后直接返回缓存的 `answer + sources + citations`,跳过检索和生成全流程。
**缓存键设计**`{query_hash}:{kb_name}:{kb_version}`
- 基于查询文本哈希 + 知识库名称 + 知识库版本号
- 知识库版本变更时(如文档更新/重新索引),相关缓存自动失效
**已修复的问题**
1. **键不匹配问题(已修复)**:此前 `set_query_result()` 在有 `doc_ids` 参数时使用 `doc_hash` 分支生成键,而 `get_query_result()` 始终使用 `kb_version` 分支——导致 GET 和 SET 的键永远不匹配,命中率始终为 0%。修复后两端统一使用 `kb_version` 分支。
2. **写入阈值问题(已修复)**`CACHE_MIN_SCORE` 原值为 `0.3`,但 ChromaDB 余弦距离经 `1 - dist` 计算后得分通常在 0.03-0.06 之间,远低于阈值,导致几乎不写入缓存。修复后设为 `0.0`
### 3.3 Embedding Cache
缓存文本向量化结果,由 `RAGEngine` 在调用 embedding 模型前后自动读写。键为查询文本哈希,避免对相同文本重复调用 embedding 模型(如 DashScope text-embedding-v3
### 3.4 Rerank Cache
缓存 Rerank 重排序的分数结果。键为 `query + sorted(doc_ids)` 的精确匹配。注意:由于 RRF 融合产出差异,命中率可能偏低。
### 3.5 语义缓存Semantic Cache
语义缓存基于 FAISS 向量索引实现语义级匹配——即使查询文字不完全相同只要语义足够相似cosine similarity ≥ 0.92),就能命中缓存。
**工作机制**
```
用户查询 → embedding 编码 → FAISS 向量检索
→ cosine ≥ 0.92 → 命中:返回缓存的 answer + sources + citations~100ms
→ cosine < 0.92 → 未命中:执行完整 RAG 流程后写入缓存
```
**集成位置**
- **读取**:在 `generate()` 函数的意图分析之后、混合检索之前(跳过整个检索+生成流程)
- **写入**:在 `generate()` 函数生成完整答案后、发送 `finish` 事件之前
- **缓存内容**answer、sources、citations、images、tables
**验证结果**:语义缓存命中率约 66.7%,平均响应从 ~9.2 秒降至 ~100 毫秒(约 92 倍加速)。
### 3.6 缓存失效机制
所有基于 LRU 的缓存L1-L3均支持基于知识库版本号`kb_version`)的自动失效:
- 每个缓存条目关联 `kb_version`
- 知识库文档变更时 `kb_version` 递增
- 读取时检查 `kb_version` 是否匹配,不匹配则视为过期
语义缓存L4当前无 TTL 过期机制,仅受 `max_size=10000` 容量限制。
### 3.7 缓存配置
```python
# config.py / config.example.py
# 查询结果缓存
QUERY_CACHE_ENABLED = True
QUERY_CACHE_SIZE = 500
QUERY_CACHE_TTL = 3600 # 秒
# Embedding 缓存
EMBEDDING_CACHE_ENABLED = True
EMBEDDING_CACHE_SIZE = 2000
EMBEDDING_CACHE_TTL = 86400 # 24小时
# Rerank 缓存
RERANK_CACHE_ENABLED = True
RERANK_CACHE_SIZE = 1000
RERANK_CACHE_TTL = 3600
# 语义缓存
SEMANTIC_CACHE_ENABLED = True
SEMANTIC_CACHE_THRESHOLD = 0.92 # cosine 相似度阈值
# 缓存写入最低置信度
CACHE_MIN_SCORE = 0.0 # ChromaDB 余弦距离经 1-dist 后得分约 0.03-0.06,须设为 0
```
### 3.8 部署注意事项
当前所有缓存均为**进程内内存存储**LRU 使用 `OrderedDict`,语义缓存使用 FAISS 内存索引),有以下部署影响:
- **单 Worker**Gunicorn 默认 1 个 worker所有请求共享同一缓存实例缓存有效
- **多 Worker**:每个 worker 有独立缓存,不共享,缓存效率降低
- **冷启动**:进程重启后缓存全部丢失,需重新预热
- **`max_requests=1000`**Gunicorn worker 定期重启会导致缓存周期性清空
对于生产环境多实例部署场景,已规划 Redis 外部缓存迁移方案(见 `reports/redis_migration_plan.md`)。
---
## 四、意图分析流程
### 4.1 IntentAnalyzer 双层判断
意图分析由 `core/intent_analyzer.py``IntentAnalyzer` 类完成,采用 **LLM 驱动** 的双层判断:
```
用户输入 + 对话历史
┌─────────────────────────────────────────────┐
│ IntentAnalyzer.analyze() │
│ │
│ Step 1: 查询改写 │
│ - 指代消解:"那请假呢" → "出差相关请假流程"│
│ - 省略补全:"标准" → "差旅报销标准" │
│ - 语义缓存:相似问题复用结果 │
│ │
│ Step 2: 双层判断 │
│ - 第一层:历史上下文是否可答? │
│ - 第二层:是否需要外部知识(检索)? │
│ │
│ Step 3: 子查询拆分(对比/推理类) │
│ - "年假和调休的区别" → ["年假规定", "调休规定"]│
└─────────────────────────────────────────────┘
IntentAnalysis:
- rewritten_query: 改写后的查询
- use_context: 是否使用上下文
- need_retrieval: 是否需要检索
- sub_queries: 子查询列表
- intent: factual/comparison/reasoning/instruction/other
```
### 4.2 QueryClassifier 规则分类
`core/query_classifier.py` 提供无 LLM 调用的快速规则分类:
| 查询类型 | 说明 | 示例 |
|----------|------|------|
| `META` | 元问题(文件列表、权限) | "有哪些文档?" |
| `REALTIME` | 实时信息 | "今天天气" |
| `SIMPLE` | 简单单属性查询 | "出差标准" |
| `FACT` | 事实查询 | "差旅补贴标准是多少" |
| `ENUMERATION` | 枚举/清单/条款 | "严禁哪些情形" |
| `COMPARISON` | 比较分析 | "年假和调休的区别" |
| `PROCESS` | 流程指引 | "如何申请调岗" |
| `FILE_SPECIFIC` | 特定文件内查询 | "xxx.pdf中有哪些图片" |
---
## 五、检索管线详解
### 5.1 完整检索流程
```
search_knowledge(query, top_k=30)
├─ 1. 查询缓存检查 → 命中则直接返回
├─ 2. 子查询并行检索(如有 sub_queries
│ └─ 各子查询独立检索后合并去重
├─ 3. 查询拆分QueryDecomposer
│ └─ 对比/推理类查询自动拆分
├─ 4. 查询扩展QueryExpansion
│ └─ 同义词/语义扩展threshold=0.8
├─ 5. 多知识库检索USE_MULTI_KB=True
│ ├─ 各向量库并行检索ThreadPoolExecutor
│ ├─ 每个库: 向量检索 + BM25 + 图片独立召回
│ ├─ FAQ 集合独立召回
│ └─ RRF 融合
├─ 6. 废止切片过滤status != "active"
├─ 7. 章节过滤(查询提到章节时优先匹配)
├─ 8. ★ Rerank 重排 ★(云端 DashScope qwen3-rerank 或本地 BGE
│ └─ rerank_results(query, results, top_k)
│ └─ 由 RERANK_BACKEND 控制local/cloud/fallback
├─ 9. MMR 去重
│ ├─ 语义向量版MMR_USE_EMBEDDING=True
│ └─ 文本 Jaccard 版MMR_USE_EMBEDDING=False生产推荐
├─ 10. FAQ 分数加权Score Boosting
├─ 11. 黑名单过滤(负反馈降权)
├─ 12. 时间衰减Time Decay
├─ 13. 上下文扩展Rerank 后,补充相邻切片)
├─ 14. 自适应 TopK根据置信度调整返回数量
└─ 15. 缓存写入 → 返回结果
```
### 5.2 混合检索代码示例
```python
# 向量检索(语义相似)
vector_results = collection.query(query_embeddings=[query_vector], n_results=recall_k)
# BM25 检索(关键词匹配)
bm25_results = bm25_index.search(query, top_k=recall_k)
# FAQ 独立召回
faq_results = faq_collection.query(query_embeddings=[query_vector], n_results=3)
# 图片独立召回P0 通道)
image_results = collection.query(
query_embeddings=[query_vector], n_results=5,
where={"chunk_type": {"$in": ["image", "chart", "table"]}}
)
# RRF 融合(动态权重)
fused = reciprocal_rank_fusion([vector_results, bm25_results], weights=[vector_w, bm25_w])
# Rerank 重排(云端 DashScope qwen3-rerank 或本地 BGE
reranked = rerank_results(query, fused, top_k=15)
# MMR 去重
mmr_results = mmr_rerank(query_emb, reranked, top_k=30, lambda_param=0.5)
```
### 5.3 RRF 融合算法
```
RRF分数 = Σ (权重 / (k + 排名位置))
示例k=60
文档A: 向量排名1 → 0.5/(60+1) = 0.00820
BM25排名3 → 0.5/(60+3) = 0.00794
总分 = 0.01614
动态权重策略:
- 短查询(<15字: BM25权重↑ (0.6), 向量权重↓ (0.4)
- 长查询(>50字: 向量权重↑ (0.7), BM25权重↓ (0.3)
- 查询类型驱动: FACT→BM25优先, PROCESS→向量优先
```
### 5.4 Rerank 重排
**后端**:支持三种模式,由 `RERANK_BACKEND` 环境变量控制
| RERANK_BACKEND | 说明 |
|----------------|------|
| `"cloud"` | 仅使用云端 DashScope `qwen3-rerank` API |
| `"local"` | 仅使用本地 `BAAI/bge-reranker-base`CrossEncoder / ONNX |
| `"fallback"` | 优先云端,失败时自动回退本地(推荐生产环境) |
**云端 Reranker推荐**
```python
# config.py
RERANK_BACKEND = os.getenv("RERANK_BACKEND", "local")
RERANK_CLOUD_MODEL = "qwen3-rerank"
RERANK_CLOUD_API_KEY = os.getenv("RERANK_CLOUD_API_KEY", DASHSCOPE_API_KEY)
RERANK_CLOUD_BASE_URL = "https://dashscope.aliyuncs.com/compatible-api/v1/reranks"
RERANK_CLOUD_TIMEOUT = 15
```
`CloudReranker` 类(`core/engine.py`)封装 DashScope 的 `/compatible-api/v1/reranks` 接口,提供与本地 `CrossEncoder.predict()` / `ONNXReranker.predict()` 一致的调用接口。
**本地 Reranker备选**
```python
def rerank_results(self, query, results, top_k=5):
pairs = [(query, doc) for doc in results['documents'][0]]
scores = self.reranker.predict(pairs) # CrossEncoder / ONNXReranker / CloudReranker
sorted_indices = np.argsort(scores)[::-1]
# 返回 top_k 个最高分结果
```
**调用位置**`core/engine.py``search_knowledge()``_search_multi_kb()`RRF 融合 + 废止/章节过滤之后、MMR 去重之前执行。
---
## 六、生产 /rag 完整流程
入口 `api/chat_routes.py::rag() → generate()`
```
POST /rag (SSE 流式)
[chat_routes.generate()]
├─ 发 SSE: start
├─ 1. 语义缓存检查 SemanticCache.get() # chat_routes
│ ├─ 命中 → 直接流式发 SSE: chunk + finish结束~100ms
│ └─ 未命中 → 继续;记录 embedding 供后续写入
├─ 2. 意图分析 intent_analyzer.analyze_intent()
│ ├─ need_retrieval=False → 直接 LLM 回答(流式发 SSE: chunk结束
│ │ └─ use_context=True 时带历史上下文use_context=False 时纯闲聊
│ └─ 否则继续sub_queries 传入检索
│ └─[DEV] 发 SSE: intent_result
├─ 3. 混合检索 search_hybrid() → engine.search_knowledge()
│ (内部:查询缓存检查 → 向量+BM25+RRF+废止过滤+章节过滤
│ +云端Rerank+MMR去重+FAQ加权+黑名单+时间衰减
│ +上下文扩展+自适应TopK
│ └─[DEV] 发 SSE: retrieval_debug
├─ 4. 提取上下文/来源(按 source 去重doc_type 驱动溯源展示)
│ └─[DEV] 发 SSE: chunks_retrieved
│ └─ 发 SSE: sources
├─ 5. 图片补充检索 + 图片打分选择 (select_images)
│ └─[DEV] 发 SSE: images_selected
├─ 6. 构建上下文 (_order_texts_for_prompt)
│ └─[DEV] 发 SSE: context_built
├─ 7. 流式答案生成 engine.generate_answer_stream()
│ └─ 逐 token 发 SSE: chunk
├─ 8. 答案图号对齐过滤
├─ 9. 引用标注 _attach_citations()
├─ 10. 敏感信息过滤 filter_response()
├─ 11. 语义缓存写入 SemanticCache.set() # 写入缓存供后续命中
├─ 12. 发 SSE: finishanswer + sources + citations + images + timing
└─[异常] 发 SSE: error
```
### SSE 事件序列
| SSE 事件 `type` | 含义 |
|----------------|------|
| `start` | 请求开始处理 |
| `intent_result` | 意图分析结果 [DEV] |
| `retrieval_debug` | 检索管线各步骤 [DEV] |
| `chunks_retrieved` | 召回切片详情 [DEV] |
| `sources` | 检索到的来源列表 |
| `images_selected` | 图片选择详情 [DEV] |
| `context_built` | 最终上下文构建 [DEV] |
| `chunk` | 流式答案的每个 token |
| `finish` | 含 `timing``sources``citations``images` |
| `error` | 处理异常时的错误信息 |
> 标注 [DEV] 的事件仅在 `IS_DEV=True` 时发送。
---
## 七、API 调用方式
### 7.1 SSE 流式问答(主要接口)
```bash
curl -X POST http://localhost:5001/rag \
-H "Content-Type: application/json" \
-H "Authorization: Bearer mock-token-admin" \
-d '{
"query": "出差补助标准是什么?",
"chat_history": []
}'
```
**响应格式**SSEServer-Sent Events流式返回
```
event: token
data: {"text": "根据"}
event: token
data: {"text": "规定"}
...
event: finish
data: {"answer": "完整答案", "sources": [...], "citations": [...], "images": [...], "duration_ms": 200}
```
### 7.2 代码调用
```python
from core.engine import get_engine
# 初始化引擎
engine = get_engine()
# 检索
result = engine.search_knowledge("出差补助标准是什么?", top_k=10)
# 流式生成答案
for token in engine.generate_answer_stream(query, context, history=history):
print(token, end="", flush=True)
```
### 7.3 缓存统计查询
```bash
# 查看各层缓存命中率和统计
curl http://localhost:5001/cache/stats \
-H "Authorization: Bearer mock-token-admin"
```
返回示例:
```json
{
"query_cache": {"total_entries": 50, "hits": 10, "misses": 6, "hit_rate": 0.625},
"embedding_cache": {"total_entries": 200, "hits": 0, "misses": 0, "hit_rate": 0},
"rerank_cache": {"total_entries": 100, "hits": 0, "misses": 0, "hit_rate": 0},
"semantic_cache": {"total_entries": 15, "hits": 6, "misses": 3, "hit_rate": 0.667}
}
```
> 注:当 Query Cache 或 Semantic Cache 在外层拦截了重复查询时Embedding Cache 和 Rerank Cache 的命中率为 0 是正常现象——重复查询根本不会到达这些层。
---
## 八、配置说明
### 8.1 LLM 配置
```python
# config.py
DASHSCOPE_API_KEY = "your-api-key" # 通义千问 API
DASHSCOPE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
DASHSCOPE_MODEL = "qwen3.6-flash" # 文本生成模型(主力 LLM
INTENT_MODEL = "qwen-turbo" # 意图分析模型(轻量快速)
VLM_MODEL = "qwen-vl-plus" # 视觉语言模型(图片描述)
RAG_CHAT_MODEL = "qwen3.6-flash" # RAG 对话模型
```
### 8.2 检索参数
```python
# 混合检索
USE_MULTI_KB = True # 多向量库模式
USE_HYBRID_SEARCH = True # 向量 + BM25 混合检索
VECTOR_WEIGHT = 0.5 # 向量检索权重
BM25_WEIGHT = 0.5 # BM25 检索权重
RAG_SEARCH_TOP_K = 30 # 最终返回结果数
RAG_SEARCH_CANDIDATES = 100 # 候选池大小
RECALL_MULTIPLIER = 3 # 候选池最小倍数
# 重排序
USE_RERANK = True # 启用重排序
RERANK_BACKEND = "local" # "local"=本地模型, "cloud"=云端API, "fallback"=优先云端失败回退本地
RERANK_CLOUD_MODEL = "qwen3-rerank"
RERANK_CANDIDATES = 20 # 送入重排序的候选数
RERANK_TOP_K = 15 # 重排序后保留数
RERANK_USE_ONNX = True # ONNX 加速(仅本地模式,环境变量控制)
# RRF 融合
RRF_K = 60 # RRF 常数
DYNAMIC_RRF_ENABLED = True # 动态权重
# MMR 去重
MMR_ENABLED = True
MMR_USE_EMBEDDING = False # True=语义向量False=文本Jaccard相似度生产推荐
MMR_TOP_K = 30 # MMR 保留数
MMR_LAMBDA = 0.5 # 相关性 vs 多样性权衡
```
### 8.3 设备配置
```python
DEVICE = "auto" # auto / cuda / cpu / cuda:0
EMBEDDING_DEVICE = DEVICE # 向量模型设备
RERANK_DEVICE = DEVICE # Rerank 模型设备
```
---
## 九、文件结构
```
core/ # RAG 核心引擎
├── engine.py # RAGEngine 单例检索主流程、Rerank、RRF、流式生成
├── cache.py # 三层 LRU 缓存管理器Query/Embedding/Rerank
├── semantic_cache.py # 语义缓存FAISS IndexFlatIP
├── bm25_index.py # BM25Index关键词检索
├── chunker.py # 文本分块器
├── mmr.py # MMR 去重(语义向量版 + 文本 Jaccard 版)
├── query_classifier.py # QueryClassifier规则快速分类
├── intent_analyzer.py # IntentAnalyzerLLM 意图分析)
├── query_decomposer.py # QueryDecomposer复杂查询拆分
├── query_expansion.py # 查询扩展
├── adaptive_topk.py # AdaptiveTopK自适应 TopK
├── confidence_gate.py # ConfidenceGate置信度门控当前未接入生产流程
├── quality_assessor.py # 多维质量评估(当前未接入生产流程)
├── reasoning_reflector.py # 推理反思(当前未接入生产流程)
├── loop_guard.py # 循环防护(当前未接入生产流程)
├── prompt_guard.py # Prompt 安全守卫
├── llm_budget.py # LLM 调用预算控制
├── llm_utils.py # LLM 调用工具函数
├── status_codes.py # 状态码定义
└── constants.py # 公共常量
knowledge/ # 知识库管理
├── manager.py # KnowledgeBaseManager7 个 Mixin 组合)
├── router.py # KnowledgeBaseRouter智能知识库路由
├── sync.py # KnowledgeSyncService文件变更监控+增量向量化)
├── base.py # 知识库基类定义
├── collection.py # 集合CollectionCRUD 操作
├── document.py # 文档管理(上传/删除/状态流转)
├── document_versions.py # 文档版本管理
├── chunk.py # 分块操作(创建/查询/更新)
├── index.py # 索引管理BM25 构建/重建)
├── search.py # 知识库内搜索
├── processing.py # 文档处理流水线(解析→分块→向量化)
├── permission.py # 权限控制(角色/部门级访问控制)
├── lazy_enhance.py # 延迟增强(按需生成摘要/关键词)
└── cleanup.py # 清理操作(孤立切片/过期数据)
api/ # API 路由层
├── __init__.py # create_app() 工厂
├── chat_routes.py # /chat, /rag(SSE), /search核心编排入口
├── kb_routes.py # /collections
├── document_routes.py # /documents/*
├── sync_routes.py # /sync
├── session_routes.py # /sessions会话管理
├── image_routes.py # /images图片访问/上传)
├── feedback_routes.py # /feedback用户反馈/点赞/踩)
├── auth_routes.py # /auth认证/登录/Token
├── audit_routes.py # /audit审计日志
└── response_utils.py # 响应工具函数
services/ # 业务服务层
├── session.py # 会话管理服务
├── feedback.py # 反馈处理服务
└── outline.py # 大纲生成服务
graph/ # 已清空Graph RAG 不再使用)
auth/ # 认证与安全
├── gateway.py # API 网关认证
└── security.py # 安全工具Token 验证/权限检查)
repositories/ # 数据持久层
├── session_repo.py # 会话仓储接口
├── sqlite_session_repo.py # SQLite 会话仓储实现
└── stateless_session_repo.py # 无状态会话仓储
parsers/ # 文档解析器
├── mineru_parser.py # MinerU PDF/DOCX/PPTX 解析
├── excel_parser.py # Excel 解析
├── txt_parser.py # 纯文本解析
├── image_extractor.py # 图片提取器(从文档中提取/处理图片)
└── pdf_mineru.py # MinerU PDF 辅助入口
exam_pkg/ # 出题系统
├── api.py # 出题/批阅 API
├── generator.py # 试题生成Dify 工作流)
├── grader.py # 试卷批阅
├── manager.py # 出题管理器(核心业务逻辑)
└── local_db.py # 本地数据库SQLite
tools/ # 运维/分析工具
├── export_chunks.py # 导出切片
├── llm_evaluator.py # LLM 评估器
├── chunk_analyzer.py # 切片质量分析
├── chunk_metrics.py # 切片指标统计
├── chunk_report.py # 切片报告生成
├── clean_vector_store.py # 清理向量库
├── rebuild_pdf_vectors.py # 重建 PDF 向量
└── upload_test_files.py # 上传测试文件
deploy/ # 部署配置
├── Dockerfile # 开发环境 Docker
├── Dockerfile.prod # 生产环境 Docker
├── docker-compose.yml # 开发环境编排
├── docker-compose.prod.yml # 生产环境编排
├── nginx.conf # Nginx 反向代理配置
├── gunicorn.conf.py # Gunicorn WSGI 配置
└── wsgi.py # WSGI 入口
reports/ # 分析报告
├── cache_performance_report.md # 缓存性能验证报告
└── redis_migration_plan.md # Redis 缓存迁移方案(规划中)
config/ # 运行时配置
└── banned_words.txt # 敏感词库
```
---
## 十、与传统 RAG 对比
| 特性 | 传统 RAG | 本系统 |
|------|---------|--------|
| 意图判断 | 无 | IntentAnalyzer LLM 双层判断 |
| 查询改写 | 无 | 口语化→专业术语 + 实体补全 + 指代消解 |
| 检索方式 | 单一向量检索 | 向量 + BM25 + FAQ + 图片独立召回 |
| 融合算法 | 无 | RRF 动态权重融合 |
| 去重 | 无 | MMR 语义去重 |
| 重排序 | 无 | 云端 qwen3-rerank API支持本地 BGE 回退) |
| 问题分解 | 无 | 自动拆分对比/推理类查询 |
| 闲聊处理 | 无 | 意图分析自动判断 |
| 缓存体系 | 无 | 四层缓存Query + Embedding + Rerank + 语义缓存) |
| 语义缓存 | 无 | FAISS 向量索引相似查询复用92x 加速) |
| 自适应 TopK | 固定 top_k | 根据置信度动态调整 |
| 上下文理解 | 无 | 多轮对话 + 历史上下文 |
| 响应时间 | ~2秒 | 首次 ~3-8秒 / 缓存命中 ~100-200毫秒 |
---
## 十一、Rerank 性能分析
### 11.1 Rerank 调用路径
Rerank 在生产流程中有 **一个调用路径**
| 路径 | 位置 | 说明 |
|------|------|------|
| 主检索管线 | `engine.rerank_results()` | RRF 融合后、MMR 去重前执行,对候选重排取 top_k |
### 11.2 性能特征
| 项目 | 说明 |
|------|------|
| Rerank 缓存命中率 | 偏低——键基于 `query + sorted(doc_ids)` 精确匹配RRF 融合产出稍有不同就无法命中 |
| 性能计时 | `rerank_results()` 返回 `_rerank_time_ms` 计时字段 |
| Query Cache 拦截 | 重复查询被 Query Cache 在外层拦截,不会到达 Rerank 层(正确行为) |
### 11.3 Rerank 配置参数
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `USE_RERANK` | `True` | 总开关 |
| `RERANK_BACKEND` | `"local"` | 后端选择 |
| `RERANK_CLOUD_MODEL` | `"qwen3-rerank"` | 云端模型名称 |
| `RERANK_CLOUD_API_KEY` | 同 `DASHSCOPE_API_KEY` | 云端 API 密钥 |
| `RERANK_CLOUD_BASE_URL` | `https://dashscope.aliyuncs.com/compatible-api/v1/reranks` | 云端 API 地址 |
| `RERANK_CLOUD_TIMEOUT` | `15` | 云端请求超时(秒) |
| `RERANK_MODEL_PATH` | `models/bge-reranker-base` | 本地模型路径 |
| `RERANK_CANDIDATES` | `20` | 送入 Rerank 的候选数 |
| `RERANK_TOP_K` | `15` | Rerank 后保留数 |
| `RERANK_USE_ONNX` | `True` | ONNX 加速开关 |
| `RERANK_DEVICE` | 跟随 `DEVICE` | 设备选择 |
| `RERANK_THRESHOLD` | `0.3` | 上下文过滤阈值 |
| `RERANK_CACHE_ENABLED` | `True` | 缓存开关 |
---
## 十二、最佳实践
### 12.1 何时使用 /rag 接口
**推荐使用**
- 复杂问题需要检索知识库
- 用户表达模糊需要改写
- 需要区分闲聊和知识问答
- 需要多轮对话记忆
- 需要引用来源和证据
**不推荐使用**(改用 `/search` 接口更快):
- 简单明确的问题,只需返回原始检索结果
- 对响应时间极度敏感且不需要 LLM 生成答案的场景
### 12.2 性能优化
```python
# ONNX 加速默认已开启;如有兼容性问题可关闭(环境变量)
# RERANK_USE_ONNX=false
# 使用轻量 MMR文本相似度代替语义向量
# config.py: MMR_USE_EMBEDDING = False
# 调整语义缓存阈值(降低阈值可提高命中率,但可能降低准确性)
# config.py: SEMANTIC_CACHE_THRESHOLD = 0.90
# 调整缓存容量
# config.py: QUERY_CACHE_SIZE = 1000 # 增大查询缓存容量
```
### 12.3 调试技巧
```python
# 查看检索调试信息
result = engine.search_knowledge("问题", top_k=10)
debug = result.get('_debug', {})
for step in debug.get('steps', []):
print(f"步骤: {step['name']}, 详情: {step}")
# 查看缓存统计
from core.cache import get_cache_manager
cm = get_cache_manager()
print(cm.get_stats())
# 查看语义缓存统计
from core.semantic_cache import get_semantic_cache
sc = get_semantic_cache()
print({"hits": sc.hits, "misses": sc.misses, "total": sc.total_entries})
```
---
## 十三、演进记录
### v4.02026-06-05— 统一编排 + 四层缓存修复
**删除未使用的备用编排路径**:移除了 `core/agentic.py` 及 8 个 Mixin 文件(共 10 个文件 ~2050 行)。这些文件实现了完整的决策循环编排(含置信度门控、质量评估、推理反思等),但从未接入任何 HTTP 路由。
**修复 Query Cache**
1. 修复 GET/SET 键不匹配——此前 SET 使用 `doc_hash` 分支GET 使用 `kb_version` 分支,两端永远不匹配,命中率始终为 0%
2. 修复 `CACHE_MIN_SCORE = 0.3` 阈值过高——ChromaDB 余弦距离经 `1-dist` 后得分约 0.03-0.06,远低于 0.3,导致几乎不写入缓存
**集成语义缓存**:将 FAISS 语义缓存从已删除的备用路径移植到生产 `/rag` 端点,在意图分析后、混合检索前检查,命中时跳过整个检索+生成流程。验证结果:命中率 66.7%92 倍加速。
### v3.2 — 模型/Reranker/管线更新
引入云端 qwen3-rerank、ONNX 加速、动态 RRF 权重等。
---
## 十四、未来规划
### Redis 缓存外部化
当前四层缓存均为进程内内存存储,在多 Worker / 多实例部署时无法共享。已规划 Redis 迁移方案(详见 `reports/redis_migration_plan.md`),核心设计:
- `RedisCacheManager` 提供与 `RAGCacheManager` 相同的接口
- 通过 `REDIS_CACHE_URL` 环境变量启用,向后兼容
- 语义缓存采用混合方案FAISS 索引保持在进程内,缓存结果存储到 Redis
- Query Cache、Embedding Cache、Rerank Cache 全部迁移到 Redis
### 可选能力接入
`core/` 目录下仍保留以下独立模块,当前未接入生产流程,可按需启用:
- `confidence_gate.py`:置信度门控,基于 Reranker 分数判断检索质量
- `quality_assessor.py`:多维质量评估(相关性/完整性/准确性/覆盖面)
- `reasoning_reflector.py`:推理反思,检查未验证的假设
- `loop_guard.py`:循环防护,防止重复检索
---
## 参考资料
1. Xiong, G., et al. (2025). RAG-Gym: Systematic Optimization of Language Agents for Retrieval-Augmented Generation. arXiv:2502.13957
2. Zhang, W., et al. (2025). Process vs. Outcome Reward: Which is Better for Agentic RAG Reinforcement Learning. arXiv:2505.14069