Files
rag/docs/RAG数据流程.md
lacerate551 cb75b9b274 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
- 更新多篇现有文档
2026-06-04 23:58:44 +08:00

610 lines
22 KiB
Markdown
Raw Permalink 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 数据流程
> 本文档基于 v7.0.0 架构,详细梳理 RAG 系统从用户查询到回答生成的完整数据流。
> 涵盖意图分析、混合检索、云端重排序、MMR 去重、上下文构建、LLM 生成和引用溯源等核心环节。
---
## 一、概述
### 1.1 检索管线总览
```
用户查询
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. 意图分析 (intent_analyzer) │
│ 问题改写 · 指代消解 · 是否需要检索 · 重复提问检测 │
│ 模型qwen-turbo │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 2. 混合检索 (engine.search_knowledge) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 向量检索 │ │ BM25 检索 │ │
│ │ (ChromaDB) │ │ (关键词匹配) │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ └────────┬──────────┘ │
│ ▼ │
│ RRF 融合 (Reciprocal Rank Fusion) │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 3. 云端 Rerank (DashScope API) │
│ 模型qwen3-rerank │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 4. MMR 去重 (文本 Jaccard 模式) │
│ MMR_USE_EMBEDDING=false │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 5. 上下文扩展 + 图片选择 │
│ 上下文增强 · 图文关联补充 · 懒加载 VLM 描述 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 6. LLM 生成 (AgenticRAG 引擎) │
│ 模型qwen3.6-flash主 LLM/ qwen-vl-plusVLM
│ 流式输出 · 引用标注 │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 7. 返回结果 │
│ { answer, sources, images } │
└─────────────────────────────────────────────────────────────────────────┘
```
### 1.2 模型配置一览
| 用途 | 模型 | 说明 |
|------|------|------|
| 主 LLM | qwen3.6-flash | 回答生成、上下文理解 |
| 意图分析 | qwen-turbo | 轻量快速,用于问题改写与意图判断 |
| VLM | qwen-vl-plus | 图片理解与描述生成 |
| 云端重排序 | qwen3-rerank | DashScope API 调用,替代本地 BGE-reranker |
### 1.3 v7.0.0 架构变更要点
- **Reranker**:从本地 BGE-reranker 切换为云端 DashScope APIqwen3-rerank
- **MMR 去重**:使用文本相似度模式(`MMR_USE_EMBEDDING=false`),基于 Jaccard 系数
- **Agentic 引擎**拆分为多个子模块agentic_search / agentic_answer / agentic_citation 等)
- **Graph RAG**:模块已清空,不再使用
---
## 二、请求入口
### 2.1 API 路由
**入口文件**`api/chat_routes.py`
用户通过 API 发送查询请求,由 `generate()` 函数统一调度:
```
POST /rag 或 POST /chat
Body: { "query": "用户问题", "kb_name": "知识库名称" }
```
`generate()` 的职责:
1. 调用意图分析模块,获取改写后的查询与检索决策
2. 若需要检索,调用混合检索管线
3. 执行图片选择与上下文构建
4. 调用 LLM 生成回答(流式输出)
5. 组装最终响应(回答 + 引用来源 + 图片)
### 2.2 请求数据结构
```python
# 请求
{
"query": "蓄水以来逐年发电量",
"kb_name": "public_kb",
"history": [...] # 可选:对话历史
}
# 响应
{
"type": "finish",
"answer": "完整回答文本",
"sources": [...], # 引用来源列表
"images": [...] # 精选图片列表
}
```
---
## 三、意图分析
**入口文件**`core/intent_analyzer.py`
**使用模型**qwen-turbo轻量快速
### 3.1 核心功能
意图分析器在检索前对用户查询进行预处理,输出结构化决策:
```python
@dataclass
class IntentAnalysis:
rewritten_query: str # 改写后的查询(指代消解、省略补全)
use_context: bool # 是否使用历史上下文
need_retrieval: bool # 是否需要检索知识库
```
### 3.2 处理逻辑
| 功能 | 说明 |
|------|------|
| 问题改写 | 指代消解("它" → 具体实体)、省略补全(补全缺失主语) |
| 上下文判断 | 根据对话历史决定是否需要前文信息 |
| 检索决策 | 判断是否需要查询知识库(闲聊类问题可跳过) |
| 重复提问检测 | 用户重复提问相同问题时,强制设置 `need_retrieval=true` |
### 3.3 重复提问规则
当用户重复提问相同或相似问题时,意图分析器必须设置 `need_retrieval = true`。原因:用户可能对之前的回答不满意,或之前的回答包含错误信息,需要重新检索以获取更准确的结果。
---
## 四、检索阶段
**入口文件**`core/engine.py`
**核心函数**`search_knowledge()`
### 4.1 查询缓存检查
检索前先检查缓存,命中则直接返回历史结果,避免重复计算:
```python
cached = cache.get_query_result(query, kb_name)
if cached:
return cached
```
### 4.2 向量检索
使用 embedding 模型将查询编码为向量,在 ChromaDB 中执行 ANN 检索:
```python
query_vector = embedding_model.encode(query)
text_results = collection.query(
query_embeddings=[query_vector],
n_results=100 # recall_k召回候选数量
)
```
**图片独立召回**:同时对图片切片进行独立检索,保证图片有足够的召回机会:
```python
image_results = collection.query(
query_embeddings=[query_vector],
where={'chunk_type': {'$in': ['image', 'chart', 'table']}},
n_results=5
)
```
### 4.3 BM25 关键词检索
基于 BM25 算法执行关键词匹配检索,弥补向量检索在精确匹配上的不足:
```python
bm25_results = bm25_index.search(query, top_k=100)
```
### 4.4 RRF 融合
使用 Reciprocal Rank Fusion 算法将向量检索和 BM25 检索的结果进行融合排序:
```python
fused_results = reciprocal_rank_fusion(
[text_results, image_results, bm25_results],
weights=[VECTOR_WEIGHT, IMAGE_WEIGHT, BM25_WEIGHT]
)
```
**融合参数**
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `recall_k` | 100 | 各通道召回候选数量 |
| `VECTOR_WEIGHT` | 0.6 | 向量检索权重 |
| `BM25_WEIGHT` | 0.4 | BM25 检索权重 |
---
## 五、重排序
### 5.1 云端 Rerank
**v7.0.0 变更**:从本地 BGE-reranker 切换为云端 DashScope API。
**调用模型**qwen3-rerank
RRF 融合后的候选结果通过云端 Reranker 进行精排,基于查询与文档的语义相关性重新打分排序:
```python
reranked = rerank_results(
query,
fused_results,
model="qwen3-rerank", # DashScope API
top_k=15
)
```
### 5.2 与旧版的区别
| 对比项 | 旧版(本地 BGE-reranker | v7.0.0(云端 qwen3-rerank |
|--------|--------------------------|---------------------------|
| 部署方式 | 本地模型加载 | DashScope API 远程调用 |
| 资源占用 | 需要 GPU 显存 | 无本地资源消耗 |
| 模型能力 | BGE-reranker较小 | qwen3-rerank更强 |
| 延迟 | 低(本地推理) | 中等(网络往返) |
---
## 六、MMR 去重
### 6.1 配置
v7.0.0 采用文本相似度模式进行 MMRMaximal Marginal Relevance去重
```
MMR_USE_EMBEDDING = false
```
### 6.2 工作原理
使用文本 Jaccard 相似度(而非 embedding 向量余弦相似度)来衡量候选文档之间的重复程度:
```python
def _apply_mmr(query, candidates, top_k=30, lambda_param=0.7):
"""
MMR 去重:在相关性和多样性之间取得平衡
lambda_param=0.7: 70% 权重给相关性30% 权重给多样性
相似度度量:文本 Jaccard 系数(基于词集合交集/并集)
"""
selected = []
for candidate in candidates:
if not selected:
selected.append(candidate)
continue
# Jaccard 相似度(文本模式)
max_sim = max(
jaccard_similarity(candidate.tokens, s.tokens)
for s in selected
)
mmr_score = lambda_param * relevance - (1 - lambda_param) * max_sim
if mmr_score > threshold:
selected.append(candidate)
return selected[:top_k]
```
### 6.3 选择文本模式的原因
- **速度更快**:无需计算 embedding 向量之间的余弦相似度
- **效果直观**Jaccard 系数直接反映文本内容的重叠程度
- **避免向量偏差**embedding 模型可能对格式化内容(如图片描述)产生不准确的相似度
---
## 七、上下文构建
### 7.1 检索结果处理
**入口文件**`api/chat_routes.py`
将检索结果转换为 LLM 可用的上下文列表:
```python
contexts = []
for result in search_results:
meta = result['metadata']
if meta['chunk_type'] in ('image', 'chart'):
# 图片切片:使用完整描述(而非轻量描述)
doc = meta.get('full_description', result['document'])
else:
doc = result['document']
contexts.append({'doc': doc, 'meta': meta})
```
### 7.2 懒加载增强
对没有 VLM 描述的图片切片,按需调用 VLM 生成更精准的语义描述:
```python
enhance_retrieved_chunks(contexts, query, kb_name)
# 对缺少 VLM 描述的图片 → 调用 qwen-vl-plus 生成描述
```
### 7.3 图片选择
**核心函数**`select_images()`
从检索结果中筛选与查询最相关的图片:
**步骤一:意图检测**
| 查询类型 | 参数调整 |
|----------|----------|
| 精确图号查询("图2.3" | `MAX_IMAGES=2, MIN_SCORE=5.0` |
| 弱图片意图("发电量图" | `MAX_IMAGES=1` |
| 普通查询 | `MAX_IMAGES=2` |
**步骤二:提取图表引用**
从 top 5 文本块中提取 "见图2.3"、"如表2.2" 等引用,建立图号与来源文件的映射:
```python
referenced_figures = {'2.3': {'source_file': 'xxx.pdf'}, ...}
```
**步骤三:图片相关性打分**
`score_image_relevance()` 打分规则:
| 匹配项 | 加分 |
|--------|------|
| 图号精确匹配(查询中有"图2.3" | +10 分 |
| 表号精确匹配 | +10 分 |
| 关键词匹配("发电量"等) | +2 分/个 |
| 字符重叠 | +0.2 分/字符 |
| 章节匹配 | +1.5 分 |
| 图片类型chart > image | +2 / +1 分 |
| 向量相似度 | +2 分(最高) |
| 引用匹配(需章节相关) | +8 分 |
**步骤四:图文关联补充**
遍历 top 5 文本块中引用的图表编号,查找对应的图片切片并补充到结果中。
**步骤五:返回 top N 图片**
```python
scored_images.sort(key=lambda x: x['score'], reverse=True)
return scored_images[:MAX_IMAGES]
```
### 7.4 构建 LLM Prompt
```python
# 文本上下文
context_text = "\n\n".join([ctx['doc'] for ctx in contexts[:5]])
# 图片信息
if selected_images:
image_info = "【可用图片】\n" + 图片描述列表
# 最终上下文
enhanced_context = context_text + image_info
```
---
## 八、LLM 生成
### 8.1 AgenticRAG 引擎
v7.0.0 将 Agentic 引擎拆分为独立的子模块,各司其职:
| 子模块 | 职责 |
|--------|------|
| `agentic_search.py` | 检索调度:管理多轮检索、查询分解 |
| `agentic_answer.py` | 回答生成:基于上下文生成最终回答 |
| `agentic_citation.py` | 引用标注:在回答中插入来源引用标记 |
| `agentic_context.py` | 上下文管理:上下文窗口控制、截断策略 |
| `agentic_query.py` | 查询处理:查询改写、多查询生成 |
| `agentic_media.py` | 多媒体处理:图片理解、表格解析 |
| `agentic_quality.py` | 质量控制:回答质量评估、幻觉检测 |
| `agentic_meta.py` | 元数据管理:知识库信息、检索统计 |
### 8.2 流式生成
使用 SSEServer-Sent Events实现流式输出
```python
for token in engine.generate_answer_stream(query, enhanced_context):
yield token # 逐 token 推送给前端
```
**主 LLM 模型**qwen3.6-flash
**VLM 模型**qwen-vl-plus处理图片理解任务
### 8.3 回答结构
```python
{
"type": "finish",
"answer": "根据蓄水以来的统计数据,三峡电站逐年发电量呈现波动上升趋势...",
"sources": [
{
"source": "三峡公报_2022.pdf",
"page": 12,
"section": "综述 > 2.3 发电",
"chunk_id": "三峡公报_text_24"
}
],
"images": [
{
"id": "ab77281e7913.jpg",
"url": "/images/ab77281e7913.jpg",
"type": "chart",
"source": "三峡公报_2022.pdf",
"page": 12,
"description": "图2.3 柱状图2003-2022年逐年发电量"
}
]
}
```
---
## 九、引用溯源
### 9.1 引用标注机制
`agentic_citation.py` 负责在回答中插入引用标记,将回答内容与知识库来源关联:
```
根据统计数据[1]2022年三峡电站年度发电量为787.90亿千瓦时[2]。
[1] 来源三峡公报_2022.pdf第12页综述 > 2.3 发电
[2] 来源三峡公报_2022.pdf第15页表2.1
```
### 9.2 引用数据来源
每个引用标记对应检索结果中的一个切片,包含:
| 字段 | 说明 |
|------|------|
| `source` | 源文件名 |
| `page` | 页码 |
| `section` | 章节路径 |
| `chunk_id` | 切片唯一标识 |
| `chunk_type` | 类型text / table / image / chart |
### 9.3 前端引用跳转
前端解析回答中的引用标记(如 `[1]`),渲染为可点击的链接,点击后跳转到对应的来源文件或页面。
---
## 十、文档入库流程(补充参考)
> 入库流程为检索提供数据基础,以下简要说明关键环节。
### 10.1 文档解析
**入口文件**`parsers/mineru_parser.py`
**核心函数**`parse_with_mineru()`
MinerU 解析 PDF/Word/Excel 文件,输出结构化内容:
```
.data/mineru_temp/{file_hash}/
├── auto/
│ ├── {doc_name}.md # Markdown 内容
│ ├── {doc_name}_content_list.json # 结构化内容列表(核心)
│ └── images/ # 提取的图片文件
```
**content_list.json 中的条目类型**
| item_type | 处理方式 | 关键字段 |
|-----------|----------|----------|
| `text` | 文本块 | content, section_path, text_level |
| `table` | 表格 | content, table_html, image_path |
| `image` | 图片 | content(=caption), image_path, context_before/after |
| `chart` | 图表 | content(=caption), image_path, context_before/after |
### 10.2 MinerUChunk 数据结构
```python
@dataclass
class MinerUChunk:
content: str # 文本内容
chunk_type: str # 类型: text, table, image, chart
page_start: int = 1 # 起始页码
page_end: int = 1 # 结束页码
text_level: int = 0 # 标题级别 (0=正文, 1=h1, 2=h2...)
title: str = "" # 标题文本
section_path: str = "" # 章节路径
bbox: Optional[List[float]] = None # 边界框 [x0, y0, x1, y1]
source_file: str = "" # 源文件名
table_html: Optional[str] = None # 表格 HTML
image_path: Optional[str] = None # 图片路径
images: Optional[List[Dict]] = None # 关联图片列表
context_before: str = "" # 图片前的文本上下文
context_after: str = "" # 图片后的文本上下文
```
### 10.3 切片入库
**入口文件**`knowledge/manager.py`
**核心函数**`add_file_to_kb()`
```
MinerUChunk 列表
├── 文本块 → 计算 embedding → 存入 ChromaDB
├── 表格块 → 生成语义增强摘要 → 存入 ChromaDB
└── 图片块 → VLM 缓存检查 → 生成描述 → 存入 ChromaDB
```
**图片描述策略**
1. 优先使用 VLM 缓存描述(语义更丰富,由 qwen-vl-plus 生成)
2. 若无缓存,生成轻量描述(基于文件名 + 章节路径 + 上下文)
轻量描述示例:
```
图表图2.3,位于「综述 > 2.3发电」第12页
前文受长江流域性严重枯水影响2022年三峡电站年度发电量为787.90亿千瓦时...
后文2.4航运 三峡船闸和葛洲坝船闸实行统一调度...
```
VLM 描述示例(更精准):
```
图2.3 柱状图 主要内容描述该柱状图展示了2003年至2022年每年的发电量单位亿千瓦时
发电量在2003年为86.07亿千瓦时随后逐年波动上升至2020年达到峰值1118.02亿千瓦时...
```
### 10.4 ChromaDB 存储结构
| 字段 | 类型 | 说明 |
|------|------|------|
| `ids` | str | 切片唯一标识,如 `doc.pdf_text_24` |
| `embeddings` | List[float] | 向量表示 |
| `documents` | str | 切片内容(文本/描述/摘要) |
| `metadatas` | dict | 元数据(见下表) |
**图片切片 metadata 示例**
```python
{
'source': '三峡公报_2022.pdf',
'page': 12,
'chunk_type': 'chart',
'section': '综述 > 2.3发电',
'figure_number': '2.3',
'image_path': 'ab77281e7913.jpg',
'has_vlm_desc': True,
'preview': '图2.3 柱状图 主要内容...'
}
```
---
## 十一、关键文件索引
| 文件 | 职责 | 关键函数/类 |
|------|------|-------------|
| `api/chat_routes.py` | API 路由与请求调度 | `generate()`, `select_images()`, `score_image_relevance()` |
| `core/intent_analyzer.py` | 意图分析 | `analyze()`, `IntentAnalysis` |
| `core/engine.py` | 检索引擎核心 | `search_knowledge()`, `reciprocal_rank_fusion()`, `rerank_results()` |
| `core/agentic_search.py` | 检索调度 | 多轮检索、查询分解 |
| `core/agentic_answer.py` | 回答生成 | 基于上下文生成最终回答 |
| `core/agentic_citation.py` | 引用标注 | 来源引用标记插入 |
| `core/agentic_context.py` | 上下文管理 | 上下文窗口控制、截断 |
| `core/agentic_query.py` | 查询处理 | 查询改写、多查询生成 |
| `core/agentic_media.py` | 多媒体处理 | 图片理解、表格解析 |
| `core/agentic_quality.py` | 质量控制 | 回答质量评估、幻觉检测 |
| `core/agentic_meta.py` | 元数据管理 | 知识库信息、检索统计 |
| `parsers/mineru_parser.py` | 文档解析 | `parse_with_mineru()`, `MinerUChunk` |
| `knowledge/manager.py` | 知识库管理 | `add_file_to_kb()`, `generate_lightweight_image_description()` |
| `knowledge/lazy_enhance.py` | 懒加载增强 | `lazy_vlm_description()`, `enhance_retrieved_chunks()` |