fix(boundary): 修复多库边界问题、版本管理及删除清理

多库检索与存储修复:
- RRF 融合去重改用 (collection, chunk_id) 复合键,修复同名文件结果被吞
- DocStore 存储路径加 collection 前缀,修复跨库同名切片数据覆盖
- search_multiple 去重改用复合键
- chunk_id 解析改用 rsplit 兼容下划线文件名

上传与版本管理修复:
- 同名文件上传改为覆盖模式,自动清理旧切片
- 修复首次上传不创建版本记录
- 修复覆盖上传版本号回退到 v1
- sync ADDED 分支改用动态版本号生成
- _generate_version_id 改为基于全部版本递增
- 废止/恢复操作同步 SQLite 版本记录
- mark_document_as_superseded 改为仅更新 SQLite

删除清理修复:
- 删除文档时同步清理 SQLite 版本记录和变更日志
- 删除向量库时同步清理该库所有版本记录
- cleanup 改为清理 SQLite 记录而非 ChromaDB

测试:
- test_version_management.py: 27 条版本管理单元测试
- test_edge_cases.py: 28 条边界用例测试
- test_upload_dedup.py: 5 条上传去重测试
- e2e_risk_test.py: 27 条端到端风险测试

文档:
- 新增风险边界问题修复注意事项.md(面向后端的对接文档)
- 新增向量库边界风险分析.md
- 更新多篇现有文档
This commit is contained in:
lacerate551
2026-06-04 23:58:44 +08:00
parent a1a0814633
commit cb75b9b274
50 changed files with 6385 additions and 6248 deletions

View File

@@ -1,9 +1,9 @@
# Agentic RAG 完整指南
> **版本**: v3.1 (文档勘误修正)
> **版本**: v3.2模型/Reranker/管线更新)
> **生产入口**: `api/chat_routes.py::rag()` → `core/engine.py`(轻量编排,当前启用)
> **备用编排**: `core/agentic.py::AgenticRAG.process()` + 8 个 Mixin完整决策循环未接线
> **最后更新**: 2026-06-03
> **最后更新**: 2026-06-04
>
> ⚠️ 项目存在两套编排,生产 `/rag` 走的不是 `AgenticRAG`——详见下方「一·五、两套编排路径」。
@@ -16,7 +16,7 @@ Agentic RAG 是一个智能问答系统,基于 Mixin 模式组合 8 个功能
| **意图分析** | LLM 驱动的查询改写 + 双层判断(是否需要检索) | `IntentAnalyzer`(独立模块) |
| **查询重写** | 口语化→专业术语、实体补全、指代消解 | `QueryRewriteMixin` |
| **混合检索** | 向量检索 + BM25 + RRF 融合 + Rerank 重排 | `SearchMixin``RAGEngine` |
| **多源融合** | 知识库 + 网络搜索 + 知识图谱,智能处理冲突 | `AnswerMixin` |
| **多源融合** | 知识库 + 网络搜索,智能处理冲突 | `AnswerMixin` |
| **幻觉验证** | 基于参考信息验证答案,防止 LLM 编造 | `AnswerMixin` |
| **引用标注** | 自动标注信息来源和引用编号 | `CitationMixin` |
| **富媒体提取** | 图片/表格的智能提取与展示 | `RichMediaMixin` |
@@ -132,7 +132,7 @@ grep -rn "\.process(" --include="*.py" api/ # /rag、/chat 均无 .proce
│ 3. 知识库检索 (RAGEngine.search_knowledge)│
│ 4. 上下文压缩 (ContextMixin) │
│ 5. 网络搜索 (SearchMixin, 可选) │
│ 6. 图谱检索 (SearchMixin, 可选)
│ 6. 图谱检索已废弃graph/ 目录已清空)
│ 7. 融合答案生成 (AnswerMixin) │
│ 8. 幻觉验证 (AnswerMixin) │
│ 9. 富媒体提取 (RichMediaMixin) │
@@ -152,9 +152,9 @@ grep -rn "\.process(" --include="*.py" api/ # /rag、/chat 均无 .proce
│ └──────┬───────┘ │
│ ↓ │
│ ┌───────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 废止过滤 │→ │ MMR 去重 │→ │ Rerank 重排 │ │
│ └───────────┘ └──────┬───────┘ └──────┬───────┘ │
↓ │
│ │ 废止过滤 │→ │ Rerank 重排 │→ │ MMR 去重 │ │
│ └───────────┘ │ (云端API) │ └──────┬───────┘ │
└──────┬───────┘ ↓ │
│ ┌───────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ FAQ 加权 │→ │ 黑名单过滤 │→ │ 时间衰减 │ │
│ └───────────┘ └──────────────┘ └──────┬───────┘ │
@@ -177,7 +177,7 @@ grep -rn "\.process(" --include="*.py" api/ # /rag、/chat 均无 .proce
```python
class AgenticRAG(
QueryRewriteMixin, # 查询重写:口语化→专业术语、实体补全
SearchMixin, # 检索功能:网络搜索、图谱检索
SearchMixin, # 检索功能:网络搜索
AnswerMixin, # 答案生成:融合回答、幻觉验证
CitationMixin, # 引用处理:来源标注、引用编号
RichMediaMixin, # 富媒体:图片/表格提取
@@ -208,8 +208,9 @@ POST /rag (SSE 流式)
│ └─[DEV] 发 SSE: intent_result
├─ 2. 混合检索 search_hybrid() → engine.search_knowledge() # chat_routes:1300
│ (内部:向量+BM25+RRF+废止过滤+章节过滤+MMR+Rerank
│ +FAQ加权+黑名单+时间衰减+上下文扩展+自适应TopK
│ (内部:向量+BM25+RRF+废止过滤+章节过滤
│ +云端Rerank+MMR去重+FAQ加权+黑名单+时间衰减
│ +上下文扩展+自适应TopK
│ └─[DEV] 发 SSE: retrieval_debug
├─ 3. 提取上下文/来源(按 source 去重doc_type 驱动溯源展示) # chat_routes:1362
@@ -313,26 +314,25 @@ search_knowledge(query, top_k=30)
├─ 7. 章节过滤(查询提到章节时优先匹配)
├─ 8. 上下文扩展MMR 前,防止邻居被去重
├─ 9. MMR 去重(前置到 Rerank 前)
│ ├─ 语义向量版MMR_USE_EMBEDDING=True
│ └─ 文本 Jaccard 版MMR_USE_EMBEDDING=False
├─ 10. ★ Rerank 重排 ★
├─ 8. ★ Rerank 重排 ★(云端 DashScope qwen3-rerank 或本地 BGE
│ └─ rerank_results(query, results, top_k)
│ └─ 由 RERANK_BACKEND 控制local/cloud/fallback
├─ 11. FAQ 分数加权Score Boosting
├─ 9. MMR 去重
│ ├─ 语义向量版MMR_USE_EMBEDDING=True
│ └─ 文本 Jaccard 版MMR_USE_EMBEDDING=False生产推荐
├─ 12. 黑名单过滤(负反馈降权
├─ 10. FAQ 分数加权Score Boosting
├─ 13. 时间衰减Time Decay
├─ 11. 黑名单过滤(负反馈降权
├─ 14. 上下文扩展Rerank 后,补充相邻切片
├─ 12. 时间衰减Time Decay
├─ 15. 自适应 TopK根据置信度调整返回数量
├─ 13. 上下文扩展Rerank 后,补充相邻切片
─ 16. 缓存写入 → 返回结果
─ 14. 自适应 TopK根据置信度调整返回数量
└─ 15. 缓存写入 → 返回结果
```
### 4.2 混合检索代码示例
@@ -353,11 +353,11 @@ image_results = collection.query(query_embeddings=[query_vector], n_results=5, w
# RRF 融合(动态权重)
fused = reciprocal_rank_fusion([vector_results, bm25_results], weights=[vector_w, bm25_w])
# MMR 去重
mmr_results = mmr_rerank(query_emb, candidates, top_k=30, lambda_param=0.5)
# Rerank 重排(云端 DashScope qwen3-rerank 或本地 BGE
reranked = rerank_results(query, fused, top_k=15)
# Rerank 重排
final = rerank_results(query, mmr_results, top_k=15)
# MMR 去重
mmr_results = mmr_rerank(query_emb, reranked, top_k=30, lambda_param=0.5)
```
### 4.3 RRF 融合算法
@@ -378,19 +378,42 @@ RRF分数 = Σ (权重 / (k + 排名位置))
### 4.4 Rerank 重排
**模型**: `BAAI/bge-reranker-base`CrossEncoder 交叉编码器,~278MB
**后端**: 支持三种模式,由 `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") # local / cloud / fallback
RERANK_CLOUD_MODEL = "qwen3-rerank" # DashScope 云端 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
scores = self.reranker.predict(pairs) # CrossEncoder / ONNXReranker / CloudReranker
sorted_indices = np.argsort(scores)[::-1]
# 返回 top_k 个最高分结果
```
**调用位置**: `core/engine.py``search_knowledge()``_search_multi_kb()`MMR 去重之执行。
**调用位置**: `core/engine.py``search_knowledge()``_search_multi_kb()` 中,RRF 融合 + 废止/章节过滤之后、MMR 去重之执行。
**ONNX 加速**: `ONNXReranker` 使用 `optimum.onnxruntime` 进行推理CPU 上比原生 PyTorch 快 2-3 倍。首次使用时自动将 PyTorch 模型导出为 ONNX 格式。
**引擎初始化顺序**: `RAGEngine.__init__()` 中按 `RERANK_BACKEND` 决定加载策略:
- `cloud` / `fallback`:先尝试创建 `CloudReranker`,需要 `RERANK_CLOUD_API_KEY`
- `local` / `fallback`(云端失败时):加载本地 `BAAI/bge-reranker-base`,支持 ONNX 加速
---
@@ -444,7 +467,7 @@ def process(self, query, verbose=True, history=None,
| 3 | `engine.search_knowledge()` / `engine.search_multiple()` | 知识库检索(含向量+BM25+RRF+MMR+Rerank |
| 4 | `_compress_contexts()` | 上下文压缩Rerank 阈值过滤) |
| 5 | `_web_search_flow()` | 网络搜索(可选,需 SERPER_API_KEY |
| 6 | `_graph_search()` | 图谱检索(可选,需 Neo4j |
| 6 | ~~`_graph_search()`~~ | ~~图谱检索(已废弃graph/ 目录已清空)~~ |
| 7 | `_generate_fused_answer()` | 融合答案生成(多源信息+冲突处理) |
| 8 | `_verify_and_refine_answer()` | 幻觉验证(防止 LLM 编造) |
| 9 | `_extract_rich_media()` | 富媒体提取(图片/表格) |
@@ -502,7 +525,7 @@ for token in engine.generate_answer_stream(query, context, history=history):
```python
from core.agentic import AgenticRAG
rag = AgenticRAG(max_iterations=3, enable_web_search=True, enable_graph=True)
rag = AgenticRAG(max_iterations=3, enable_web_search=True)
result = rag.process("出差补助标准是什么?")
print(f"答案: {result['answer']}")
@@ -521,9 +544,10 @@ print(f"引用: {result['citations']}")
# config.py
DASHSCOPE_API_KEY = "your-api-key" # 通义千问 API
DASHSCOPE_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
DASHSCOPE_MODEL = "qwen-max" # 文本生成模型
DASHSCOPE_VL_MODEL = "qwen-vl-plus" # 视觉模型(图片描述
RAG_CHAT_MODEL = "qwen-max" # RAG 对话模型
DASHSCOPE_MODEL = "qwen3.6-flash" # 文本生成模型(主力 LLM
INTENT_MODEL = "qwen-turbo" # 意图分析模型(轻量快速
VLM_MODEL = "qwen-vl-plus" # 视觉语言模型(图片描述)
RAG_CHAT_MODEL = "qwen3.6-flash" # RAG 对话模型
```
### 8.2 检索参数
@@ -540,9 +564,11 @@ RECALL_MULTIPLIER = 3 # 候选池最小倍数
# 重排序
USE_RERANK = True # 启用重排序
RERANK_BACKEND = "local" # "local"=本地模型, "cloud"=云端API, "fallback"=优先云端失败回退本地
RERANK_CLOUD_MODEL = "qwen3-rerank" # 云端 Rerank 模型DashScope API
RERANK_CANDIDATES = 20 # 送入重排序的候选数
RERANK_TOP_K = 15 # 重排序后保留数
RERANK_USE_ONNX = True # ONNX 加速(环境变量控制,默认开启)
RERANK_USE_ONNX = True # ONNX 加速(仅本地模式,环境变量控制,默认开启)
# RRF 融合
RRF_K = 60 # RRF 常数
@@ -550,7 +576,7 @@ DYNAMIC_RRF_ENABLED = True # 动态权重
# MMR 去重
MMR_ENABLED = True
MMR_USE_EMBEDDING = True # True=语义向量False=文本相似度(快
MMR_USE_EMBEDDING = False # True=语义向量False=文本Jaccard相似度生产推荐
MMR_TOP_K = 30 # MMR 保留数
MMR_LAMBDA = 0.5 # 相关性 vs 多样性权衡
```
@@ -596,7 +622,7 @@ core/ # RAG 核心引擎
├── agentic.py # AgenticRAG 主类Mixin 组合)
├── agentic_base.py # 基础常量与条件导入
├── agentic_query.py # QueryRewriteMixin查询重写
├── agentic_search.py # SearchMixin网络/图谱检索)
├── agentic_search.py # SearchMixin网络索)
├── agentic_answer.py # AnswerMixin答案生成、幻觉验证
├── agentic_citation.py # CitationMixin引用标注
├── agentic_media.py # RichMediaMixin富媒体提取
@@ -656,11 +682,7 @@ services/ # 业务服务层
├── feedback.py # 反馈处理服务
└── outline.py # 大纲生成服务
graph/ # 知识图谱
├── graph_manager.py # 图谱管理器Neo4j 连接/操作)
├── entity_extractor.py # 实体提取器
├── graph_build.py # 图谱构建(从文档→实体→关系)
└── graph_rag.py # 图谱 RAG 检索
graph/ # 已清空Graph RAG 不再使用)
auth/ # 认证与安全
├── gateway.py # API 网关认证
@@ -719,11 +741,11 @@ config/ # 运行时配置
| 检索方式 | 单一向量检索 | 向量 + BM25 + FAQ + 图片独立召回 |
| 融合算法 | 无 | RRF 动态权重融合 |
| 去重 | 无 | MMR 语义去重 |
| 重排序 | 无 | CrossEncoder Rerank支持 ONNX 加速 |
| 重排序 | 无 | 云端 qwen3-rerank API支持本地 BGE 回退 |
| 问题分解 | 无 | 自动拆分对比/推理类查询 |
| 闲聊处理 | 无 | 意图分析自动判断 |
| 网络搜索 | 无 | 可选支持Serper API |
| 知识图谱 | 无 | 可选支持Neo4j |
| 知识图谱 | 无 | ~~可选支持Neo4j~~已废弃graph/ 目录已清空) |
| 幻觉验证 | 无 | 基于参考信息的答案验证 |
| 置信度门控 | 无 | Reranker 分数驱动,低分触发补救 |
| 缓存 | 无 | 三层缓存 + 语义缓存 |
@@ -741,16 +763,16 @@ Rerank 在系统中有 **两个独立调用路径**
| 路径 | 位置 | 说明 |
|------|------|------|
| 主检索管线 | `engine.rerank_results()` | MMR 去重执行,对 30 个候选重排取 top_k |
| 主检索管线 | `engine.rerank_results()` | RRF 融合后、MMR 去重执行,对候选重排取 top_k |
| 置信度门控 | `confidence_gate._compute_scores()` | 直接调用 `reranker.predict()`,可能重复推理 |
### 11.2 性能瓶颈
| 瓶颈 | 严重程度 | 说明 |
|------|---------|------|
| Rerank 缓存命中率偏低 | 🟡 中 | `rerank_results()` 已正确调用缓存读写,但缓存 key 基于 `query + sorted(doc_ids)` 精确匹配,MMR 去重产出稍有不同就无法命中 |
| Rerank 缓存命中率偏低 | 🟡 中 | `rerank_results()` 已正确调用缓存读写,但缓存 key 基于 `query + sorted(doc_ids)` 精确匹配,RRF 融合产出稍有不同就无法命中 |
| 置信度门控重复推理 | 🟡 中 | 同一 query+documents 可能被 Rerank 两次(当前仅备用路径使用,暂未影响生产) |
| 无性能计时 | 🟡 中 | `rerank_results()` 内无计时代码,无法量化耗时占比 |
| ~~无性能计时~~ | ~~🟡 中~~ | 已修复:`rerank_results()` 现返回 `_rerank_time_ms` 计时字段 |
| 查询分类器策略未生效 | 🟢 低 | `QueryClassifier` 定义的差异化 rerank 参数未传递到引擎 |
### 11.3 Rerank 配置参数
@@ -758,11 +780,16 @@ Rerank 在系统中有 **两个独立调用路径**
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `USE_RERANK` | `True` | 总开关 |
| `RERANK_MODEL_PATH` | `models/bge-reranker-base` | 模型路径 |
| `RERANK_BACKEND` | `"local"` | 后端选择:`local`=本地模型, `cloud`=云端API, `fallback`=优先云端失败回退本地 |
| `RERANK_CLOUD_MODEL` | `"qwen3-rerank"` | 云端 Rerank 模型名称DashScope API |
| `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` | 本地模型路径(仅 local/fallback 模式) |
| `RERANK_CANDIDATES` | `20` | 送入 Rerank 的候选数 |
| `RERANK_TOP_K` | `15` | Rerank 后保留数 |
| `RERANK_USE_ONNX` | `True`(环境变量默认) | ONNX 加速开关 |
| `RERANK_DEVICE` | 跟随 `DEVICE` | 设备选择 |
| `RERANK_USE_ONNX` | `True`(环境变量默认) | ONNX 加速开关(仅本地模式) |
| `RERANK_DEVICE` | 跟随 `DEVICE` | 设备选择(仅本地模式) |
| `RERANK_THRESHOLD` | `0.3` | 上下文过滤阈值 |
| `RERANK_CACHE_ENABLED` | `True` | 缓存开关(已在 `rerank_results()` 中使用) |
@@ -820,8 +847,8 @@ Agentic RAG 构建了动态的决策闭环,核心组件包括:
- **意图分析器**LLM 驱动的双层判断,替代硬编码规则
- **查询重写器**:口语化→专业术语、实体补全、指代消解
- **混合检索引擎**:向量 + BM25 + FAQ + 图片独立召回 + RRF 融合
- **MMR 去重**:平衡相关性与多样性,前置到 Rerank 前减少输入量
- **Rerank 重排**CrossEncoder 精确排序,支持 ONNX 加速
- **MMR 去重**平衡相关性与多样性Rerank 后进一步精炼结果
- **Rerank 重排**云端 qwen3-rerank API 精确排序,支持本地 BGE 回退
- **置信度门控**Reranker 分数驱动,低分触发补救流程
- **幻觉验证**:基于参考信息验证答案,防止 LLM 编造
@@ -837,8 +864,8 @@ Agentic RAG 构建了动态的决策闭环,核心组件包括:
- **多路召回与融合**:向量 + BM25 + FAQ + 图片独立召回
- **动态 RRF 权重**:查询类型/长度驱动的权重调整
- **MMR 去重**前置到 Rerank 前,减少 Rerank 输入量召回100 → MMR取30 → Rerank取15
- **Rerank 重排**CrossEncoder 精排,置信度门控过滤低质量结果
- **MMR 去重**Rerank 后进一步精炼平衡相关性与多样性召回100 → Rerank取15 → MMR精炼
- **Rerank 重排**云端 qwen3-rerank 精排,置信度门控过滤低质量结果
#### 3. 检索后:质量评估与自我迭代