docs: 重写 RAG 系统指南并重命名(移除 AgenticRAG 内容)
- 全面重写文档,反映统一编排路径和四层缓存架构 - 添加 Query Cache 修复说明和语义缓存集成文档 - 重命名 Agentic_RAG完整指南.md → RAG系统完整指南.md - 同步更新开发与系统模块说明.md 中的引用链接
This commit is contained in:
852
docs/RAG系统完整指南.md
Normal file
852
docs/RAG系统完整指南.md
Normal file
@@ -0,0 +1,852 @@
|
||||
# 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 + Rerank(LRU)+ 语义缓存(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: finish(answer + 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": []
|
||||
}'
|
||||
```
|
||||
|
||||
**响应格式**:SSE(Server-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 # IntentAnalyzer(LLM 意图分析)
|
||||
├── 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 # KnowledgeBaseManager(7 个 Mixin 组合)
|
||||
├── router.py # KnowledgeBaseRouter(智能知识库路由)
|
||||
├── sync.py # KnowledgeSyncService(文件变更监控+增量向量化)
|
||||
├── base.py # 知识库基类定义
|
||||
├── collection.py # 集合(Collection)CRUD 操作
|
||||
├── 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.0(2026-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
|
||||
Reference in New Issue
Block a user