多库检索与存储修复: - 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 - 更新多篇现有文档
22 KiB
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-plus(VLM) │
│ 流式输出 · 引用标注 │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 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 API(qwen3-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() 的职责:
- 调用意图分析模块,获取改写后的查询与检索决策
- 若需要检索,调用混合检索管线
- 执行图片选择与上下文构建
- 调用 LLM 生成回答(流式输出)
- 组装最终响应(回答 + 引用来源 + 图片)
2.2 请求数据结构
# 请求
{
"query": "蓄水以来逐年发电量",
"kb_name": "public_kb",
"history": [...] # 可选:对话历史
}
# 响应
{
"type": "finish",
"answer": "完整回答文本",
"sources": [...], # 引用来源列表
"images": [...] # 精选图片列表
}
三、意图分析
入口文件:core/intent_analyzer.py
使用模型:qwen-turbo(轻量快速)
3.1 核心功能
意图分析器在检索前对用户查询进行预处理,输出结构化决策:
@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 查询缓存检查
检索前先检查缓存,命中则直接返回历史结果,避免重复计算:
cached = cache.get_query_result(query, kb_name)
if cached:
return cached
4.2 向量检索
使用 embedding 模型将查询编码为向量,在 ChromaDB 中执行 ANN 检索:
query_vector = embedding_model.encode(query)
text_results = collection.query(
query_embeddings=[query_vector],
n_results=100 # recall_k,召回候选数量
)
图片独立召回:同时对图片切片进行独立检索,保证图片有足够的召回机会:
image_results = collection.query(
query_embeddings=[query_vector],
where={'chunk_type': {'$in': ['image', 'chart', 'table']}},
n_results=5
)
4.3 BM25 关键词检索
基于 BM25 算法执行关键词匹配检索,弥补向量检索在精确匹配上的不足:
bm25_results = bm25_index.search(query, top_k=100)
4.4 RRF 融合
使用 Reciprocal Rank Fusion 算法将向量检索和 BM25 检索的结果进行融合排序:
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 进行精排,基于查询与文档的语义相关性重新打分排序:
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 采用文本相似度模式进行 MMR(Maximal Marginal Relevance)去重:
MMR_USE_EMBEDDING = false
6.2 工作原理
使用文本 Jaccard 相似度(而非 embedding 向量余弦相似度)来衡量候选文档之间的重复程度:
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 可用的上下文列表:
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 生成更精准的语义描述:
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" 等引用,建立图号与来源文件的映射:
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 图片
scored_images.sort(key=lambda x: x['score'], reverse=True)
return scored_images[:MAX_IMAGES]
7.4 构建 LLM Prompt
# 文本上下文
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 流式生成
使用 SSE(Server-Sent Events)实现流式输出:
for token in engine.generate_answer_stream(query, enhanced_context):
yield token # 逐 token 推送给前端
主 LLM 模型:qwen3.6-flash VLM 模型:qwen-vl-plus(处理图片理解任务)
8.3 回答结构
{
"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 数据结构
@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
图片描述策略:
- 优先使用 VLM 缓存描述(语义更丰富,由 qwen-vl-plus 生成)
- 若无缓存,生成轻量描述(基于文件名 + 章节路径 + 上下文)
轻量描述示例:
图表:图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 示例:
{
'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() |