docs: 模型切换至 mimo-v2.5 + 文档全面更新
- config.py: INTENT_MODEL 从 deepseek-v4-flash 切换至 mimo-v2.5 - config.py: get_intent_client() 从百炼 API 切换至 mimo API - RAG系统完整指南.md: v4.0→v4.1,新增图片检索子系统、P0安全网、 救援管线、五层缓存架构文档;替换所有旧模型名和API地址 - RAG数据流程.md: 完全重写,匹配实际代码逻辑 - curl测试手册.md: 更新模型名和Reranker配置 - MinerU模型部署指南.md: VLM_MODEL 更新为 mimo-v2.5 - 开发与系统模块说明.md: API地址和模型名更新 - 测试指南.md: API地址和模型名更新
This commit is contained in:
549
docs/RAG数据流程.md
549
docs/RAG数据流程.md
@@ -1,7 +1,7 @@
|
||||
# RAG 数据流程
|
||||
|
||||
> 本文档基于 v7.0.0 架构,详细梳理 RAG 系统从用户查询到回答生成的完整数据流。
|
||||
> 涵盖意图分析、混合检索、云端重排序、MMR 去重、上下文构建、LLM 生成和引用溯源等核心环节。
|
||||
> 本文档基于当前代码实际逻辑梳理,详细记录 RAG 系统从用户查询到回答生成的完整数据流。
|
||||
> 涵盖意图分析、混合检索、重排序、救援管道、上下文构建、图片选择与注入、LLM 生成和引用溯源等核心环节。
|
||||
|
||||
---
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 1. 意图分析 (intent_analyzer) │
|
||||
│ 问题改写 · 指代消解 · 是否需要检索 · 重复提问检测 │
|
||||
│ 模型:qwen-turbo │
|
||||
│ 模型:mimo-v2.5 │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
@@ -29,31 +29,37 @@
|
||||
│ └────────┬──────────┘ │
|
||||
│ ▼ │
|
||||
│ RRF 融合 (Reciprocal Rank Fusion) │
|
||||
│ │ │
|
||||
│ 查询缓存检查 (命中则跳过 3-7 步) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 3. 云端 Rerank (DashScope API) │
|
||||
│ 模型:qwen3-rerank │
|
||||
│ 3. 云端 Rerank │
|
||||
│ 模型:xop3qwen8breranker (讯飞云) │
|
||||
│ 结果缓存:rerank_cache (TTL=1h, 版本失效自动清除) │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 4. MMR 去重 (文本 Jaccard 模式) │
|
||||
│ MMR_USE_EMBEDDING=false │
|
||||
│ 4. 救援管道 (chat_routes.py) │
|
||||
│ BM25 分歧救援 · 词法匹配救援 · 章节聚类救援 │
|
||||
│ 目的:挽救被 CrossEncoder 低估但实际相关的切片 │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 5. 上下文扩展 + 图片选择 │
|
||||
│ 上下文增强 · 图文关联补充 · 懒加载 VLM 描述 │
|
||||
│ 5. 上下文构建 + 图片选择 │
|
||||
│ _order_text_contexts_for_prompt · _build_context_with_budget │
|
||||
│ select_images · VLM 相关性筛选 · CrossEncoder 精排 │
|
||||
│ 懒加载 VLM 描述 · 图片描述注入 · P0 安全网 │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ 6. LLM 生成 (AgenticRAG 引擎) │
|
||||
│ 模型:qwen3.6-flash(主 LLM)/ qwen-vl-plus(VLM) │
|
||||
│ 流式输出 · 引用标注 │
|
||||
│ 6. LLM 生成 │
|
||||
│ 模型:mimo-v2.5 │
|
||||
│ 流式输出 · 置信度指令 · 引用标注 · 图片后置过滤 │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
@@ -65,19 +71,26 @@
|
||||
|
||||
### 1.2 模型配置一览
|
||||
|
||||
| 用途 | 模型 | 说明 |
|
||||
|------|------|------|
|
||||
| 主 LLM | qwen3.6-flash | 回答生成、上下文理解 |
|
||||
| 意图分析 | qwen-turbo | 轻量快速,用于问题改写与意图判断 |
|
||||
| VLM | qwen-vl-plus | 图片理解与描述生成 |
|
||||
| 云端重排序 | qwen3-rerank | DashScope API 调用,替代本地 BGE-reranker |
|
||||
| 用途 | 模型 | API 端点 | 说明 |
|
||||
|------|------|----------|------|
|
||||
| 主 LLM | mimo-v2.5 | xiaomimimo.com | 回答生成、上下文理解 |
|
||||
| 意图分析 | mimo-v2.5 | xiaomimimo.com | 问题改写与意图判断 |
|
||||
| VLM | mimo-v2.5 | xiaomimimo.com | 图片理解与描述生成 |
|
||||
| 云端重排序 | xop3qwen8breranker | 讯飞云 Maas API | 替代本地 BGE-reranker |
|
||||
| 向量编码 | bge-base-zh-v1.5 | 本地模型 | 查询与文档 embedding |
|
||||
|
||||
### 1.3 v7.0.0 架构变更要点
|
||||
> 模型可通过环境变量覆盖:`DASHSCOPE_MODEL`、`INTENT_MODEL`、`VLM_MODEL`、`RERANK_CLOUD_MODEL`
|
||||
|
||||
- **Reranker**:从本地 BGE-reranker 切换为云端 DashScope API(qwen3-rerank)
|
||||
- **MMR 去重**:使用文本相似度模式(`MMR_USE_EMBEDDING=false`),基于 Jaccard 系数
|
||||
- **Agentic 引擎**:拆分为多个子模块(agentic_search / agentic_answer / agentic_citation 等)
|
||||
- **Graph RAG**:模块已清空,不再使用
|
||||
### 1.3 关键配置参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `RERANK_CONTEXT_MIN_SCORE` | 0.05 | Rerank 分数低于此值的切片不送入 LLM |
|
||||
| `MAX_CONTEXT_CHUNKS` | 20 | 送给 LLM 的最大文本切片数 |
|
||||
| `CONTEXT_MAX_CHARS` | 8000 | 上下文最大字符数(约 4000 token) |
|
||||
| `CONTEXT_SOFT_LIMIT` | 6000 | 软限制,超过后只接受高分切片组 |
|
||||
| `CONFIDENCE_WARN_THRESHOLD` | 0.15 | top-3 均分低于此值时,提示 LLM 谨慎回答 |
|
||||
| `CONFIDENCE_CAUTION_THRESHOLD` | 0.30 | top-3 均分低于此值时,提示 LLM 优先引用原文 |
|
||||
|
||||
---
|
||||
|
||||
@@ -87,38 +100,41 @@
|
||||
|
||||
**入口文件**:`api/chat_routes.py`
|
||||
|
||||
用户通过 API 发送查询请求,由 `generate()` 函数统一调度:
|
||||
|
||||
```
|
||||
POST /rag 或 POST /chat
|
||||
Body: { "query": "用户问题", "kb_name": "知识库名称" }
|
||||
POST /chat — 普通聊天(不检索知识库,直接 LLM 回答)
|
||||
POST /rag — 知识库问答(检索 + LLM 生成)
|
||||
POST /search — 纯检索(返回切片,不生成回答)
|
||||
```
|
||||
|
||||
`generate()` 的职责:
|
||||
`/rag` 的核心函数 `rag()` 的职责:
|
||||
|
||||
1. 调用意图分析模块,获取改写后的查询与检索决策
|
||||
2. 若需要检索,调用混合检索管线
|
||||
3. 执行图片选择与上下文构建
|
||||
4. 调用 LLM 生成回答(流式输出)
|
||||
5. 组装最终响应(回答 + 引用来源 + 图片)
|
||||
2. 若需要检索,调用混合检索管线(`search_hybrid()`)
|
||||
3. 执行救援管道(BM25 分歧 / 词法匹配 / 章节聚类)
|
||||
4. 执行图片选择(`select_images()`)
|
||||
5. 构建 LLM 上下文(排序 + 预算截断 + 图片注入)
|
||||
6. 调用 LLM 生成回答(流式输出)
|
||||
7. 后置图片过滤(`_filter_images_by_answer()`)
|
||||
8. 组装最终响应(回答 + 引用来源 + 图片)
|
||||
|
||||
### 2.2 请求数据结构
|
||||
|
||||
```python
|
||||
# 请求
|
||||
{
|
||||
"query": "蓄水以来逐年发电量",
|
||||
"kb_name": "public_kb",
|
||||
"message": "用户问题",
|
||||
"history": [...] # 可选:对话历史
|
||||
}
|
||||
|
||||
# 响应
|
||||
{
|
||||
"type": "finish",
|
||||
"answer": "完整回答文本",
|
||||
"sources": [...], # 引用来源列表
|
||||
"images": [...] # 精选图片列表
|
||||
}
|
||||
# SSE 响应事件流
|
||||
data: {"type": "intent_result", "data": {...}}
|
||||
data: {"type": "chunks_retrieved", "data": {...}}
|
||||
data: {"type": "images_selected", "data": {...}}
|
||||
data: {"type": "context_built", "data": {...}}
|
||||
data: {"type": "chunk", "content": "部分回答"}
|
||||
data: {"type": "chunk", "content": "部分回答"}
|
||||
...
|
||||
data: {"type": "finish", "answer": "完整回答", "sources": [...], "images": [...]}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -126,7 +142,7 @@ Body: { "query": "用户问题", "kb_name": "知识库名称" }
|
||||
## 三、意图分析
|
||||
|
||||
**入口文件**:`core/intent_analyzer.py`
|
||||
**使用模型**:qwen-turbo(轻量快速)
|
||||
**使用模型**:mimo-v2.5
|
||||
|
||||
### 3.1 核心功能
|
||||
|
||||
@@ -138,6 +154,8 @@ class IntentAnalysis:
|
||||
rewritten_query: str # 改写后的查询(指代消解、省略补全)
|
||||
use_context: bool # 是否使用历史上下文
|
||||
need_retrieval: bool # 是否需要检索知识库
|
||||
intent: str # 意图类型:factual/comparison/reasoning/instruction
|
||||
sub_queries: list # 子查询列表(对比/复杂查询拆分)
|
||||
```
|
||||
|
||||
### 3.2 处理逻辑
|
||||
@@ -147,7 +165,9 @@ class IntentAnalysis:
|
||||
| 问题改写 | 指代消解("它" → 具体实体)、省略补全(补全缺失主语) |
|
||||
| 上下文判断 | 根据对话历史决定是否需要前文信息 |
|
||||
| 检索决策 | 判断是否需要查询知识库(闲聊类问题可跳过) |
|
||||
| 重复提问检测 | 用户重复提问相同问题时,强制设置 `need_retrieval=true` |
|
||||
| 意图分类 | 识别查询意图(事实查询/对比分析/推理/操作指导) |
|
||||
| 子查询拆分 | 对比类查询拆分为多个子查询并行检索 |
|
||||
| 语义缓存 | 相似度 ≥ 0.92 时复用历史意图分析结果 |
|
||||
|
||||
### 3.3 重复提问规则
|
||||
|
||||
@@ -170,9 +190,16 @@ if cached:
|
||||
return cached
|
||||
```
|
||||
|
||||
缓存层级(5 层):
|
||||
1. `query_cache` — 精确查询结果缓存
|
||||
2. `embedding_cache` — embedding 向量缓存
|
||||
3. `rerank_cache` — rerank 分数缓存(TTL=1h,知识库版本变更自动失效)
|
||||
4. `semantic_cache` — 语义相似查询缓存(阈值 0.92)
|
||||
5. `intent_exact_cache` — 意图分析精确缓存
|
||||
|
||||
### 4.2 向量检索
|
||||
|
||||
使用 embedding 模型将查询编码为向量,在 ChromaDB 中执行 ANN 检索:
|
||||
使用本地 bge-base-zh-v1.5 模型将查询编码为向量,在 ChromaDB 中执行 ANN 检索:
|
||||
|
||||
```python
|
||||
query_vector = embedding_model.encode(query)
|
||||
@@ -211,13 +238,9 @@ fused_results = reciprocal_rank_fusion(
|
||||
)
|
||||
```
|
||||
|
||||
**融合参数**:
|
||||
### 4.5 子查询并行检索
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `recall_k` | 100 | 各通道召回候选数量 |
|
||||
| `VECTOR_WEIGHT` | 0.6 | 向量检索权重 |
|
||||
| `BM25_WEIGHT` | 0.4 | BM25 检索权重 |
|
||||
意图分析器拆分的子查询(如对比类查询)会并行检索,结果合并后再 rerank。
|
||||
|
||||
---
|
||||
|
||||
@@ -225,204 +248,233 @@ fused_results = reciprocal_rank_fusion(
|
||||
|
||||
### 5.1 云端 Rerank
|
||||
|
||||
**v7.0.0 变更**:从本地 BGE-reranker 切换为云端 DashScope API。
|
||||
|
||||
**调用模型**:qwen3-rerank
|
||||
**调用模型**:xop3qwen8breranker(讯飞云 Maas API)
|
||||
|
||||
RRF 融合后的候选结果通过云端 Reranker 进行精排,基于查询与文档的语义相关性重新打分排序:
|
||||
|
||||
```python
|
||||
reranked = rerank_results(
|
||||
query,
|
||||
fused_results,
|
||||
model="qwen3-rerank", # DashScope API
|
||||
top_k=15
|
||||
)
|
||||
reranked = rerank_results(query, fused_results, top_k=15)
|
||||
```
|
||||
|
||||
### 5.2 与旧版的区别
|
||||
**重要特征**:CrossEncoder 对不同类型切片的评分分布差异显著:
|
||||
|
||||
| 对比项 | 旧版(本地 BGE-reranker) | v7.0.0(云端 qwen3-rerank) |
|
||||
|--------|--------------------------|---------------------------|
|
||||
| 部署方式 | 本地模型加载 | DashScope API 远程调用 |
|
||||
| 资源占用 | 需要 GPU 显存 | 无本地资源消耗 |
|
||||
| 模型能力 | BGE-reranker(较小) | qwen3-rerank(更强) |
|
||||
| 延迟 | 低(本地推理) | 中等(网络往返) |
|
||||
| 切片类型 | 典型分数范围 | 说明 |
|
||||
|----------|------------|------|
|
||||
| 文本切片 | 0.3 ~ 0.99 | 正常分布 |
|
||||
| 表格切片 | 0.1 ~ 0.5 | 偏低,有专用救援逻辑 |
|
||||
| 图片/图表切片 | 0.002 ~ 0.08 | 系统性偏低,需要宽松阈值 |
|
||||
|
||||
### 5.2 Rerank 缓存
|
||||
|
||||
- 每个 (query, doc_id) 对的 rerank 分数独立缓存
|
||||
- TTL = 1 小时
|
||||
- 知识库版本号变更时自动失效
|
||||
|
||||
---
|
||||
|
||||
## 六、MMR 去重
|
||||
## 六、救援管道
|
||||
|
||||
### 6.1 配置
|
||||
**入口文件**:`api/chat_routes.py`
|
||||
**目的**:挽救被 CrossEncoder 低估但实际相关的切片
|
||||
|
||||
v7.0.0 采用文本相似度模式进行 MMR(Maximal Marginal Relevance)去重:
|
||||
在 `_order_text_contexts_for_prompt` 之前执行,三个救援管道按顺序运行:
|
||||
|
||||
```
|
||||
MMR_USE_EMBEDDING = false
|
||||
```
|
||||
### 6.1 BM25 分歧救援 `_rescue_bm25_divergence`
|
||||
|
||||
### 6.2 工作原理
|
||||
**触发条件**:BM25 排名 top-3 但 CrossEngineer 评分低于 min_score 的切片
|
||||
|
||||
使用文本 Jaccard 相似度(而非 embedding 向量余弦相似度)来衡量候选文档之间的重复程度:
|
||||
**逻辑**:BM25 是关键词精确匹配的强信号,如果 BM25 认为相关但 CE 误判,提升分数至保底值。
|
||||
|
||||
```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]
|
||||
# 配置
|
||||
BM25_DIVERGENCE_RESCUE_ENABLED = True
|
||||
BM25_DIVERGENCE_MAX_RANK = 3 # 仅救援 BM25 rank <= 3 的切片
|
||||
```
|
||||
|
||||
### 6.3 选择文本模式的原因
|
||||
### 6.2 词法匹配救援 `_rescue_lexical_match`
|
||||
|
||||
- **速度更快**:无需计算 embedding 向量之间的余弦相似度
|
||||
- **效果直观**:Jaccard 系数直接反映文本内容的重叠程度
|
||||
- **避免向量偏差**:embedding 模型可能对格式化内容(如图片描述)产生不准确的相似度
|
||||
**触发条件**:切片文本精确包含查询关键词(bigram 匹配率 > 35%)但 CE 评分低
|
||||
|
||||
**逻辑**:当切片文本直接包含查询中的关键词组合时,说明语义相关,CE 可能因为表述差异低估。
|
||||
|
||||
### 6.3 章节聚类救援 `_rescue_section_cluster`
|
||||
|
||||
**触发条件**:同一 section 内所有切片都被 min_score 过滤("全灭 section")
|
||||
|
||||
**逻辑**:如果同一章节的切片全部被过滤,但其他章节有切片通过,说明 CE 对该章节整体低估。为全灭 section 的切片分配保底分数(0.06)。
|
||||
|
||||
```python
|
||||
# 配置
|
||||
SECTION_CLUSTER_RESCUE_ENABLED = True
|
||||
CLUSTER_RESCUE_FLOOR = 0.06 # 略高于 RERANK_CONTEXT_MIN_SCORE=0.05
|
||||
```
|
||||
|
||||
### 6.4 表格救援 `_rescue_table_chunks`
|
||||
|
||||
**触发条件**:查询涉及表格但上下文中没有表格数据(表格被预算截断)
|
||||
|
||||
**逻辑**:从被截断的切片中补回表格数据。
|
||||
|
||||
---
|
||||
|
||||
## 七、上下文构建
|
||||
|
||||
### 7.1 检索结果处理
|
||||
### 7.1 切片排序与过滤 `_order_text_contexts_for_prompt`
|
||||
|
||||
**入口文件**:`api/chat_routes.py`
|
||||
**核心逻辑**:
|
||||
|
||||
将检索结果转换为 LLM 可用的上下文列表:
|
||||
1. **文本切片 + 表格切片** → 进入 `text_contexts`
|
||||
2. **图片/图表切片** → 进入 `chart_contexts`(独立处理)
|
||||
3. **min_score 过滤**:
|
||||
- `text_contexts`:使用 `RERANK_CONTEXT_MIN_SCORE`(0.05)
|
||||
- 表格保护:同 section 内如有切片通过阈值,table 切片保底分数为 `min_score * 0.3`
|
||||
- `chart_contexts`:使用宽松阈值 `min_score * 0.5`(0.025)
|
||||
- 原因:CrossEncoder 对图片描述评分系统性偏低
|
||||
- 实际内容相关性由 `select_images` 独立评分保证
|
||||
4. **合并**:`text_contexts + chart_contexts[:3]`
|
||||
|
||||
```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 预算构建 `_build_context_with_budget`
|
||||
|
||||
### 7.2 懒加载增强
|
||||
按字符预算构建上下文文本:
|
||||
|
||||
对没有 VLM 描述的图片切片,按需调用 VLM 生成更精准的语义描述:
|
||||
1. 按 (source, section) 分组
|
||||
2. 组内按 chunk_index 排序(保持原文连续性)
|
||||
3. 组间按组内最高 Rerank 分数降序
|
||||
4. 逐组加入直到达到 `CONTEXT_MAX_CHARS`(8000)
|
||||
5. 超过 `CONTEXT_SOFT_LIMIT`(6000)后收紧准入
|
||||
|
||||
```python
|
||||
enhance_retrieved_chunks(contexts, query, kb_name)
|
||||
# 对缺少 VLM 描述的图片 → 调用 qwen-vl-plus 生成描述
|
||||
```
|
||||
**特殊处理**:表格切片不受预算截断(结构化关键内容)
|
||||
|
||||
### 7.3 图片选择
|
||||
|
||||
**核心函数**:`select_images()`
|
||||
### 7.3 图片选择 `select_images()`
|
||||
|
||||
从检索结果中筛选与查询最相关的图片:
|
||||
|
||||
**步骤一:意图检测**
|
||||
**步骤一:动态预算**
|
||||
|
||||
| 查询类型 | 参数调整 |
|
||||
|----------|----------|
|
||||
| 精确图号查询("图2.3") | `MAX_IMAGES=2, MIN_SCORE=5.0` |
|
||||
| 弱图片意图("发电量图") | `MAX_IMAGES=1` |
|
||||
| 普通查询 | `MAX_IMAGES=2` |
|
||||
| 查询类型 | MAX_IMAGES | MIN_SCORE | 检测方式 |
|
||||
|----------|------------|-----------|----------|
|
||||
| 精确图号查询("图2.3") | 2 | 5.0 | 正则匹配 `图\s*(\d+\.?\d*)` |
|
||||
| 有图片数据(检索含 image/chart) | 5 | 2.0 | 检查检索结果 chunk_type |
|
||||
| 文本引用图表 | 3 | 2.0 | 提取 top-5 文本中的图号/表号 |
|
||||
| 普通查询 | 2 | 3.0 | 默认 |
|
||||
|
||||
**步骤二:提取图表引用**
|
||||
|
||||
从 top 5 文本块中提取 "见图2.3"、"如表2.2" 等引用,建立图号与来源文件的映射:
|
||||
从 top 5 文本块中提取 "见图2.3"、"如表2.2" 等引用,建立图号与来源文件的映射。
|
||||
|
||||
```python
|
||||
referenced_figures = {'2.3': {'source_file': 'xxx.pdf'}, ...}
|
||||
```
|
||||
|
||||
**步骤三:图片相关性打分**
|
||||
|
||||
`score_image_relevance()` 打分规则:
|
||||
**步骤三:图片相关性打分 `score_image_relevance()`**
|
||||
|
||||
| 匹配项 | 加分 |
|
||||
|--------|------|
|
||||
| 图号精确匹配(查询中有"图2.3") | +10 分 |
|
||||
| 表号精确匹配 | +10 分 |
|
||||
| 关键词匹配("发电量"等) | +2 分/个 |
|
||||
| 字符重叠 | +0.2 分/字符 |
|
||||
| 章节匹配 | +1.5 分 |
|
||||
| 图片类型(chart > image) | +2 / +1 分 |
|
||||
| 关键词匹配(jieba 分词) | +2 分/个,上限 8 分 |
|
||||
| 字符重叠 | +0.2 分/字符,上限 3 分 |
|
||||
| 章节匹配 | +1.5 分/关键词 |
|
||||
| 图片类型(chart) | +2 分 |
|
||||
| 图片类型(image) | +1 分 |
|
||||
| 向量相似度 | +2 分(最高) |
|
||||
| 引用匹配(需章节相关) | +8 分 |
|
||||
| 引用匹配 + 章节相关 | +8 分 + 5 分 |
|
||||
| VLM 相关性 < 0.3 | -3 分(VLM 描述与查询不相关) |
|
||||
| VLM 相关性 ≥ 0.5 | +2 分 |
|
||||
| 章节不相关 | -5 分(除非被文本引用) |
|
||||
|
||||
**步骤四:图文关联补充**
|
||||
**步骤四:VLM 相关性筛选**
|
||||
|
||||
遍历 top 5 文本块中引用的图表编号,查找对应的图片切片并补充到结果中。
|
||||
对有 VLM 描述的图片,使用关键词重叠率判断描述与查询的相关性:
|
||||
- 重叠率 < 0.3:降分 -3(描述与查询不相关)
|
||||
- 重叠率 ≥ 0.5:加分 +2
|
||||
|
||||
**步骤五:返回 top N 图片**
|
||||
**步骤五:CrossEncoder 精排**
|
||||
|
||||
对 `select_images` 候选用 CE 做语义精排:
|
||||
- CE < 0:直接剔除(语义不相关)
|
||||
- CE 0~2:保留但不加分(弱相关)
|
||||
- CE > 2:加分 `min((ce - 2) / 3, 1) * 5`
|
||||
|
||||
**步骤六:表格嵌入图片补充**
|
||||
|
||||
处理 `images_json` 字段的表格切片,补充表格中嵌入的图片。
|
||||
|
||||
**步骤七:文本引用图片补充**
|
||||
|
||||
从 top 5 文本块中提取 `referenced_images` 字段,补充未选中的关联图片。
|
||||
|
||||
**步骤八:返回 top N**
|
||||
|
||||
```python
|
||||
scored_images.sort(key=lambda x: x['score'], reverse=True)
|
||||
return scored_images[:MAX_IMAGES]
|
||||
```
|
||||
|
||||
### 7.4 构建 LLM Prompt
|
||||
### 7.4 图片描述注入
|
||||
|
||||
```python
|
||||
# 文本上下文
|
||||
context_text = "\n\n".join([ctx['doc'] for ctx in contexts[:5]])
|
||||
**核心逻辑**:将 `select_images` 选中的图片的 `full_description` 注入到 LLM context 中。
|
||||
|
||||
# 图片信息
|
||||
if selected_images:
|
||||
image_info = "【可用图片】\n" + 图片描述列表
|
||||
|
||||
# 最终上下文
|
||||
enhanced_context = context_text + image_info
|
||||
```
|
||||
context_text(文本切片)
|
||||
+
|
||||
【相关图片信息】
|
||||
【图片1】VLM 完整描述(来源:xxx 第N页)
|
||||
【图片2】VLM 完整描述(来源:xxx 第N页)
|
||||
+
|
||||
【回答要求】回答时请简要介绍每张图片的内容和用途。
|
||||
```
|
||||
|
||||
**P0 安全网**(防止图片描述被过滤遗漏):
|
||||
|
||||
1. 若 `selected_images` 非空但 `【相关图片信息】` 未生成 → 补注入所有选中图片描述
|
||||
2. 若已有 `【相关图片信息】` 但遗漏部分图片 → 追加遗漏的描述
|
||||
3. DEV 模式日志:检查所有选中图片的 ID 是否出现在 context 中
|
||||
|
||||
### 7.5 置信度指令
|
||||
|
||||
根据 top-3 切片的平均 Rerank 分数注入不同的回答指令:
|
||||
|
||||
| 置信度范围 | 指令 |
|
||||
|-----------|------|
|
||||
| < 0.15 | "参考资料与问题的相关性较低,仅基于明确信息回答,不足则说明" |
|
||||
| 0.15 ~ 0.30 | "参考资料的相关性一般,优先引用资料原文,避免推测" |
|
||||
| ≥ 0.30 | 无额外指令 |
|
||||
|
||||
---
|
||||
|
||||
## 八、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 流式生成
|
||||
### 8.1 流式生成
|
||||
|
||||
使用 SSE(Server-Sent Events)实现流式输出:
|
||||
|
||||
```python
|
||||
for token in engine.generate_answer_stream(query, enhanced_context):
|
||||
for token in engine.generate_answer_stream(query, enhanced_context, history):
|
||||
yield token # 逐 token 推送给前端
|
||||
```
|
||||
|
||||
**主 LLM 模型**:qwen3.6-flash
|
||||
**VLM 模型**:qwen-vl-plus(处理图片理解任务)
|
||||
**LLM 模型**:mimo-v2.5
|
||||
|
||||
### 8.3 回答结构
|
||||
### 8.2 System Prompt
|
||||
|
||||
```
|
||||
你是一个严谨的知识库问答助手。
|
||||
你必须且只能根据用户提供的【参考资料】回答问题。
|
||||
如果参考资料中有答案,必须引用对应内容回答,并在回答末尾标注引用编号。
|
||||
如果参考资料中确实没有相关信息,简短说明即可,不要编造或补充资料外的内容。
|
||||
禁止使用参考资料以外的知识进行补充或推测。
|
||||
【重要-表格处理规则】当用户询问表格时,必须将 Markdown 表格原样输出...
|
||||
```
|
||||
|
||||
### 8.3 图片后置过滤 `_filter_images_by_answer`
|
||||
|
||||
LLM 生成回答后,根据回答内容对图片做最终过滤:
|
||||
|
||||
1. **候选 ≤ 1 张**:直接返回
|
||||
2. **无图意图早退**:回答和查询都不含图片引用词 → 返回空
|
||||
3. **图号精确豁免**:图片描述包含回答引用的具体图号/表号 → 无条件保留
|
||||
4. **主题一致性**:合并查询+回答关键词,图片描述重叠 ≥ 阈值才保留
|
||||
- 子章节惩罚:图片子章节与主要检索章节不一致时,阈值 +2
|
||||
5. **兜底**:过滤后为空且有图片意图 → 保留分数最高的 1 张
|
||||
|
||||
### 8.4 回答结构
|
||||
|
||||
```python
|
||||
{
|
||||
@@ -443,7 +495,8 @@ for token in engine.generate_answer_stream(query, enhanced_context):
|
||||
"type": "chart",
|
||||
"source": "三峡公报_2022.pdf",
|
||||
"page": 12,
|
||||
"description": "图2.3 柱状图:2003-2022年逐年发电量"
|
||||
"description": "图2.3 柱状图:2003-2022年逐年发电量",
|
||||
"full_description": "...完整 VLM 描述..."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -455,13 +508,10 @@ for token in engine.generate_answer_stream(query, enhanced_context):
|
||||
|
||||
### 9.1 引用标注机制
|
||||
|
||||
`agentic_citation.py` 负责在回答中插入引用标记,将回答内容与知识库来源关联:
|
||||
`_attach_citations()` 负责在回答中插入引用标记,将回答内容与知识库来源关联:
|
||||
|
||||
```
|
||||
根据统计数据[1],2022年三峡电站年度发电量为787.90亿千瓦时[2]。
|
||||
|
||||
[1] 来源:三峡公报_2022.pdf,第12页,综述 > 2.3 发电
|
||||
[2] 来源:三峡公报_2022.pdf,第15页,表2.1
|
||||
根据统计数据[ref:三峡公报_text_24],2022年三峡电站年度发电量为787.90亿千瓦时[ref:三峡公报_table_5]。
|
||||
```
|
||||
|
||||
### 9.2 引用数据来源
|
||||
@@ -476,10 +526,6 @@ for token in engine.generate_answer_stream(query, enhanced_context):
|
||||
| `chunk_id` | 切片唯一标识 |
|
||||
| `chunk_type` | 类型(text / table / image / chart) |
|
||||
|
||||
### 9.3 前端引用跳转
|
||||
|
||||
前端解析回答中的引用标记(如 `[1]`),渲染为可点击的链接,点击后跳转到对应的来源文件或页面。
|
||||
|
||||
---
|
||||
|
||||
## 十、文档入库流程(补充参考)
|
||||
@@ -510,28 +556,7 @@ MinerU 解析 PDF/Word/Excel 文件,输出结构化内容:
|
||||
| `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 切片入库
|
||||
### 10.2 切片入库
|
||||
|
||||
**入口文件**:`knowledge/manager.py`
|
||||
**核心函数**:`add_file_to_kb()`
|
||||
@@ -547,63 +572,95 @@ MinerUChunk 列表
|
||||
```
|
||||
|
||||
**图片描述策略**:
|
||||
1. 优先使用 VLM 缓存描述(语义更丰富,由 qwen-vl-plus 生成)
|
||||
1. 优先使用 VLM 缓存描述(由 mimo-v2.5 生成,语义更丰富)
|
||||
2. 若无缓存,生成轻量描述(基于文件名 + 章节路径 + 上下文)
|
||||
3. VLM 描述异步懒加载:首次检索命中时后台生成,下次查询即可使用
|
||||
|
||||
轻量描述示例:
|
||||
```
|
||||
图表:图2.3,位于「综述 > 2.3发电」,第12页
|
||||
前文:受长江流域性严重枯水影响,2022年三峡电站年度发电量为787.90亿千瓦时...
|
||||
后文:2.4航运 三峡船闸和葛洲坝船闸实行统一调度...
|
||||
```
|
||||
|
||||
VLM 描述示例(更精准):
|
||||
```
|
||||
图2.3 柱状图 主要内容描述:该柱状图展示了2003年至2022年每年的发电量(单位:亿千瓦时)。
|
||||
发电量在2003年为86.07亿千瓦时,随后逐年波动上升,至2020年达到峰值1118.02亿千瓦时...
|
||||
```
|
||||
|
||||
### 10.4 ChromaDB 存储结构
|
||||
### 10.3 ChromaDB 存储结构
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `ids` | str | 切片唯一标识,如 `doc.pdf_text_24` |
|
||||
| `embeddings` | List[float] | 向量表示 |
|
||||
| `documents` | str | 切片内容(文本/描述/摘要) |
|
||||
| `documents` | str | 切片内容(文本/VLM 描述/摘要) |
|
||||
| `metadatas` | dict | 元数据(见下表) |
|
||||
|
||||
**图片切片 metadata 示例**:
|
||||
**图片切片 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 柱状图 主要内容...'
|
||||
'source': '三峡公报_2022.pdf', # 源文件名
|
||||
'page': 12, # 页码
|
||||
'chunk_type': 'chart', # 切片类型
|
||||
'section': '综述 > 2.3发电', # 章节路径
|
||||
'image_path': 'ab77281e7913.jpg', # 图片文件名
|
||||
'has_vlm_desc': True, # 是否有 VLM 描述
|
||||
'vlm_desc': '图2.3 柱状图...', # VLM 描述内容
|
||||
'chunk_id': '三峡公报_chart_5', # 切片唯一标识
|
||||
'version': 'v10', # 知识库版本号
|
||||
'status': 'active', # 状态
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**:ChromaDB metadata 中**没有** `full_description` 字段。图片的完整描述存储在 `vlm_desc` 字段或 `documents` 字段中。`full_description` 仅在 `select_images()` 运行时动态计算。
|
||||
|
||||
### 10.4 懒加载增强
|
||||
|
||||
**入口文件**:`knowledge/lazy_enhance.py`
|
||||
|
||||
对检索命中但没有 VLM 描述的图片切片,在后台线程异步调用 VLM 生成描述:
|
||||
|
||||
```python
|
||||
# 检索命中 → 后台线程调用 VLM → 写入文件缓存 + ChromaDB
|
||||
enhance_retrieved_chunks(contexts, query, kb_name, defer_chromadb=True)
|
||||
```
|
||||
|
||||
- **image/chart 切片**:更新 `ctx['doc']` 字段
|
||||
- **table 切片**:更新 `ctx['image_description']` 字段
|
||||
- **缓存目录**:`.data/cache/vlm/`、`.data/cache/llm/`
|
||||
|
||||
---
|
||||
|
||||
## 十一、缓存系统
|
||||
|
||||
### 11.1 缓存层级
|
||||
|
||||
| 层级 | 类 | 用途 | 容量 | TTL |
|
||||
|------|-----|------|------|-----|
|
||||
| query_cache | LRUCache | 精确查询结果缓存 | 500 | 无 |
|
||||
| embedding_cache | LRUCache | 查询 embedding 向量 | 500 | 无 |
|
||||
| rerank_cache | TTLCache | Rerank 分数缓存 | 500 | 1h |
|
||||
| semantic_cache | SemanticCache | 语义相似查询缓存 | 200 | 无 |
|
||||
| intent_exact_cache | LRUCache | 意图分析精确缓存 | 200 | 无 |
|
||||
|
||||
### 11.2 缓存失效
|
||||
|
||||
- **版本失效**:知识库版本号变更时,query_cache 和 rerank_cache 自动失效
|
||||
- **手动清除**:`POST /cache/clear`(DEV 模式)
|
||||
|
||||
### 11.3 缓存 API
|
||||
|
||||
```
|
||||
GET /cache/stats — 查看缓存统计
|
||||
POST /cache/clear — 清除所有缓存
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十一、关键文件索引
|
||||
## 十二、关键文件索引
|
||||
|
||||
| 文件 | 职责 | 关键函数/类 |
|
||||
|------|------|-------------|
|
||||
| `api/chat_routes.py` | API 路由与请求调度 | `generate()`, `select_images()`, `score_image_relevance()` |
|
||||
| `api/chat_routes.py` | API 路由与请求调度 | `rag()`, `select_images()`, `score_image_relevance()`, `_filter_images_by_answer()`, `_order_text_contexts_for_prompt()`, `_build_context_with_budget()` |
|
||||
| `core/engine.py` | 检索引擎核心 | `search_knowledge()`, `rerank_results()`, `generate_answer_stream()` |
|
||||
| `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` | 元数据管理 | 知识库信息、检索统计 |
|
||||
| `core/bm25_index.py` | BM25 关键词检索 | `BM25Index.search()` |
|
||||
| `core/mmr.py` | MMR 去重 | `mmr_rerank()` |
|
||||
| `core/cache.py` | 缓存管理 | `RAGCacheManager`, `get_cache_manager()` |
|
||||
| `core/semantic_cache.py` | 语义缓存 | `SemanticCache` |
|
||||
| `core/chunker.py` | 语义分块器 | `SemanticChunker` |
|
||||
| `core/llm_utils.py` | LLM 调用工具 | `call_llm()`, `call_llm_stream()` |
|
||||
| `parsers/mineru_parser.py` | 文档解析 | `parse_with_mineru()`, `MinerUChunk` |
|
||||
| `knowledge/manager.py` | 知识库管理 | `add_file_to_kb()`, `generate_lightweight_image_description()` |
|
||||
| `knowledge/manager.py` | 知识库管理 | `add_file_to_kb()`, `KBManager` |
|
||||
| `knowledge/lazy_enhance.py` | 懒加载增强 | `lazy_vlm_description()`, `enhance_retrieved_chunks()` |
|
||||
| `config.py` | 配置集中管理 | 模型/参数/开关 |
|
||||
|
||||
Reference in New Issue
Block a user