# RAG 系统完整指南 > **版本**: v4.1(模型切换 + 图片检索修复) > **生产入口**: `api/chat_routes.py::rag()` → `generate()` → `core/engine.py` > **最后更新**: 2026-06-21 > > 本次更新:LLM/意图/VLM 模型统一切换至 mimo-v2.5(xiaomimimo.com API),Reranker 切换至 xop3qwen8breranker(讯飞云 API),新增 chart_contexts 降门槛、P0 安全网、答案后过滤等图片检索修复逻辑。 ## 一、功能概述 本系统是一个检索增强生成(RAG)问答系统,采用**单一统一编排路径**,由 `api/chat_routes.py` 的 `generate()` 函数直接编排全流程。核心能力包括: | 功能 | 说明 | 实现位置 | |------|------|----------| | **意图分析** | LLM 驱动的双层判断(是否需要检索)+ 查询改写 | `core/intent_analyzer.py` | | **混合检索** | 向量检索 + BM25 + RRF 融合 + Rerank 重排 | `core/engine.py` | | **五层缓存** | Query + Embedding + Rerank(LRU)+ 语义缓存(FAISS)+ ChromaDB 元数据缓存 | `core/cache.py` + `core/semantic_cache.py` | | **流式生成** | SSE 流式答案输出,逐 token 推送 | `core/engine.py::generate_answer_stream()` | | **引用标注** | 自动标注信息来源和引用编号 | `api/chat_routes.py::_attach_citations()` | | **富媒体** | 图片/表格的智能提取与展示 + P0 安全网 + 答案后过滤 | `api/chat_routes.py` | | **查询理解** | 查询分解、扩展、MMR 去重、自适应 TopK | `core/` 各独立模块 | | **安全护栏** | 敏感信息过滤、Prompt 安全守卫 | `api/response_utils.py`、`core/prompt_guard.py` | | **救援管线** | BM25 散度救援、词法匹配救援、章节聚类救援、表格救援 | `core/engine.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 │→ │ 救援管线 │ │ │ └───────────────┘ └──────────────┘ │ (BM25散度/ │ │ │ │ 词法/章节/ │ │ │ │ 表格救援) │ │ │ └──────────────┘ │ └─────────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────────┐ │ 答案生成 (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 条 | 无过期 | 语义级缓存,相似查询也能命中 | | L5 | ChromaDB 元数据缓存 | 进程内 dict | 无限制 | 无过期 | 缓存 ChromaDB Collection 元数据(kb_version 等),避免频繁查询 ChromaDB | ### 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 重排 ★(云端讯飞 xop3qwen8breranker 或本地 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. 救援管线(BM25散度/词法匹配/章节聚类/表格救援) │ └─ 16. 缓存写入 → 返回结果 ``` ### 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 重排(云端讯飞 xop3qwen8breranker 或本地 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"` | 仅使用云端讯飞 `xop3qwen8breranker` API | | `"local"` | 仅使用本地 `BAAI/bge-reranker-base`(CrossEncoder / ONNX) | | `"fallback"` | 优先云端,失败时自动回退本地(推荐生产环境) | **云端 Reranker(推荐)**: ```python # config.py RERANK_BACKEND = os.getenv("RERANK_BACKEND", "local") RERANK_CLOUD_MODEL = "xop3qwen8breranker" RERANK_CLOUD_API_KEY = os.getenv("RERANK_CLOUD_API_KEY", DASHSCOPE_API_KEY) RERANK_CLOUD_BASE_URL = "https://maas-api.cn-huabei-1.xf-yun.com/v1/rerank" RERANK_CLOUD_TIMEOUT = 15 ``` `CloudReranker` 类(`core/engine.py`)封装讯飞云的 `/v1/rerank` 接口,提供与本地 `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) │ └─ chart_contexts 使用 min_score * 0.5 降门槛 │ └─[DEV] 发 SSE: context_built │ ├─ 6.5. P0 安全网 — 确保 selected_images 描述完整进入 LLM 上下文 │ ├─ 7. 流式答案生成 engine.generate_answer_stream() │ └─ 逐 token 发 SSE: chunk │ ├─ 8. 答案图号对齐过滤 ├─ 8.5. 答案后过滤 (_filter_images_by_answer) — 根据 LLM 答案过滤不相关图片 ├─ 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` 时发送。 ### 6.1 图片检索子系统 图片检索是独立于文本上下文管线的子系统,存在特有的"两管线断裂"问题,已通过多重修复保障完整性。 **图片检索流程**: ``` search_knowledge() 返回混合检索结果 ↓ ┌──────────────────────────────────────────────────────────────┐ │ _order_text_contexts_for_prompt() — 上下文提取与构建 │ │ │ │ 1. 分离 text_contexts 和 chart_contexts │ │ 2. chart_contexts 使用 min_score * 0.5 降门槛过滤 │ │ (CrossEncoder 对图片打分系统性偏低:0.002-0.08) │ │ 3. chart_contexts 的描述注入 context_text 【相关图片信息】 │ │ 4. text_contexts 正常注入 context_text │ └──────────────────────────────────────────────────────────────┘ ↓ ↓ ┌──────────────────┐ ┌──────────────────────────────────────┐ │ select_images() │ │ context_text 送入 LLM │ │ 独立图片选择 │ │ (可能不包含所有选中图片的描述) │ │ P1: BM25/向量 │ └──────────────────────────────────────┘ │ P2: VLM 检查 │ │ P3: CE 排名 │ │ P4: 多样性去重 │ └────────┬─────────┘ ↓ ┌──────────────────────────────────────────────────────────────┐ │ P0 安全网 — 保证 selected_images 描述完整进入 LLM 上下文│ │ │ │ 检查 context_text 中的 【相关图片信息】 部分, │ │ 若 selected_images 的描述不在 context_text 中, │ │ 强制追加缺失的描述。 │ │ (解决两管线断裂:图片被选中但描述被 min_score 过滤掉) │ └──────────────────────────────────────────────────────────────┘ ↓ ┌──────────────────────────────────────────────────────────────┐ │ _filter_images_by_answer() — 答案后过滤 │ │ │ │ LLM 生成答案后,根据答案内容过滤不相关图片。 │ │ ⚠️ 鸡生蛋问题:若 LLM 因上下文缺失而回答"未找到", │ │ 会导致正确图片被误过滤。P0 安全网可缓解此问题。 │ └──────────────────────────────────────────────────────────────┘ ``` **select_images 四阶段选择**: | 阶段 | 方法 | 说明 | |------|------|------| | P1 | BM25/向量召回 | 从检索结果中筛选 chunk_type 为 image/chart 的候选 | | P2 | VLM 相关性检查 | `_check_vlm_relevance()` 判断图片与查询的相关性,不相关者 -3.0 惩罚 | | P3 | CrossEncoder 排名 | CE<0 移除,CE 0~2 保留无加成,CE>2 加分 | | P4 | 多样性去重 | 同文档多图片按分数保留 TopN,避免单一文档图片垄断 | **CrossEncoder 图片评分特征**:CrossEncoder 对图片/图表的打分系统性低于文本(0.002-0.08 vs 0.3-0.9),因此 `chart_contexts` 使用 `min_score * 0.5` 的降门槛策略。 **VLM 相关性检查**:`_check_vlm_relevance()` 使用 VLM 模型判断图片内容与查询的相关性,阈值 0.3,不相关图片会被施加 -3.0 的分数惩罚。 ### 6.2 救援管线 当常规检索召回不足时,以下救援机制依次尝试补充结果: | 救援类型 | 触发条件 | 策略 | |----------|----------|------| | BM25 散度救援 | 向量与 BM25 结果差异大 | 补充 BM25 独有但向量遗漏的高分结果 | | 词法匹配救援 | 专有名词/术语精确匹配 | 对查询中的关键术语做精确匹配补充 | | 章节聚类救援 | 同章节切片被部分召回 | 补充同章节内相邻切片(rerank 后、扩展前) | | 表格救援 | 表格数据被碎片化 | 对 table 类型切片做整表补充 | --- ## 七、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" # mimo API 密钥 DASHSCOPE_BASE_URL = "https://token-plan-cn.xiaomimimo.com/v1" DASHSCOPE_MODEL = "mimo-v2.5" # 文本生成模型(主力 LLM) INTENT_MODEL = "mimo-v2.5" # 意图分析模型(轻量快速) VLM_MODEL = "mimo-v2.5" # 视觉语言模型(图片描述) RAG_CHAT_MODEL = "mimo-v2.5" # RAG 对话模型 # Embedding 模型(本地) EMBEDDING_MODEL_PATH = "models/bge-base-zh-v1.5" # Reranker 模型 RERANK_MODEL_PATH = "models/bge-reranker-base" # 本地备选 RERANK_CLOUD_MODEL = "xop3qwen8breranker" # 云端(讯飞云) RERANK_CLOUD_BASE_URL = "https://maas-api.cn-huabei-1.xf-yun.com/v1/rerank" ``` > **注意**:百炼 API(DashScope)额度用尽后,所有 LLM 调用已统一切换至 mimo API(xiaomimimo.com)。`get_intent_client()` 也使用 `DASHSCOPE_API_KEY` / `DASHSCOPE_BASE_URL`(即 mimo API),不再使用百炼 API。 ### 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 = "xop3qwen8breranker" 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 语义去重 | | 重排序 | 无 | 云端 xop3qwen8breranker 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` | `"xop3qwen8breranker"` | 云端模型名称 | | `RERANK_CLOUD_API_KEY` | 同 `DASHSCOPE_API_KEY` | 云端 API 密钥 | | `RERANK_CLOUD_BASE_URL` | `https://maas-api.cn-huabei-1.xf-yun.com/v1/rerank` | 云端 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.1(2026-06-21)— 模型切换 + 图片检索修复 **模型统一切换**:百炼 API(DashScope)额度用尽后,所有 LLM 调用统一切换至 mimo API(xiaomimimo.com): - `DASHSCOPE_MODEL` / `RAG_CHAT_MODEL` / `INTENT_MODEL` / `VLM_MODEL`:`qwen3.6-flash` / `qwen-turbo` / `qwen-vl-plus` → `mimo-v2.5` - `DASHSCOPE_BASE_URL`:`dashscope.aliyuncs.com` → `token-plan-cn.xiaomimimo.com/v1` - `get_intent_client()`:从百炼 API 切换至 mimo API **Reranker 切换**: - `RERANK_CLOUD_MODEL`:`qwen3-rerank` → `xop3qwen8breranker`(讯飞云) - `RERANK_CLOUD_BASE_URL`:`dashscope.aliyuncs.com` → `maas-api.cn-huabei-1.xf-yun.com/v1/rerank` **图片检索修复**(P0 级): 1. **chart_contexts 降门槛**:CrossEncoder 对图片/图表打分系统性偏低(0.002-0.08 vs 文本 0.3-0.9),原 `min_score=0.05` 过滤掉几乎所有图表。修复为 `min_score * 0.5 = 0.025`。 2. **P0 安全网**:`select_images` 独立于文本上下文管线选择图片,但图片描述可能因 min_score 过滤未进入 LLM 上下文。新增安全网检查:若 `selected_images` 的描述未出现在 `context_text` 中,强制注入。 3. **答案后过滤**(`_filter_images_by_answer`):LLM 生成答案后,根据答案内容过滤不相关图片。注意:若 LLM 因上下文缺失而回答"未找到",会导致正确图片被误过滤(鸡生蛋问题)。 **救援管线**:新增 BM25 散度救援、词法匹配救援、章节聚类救援、表格救援等机制,确保低召回场景下的结果覆盖。 **缓存层扩展**:从四层缓存扩展为五层,新增 ChromaDB 元数据缓存(L5),避免频繁查询 ChromaDB 获取 kb_version 等元数据。 ### 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/管线更新 引入云端 Reranker(现 xop3qwen8breranker/讯飞云,原 qwen3-rerank/DashScope)、ONNX 加速、动态 RRF 权重等。 --- ## 十四、未来规划 ### Redis 缓存外部化 当前五层缓存均为进程内内存存储(L5 ChromaDB 元数据缓存除外),在多 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