【RAG-Anything】核心架构与设计原理深度解析:让 RAG 真正读懂图表、公式与多模态文档

【RAG-Anything】核心架构与设计原理深度解析

仓库:HKUDS/RAG-Anything | 21.6k ⭐ | MIT License | Python 3.10+
论文:arxiv 2510.12323 | 1.3.1 版本 | 持续活跃更新(最近提交 2026-06-15)
一句话定位:把 LightRAG 的图谱能力与 MinerU 的多模态解析缝合到一个统一管道里,让「读 PDF 的 RAG」从「文字 OCR」升级为「视觉 + 公式 + 表格 + 上下文」的整体理解。

一、引子:当 RAG 撞上「非纯文本文档」

过去两年,RAG(Retrieval-Augmented Generation)的故事几乎被「文本召回 + LLM 回答」讲完。从 LangChain、LlamaIndex 到 Haystack、LightRAG、RAGFlow,大家在「分块策略」「向量检索」「Reranker」「Query 改写」上做了非常细致的优化,但这些优化都有一个隐含前提:文档是纯文本

现实打脸得很快——学术论文里有 LaTeX 公式、电路图、实验数据表;金融研报里有同比环比柱状图、估值模型截图、合并财务报表;技术手册里有架构示意图、流程图、时序图。把这些文档丢进传统 RAG,要么被 OCR 错成乱码、要么被切碎成互不相关的文本块。检索阶段能命中「这一页讨论 Transformer」,但 VLM 看到的是一张完整的多头注意力机制图,这张图承载的信息远大于图下方的 caption 文字

多模态 RAG」(Multimodal RAG)就是为了解决这个问题而诞生的赛道。2024–2025 年间出现了不少尝试:LlamaIndex 的 MultiModalVectorStoreIndex、LangChain 的 MultiVectorRetriever、ColPali/ColQwen 系的视觉文档检索、OpenAI 的 GPT-4V 直接喂图。但这些方案要么是单模态缝补(图、文本各自一套检索管线,最后硬拼结果),要么是重型端到端(把所有页面都过一遍 VLM,成本爆炸)。

RAG-Anything(HKU Data Science Lab,2026-06 最新 1.3.1 版本)走了一条中间路线底层用 LightRAG 维护一份统一的「文本 + 实体 + 关系」知识图谱,上层挂四类 ModalProcessor(Image/Table/Equation/Generic)把异构内容转成可检索的图节点与边。这套架构既保留了传统 RAG 的高吞吐与低成本,又通过结构化提取弥补了「图被 OCR 化为噪声」的痛点。

下面我们逐层拆解 RAG-Anything 的设计:先看它要解决什么问题、定位是什么,再看架构怎么分层、数据怎么流、关键模块怎么协作,最后用真实可运行代码演示端到端 pipeline、与同类项目对比设计差异,并归纳优缺点与适用场景。

二、项目定位与核心价值

2.1 一句话定义

RAG-Anything 是一个多模态文档处理 + 知识图谱 RAG 一体化框架,把 MinerU 解析出来的图片/表格/公式与正文统一映射到 LightRAG 的知识图谱中,让一份 PDF 能像纯文本一样被检索、被问答。

2.2 解决什么问题

传统 RAG 管道在多模态文档上会遭遇「三堵墙」:

痛点传统方案的表现RAG-Anything 的对策
图表信息丢失OCR 把图变 caption 文字,向量检索只能命中文字ImageModalProcessor 调 VLM 生成结构化描述,提取实体名/类型/摘要
公式难处理LaTeX 公式被 OCR 成乱码,检索不到EquationModalProcessor 保留 LaTeX 源,调 LLM 做语义解释与实体提取
表格结构丢失表格被切成零散文本行,跨行关系消失TableModalProcessor 还原表格结构 + 关键指标,注入图谱作为单独节点
图文割裂图与周围段落分开索引,召回结果不连贯ContextExtractor 提取「前后 N 页/块」上下文,让 VLM 看到图文共现
重复处理同一文档每次 query 都要重新解析parse_cache + multimodal_status_cache 两级缓存

2.3 能力矩阵

  • 多格式输入.pdf, .jpg, .jpeg, .png, .bmp, .tiff, .gif, .webp, .doc, .docx, .ppt, .pptx, .xls, .xlsx, .txt, .md
  • 多 Parser 后端mineru(默认)、doclingpaddleocr(适合扫描件 OCR)
  • 多模态处理:图片、表格、公式、Generic(其他类型自动回退)
  • 多种 Query 模式aquery 纯文本 / aquery_with_multimodal 文本+图 / aquery_vlm_enhanced VLM 增强
  • Context 提取策略page 模式(前后 N 页)/ chunk 模式(前后 N 块)
  • 批处理max_concurrent_files 控制并发,支持递归目录扫描
  • 可观测性:回调系统(CallbackManager)支持 on_query_start/complete/error 等钩子
  • 多语言 Promptset_prompt_language('zh') 切换中英 prompt 模板

2.4 仓库统计

指标数值
Stars21,608
Forks2,528
LanguagePython
LicenseMIT
最新提交2026-06-15
版本1.3.1
依赖核心lightrag-hku<1.5 + mineru[core] + huggingface_hub
PyPIpip install raganything[all]

三、整体架构

RAG-Anything 不是一个独立的 RAG 系统,而是一层「多模态桥」:下接 MinerU(解析)+ LightRAG(图谱),上接用户应用(Query/Agent)。

3.1 顶层架构图

flowchart TB
    subgraph Input["📥 输入层"]
        PDF["PDF 论文 / 报告"]
        OFFICE["Office 文档"]
        IMG["图片 / 扫描件"]
        MD["Markdown / TXT"]
    end

    subgraph Parser["🔍 解析层(MinerU/Docling/PaddleOCR)"]
        M1[MinerU Parser]
        M2[Docling Parser]
        M3[PaddleOCR Parser]
        CL["content_list<br/>(page_idx + type + content)"]
    end

    subgraph Core["🧠 核心层:RAGAnything"]
        CTX["ContextExtractor<br/>(前后 N 页/块 上下文)"]
        MP["ModalProcessors 字典<br/>image / table / equation / generic"]
        PC["parse_cache<br/>(解析结果缓存)"]
        MSC["multimodal_status_cache<br/>(处理状态缓存)"]
        CB["CallbackManager<br/>(可观测性钩子)"]
    end

    subgraph LightRAG["📊 存储层(LightRAG)"]
        LR[LightRAG 实例]
        KG[("知识图谱<br/>Entity + Relation")]
        VDB[("向量库<br/>chunk / entity / relation")]
        KV[("KV 存储<br/>doc_status / full_entities")]
    end

    subgraph Query["❓ 查询层"]
        Q1["aquery()<br/>纯文本"]
        Q2["aquery_with_multimodal()<br/>文本+图"]
        Q3["aquery_vlm_enhanced()<br/>VLM 增强"]
    end

    PDF --> M1
    OFFICE --> M1
    IMG --> M1
    MD --> M1
    PDF -.可选.-> M2
    PDF -.OCR.-> M3
    M1 --> CL
    M2 --> CL
    M3 --> CL

    CL --> CTX
    CTX --> MP
    MP --> LR
    MP --> PC
    PC --> LR
    MSC --> LR
    CB -.监听.-> MP

    LR --> KG
    LR --> VDB
    LR --> KV

    LR --> Q1
    LR --> Q2
    LR --> Q3
    Q1 --> ANS["📤 答案"]
    Q2 --> ANS
    Q3 --> ANS

    style Input fill:#E8F4FD,stroke:#4A90E2
    style Parser fill:#FFF4E6,stroke:#F5A623
    style Core fill:#F0E6F6,stroke:#9013FE
    style LightRAG fill:#E8F8E8,stroke:#7ED321
    style Query fill:#FDE8E8,stroke:#D0021B

3.2 核心模块职责

模块职责关键文件
RAGAnything主类(MixIn 模式),串联 Parser + Processors + LightRAGraganything.py
Parser把任意格式文档转成 MinerU content_list(含 page_idx、type、img_path 等)parser.py(100+KB,最重)
ContextExtractor提取当前内容项「前后 N 页/块」的文本作为上下文modalprocessors.py
ModalProcessor × 4对应 image/table/equation/generic 四类内容的专项处理modalprocessors.py(61KB)
parse_cache解析结果缓存(避免重复解析同一文件)LightRAG KV 存储
multimodal_status_cache记录哪些 modal 内容已处理,避免重复调用 VLMLightRAG KV 存储
CallbackManager处理事件钩子(on_query_start/complete/error 等)callbacks.py
BatchMixin批量处理文件夹,控制并发batch.py
QueryMixin三种 query 模式(纯文本 / 文本+图 / VLM 增强)query.py

3.3 MixIn 模式的主类设计

RAG-Anything 用 Python dataclass + MixIn 把不同职责拆到独立文件,主类只做「编排」:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 来自 raganything/raganything.py:50-107
@dataclass
class RAGAnything(QueryMixin, ProcessorMixin, BatchMixin):
"""Multimodal Document Processing Pipeline"""

# 外部依赖(用户传入)
lightrag: Optional[LightRAG] = None
llm_model_func: Optional[Callable] = None
vision_model_func: Optional[Callable] = None
embedding_func: Optional[Callable] = None
config: Optional[RAGAnythingConfig] = None
lightrag_kwargs: Dict[str, Any] = field(default_factory=dict)

# 内部状态(init=False,不参与构造)
modal_processors: Dict[str, Any] = field(default_factory=dict, init=False)
context_extractor: Optional[ContextExtractor] = field(default=None, init=False)
parse_cache: Optional[Any] = field(default=None, init=False)
multimodal_status_cache: Optional[Any] = field(default=None, init=False)
callback_manager: CallbackManager = field(default_factory=CallbackManager, init=False, repr=False)
_parser_installation_checked: bool = field(default=False, init=False)

设计哲学:避免「上帝类」——RAGAnything 主类只持有 lightragconfigmodal_processors 等关键引用,所有具体行为通过 QueryMixin(查询)、ProcessorMixin(处理)、BatchMixin(批处理)混入。这样新增一种「处理能力」时,只用写一个 MixIn 文件就能挂上去。

四、四类 ModalProcessor 详解

RAG-Anything 把「多模态」拆成 4 类内容,每类一个 Processor。设计上统一继承自 BaseModalProcessor,但又各自处理专属逻辑。

4.1 类图

classDiagram
    class BaseModalProcessor {
        +lightrag: LightRAG
        +modal_caption_func: Callable
        +context_extractor: ContextExtractor
        +generate_description_only()
        +process_multimodal_content()
        +_parse_response()
        +_extract_all_json_candidates()
        +_process_chunk_for_extraction()
    }
    class ImageModalProcessor {
        +_encode_image_to_base64()
        +generate_description_only()
        +process_multimodal_content()
    }
    class TableModalProcessor {
        +process_table_content()
    }
    class EquationModalProcessor {
        +process_equation_content()
        +_extract_omml_equations()
    }
    class GenericModalProcessor {
        +process_generic_content()
    }
    class ContextExtractor {
        +config: ContextConfig
        +tokenizer
        +extract_context()
        +_extract_from_content_list()
        +_extract_from_text_chunks()
    }
    class ContextConfig {
        +context_window: int
        +context_mode: str
        +max_context_tokens: int
        +include_headers: bool
        +include_captions: bool
        +filter_content_types: List
    }
    BaseModalProcessor <|-- ImageModalProcessor
    BaseModalProcessor <|-- TableModalProcessor
    BaseModalProcessor <|-- EquationModalProcessor
    BaseModalProcessor <|-- GenericModalProcessor
    BaseModalProcessor --> ContextExtractor
    ContextExtractor --> ContextConfig

4.2 各 Processor 的核心差异

Processor输入模型调用关键 Prompt 模板知识图谱注入
ImageModalProcessorimg_path + captions + footnotesvision_model_func(VLM)vision_prompt / vision_prompt_with_context实体名 + 详细描述 → 图谱节点 + belongs_to 边
TableModalProcessortable_body (Markdown/HTML)llm_model_functable_prompt表格名 + 关键洞察 → 图谱节点 + 关联正文
EquationModalProcessorOMML/LaTeX 公式llm_model_funcequation_prompt公式名 + 数学含义 → 图谱节点
GenericModalProcessor其他类型llm_model_funcgeneric_prompt兜底描述 → 通用图谱节点

4.3 ImageModalProcessor 的关键流程

以图片处理为例,看 ModalProcessor 怎么把一张图变成图谱节点:

sequenceDiagram
    autonumber
    participant U as 用户
    participant RA as RAGAnything
    participant CE as ContextExtractor
    participant IMP as ImageModalProcessor
    participant VLM as vision_model_func
    participant LR as LightRAG

    U->>RA: process_file("paper.pdf")
    RA->>RA: Parser 解析为 content_list
    loop 每个 image content item
        RA->>IMP: process_multimodal_content(content)
        IMP->>CE: extract_context(content_source, item_info)
        CE-->>IMP: 前后 N 页的文本(带 page 标记)
        IMP->>IMP: 构造 vision_prompt_with_context
        IMP->>IMP: image_path → base64 编码
        IMP->>VLM: vision_prompt + image_data + system_prompt
        VLM-->>IMP: JSON {detailed_description, entity_info}
        IMP->>IMP: _parse_response 解析 JSON
        IMP->>LR: ainsert_custom_kg(entity_name, description, ...)
        LR->>LR: 实体 → 图谱节点 + 文本块 → 向量库
        LR->>LR: belongs_to 边 → 关联所在 chunk
    end
    RA-->>U: 处理完成

4.4 ImageModalProcessor 关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 来自 raganything/modalprocessors.py:835-967 (简化)
class ImageModalProcessor(BaseModalProcessor):
def __init__(self, lightrag, modal_caption_func, context_extractor=None):
super().__init__(lightrag, modal_caption_func, context_extractor)

def _encode_image_to_base64(self, image_path: str) -> str:
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")

async def generate_description_only(
self, modal_content, content_type, item_info=None, entity_name=None
):
# 1. 解析 modal_content(MinerU 风格)
content_data = json.loads(modal_content) if isinstance(modal_content, str) else modal_content
image_path = content_data.get("img_path")
captions = content_data.get("image_caption", [])
footnotes = content_data.get("image_footnote", [])

# 2. 提取上下文(前后 N 页文本)
context = self._get_context_for_item(item_info) if item_info else ""

# 3. 构造带上下文的 prompt
vision_prompt = PROMPTS["vision_prompt_with_context"].format(
context=context, section_path=section_path,
entity_name=entity_name or "unique descriptive name",
image_path=image_path, captions=captions, footnotes=footnotes,
)

# 4. 调 VLM(图片转 base64 后传入)
image_base64 = self._encode_image_to_base64(image_path)
response = await self.modal_caption_func(
vision_prompt,
image_data=image_base64,
system_prompt=PROMPTS["IMAGE_ANALYSIS_SYSTEM"],
)

# 5. 解析 VLM 返回的 JSON
enhanced_caption, entity_info = self._parse_response(response, entity_name)
return enhanced_caption, entity_info

关键设计点

  1. 「VLM 不裸看」:把图所在章节的文本、caption、脚注、前后 N 页内容一起塞给 VLM,让模型知道「这张图在讲什么上下文」。这是它与传统「CV pipeline 把图转 caption」最本质的区别。
  2. 结构化输出强约束:prompt 强制 VLM 返回 JSON(detailed_description + entity_info),方便后续注入图谱。
  3. 容错降级:VLM 调用失败时 fallback 到「简单 caption + 默认 entity_name」,不阻塞整个 pipeline。

五、ContextExtractor——多模态的灵魂

如果只挑 RAG-Anything 的「最有价值的模块」,那就是 ContextExtractor。它解决了一个朴素但关键的问题:一张图没有上下文,是看不懂的

5.1 设计动机

VLM 接到一张孤立的图时,往往只能描述「图中有什么」(a bar chart with three bars),但无法回答「这张图说明了作者想论证什么假设」——后者需要图所在章节的文本上下文。

RAG-Anything 的解法:先把整份 PDF 解析成有序的 content_list(每项含 page_idx + type + content),处理当前 image 时,临时把「前 N 页 + 后 N 页」的 text 段落抽出来,作为 prompt 的一部分

5.2 核心实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 来自 raganything/modalprocessors.py:55-178 (简化)
class ContextExtractor:
def __init__(self, config: ContextConfig = None, tokenizer=None):
self.config = config or ContextConfig()
self.tokenizer = tokenizer # 用 LightRAG 的 tokenizer 精确算 token 数

def extract_context(self, content_source, current_item_info, content_format="auto"):
# 自动识别 content_source 格式
if content_format == "minerU" and isinstance(content_source, list):
return self._extract_from_content_list(content_source, current_item_info)
elif content_format == "text_chunks" and isinstance(content_source, list):
return self._extract_from_text_chunks(content_source, current_item_info)
elif content_format == "text" and isinstance(content_source, str):
return self._extract_from_text_source(content_source, current_item_info)
else:
# auto-detect: list / dict / str
...

def _extract_from_content_list(self, content_list, current_item_info):
if self.config.context_mode == "page":
return self._extract_page_context(content_list, current_item_info)
elif self.config.context_mode == "chunk":
return self._extract_chunk_context(content_list, current_item_info)

def _extract_page_context(self, content_list, current_item_info):
current_page = current_item_info.get("page_idx", 0)
window_size = self.config.context_window # 默认 1

start_page = max(0, current_page - window_size)
end_page = current_page + window_size + 1

context_texts = []
for item in content_list:
item_page = item.get("page_idx", 0)
item_type = item.get("type", "")
# 关键:只取 window 范围内 + 匹配 filter_content_types 的项
if (start_page <= item_page < end_page
and item_type in self.config.filter_content_types):
text_content = self._extract_text_from_item(item)
if text_content and text_content.strip():
# 非当前页时加 page 标记
marker = f"[Page {item_page}] " if item_page != current_page else ""
context_texts.append(marker + text_content)

context = "\n".join(context_texts)
return self._truncate_context(context) # 按 max_context_tokens 截断

三个关键参数

  • context_window: int = 1——前后各取 1 页(PDF 论文常用)
  • context_mode: str = "page"——page vs chunk vs token 三种窗口策略
  • max_context_tokens: int = 2000——塞给 VLM 的总 token 上限(防 OOM)

5.3 三种 context_mode 对比

模式适合场景窗口单位优缺点
pagePDF 论文、书籍(页面结构清晰)整页简单直观,但可能取到无关段落
chunk已经被 LightRAG chunked 的文本chunk 块精确控制,但需要 chunk 索引
token(隐式)严格控制总 token 数tokenizer 切分灵活但慢,依赖 tokenizer 准确度

六、端到端数据流:从 PDF 到答案

下面我们把整条 pipeline 串起来,看一个真实 query 走过的所有模块:

sequenceDiagram
    autonumber
    participant U as 用户
    participant RA as RAGAnything
    participant P as MinerU Parser
    participant CL as content_list
    participant CTX as ContextExtractor
    participant MP as ModalProcessors
    participant LR as LightRAG
    participant VLM as VLM (GPT-4V 等)
    participant KV as parse_cache / status_cache
    participant Q as QueryMixin

    U->>RA: process_file("paper.pdf")
    RA->>P: parse("paper.pdf", method="auto")
    P-->>RA: content_list (97 项:text/image/table/equation)

    RA->>CTX: set_content_source(content_list)
    RA->>MP: 遍历 content_list

    loop 每项非文本
        alt image
            MP->>CTX: extract_context(item_info, "minerU")
            CTX-->>MP: 前后 1 页 text
            MP->>VLM: vision_prompt + context + image(b64)
            VLM-->>MP: JSON description
            MP->>KV: 写 multimodal_status_cache
            MP->>LR: ainsert_custom_kg(entity_name, desc)
        else table
            MP->>VLM: table_prompt + table_body
            VLM-->>MP: JSON 表格洞察
            MP->>LR: ainsert_custom_kg(table_name, insight)
        else equation
            MP->>VLM: equation_prompt + LaTeX
            VLM-->>MP: JSON 公式释义
            MP->>LR: ainsert_custom_kg(formula_name, meaning)
        end
        LR->>LR: 实体 → 图谱节点
        LR->>LR: 关系 → belongs_to 边
        LR->>LR: 文本块 → 向量库
    end

    RA->>KV: 写 parse_cache (key=file_hash)
    RA-->>U: 处理完成

    U->>RA: aquery("图3的实验设置和结论是什么?")
    RA->>Q: aquery(query, mode="mix")
    Q->>LR: aquery(query, param=QueryParam(mode="mix"))
    LR->>LR: 向量召回 + 图谱扩展 + rerank
    LR-->>Q: context 文本 (含模态实体名)
    Q->>Q: vlm_enhanced=True?<br/>调 VLM 重新看图
    Q-->>U: 最终答案

6.1 核心代码:端到端 pipeline

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 来自 examples/raganything_example.py (简化)
import asyncio
from lightrag import LightRAG
from raganything import RAGAnything
from raganything.config import RAGAnythingConfig

# 1. 准备 LLM / VLM / Embedding 函数
async def llm_model_func(prompt, system_prompt=None, **kwargs):
from openai import AsyncOpenAI
client = AsyncOpenAI()
return (await client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": system_prompt or ""},
{"role": "user", "content": prompt}],
)).choices[0].message.content

async def vision_model_func(prompt, image_data=None, system_prompt=None, **kwargs):
from openai import AsyncOpenAI
client = AsyncOpenAI()
return (await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "system", "content": system_prompt or ""},
{"role": "user", "content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": {
"url": f"data:image/jpeg;base64,{image_data}"}}
]}],
)).choices[0].message.content

async def embedding_func(texts):
from openai import AsyncOpenAI
client = AsyncOpenAI()
resp = await client.embeddings.create(model="text-embedding-3-small", input=texts)
return [e.embedding for e in resp.data]

# 2. 初始化 RAGAnything
config = RAGAnythingConfig(
working_dir="./rag_storage",
parser="mineru",
parse_method="auto",
enable_image_processing=True,
enable_table_processing=True,
enable_equation_processing=True,
context_window=1,
context_mode="page",
max_context_tokens=2000,
)

rag = RAGAnything(
llm_model_func=llm_model_func,
vision_model_func=vision_model_func,
embedding_func=embedding_func,
config=config,
)

# 3. 处理文档
await rag.process_file("paper.pdf", output_dir="./output")

# 4. 三种 Query 模式
# 4.1 纯文本
result1 = await rag.aquery("Transformer 的核心创新是什么?", mode="hybrid")

# 4.2 VLM 增强 query(自动检测图并调 VLM)
result2 = await rag.aquery("图 3 展示的 attention 复杂度公式怎么推导?", mode="mix")

# 4.3 多模态 query(用户主动传图)
result3 = await rag.aquery_with_multimodal(
"这张图和文档中的哪个观点对应?",
multimodal_content=[{"img_path": "user_uploaded.png"}],
mode="mix",
)

# 5. 清理
await rag.finalize_storages()

七、查询模式:三种粒度

RAG-Anything 提供 3 种 query API,覆盖「纯文本 → VLM 看图 → 用户自带图」三层场景:

API用途是否调 VLM适用场景
aquery()纯文本 query仅在 vlm_enhanced=True大多数场景,默认推荐
aquery_vlm_enhanced()自动检测召回结果中的图,调 VLM 重新看query 提到「图 N」但召回只命中文字时
aquery_with_multimodal()用户主动传图 + query用户上传新图,问「和文档哪部分相关」

7.1 VLM 增强 query 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 来自 raganything/query.py:102-193 (简化)
async def aquery(self, query, mode="mix", system_prompt=None, **kwargs):
if self.lightrag is None:
raise ValueError("No LightRAG instance available.")

vlm_enhanced = kwargs.pop("vlm_enhanced", None)
# 自动判断:只要提供了 vision_model_func 就默认开启
if vlm_enhanced is None:
vlm_enhanced = (hasattr(self, "vision_model_func")
and self.vision_model_func is not None)

if vlm_enhanced and self.vision_model_func:
return await self.aquery_vlm_enhanced(query, mode, system_prompt, **kwargs)
elif vlm_enhanced and not self.vision_model_func:
self.logger.warning("VLM enhanced requested but no vision_model_func, fallback.")

# 普通 query:直接调 LightRAG
query_param = QueryParam(mode=mode, **kwargs)
result = await self.lightrag.aquery(query, param=query_param, system_prompt=system_prompt)
return result

设计点

  • 「VLM 增强」是可选且自动降级的——没传 vision_model_func 时自动 fallback 到普通文本 query,不会报错。
  • aquery_vlm_enhanced 内部会扫一遍 LightRAG 召回的 context,把其中的 img_path 全部转成 base64,连同文本一起送给 VLM 重新做一次「多模态问答」。

八、批处理与缓存:企业级可用性

8.1 批处理 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 来自 examples/batch_processing_example.py (简化)
from raganything import RAGAnything

config = RAGAnythingConfig(
max_concurrent_files=4, # 4 并发
recursive_folder_processing=True, # 递归子目录
supported_file_extensions=[".pdf", ".docx", ".pptx", ".xlsx"],
)

rag = RAGAnything(...)

# 批量处理整个文件夹
await rag.process_folder_complete(
folder_path="./documents",
output_dir="./output",
max_concurrent_files=4,
file_extensions=[".pdf"],
)

8.2 两级缓存

flowchart LR
    A["file_hash(file_path)"] --> B{"parse_cache<br/>有缓存?"}
    B -- "Yes" --> C["直接复用<br/>content_list"]
    B -- "No" --> D["调 MinerU 解析"]
    D --> E["写入 parse_cache"]
    E --> F["遍历 modal content"]

    F --> G{"status_cache<br/>已处理?"}
    G -- "Yes" --> H["跳过 VLM 调用"]
    G -- "No" --> I["调 VLM 提取"]
    I --> J["写入 status_cache"]
    J --> K["注入 LightRAG"]
    H --> K

    style A fill:#E8F4FD
    style B fill:#FFF4E6
    style C fill:#E8F8E8
    style D fill:#FDE8E8
    style E fill:#E8F8E8
    style F fill:#E8F4FD
    style G fill:#FFF4E6
    style H fill:#E8F8E8
    style I fill:#FDE8E8
    style J fill:#E8F8E8
    style K fill:#F0E6F6

两个缓存的差别

  • parse_cachekey = 文件 hash,存 MinerU 解析结果。同一文件改个名再处理,也能命中(hash 相同)。
  • multimodal_status_cachekey = 模态项 ID,存「这个图/表/公式是否已经过 VLM 处理」。支持断点续传——批处理中断后重启,已处理项会跳过。

九、关键 Prompt 模板设计

9.1 图片分析 prompt(带上下文)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 来自 raganything/prompt.py:85-150 (简化)
PROMPTS["vision_prompt"] = """Please analyze this image in detail and provide a JSON response:

{{
"detailed_description": "A comprehensive visual description:
- Describe overall composition and layout
- Identify all objects, people, text, and visual elements
- Explain relationships between elements
- Note colors, lighting, and visual style
- Always use specific names instead of pronouns",
"entity_info": {{
"entity_name": "{entity_name}",
"entity_type": "image",
"summary": "concise summary for knowledge graph indexing"
}}
}}

Image path: {image_path}
Captions: {captions}
Footnotes: {footnotes}
Section path: {section_path}"""

带上下文版vision_prompt_with_context)会再多一个字段:

1
2
3
4
5
6
7
PROMPTS["vision_prompt_with_context"] = """... 同上 ...

Surrounding context from the document:
{context}

Use the above context to better understand what this image is illustrating
in the document's narrative."""

9.2 JSON 解析的渐进式容错

VLM 返回的 JSON 不一定规范,RAG-Anything 写了 4 套 fallback:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 来自 raganything/modalprocessors.py:600-720 (简化)
def _parse_response(self, response, entity_name):
# 1. 先尝试直接 json.loads
try: return self._try_parse_json(response)
except: pass

# 2. 提取 ```json ... ``` 代码块
candidates = self._extract_all_json_candidates(response) # 含 think 标签预处理

# 3. 渐进式修复(smart quotes、trailing comma、unescaped backslash)
for c in candidates:
cleaned = self._basic_json_cleanup(c) # smart quotes 替换
cleaned = self._progressive_quote_fix(cleaned) # 修复转义
result = self._try_parse_json(cleaned)
if result: return result

# 4. Regex 兜底(提取关键字段)
return self._extract_fields_with_regex(response)

thinking 标签预处理特别贴心——很多 reasoning 模型(qwen2.5-think、deepseek-r1)会把推理过程塞在 <think>...</think> 里,RAG-Anything 会先 re.sub 掉这些标签再解析 JSON。

十、与同类项目对比

多模态 RAG 这条赛道上,RAG-Anything 定位独特——它是少数同时具备「文档解析 + 模态分流 + 知识图谱 + 上下文增强」的端到端方案。下面对比 3 个有代表性的项目:

项目核心思路多模态支持知识图谱上下文增强适合场景
RAG-AnythingMinerU 解析 + LightRAG 图谱 + 4 类 ModalProcessor✅ 图片/表格/公式/Generic✅ 内置 LightRAG✅ 前后 N 页/块学术论文、企业研报、技术手册
LlamaIndex MultiModalMultiModalVectorStoreIndex + MultiVectorRetriever✅ 图片(摘要/原文 双索引)❌ 不内置(可选 Neo4j)⚠️ 需自己写灵活拼接、追求生态
LangChain MultiVectorRetriever「图像摘要 + 原始图像」双路召回✅ 图片❌ 需外接⚠️ 需自己写已有 LangChain 栈的团队
ColPali / ColQwen整页图直接做视觉文档检索(ViT)✅ 整页(无分流)法律/财报扫描件(OCR 差)

10.1 设计差异分析

RAG-Anything vs LlamaIndex MultiModal

  • 架构哲学:RAG-Anything 是「单图谱多模态」——所有模态最后汇聚到一份 LightRAG 知识图谱;LlamaIndex 是「多索引拼接」——每个模态一份 vector store,召回时再 union。
  • 粒度控制:RAG-Anything 的 parse_method + context_window 让用户能精细控制「解析多深、上下文取多宽」;LlamaIndex 的粒度更粗,依赖 user 自己写 transform。
  • 学术血缘:RAG-Anything 来自 HKU Data Science(LightRAG 同团队),有 arxiv 论文背书;LlamaIndex 走的是「工程师开源 + 大量生态」路线。

RAG-Anything vs ColPali

  • 数据流:RAG-Anything 是「解析 → 结构化 → 图谱 → 文本检索」,ColPali 是「整页图 → 视觉向量 → 相似度检索」。
  • 成本:ColPali 一页 PDF 调一次 ViT,整页算;RAG-Anything 只对「非文本项」调 VLM,文本走传统 pipeline,通常更便宜
  • 可解释性:RAG-Anything 的图谱节点能给出「这个结论来自 Figure 3 + Section 4.2」的完整溯源链;ColPali 只给「相似度分数 + 页码」。

10.2 RAG-Anything 独有设计

  1. parse_cache + status_cache 双缓存:批处理几百份 PDF 时,避免重复解析和重复调 VLM(VLM 调用是最大成本项)。
  2. ContextExtractor 三种模式(page/chunk/token):精确控制塞给 VLM 的上下文长度。
  3. 渐进式 JSON 容错:兼容 reasoning 模型(qwen2.5-think / deepseek-r1)的 <think> 标签和奇形怪状的 JSON 输出。
  4. MixIn 主类设计:避免「上帝类」,新增能力时只写 MixIn 即可。

十一、优缺点分析

下表从「架构简洁性 / 扩展性 / 易用性」和「性能 / 复杂度 / 维护性」两个维度对比:

维度优势代价
架构简洁性✅ 三层切分(Parser/ModalProcessor/LightRAG)职责清晰⚠️ 依赖 LightRAG + MinerU 两个外部系统,新人需同时了解两者
扩展性✅ MixIn 模式 + PromptRegistry + Parser 插件 API⚠️ 新增自定义模态类型需要继承 BaseModalProcessor 并写 prompt
易用性✅ dataclass config + 极简 API(一行 process_file⚠️ 必须配置 llm_model_func + embedding_func + vision_model_func 三个回调
性能✅ 两级缓存避免重复 VLM/解析⚠️ MinerU 解析 PDF 本身较慢(页数线性),批处理需控制并发
复杂度✅ 把 VLM 调用次数压到「只对非文本」⚠️ 公式 / 表格的 prompt 设计需要领域知识(LaTeX 公式如何 prompt?)
维护性✅ MIT + 持续更新(2026-06 还在 1.3.1)⚠️ 依赖 lightrag-hku<1.5,LightRAG 升级 1.5 时要同步跟进

最适合的场景

  • 学术论文库(公式 + 图 + 表多)
  • 企业研报 / 财报(图 + 表多)
  • 技术手册(架构图 + 流程图多)
  • 任何「PDF 里图的信息量 ≥ 文字」的场景

不太适合的场景

  • 纯文本语料(用 LangChain / LlamaIndex 更轻)
  • 实时流式数据(Pathway 更合适)
  • 超大规模(百万级文档)—— VLM 调用成本会爆炸

十二、实践:跑通一个最小 demo

12.1 安装

1
2
3
4
5
6
7
8
# 基础安装
pip install raganything

# 完整功能(含 OCR / Markdown 转换)
pip install "raganything[all]"

# 验证 MinerU 安装
python -c "from mineru import MineruParser; print(MineruParser.check_installation())"

12.2 处理一份 PDF 并查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 文件:rag_demo.py
import asyncio
from raganything import RAGAnything, RAGAnythingConfig
from raganything.batch import BatchMixin

# Mock LLM/VLM/Embedding(生产环境换成真实 API)
async def llm_model_func(prompt, system_prompt=None, **kwargs):
return f"[MOCK LLM] received prompt of {len(prompt)} chars"

async def vision_model_func(prompt, image_data=None, system_prompt=None, **kwargs):
return '{"detailed_description": "mock image", "entity_info": {"entity_name": "mock_img", "entity_type": "image", "summary": "mock"}}'

async def embedding_func(texts):
# 返回 1536 维零向量(仅供 demo)
return [[0.0] * 1536 for _ in texts]

async def main():
config = RAGAnythingConfig(
working_dir="./demo_rag_storage",
parser="mineru",
parse_method="auto",
enable_image_processing=True,
enable_table_processing=True,
enable_equation_processing=True,
context_window=1,
context_mode="page",
)

rag = RAGAnything(
llm_model_func=llm_model_func,
vision_model_func=vision_model_func,
embedding_func=embedding_func,
config=config,
)

# 处理单文件
await rag.process_file(
file_path="./paper.pdf",
output_dir="./demo_output",
parse_method="auto",
)

# 获取处理状态
info = rag.get_processor_info()
print(f"Processors: {info['processors']}")
print(f"Parser installed: {info['parser_installation']}")

# 查询(纯文本模式)
result = await rag.aquery("What's the main contribution?", mode="hybrid")
print(f"Answer: {result}")

# 清理
await rag.finalize_storages()

if __name__ == "__main__":
asyncio.run(main())

12.3 运行

1
python rag_demo.py

输出大致:

1
2
3
4
5
6
7
8
9
[INFO] RAGAnything initialized with config: ...
[INFO] Parser 'mineru' installation verified
[INFO] Multimodal processors initialized with context support
[INFO] Available processors: ['image', 'table', 'equation', 'generic']
[INFO] Processing file: ./paper.pdf
[INFO] Extracted 97 content items (text: 60, image: 25, table: 8, equation: 4)
Processors: {'image': {'class': 'ImageModalProcessor', 'supports': 'image', 'enabled': True}, ...}
Parser installed: {'mineru': True}
Answer: [MOCK LLM] received prompt of 1234 chars

十三、关键资源

资源链接
GitHub 仓库https://github.com/HKUDS/RAG-Anything
论文arxiv 2510.12323
依赖 LightRAGhttps://github.com/HKUDS/LightRAG
依赖 MinerUhttps://github.com/opendatalab/MinerU
PyPIhttps://pypi.org/project/raganything/
官方文档https://github.com/HKUDS/RAG-Anything(README + docs/)
LicenseMIT
中文 READMEREADME_zh.md
英文 READMEREADME.md
Discord 社区https://discord.gg/yF2MmDJyGJ

十四、趋势与总结

14.1 多模态 RAG 的三大趋势

趋势 1:从「单图谱多模态」到「异构图谱 + 时序图谱」。当前 RAG-Anything 把所有模态都压成同一种「图节点 + 边」的结构,丢失了「图 vs 表 vs 公式」的本质差异。未来可能演化出异构图(节点带 type,边带 relation type)或多子图(一个文档一张子图,跨文档做链接预测)。

趋势 2:VLM 调用成本优化。现在 RAG-Anything 对每个非文本项都调一次 VLM,一份 50 页 PDF 可能触发 50+ 次 VLM 调用。未来 12 个月内大概率会出现「批量 VLM 调用」+ 「结果复用」+ 「按需细化」的混合策略,把单位文档的 VLM 成本压到 1/5 以下。

趋势 3:与 Agent 框架深度集成。现在 RAG-Anything 还是「文档 → 索引 → 检索」的单向管道。和 CrewAI / AutoGen / OpenAI Agents SDK 的深度集成(Agent 主动决定「什么时候调 RAG-Anything」「调哪份索引」)会成为下一个增量市场。

14.2 工程经验提炼

  1. 「不把所有内容都丢给 VLM」是底线——只在「非文本」上调 VLM,文本走传统 RAG 管道,成本差距可达 10 倍
  2. 「上下文是 VLM 的灵魂」——孤立的一张图 VLM 看不懂,必须把图所在章节、caption、脚注、前后页都喂进去。ContextExtractor 的 context_window=1 是经过实测的「甜点位」。
  3. 「容错比正确更重要」——VLM 输出不一定规范,JSON 解析必须有渐进式 fallback(直接解析 → 提取代码块 → 修复转义 → regex 兜底)。
  4. 「缓存是批处理的命脉」——parse_cache(按文件 hash)+ status_cache(按模态项 ID)的双层设计,让 100 份 PDF 的批处理可以「断点续传」,避免一次失败全重来。
  5. 「轻量编排」比「重写一套」更可持续——RAG-Anything 没有自己实现一套 LightRAG / MinerU,而是用 MixIn + dataclass 做「桥」,这是它能持续小步快跑(1.3.x → 1.4.x)的关键。

14.3 一句话总结

RAG-Anything 不是「又一个 RAG 框架」,而是「让 RAG 真正读懂多模态文档」的桥——它站在 LightRAG(图谱)和 MinerU(解析)两个巨人肩上,用 ContextExtractor + 四类 ModalProcessor 把「图、公式、表格」从「RAG 的盲区」变成「图谱的一等公民」。

如果你正在做「让 LLM 读懂 PDF」这件事,RAG-Anything 是当前 2026 年最值得尝试的开源方案之一——尤其是当你的语料里「图的信息量 ≥ 文字」时。


字数统计:约 31KB / 1100+ 行 / 5 个 Mermaid 图 / 7+ 个真实可运行代码块