Files
rag/docs/RAG数据流程.md
lacerate551 475900e103 docs: RAG数据流程新增缓存/图片/VLM/切片/性能章节 + 待解决风险项
- RAG数据流程.md: 更新模型配置为 deepseek-v4-flash,补充 v7.1 变更
- 新增十二~十六章节:缓存系统、图片选择管线、VLM 懒加载、
  文档解析与切片、性能插桩
- 待解决风险项.md: 记录 7 个已识别但未实施的风险项
2026-06-21 20:25:25 +08:00

34 KiB
Raw Blame History

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 deepseek-v4-flash 回答生成、上下文理解
意图分析 deepseek-v4-flash JSON 结构化输出(INTENT_RESPONSE_FORMAT=True
VLM qwen-vl-plus 图片理解与描述生成
云端重排序 qwen3-rerank DashScope API 调用
本地重排序 bge-reranker-base ONNX Runtime用于图片 CrossEncoder 二次评分
Embedding bge-base-zh-v1.5 文本向量化

1.3 架构变更要点

  • Reranker:从本地 BGE-reranker 切换为云端 DashScope APIqwen3-rerank本地 reranker 保留用于图片 CrossEncoder 二次评分
  • MMR 去重:使用文本相似度模式(MMR_USE_EMBEDDING=false),基于 Jaccard 系数
  • Agentic 引擎拆分为多个子模块agentic_search / agentic_answer / agentic_citation 等)
  • Graph RAG:模块已清空,不再使用

v7.1 新增变更

  • 性能插桩/rag 流式响应新增 stages_ms 字段,记录 8 个管线阶段耗时intent / cache / search / vlm / image / rescue / llm / post
  • CrossEncoder 图片二次评分select_images 最终排序前新增 P3 阶段,用本地 bge-reranker-base 对 (query, vlm_desc) 做语义精排,映射为 ±5 分调整量CE 负分图片直接剔除
  • 意图分析 JSON 强制输出:新增 INTENT_RESPONSE_FORMAT 配置开关,推理模型需设为 False
  • 缓存管理端点:新增 POST /cache/clear 一键清除精确缓存 + 语义缓存

二、请求入口

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 请求数据结构

# 请求
{
    "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 采用文本相似度模式进行 MMRMaximal 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=5, MIN_SCORE=2.0
文本引用图表 MAX_IMAGES=3, MIN_SCORE=2.0
普通查询 MAX_IMAGES=2, MIN_SCORE=3.0

表格嵌入图片时 MAX_IMAGES 可扩展至 15。

步骤二:提取图表引用

从 top 5 文本块中提取 "见图2.3"、"如表2.2" 等引用,建立图号与来源文件的映射:

referenced_figures = {'2.3': {'source_file': 'xxx.pdf'}, ...}

步骤三:图片相关性打分

score_image_relevance() 打分规则:

匹配项 加分
图号精确匹配(查询中有"图2.3" +10 分
表号精确匹配 +10 分
关键词匹配("发电量"等) +2 分/个,上限 8
字符重叠 +0.2 分/字符,上限 3
章节匹配 +1.5 分
图片类型chart > image +2 / +1 分

步骤四VLM 与章节关联调整

条件 调整
VLM 描述与查询相关(_check_vlm_relevance >= 0.5 +2 分
VLM 描述与查询不相关(< 0.3 -3 分
图片章节与主检索不匹配(section_similarity < 0.3 -5 分
图号/表号被文本引用 + 章节匹配 +8~+13 分
来源文件匹配 primary_sources +2 分

步骤五CrossEncoder 语义精排P3

对所有过线候选图片,用本地 bge-reranker-base 对 (query, vlm_desc_or_caption) 做二次评分:

ce_scores = engine.reranker.predict([(query, desc) for desc in descriptions])
# 映射ce_score > 0 → adjustment ∈ (0, +5]ce_score < 0 → adjustment ∈ [-5, 0)
img['score'] += adjustment
# CE 负分图片直接剔除

步骤六:后置过滤

_filter_images_by_answer() 根据 LLM 回答内容反向筛选图片,过滤回答中未提及的无关图片。

步骤七:返回 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 流式生成

使用 SSEServer-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

图片描述策略

  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 示例

{
    '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()
core/cache.py 三层精确缓存 RAGCacheManager, get_cache_manager()
core/semantic_cache.py 语义缓存 SemanticCache, get_semantic_cache()
knowledge/image_cleanup.py 孤儿文件清理 cleanup_image_orphans(), collect_referenced_images()
cleanup_orphans.py 孤儿文件清理脚本 --force 执行删除,默认 dry-run
sync_vlm_cache.py VLM 缓存同步 --re-embed 强制重算 embedding
eval_image_retrieval.py 图片检索评测 检索层 + 选择层分层评测

十二、缓存系统

12.1 三层精确缓存core/cache.py

RAGCacheManager 管理三个独立的 LRUCache 实例:

缓存层 Key 构造 TTL 容量 kb_version 关联
Query Cache MD5(query:kb_name:kb_version) 1h 500 版本失效
Embedding Cache MD5(emb:{text}) 24h 2000 版本失效
Rerank Cache MD5(rerank:{query}:{sorted_doc_ids}) 1h 1000 变更时全清空

知识库版本失效机制

def increment_kb_version(kb_name):
    old_version = self._kb_versions.get(kb_name, 0)
    self._kb_versions[kb_name] = old_version + 1
    # 失效旧版本
    self.query_cache.invalidate_by_version(old_version)
    self.embedding_cache.invalidate_by_version(old_version)
    self.rerank_cache.clear()  # Rerank 无 kb_version全量清空

sync.py 文档变更时调用 increment_kb_version() 触发失效。

写入保护CACHE_MIN_SCORE = 0.3,检索结果最高分低于此值时不写缓存,避免低质量结果被缓存。

12.2 语义缓存core/semantic_cache.py

SemanticCache 使用 FAISS 向量索引实现语义级缓存:

查找query_embedding → FAISS ANN 搜索 → 余弦相似度 > 0.92 → 候选
二次验证Jaccard(query, cached._raw_query) >= 0.5 → 确认命中
存储query_embedding + result → FAISS add

Jaccard 二次验证(字符级):

set_a = set(query)      # 字符集合
set_b = set(cached_query)
similarity = |intersection| / |union|

Intent 与 RAG 共用 FAISS 索引,通过 cache_type 字段隔离:

缓存类型 cache_type 值 Embedding 输入 读取条件
RAG 回答 "rag_answer" `embedding(query collections)`
意图分析 "intent_analysis" embedding(query) cache_type != "rag_answer"

容量管理:默认 max_size=10000,达到上限时 self.clear() 全清空(非 LRU 淘汰)。

Intent 精确缓存(独立于 FAISS_exact_cache: Dict[str, IntentAnalysis]key = query + 最近2条历史摘要,上限 500 条,无 TTL满后停止接收新条目。

12.3 缓存命中链路

一个 /rag 请求依次经过以下缓存检查点:

请求进入 chat_routes.rag()
  │
  ├─ ① 语义缓存读取FAISS
  │    key = embedding(retrieval_query|collections)
  │    要求 cache_type == "rag_answer"
  │    二次验证: Jaccard >= 0.5
  │    ├─ 命中 → 直接流式返回缓存答案,流程结束
  │    └─ 未命中 → 继续
  │
  ├─ ② intent_analyzer.analyze()
  │    ├─ 精确缓存: key = query + 历史摘要
  │    ├─ 语义缓存: key = embedding(query)cache_type != "rag_answer"
  │    └─ 未命中 → 调 LLM → 写入两层缓存
  │
  ├─ ③ engine.search()
  │    ├─ Query Cache: MD5(query:kb_name:kb_version)
  │    ├─ Embedding Cache: MD5(emb:{text})
  │    └─ Rerank Cache: MD5(rerank:{query}:{sorted_doc_ids})
  │
  ├─ ④ LLM 生成回答
  │
  └─ ⑤ 语义缓存写入FAISS
       存入 { cache_type: "rag_answer", answer, sources, images, _raw_query }

缓存清除POST /cache/clear 端点清除 Query/Embedding/Rerank 缓存 + 语义缓存(需网关认证)。


十三、图片选择管线

13.1 评分流程select_images

完整的图片选择管线包含 7 个阶段:

score_image_relevance()      ← P1: 基础分(关键词/图号/章节/类型)
    ↓
VLM 相关性调整              ← _check_vlm_relevance()  -3 / +2 分
    ↓
章节关联惩罚                ← section_similarity < 0.3 → -5 分
    ↓
图号/表号引用加分            ← 文本引用匹配 → +8~+13 分
    ↓
来源文件加分                ← primary_sources 匹配 → +2 分
    ↓
MIN_SCORE 过滤              ← 动态阈值 2.0~5.0
    ↓
CrossEncoder 二次评分        ← P3: 本地 reranker 对 (query, desc) 精排
    ↓                             正相关 +5负相关 -5 且直接剔除
MAX_IMAGES 预算控制          ← 动态上限 2~15 张
    ↓
_filter_images_by_answer()  ← 后置过滤LLM 回答关键词重叠

13.2 图片召回保障

图片切片在检索层面临 CrossEncoder 系统性低分0.0020.08 vs 文本 0.30.9),有多重保障机制:

机制 位置 说明
图片独立召回 engine._search_image_chunks() 对 image/chart 切片单独查询 ChromaDB
_image_boost 标记 chat_routes.py 图片意图查询时,为匹配的图片切片打 1.5x/2.0x boost 标记
章节聚类救援 _rescue_section_cluster() 当整章节切片全被 rerank 压制时,保底分配分数
BM25 分歧救援 _rescue_bm25_divergence() BM25 高排名但 rerank 低分的切片被恢复
词法匹配救援 _rescue_lexical_match() 切片文本精确包含查询关键词时提升分数

十四、VLM 懒加载增强

入口文件knowledge/lazy_enhance.py 触发位置chat_routes.py generate() 函数

14.1 触发条件

切片类型 触发条件 调用模型
image / chart has_vlm_desc=Falseimage_path 非空 VLMqwen-vl-plus
table has_summary=Falsescore > 0.7 LLM表格摘要
table + 关联图片 has_vlm_desc=Falseimage_path 非空 VLM

14.2 增强流程

缓存检查(图片 MD5 哈希 → .data/cache/vlm/{hash}.txt
    ├─ 缓存命中(内容 ≥ 5 字符)→ 直接返回
    └─ 缓存未命中 →
         ├─ 调用 VLM/LLM 生成描述/摘要
         ├─ 空描述保护(< 5 字符)→ 不写缓存,不更新向量库
         ├─ 写入缓存文件
         └─ 更新 ChromaDB
              ├─ metadata: { has_vlm_desc: True, vlm_desc: description }
              ├─ embedding: 用 VLM 描述重新计算向量(通过 RAGEngine 单例获取模型)
              └─ document: 更新为 VLM 描述文本

embedding 模型获取KnowledgeBaseManager 没有 embedding_model 属性,通过 _get_embedding_model()RAGEngine 单例获取。

14.3 后台异步化

VLM/LLM 调用在后台线程执行,不阻塞主流程:

# chat_routes.py 中
_bg_contexts = copy.deepcopy(contexts)  # 深拷贝避免线程竞争
_t = threading.Thread(target=_background_enhance, daemon=True)
_t.start()
# 主流程继续 select_images + LLM 生成

后台线程使用 asyncio.run() + 60s 超时保护。首次查询时图片使用 caption + section 作为临时描述,后台生成完成后写入 ChromaDB下次查询直接使用缓存。


十五、文档解析与切片

15.1 MinerU 解析流程

入口文件parsers/mineru_parser.py

支持两种云端 API 模式:

模式 端点 延迟 适用场景
v4 precise /api/v4/ 较长(需排队) 高精度 PDF 解析
v1 agent /api/v1/agent/ ~10s 轻量级文档处理

V2 格式转换_parse_v2_content_list

MinerU 返回的 content_list 被转换为 MinerUChunk 对象,提取的字段包括:_v2_table_type_v2_table_nest_level_v2_list_typesub_type(部分字段在下游切片时被丢弃)。

标题检测parsers/heading_rules.py

基于规则的多级标题检测,_validate_level() 长度守卫防止长文本误判为标题:

级别 最大长度 超出后
H1 40 字符 降级为普通文本
H2 60 字符 降级为普通文本
H3 50 字符 降级为普通文本

chinese_article 规则额外限制 max_length=30numeric_level1 排除以标点结尾的误判。

15.2 切片后处理

_post_process_chunks 核心逻辑:

  1. 连续标题链合并:连续 title 类切片合并为一个 chunk保留最高 text_level
  2. body 合并后 text_level 重置body 合并进 title buffer 后执行 buffer.text_level = 0,防止下一个标题被错误合并
  3. 元数据写入
字段 类型 说明
chunk_type str text / title / table / image / chart
section str 章节路径(如 综述 > 2.3发电
text_level int 标题级别0=body, 1=H1, 2=H2, 3=H3
image_path str 图片文件名
bbox str 页面坐标(仅 PDF
has_vlm_desc bool 是否已有 VLM 描述

十六、性能插桩

入口文件api/chat_routes.py generate() 函数

16.1 各阶段计时

在 SSE 流式生成器中初始化计时字典,记录 8 个管线阶段耗时:

阶段 key 记录时机 覆盖范围
intent 意图分析完成后 IntentAnalyzer.analyze()
cache 语义缓存检查后 semantic_cache.lookup()
search 混合检索+切片处理完成后 engine.search() + 上下文提取
vlm VLM 懒加载完成后 enhance_retrieved_chunks()
image select_images 完成后 图片选择
rescue 救援管线+上下文构建完成后 聚类/BM25/预算
llm LLM 流式生成完成后 engine.generate_answer_stream()
post finish 事件构建完成后 答案对齐+debug 事件

16.2 数据输出

SSE finish 事件

{
  "type": "finish",
  "timing": {
    "total_ms": 12345,
    "stages_ms": {
      "intent": 4200, "cache": 50, "search": 900,
      "vlm": 10, "image": 30, "rescue": 200,
      "llm": 6800, "post": 150
    }
  }
}

服务端日志

[性能] 总12345ms | 意图4200 缓存50 检索900 VLM10 图片30 救援200 LLM6800 后处理150