编程 RAG-Anything 深度实战:把PDF里的图表公式全塞进知识图谱——港大HKUDS实验室如何重新定义多模态RAG

2026-05-16 12:46:25 +0800 CST views 2

RAG-Anything 深度实战:把PDF里的图表公式全塞进知识图谱——港大HKUDS实验室如何重新定义多模态RAG

当你用传统RAG处理一份200页的PDF技术文档时,是不是经常遇到这样的尴尬:文字能检索到,但表格数据丢了、公式变成乱码、架构图完全忽略?RAG-Anything来了——这个由香港大学数据科学实验室(HKUDS)开源的项目,正在用"知识图谱+多模态"的思路,彻底解决这个痛点。

一、背景:传统RAG的"半盲症"

1.1 传统RAG的三个致命缺陷

问题1:模态割裂——只能"看见"文字

传统RAG(Retrieval-Augmented Generation)工作流程很简单:

# 传统RAG的伪代码
def traditional_rag(query):
    # 1. 把文档切成分段
    chunks = split_document(document)  # 只处理纯文本
    
    # 2. 文本向量化
    embeddings = embed_text(chunks)
    
    # 3. 相似度检索
    relevant_chunks = cosine_similarity(query_embedding, embeddings)
    
    # 4. 丢给LLM生成答案
    answer = llm.generate(query, relevant_chunks)
    return answer

这个流程有个先天缺陷:它只能处理纯文本。当你丢给它一份包含架构图、数据表格、数学公式的PDF时:

  • 表格:被OCR识别成乱糟糟的文本,数据结构丢失
  • 公式:LaTeX格式丢失,变成无法理解的字符拼接
  • 图片:直接被忽略,或者只能用caption描述

问题2:上下文割裂——检索到的片段无法"串联"

传统RAG把文档切成固定大小的chunk(比如512个token),这样做有两个问题:

# 切分策略的问题
document = "第一章介绍整体架构[图1-1]。1.1节详细讲解模块A[表1-1]。1.2节讲解模块B..."

# 切分后可能变成:
chunk_1 = "第一章介绍整体架构[图1-1]。"
chunk_2 = "1.1节详细讲解模块A[表1-1]。"
chunk_3 = "1.2节讲解模块B..."

# 问题:chunk_2提到的"模块A"和chunk_1的"整体架构"关系丢失了!

这种切分方式破坏了文档的语义连续性,导致:

  • 检索到的片段是孤立的
  • 无法理解跨段落的引用关系("如上图所示"、"详见表2-1")
  • 多跳推理能力几乎为零

问题3:知识孤岛——不同文档间的关联被忽略

假设你有两份文档:

  • 文档A:《微服务架构设计模式》
  • 文档B:《Docker容器化部署实战》

传统RAG分别索引这两份文档,但无法建立它们之间的语义关联:

  • "文档A提到的Saga模式" 和 "文档B提到的分布式事务" 可能是同一个概念
  • 但传统RAG无法自动发现这种跨文档的知识关联

1.2 为什么需要多模态RAG?

让我们看一个真实的场景:你要构建一个"技术文档问答系统",知识库包含:

技术文档集/
├── API手册.pdf        # 包含大量代码示例、参数表格
├── 架构设计.pdf       # 包含UML图、流程图、架构图
├── 算法原理.pdf       # 包含数学公式、推导过程
└── 部署指南.pdf       # 包含命令行示例、配置表格

用传统RAG的效果

  • ❌ 用户问:"如何配置负载均衡?" → 只能检索到文字描述,配置表格丢失
  • ❌ 用户问:"Saga模式的实现原理是什么?" → 只能检索到文字,状态机图丢失
  • ❌ 用户问:"这个公式的推导过程?" → LaTeX公式变成乱码,无法理解

用多模态RAG的效果

  • ✅ 用户问:"如何配置负载均衡?" → 检索到文字说明 + 配置表格 + 架构图
  • ✅ 用户问:"Saga模式的实现原理?" → 检索到文字 + 状态机图 + 代码示例
  • ✅ 用户问:"这个公式的推导过程?" → 检索到LaTeX公式 + 推导图示 + 文字说明

核心差异:多模态RAG不是简单地"把图片存下来",而是:

  1. 理解图片的语义内容(图表、公式、表格的结构化信息)
  2. 关联图片与文字的上下文关系
  3. 检索时同时考虑文本和视觉语义
  4. 生成时能把多模态信息融合进答案

二、RAG-Anything核心技术剖析

2.1 架构概览:站在LightRAG肩膀上

RAG-Anything是HKUDS实验室在LightRAG基础上扩展的多模态RAG框架。要理解RAG-Anything,得先搞清楚LightRAG做了什么。

LightRAG的核心创新:引入知识图谱

传统RAG的检索单位是"文本块(chunk)",而LightRAG的检索单位是"实体-关系(entity-relation)":

# LightRAG的知识图谱构建
from lightrag import LightRAG

# 1. 文档 -> 知识图谱
rag = LightRAG()
kg = rag.build_knowledge_graph(document)

# kg的结构:
# {
#   "entities": ["微服务", "Saga模式", "分布式事务", "负载均衡"],
#   "relations": [
#     ("微服务", "使用", "Saga模式"),
#     ("Saga模式", "解决", "分布式事务"),
#     ("负载均衡", "部署在", "微服务前端")
#   ]
# }

# 2. 查询 -> 图谱检索
query = "如何实现分布式事务?"
subgraph = rag.retrieve_from_kg(query)  # 返回相关子图

# subgraph包含:
# - 实体:"Saga模式"、"TCC模式"、"最终一致性"
# - 关系:"Saga模式-解决-分布式事务"
# - 上下文:相关实体的详细描述

LightRAG的优势

  • ✅ 能理解实体间的复杂关系(多跳推理)
  • ✅ 检索结果有上下文(子图结构)
  • ✅ 跨文档关联(不同文档的相同实体会合并)

但LightRAG的局限

  • ❌ 只处理纯文本,无法处理图片、表格、公式
  • ❌ 知识图谱的节点只能是文本实体

RAG-Anything的扩展思路
把知识图谱从"纯文本"扩展到"多模态":

# RAG-Anything的知识图谱
from rag_anything import RAGAnything

rag = RAGAnything()

# 构建多模态知识图谱
multimodal_kg = rag.build_multimodal_kg(document)

# multimodal_kg的结构:
# {
#   "text_entities": ["微服务", "Saga模式"],  # 文本实体
#   "image_entities": ["图1-1", "架构图2-3"],  # 图片实体
#   "table_entities": ["表3-1", "性能对比表"],  # 表格实体
#   "formula_entities": ["公式4-1", "贝叶斯定理"],  # 公式实体
#   "relations": [
#     ("微服务", "见图", "图1-1"),  # 文本 -> 图片
#     ("Saga模式", "详见", "表3-1"),  # 文本 -> 表格
#     ("贝叶斯定理", "推导见", "公式4-1")  # 文本 -> 公式
#   ]
# }

2.2 五大处理阶段详解

RAG-Anything的处理流水线分为五个阶段,我们逐个拆解:

阶段1:多模态文档解析(Multimodal Document Parsing)

目标:把PDF中的文本、图片、表格、公式分别提取出来,并保留它们的位置和引用关系。

from rag_anything import MultimodalParser

parser = MultimodalParser()

# 解析PDF
parsed_result = parser.parse("技术文档.pdf")

# parsed_result结构
{
    "pages": [
        {
            "page_num": 1,
            "text_blocks": [
                {"text": "第一章 架构设计", "bbox": [50, 100, 300, 150]},
                {"text": "如图1-1所示,系统采用微服务架构...", "bbox": [50, 200, 500, 250]}
            ],
            "image_blocks": [
                {
                    "image_path": "temp/page1_img1.png",
                    "bbox": [100, 300, 600, 800],
                    "caption": "图1-1 系统架构图",
                    "inlined_text": "如图1-1所示"  # 引用关系!
                }
            ],
            "table_blocks": [
                {
                    "table_data": [["模块", "QPS", "延迟"], ["API网关", 10000, "10ms"]],
                    "bbox": [100, 900, 600, 1000],
                    "caption": "表1-1 性能参数"
                }
            ],
            "formula_blocks": [
                {
                    "latex": r"\frac{\partial L}{\partial w} = \sum_{i=1}^n (y_i - \hat{y}_i)x_i",
                    "bbox": [150, 1100, 500, 1150],
                    "context": "损失函数对权重的偏导数为"
                }
            ]
        }
    ]
}

技术难点1:如何区分"正文文本"和"图片标题"、"表格注释"?

RAG-Anything使用布局分析模型(基于Detectron2或LayoutLMv3):

# 布局分析示例
from transformers import LayoutLMv3Processor, LayoutLMv3ForTokenClassification

processor = LayoutLMv3Processor.from_pretrained("microsoft/layoutlmv3-base")
model = LayoutLMv3ForTokenClassification.from_pretrained("microsoft/layoutlmv3-base")

# 输入:PDF页面图像 + OCR文本 + 边界框坐标
inputs = processor(
    images=page_image,
    text=ocr_text,
    boxes=bbox_coordinates,
    return_tensors="pt"
)

# 输出:每个token的类别(正文/标题/图片标题/表格/公式)
outputs = model(**inputs)
predicted_classes = outputs.logits.argmax(-1)

# 类别映射:
# 0: 正文文本
# 1: 标题
# 2: 图片标题(Figure caption)
# 3: 表格标题(Table caption)
# 4: 表格内容
# 5: 公式

技术难点2:如何提取表格的结构化数据?

RAG-Anything集成两种方案:

  1. 基于深度学习的表格检测:使用TableTransformer(微软开源)
  2. 基于规则的后处理:处理合并单元格、多级表头
from table_transformer import TableTransformer

table_detector = TableTransformer.from_pretrained("microsoft/table-transformer")

# 检测表格区域
table_bbox = table_detector.detect(image)

# 结构化识别
table_structure = table_detector.recognize_structure(
    image.crop(table_bbox),
    strategy="complex"  # 处理合并单元格
)

# 输出:二维数组
# [
#   ["模块", "QPS", "延迟", "备注"],
#   ["API网关", "10000", "10ms", "支持限流"],
#   ["业务服务", "5000", "50ms", "有状态"]
# ]

阶段2:多模态实体提取(Multimodal Entity Extraction)

目标:从解析结果中,提取出不同类型的"实体",为构建知识图谱做准备。

from rag_anything import MultimodalEntityExtractor

extractor = MultimodalEntityExtractor()

# 提取实体
entities = extractor.extract(parsed_result)

# entities结构
{
    "text_entities": [
        {
            "id": "text_1",
            "content": "微服务架构是一种将单体应用拆分成多个小型服务的架构模式",
            "type": "concept",
            "embedding": [0.12, 0.34, ...]  # 文本向量
        }
    ],
    "image_entities": [
        {
            "id": "img_1",
            "content": "系统架构图,展示了API网关、业务服务、数据库之间的调用关系",
            "type": "architecture_diagram",
            "embedding": [0.56, 0.78, ...],  # 图像向量(通过CLIP提取)
            "metadata": {
                "image_path": "temp/page1_img1.png",
                "caption": "图1-1",
                "mentioned_in_text": ["text_1"]  # 被哪些文本实体引用
            }
        }
    ],
    "table_entities": [
        {
            "id": "table_1",
            "content": "性能参数表:模块、QPS、延迟",
            "type": "performance_table",
            "embedding": [0.90, 0.12, ...],  # 表格向量(通过TaBERT提取)
            "metadata": {
                "structured_data": [["模块", "QPS"], ["API网关", 10000]],
                "caption": "表1-1"
            }
        }
    ],
    "formula_entities": [
        {
            "id": "formula_1",
            "content": "梯度下降更新公式",
            "latex": r"w_{t+1} = w_t - \eta \nabla L(w_t)",
            "type": "optimization_formula",
            "embedding": [0.34, 0.56, ...],  # 公式向量(通过LaTeX嵌入模型)
            "metadata": {
                "context": "在神经网络训练中,权重更新遵循以下公式"
            }
        }
    ]
}

关键技术:如何为不同模态的内容生成向量?

RAG-Anything使用多模态嵌入模型

# 1. 文本嵌入:使用Sentence-BERT或BGE
from sentence_transformers import SentenceTransformer
text_model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
text_embedding = text_model.encode("微服务架构是一种...")

# 2. 图像嵌入:使用CLIP
from transformers import CLIPProcessor, CLIPModel
clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")

image = Image.open("temp/page1_img1.png")
inputs = clip_processor(images=image, return_tensors="pt")
image_embedding = clip_model.get_image_features(**inputs)

# 3. 表格嵌入:使用TaBERT
from rag_anything.embeddings import TaBERTEmbedder
table_model = TaBERTEmbedder.from_pretrained("tanbaojun/TaBERT")
table_embedding = table_model.encode(structured_table_data)

# 4. 公式嵌入:使用LaTeX嵌入模型
from rag_anything.embeddings import LaTeXEmbedder
formula_model = LaTeXEmbedder.from_pretrained("allenai/mathbert")
formula_embedding = formula_model.encode(r"\frac{\partial L}{\partial w}")

所有嵌入向量都会被映射到同一个向量空间,这样检索时才能跨模态计算相似度!

阶段3:多模态关系构建(Multimodal Relation Construction)

目标:建立不同实体之间的关系(包括跨模态关系)。

from rag_anything import RelationBuilder

builder = RelationBuilder()

# 构建关系
relations = builder.build(entities)

# relations结构
[
    # 文本 -> 文本关系
    {
        "subject": "text_1",  # "微服务架构"
        "predicate": "包含",
        "object": "text_2",   # "Saga模式"
        "confidence": 0.95
    },
    
    # 文本 -> 图像关系
    {
        "subject": "text_1",  # "如图1-1所示"
        "predicate": "参见",
        "object": "img_1",    # 架构图
        "confidence": 0.99
    },
    
    # 文本 -> 表格关系
    {
        "subject": "text_3",  # "性能参数见表1-1"
        "predicate": "详细数据见",
        "object": "table_1",
        # "性能参数见表1-1"
        "confidence": 0.98
    },
    
    # 图像 -> 文本关系(反向)
    {
        "subject": "img_1",   # 架构图
        "predicate": "被引用自",
        "object": "text_1",
        "confidence": 0.99
    }
]

关系抽取的技术实现

RAG-Anything使用多模态LLM(如GPT-4V、Qwen-VL)来抽取跨模态关系:

from openai import OpenAI

client = OpenAI()

# 示例:抽取文本和图片的关系
response = client.chat.completions.create(
    model="gpt-4-vision-preview",
    messages=[
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "这段文本提到了哪张图片?它们之间的关系是什么?"},
                {"type": "image", "image": open("temp/page1_img1.png", "rb")},
                {"type": "text", "text": "如图1-1所示,系统采用微服务架构..."}
            ]
        }
    ]
)

# LLM返回结构化结果
# {
#   "mentioned_image": "图1-1",
#   "relation": "文本描述了图片的内容",
#   "confidence": 0.99
# }

阶段4:多模态知识图谱存储(Multimodal Knowledge Graph Storage)

目标:把实体和关系存储到图数据库(如Neo4j)或向量数据库(如Milvus)。

存储方案选择

# 方案A:使用Neo4j存储知识图谱(保留关系结构)
from neo4j import GraphDatabase

driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "password"))

def store_to_neo4j(entities, relations):
    with driver.session() as session:
        # 1. 创建实体节点
        for entity in entities["text_entities"]:
            session.run(
                "CREATE (e:TextEntity {id: $id, content: $content, embedding: $embedding})",
                id=entity["id"],
                content=entity["content"],
                embedding=entity["embedding"].tolist()
            )
        
        for entity in entities["image_entities"]:
            session.run(
                "CREATE (e:ImageEntity {id: $id, caption: $caption, embedding: $embedding})",
                id=entity["id"],
                caption=entity["metadata"]["caption"],
                embedding=entity["embedding"].tolist()
            )
        
        # 2. 创建关系边
        for rel in relations:
            session.run(
                """
                MATCH (a {id: $subject_id})
                MATCH (b {id: $object_id})
                CREATE (a)-[:$predicate {confidence: $confidence}]->(b)
                """,
                subject_id=rel["subject"],
                object_id=rel["object"],
                predicate=rel["predicate"],
                confidence=rel["confidence"]
            )

# 方案B:使用Milvus存储向量(支持混合检索)
from pymilvus import Collection, connections

connections.connect(host="localhost", port="19530")
collection = Collection("multimodal_kg")

def store_to_milvus(entities):
    for entity in entities["text_entities"]:
        collection.insert([
            [entity["id"]],  # 主键
            [entity["content"]],  # 文本内容
            [entity["embedding"].tolist()],  # 向量
            ["text"]  # 类型
        ])
    
    for entity in entities["image_entities"]:
        collection.insert([
            [entity["id"]],
            [entity["content"]],  # 图片描述
            [entity["embedding"].tolist()],
            ["image"]
        ])

RAG-Anything的"1+3+N"架构

1个知识图谱(Neo4j)
  ↓ 存储实体关系和图结构
  ├─ 3种检索路径
  │   ├─ 文本检索(关键词 + 语义)
  │   ├─ 图像检索(视觉相似度)
  │   └─ 跨模态检索(文本查询 -> 图片/表格)
  └─ N个应用场景
      ├─ 技术文档问答
      ├─ 学术论文检索
      ├─ 法律合同分析
      └─ 医疗报告理解

阶段5:多模态检索与生成(Multimodal Retrieval & Generation)

目标:根据用户输入,从多模态知识图谱中检索相关信息,并生成包含多模态内容的答案。

from rag_anything import MultimodalRAG

rag = MultimodalRAG()

# 检索
query = "微服务架构的性能瓶颈在哪里?"
retrieved = rag.retrieve(query, top_k=5)

# retrieved结构
{
    "text_contexts": [
        {
            "content": "API网关的QPS上限为10000,成为性能瓶颈",
            "score": 0.92,
            "source": "text_3"
        }
    ],
    "image_contexts": [
        {
            "image_path": "temp/page1_img1.png",
            "caption": "图1-1 系统架构图",
            "score": 0.87,
            "relevance": "展示了API网关的位置"
        }
    ],
    "table_contexts": [
        {
            "table_data": [["模块", "QPS"], ["API网关", 10000]],
            "caption": "表1-1 性能参数",
            "score": 0.94
        }
    ]
}

# 生成
answer = rag.generate(query, retrieved)

# answer示例(Markdown格式)
"""
根据《系统架构设计文档》的分析,微服务架构的主要性能瓶颈在**API网关**处。

## 性能数据
如表1-1所示,API网关的QPS上限为10000,远低于业务服务的处理能力(50000 QPS)。

| 模块 | QPS | 延迟 |
|------|-----|------|
| API网关 | 10000 | 10ms |
| 业务服务 | 50000 | 50ms |

## 架构分析
如图1-1所示,所有外部请求都必须经过API网关,形成单点瓶颈。

![系统架构图](temp/page1_img1.png)

## 优化建议
1. 引入负载均衡器,部署多个API网关实例
2. 使用缓存(Redis)减轻API网关的计算压力
3. 考虑将部分鉴权逻辑下沉到业务服务
"""

关键技术:如何让LLM理解多模态上下文?

RAG-Anything使用多模态LLM(如GPT-4V、Claude 3 Opus、Qwen-VL)作为生成器:

from openai import OpenAI

client = OpenAI()

def generate_with_multimodal_context(query, retrieved):
    # 构建多模态上下文
    context = f"用户问题:{query}\n\n相关信息:\n"
    
    # 添加文本上下文
    for text in retrieved["text_contexts"]:
        context += f"[文本] {text['content']}\n"
    
    # 添加表格上下文
    for table in retrieved["table_contexts"]:
        context += f"[表格] {table['caption']}\n"
        context += format_table_as_markdown(table['table_data'])
    
    # 添加图片上下文(上传图片)
    images = []
    for img in retrieved["image_contexts"]:
        context += f"[图片] {img['caption']} - {img['relevance']}\n"
        images.append(img['image_path'])
    
    # 调用多模态LLM
    messages = [
        {
            "role": "system",
            "content": "你是一个技术文档助手,能根据文本、表格、图片生成综合答案。"
        },
        {
            "role": "user",
            "content": [
                {"type": "text", "text": context},
                *[{"type": "image", "image": open(img, "rb")} for img in images],
                {"type": "text", "text": "请基于以上信息回答问题。"}
            ]
        }
    ]
    
    response = client.chat.completions.create(
        model="gpt-4-vision-preview",
        messages=messages
    )
    
    return response.choices[0].message.content

三、架构分析:RAG-Anything vs. 传统RAG vs. LightRAG

3.1 对比表格

特性传统RAGLightRAGRAG-Anything
处理的模态仅文本仅文本文本+图像+表格+公式
检索单位文本块(chunk)实体-关系(子图)多模态实体-关系(子图)
跨文档关联
多跳推理
表格理解❌(OCR成文本)✅(保留结构化数据)
公式理解❌(LaTeX丢失)✅(LaTeX嵌入)
图片理解❌(忽略或仅caption)✅(视觉语义嵌入)
存储方式向量数据库知识图谱+向量多模态知识图谱+混合检索
适用场景纯文本问答复杂文本推理技术文档、学术论文、医疗报告

3.2 性能对比(基于HKUDS的实验数据)

数据集:200份技术PDF文档(共约5万页),包含大量架构图、数据表格、数学公式。

测试query类型

  1. 纯文本问题:"什么是Saga模式?"
  2. 表格相关问题:"各模块的性能参数对比?"
  3. 图片相关问题:"系统架构图中的数据流向?"
  4. 多模态综合问题:"如何优化API网关的性能?(需要结合文本说明+表格数据+架构图)"

实验结果

指标传统RAGLightRAGRAG-Anything
纯文本问题准确率78%89%91%
表格问题准确率12%15%94%
图片问题准确率3%5%87%
多模态综合准确率8%11%92%
平均检索延迟120ms350ms420ms
存储占用10GB25GB40GB

结论

  • RAG-Anything在多模态问题上的准确率远超传统方案
  • 代价是检索延迟增加(420ms vs. 120ms)和存储占用增加
  • 但对于技术文档场景,精度提升远大于性能损失

四、代码实战:从零部署RAG-Anything

4.1 环境准备

# 1. 创建虚拟环境
conda create -n rag-anything python=3.10
conda activate rag-anything

# 2. 安装PyTorch(需要CUDA支持)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 3. 安装RAG-Anything
pip install rag-anything

# 4. 安装可选依赖(完整功能)
pip install "rag-anything[all]"

# 这会安装:
# - transformers, sentence-transformers(嵌入模型)
# - clip, openclip(图像嵌入)
# - pymilvus, neo4j(向量/图数据库)
# - table-transformer(表格识别)
# - pix2tex(公式识别)

4.2 快速开始:处理一份PDF文档

from rag_anything import RAGAnything

# 1. 初始化(会自动下载所需模型)
rag = RAGAnything(
    # 文本嵌入模型
    text_embed_model="BAAI/bge-large-zh-v1.5",
    
    # 图像嵌入模型
    image_embed_model="openai/clip-vit-base-patch32",
    
    # 表格嵌入模型
    table_embed_model="tanbaojun/TaBERT",
    
    # 设备
    device="cuda:0",
    
    # 是否启用OCR(处理扫描版PDF)
    enable_ocr=True
)

# 2. 构建多模态知识图谱
rag.build_from_file(
    file_path="技术文档.pdf",
    output_dir="./kg_output",  # 输出目录
    enable_image_extraction=True,
    enable_table_extraction=True,
    enable_formula_extraction=True
)

# 3. 保存到向量数据库
rag.save_to_milvus(
    uri="http://localhost:19530",
    collection_name="tech_docs"
)

# 4. 查询
answer = rag.query(
    question="如何配置负载均衡?",
    top_k=5,
    include_images=True,  # 在答案中包含图片
    include_tables=True  # 在答案中包含表格
)

print(answer)
# 输出示例:
# 根据文档第3章的说明,负载均衡的配置步骤如下:
# 
# ## 配置参数
# | 参数 | 默认值 | 推荐值 |
# |------|--------|--------|
# | worker_num | 4 | CPU核心数 |
# | max_connections | 1024 | 10000 |
# 
# ## 架构示意
# 如图3-2所示,负载均衡器位于...
# [图片自动插入]

4.3 高级用法:批量处理 + 自定义提取规则

import os
from rag_anything import RAGAnything, MultimodalParser

# 自定义解析器(处理特殊格式的PDF)
class CustomParser(MultimodalParser):
    def extract_tables(self, page_image):
        # 你的自定义表格提取逻辑
        # 比如:处理合并单元格、多级表头
        pass
    
    def extract_formulas(self, page_text):
        # 使用正则+LaTeX解析器提取公式
        import re
        formula_pattern = r'\$(.*?)\$|\\\[(.*?)\\\]|\\\((.*?)\\\)'
        formulas = re.findall(formula_pattern, page_text)
        return formulas

# 使用自定义解析器
rag = RAGAnything(
    parser_class=CustomParser,
    ...
)

# 批量处理整个目录
pdf_dir = "./tech_documents/"
for filename in os.listdir(pdf_dir):
    if filename.endswith(".pdf"):
        rag.build_from_file(
            file_path=os.path.join(pdf_dir, filename),
            output_dir="./kg_output",
            # 增量构建(不覆盖已有知识图谱)
            incremental=True
        )

4.4 集成到Web服务(FastAPI示例)

from fastapi import FastAPI, UploadFile, File
from rag_anything import RAGAnything
import tempfile

app = FastAPI()
rag = RAGAnything.load_from_milvus(
    uri="http://localhost:19530",
    collection_name="tech_docs"
)

@app.post("/upload")
async def upload_document(file: UploadFile = File(...)):
    """上传文档并构建知识图谱"""
    with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
        tmp.write(await file.read())
        tmp_path = tmp.name
    
    # 构建知识图谱
    rag.build_from_file(tmp_path, incremental=True)
    
    return {"status": "success", "message": "文档已索引"}

@app.get("/query")
async def query_document(q: str):
    """查询"""
    answer = rag.query(
        question=q,
        top_k=5,
        return_format="markdown"  # 返回Markdown格式(包含图片链接)
    )
    
    return {"answer": answer}

# 启动服务
# uvicorn main:app --host 0.0.0.0 --port 8000

五、性能优化:让RAG-Anything生产可用

5.1 检索速度优化

问题:RAG-Anything的检索延迟(420ms)比传统RAG(120ms)高,主要原因:

  1. 需要检索多种模态的向量
  2. 需要遍历知识图谱的关系
  3. 多模态LLM的推理时间长

优化方案1:分层检索(Hierarchical Retrieval)

class HierarchicalRetriever:
    def __init__(self, rag):
        self.rag = rag
        # 预构建索引:文本 -> 可能相关的图片/表格
        self.text_to_multimodal_index = self._build_index()
    
    def _build_index(self):
        """预处理:建立文本到多模态内容的倒排索引"""
        index = {}
        for text_entity in self.rag.entities["text_entities"]:
            # 找到所有和这个文本实体相关的图片/表格
            related_multimodal = self.rag.get_related_entities(
                text_entity["id"],
                filter_types=["image", "table", "formula"]
            )
            index[text_entity["id"]] = related_multimodal
        return index
    
    def retrieve(self, query, top_k=5):
        # 第一步:只检索文本(快速过滤)
        text_results = self.rag.retrieve_text_only(query, top_k=top_k)
        
        # 第二步:根据索引,只加载相关的多模态内容
        multimodal_results = []
        for text_result in text_results:
            related = self.text_to_multimodal_index.get(text_result["id"], [])
            multimodal_results.extend(related)
        
        return {
            "text_contexts": text_results,
            "multimodal_contexts": multimodal_results
        }

优化方案2:缓存多模态嵌入

from functools import lru_cache
import hashlib

class CachedEmbedder:
    def __init__(self, model):
        self.model = model
        self.cache = {}
    
    def embed_image(self, image_path):
        # 用图片内容的hash作为缓存key
        with open(image_path, "rb") as f:
            image_hash = hashlib.md5(f.read()).hexdigest()
        
        if image_hash in self.cache:
            return self.cache[image_hash]
        
        # 计算嵌入
        embedding = self.model.encode_image(image_path)
        self.cache[image_hash] = embedding
        return embedding

优化方案3:异步并行检索

import asyncio
from concurrent.futures import ThreadPoolExecutor

async def parallel_retrieve(rag, query, top_k=5):
    # 并行检索不同模态
    loop = asyncio.get_event_loop()
    
    with ThreadPoolExecutor() as executor:
        tasks = [
            loop.run_in_executor(executor, rag.retrieve_text, query, top_k),
            loop.run_in_executor(executor, rag.retrieve_images, query, top_k),
            loop.run_in_executor(executor, rag.retrieve_tables, query, top_k)
        ]
        
        text_results, image_results, table_results = await asyncio.gather(*tasks)
    
    return {
        "text_contexts": text_results,
        "image_contexts": image_results,
        "table_contexts": table_results
    }

5.2 存储优化

问题:多模态知识图谱的存储占用(40GB)比传统RAG(10GB)高4倍。

优化方案1:向量压缩

from rag_anything.utils import compress_vectors

# 使用PQ(Product Quantization)压缩向量
# 从float32(4字节/dim)压缩到int8(1字节/dim)
compressed_vectors = compress_vectors(
    vectors=original_vectors,
    method="pq",
    code_size=8,  # 每个向量压缩成8字节
    num_centroids=256
)

# 压缩后存储占用减少75%

优化方案2:图片降采样

from PIL import Image

def downsample_image(image_path, max_size=512):
    """将图片降采样到最大512x512"""
    img = Image.open(image_path)
    img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
    
    # 保存为JPEG(有损压缩)
    output_path = image_path.replace(".png", "_compressed.jpg")
    img.save(output_path, "JPEG", quality=85)
    
    return output_path

# 在处理PDF时自动降采样
rag = RAGAnything(
    image_preprocess=downsample_image,
    ...
)

优化方案3:增量更新

# 不是每次都重新构建整个知识图谱
# 而是只处理新增/修改的文档
rag.build_from_file(
    file_path="新文档.pdf",
    incremental=True,  # 增量模式
    compare_by_hash=True  # 通过比较文件hash判断是否已处理
)

5.3 生成质量优化

问题:多模态LLM有时会"幻觉"(编造图片内容或表格数据)。

优化方案:多阶段验证

def generate_with_verification(rag, query):
    # 第一步:检索
    retrieved = rag.retrieve(query, top_k=5)
    
    # 第二步:让LLM生成答案(草稿)
    draft_answer = rag.generate(query, retrieved)
    
    # 第三步:验证答案中的每个引用是否真实存在
    verified_answer = verify_citations(draft_answer, retrieved)
    
    return verified_answer

def verify_citations(answer, retrieved):
    """验证答案中的引用"""
    import re
    
    # 提取答案中的所有引用标记
    citations = re.findall(r'\[图\d+-\d+\]|\[表\d+-\d+\]', answer)
    
    verified_answer = answer
    for citation in citations:
        # 检查这个引用是否在retrieved中存在
        if not is_citation_valid(citation, retrieved):
            # 如果引用无效,删除或标注
            verified_answer = verified_answer.replace(
                citation,
                f"{citation}(引用内容可能不准确,请核实原文)"
            )
    
    return verified_answer

六、真实案例:构建"技术文档问答系统"

6.1 需求分析

假设你在一家科技公司,需要为开发团队构建一个内部技术文档问答系统。知识库包含:

知识库/
├── 微服务架构设计.pdf  (120页,含50+架构图,30+配置表格)
├── API接口文档.pdf     (300页,含200+接口表格)
├── 数据库设计文档.pdf   (80页,含ER图、SQL示例)
└── 运维手册.pdf        (150页,含命令速查表、故障排查流程图)

传统RAG的问题

  • 开发者问:"如何配置Redis缓存?" → 只能检索到文字,配置表格丢失
  • 开发者问:"微服务间的调用流程是什么?" → 架构图被忽略,只能看到文字描述
  • 开发者问:"数据库表结构如何设计?" → ER图丢失,只能看到字段列表

使用RAG-Anything后的效果

  • ✅ 检索到配置步骤 + 配置表格 + 架构图中的相关部分
  • ✅ 检索到文字说明 + 架构图 + 示例代码
  • ✅ 检索到表结构说明 + ER图 + SQL示例

6.2 系统架构

┌─────────────────────────────────────────────────────┐
│                  前端(Web UI)                      │
│  - 提问框(支持文本 + 图片上传)                     │
│  - 答案展示(Markdown渲染,图片懒加载)              │
└──────────────────┬──────────────────────────────────┘
                   │ HTTP API
┌──────────────────▼──────────────────────────────────┐
│             后端服务(FastAPI)                      │
│  - /upload  上传文档                                 │
│  - /query   问答接口                                 │
│  - /feedback 用户反馈                                │
└──────────────────┬──────────────────────────────────┘
                   │ SDK调用
┌──────────────────▼──────────────────────────────────┐
│          RAG-Anything核心引擎                       │
│  - 多模态文档解析                                    │
│  - 多模态知识图谱构建                                │
│  - 混合检索(文本+图像+表格)                        │
│  - 多模态LLM生成                                     │
└──────────────────┬──────────────────────────────────┘
                   │ 读写
┌──────────────────▼──────────────────────────────────┐
│              存储层                                  │
│  - Neo4j:知识图谱(实体关系)                       │
│  - Milvus:向量数据库(多模态嵌入)                   │
│  - MinIO:原始文件存储(PDF、图片)                   │
└─────────────────────────────────────────────────────┘

6.3 核心代码实现

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from rag_anything import RAGAnything
import asyncio

app = FastAPI()
rag = RAGAnything.load_from_milvus(...)

class QueryRequest(BaseModel):
    question: str
    top_k: int = 5
    include_images: bool = True
    include_tables: bool = True

class UploadResponse(BaseModel):
    status: str
    document_id: str
    processed_pages: int
    extracted_images: int
    extracted_tables: int

@app.post("/upload", response_model=UploadResponse)
async def upload_document(file: UploadFile):
    """上传并索引文档"""
    try:
        # 保存临时文件
        tmp_path = f"/tmp/{file.filename}"
        with open(tmp_path, "wb") as f:
            f.write(await file.read())
        
        # 构建知识图谱(异步,避免阻塞)
        loop = asyncio.get_event_loop()
        result = await loop.run_in_executor(
            None,
            rag.build_from_file,
            tmp_path,
            "./kg_output",
            True  # incremental
        )
        
        return {
            "status": "success",
            "document_id": result["doc_id"],
            "processed_pages": result["num_pages"],
            "extracted_images": result["num_images"],
            "extracted_tables": result["num_tables"]
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/query")
async def query_document(req: QueryRequest):
    """问答接口"""
    try:
        # 检索 + 生成
        answer = await asyncio.get_event_loop().run_in_executor(
            None,
            rag.query,
            req.question,
            req.top_k,
            req.include_images,
            req.include_tables
        )
        
        # 解析答案中的图片引用,生成可访问的URL
        import re
        answer = re.sub(
            r'!\[(.*?)\]\((.*?)\)',
            r'![\\1](/api/images/\2)',
            answer
        )
        
        return {"answer": answer}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/images/{image_path:path}")
async def get_image(image_path: str):
    """返回图片文件(支持懒加载)"""
    from fastapi.responses import FileResponse
    return FileResponse(f"./kg_output/images/{image_path}")

6.4 效果演示

Query 1:"如何配置微服务间的鉴权?"

传统RAG的答案

根据文档第4章,微服务间的鉴权可以通过JWT Token实现。需要在每个服务的请求头中加入Authorization字段...

(缺失:配置表格、架构图、代码示例)

RAG-Anything的答案

根据《微服务架构设计》第4章和《运维手册》第7章的说明,微服务间鉴权配置如下:

配置步骤

如表4-2所示,需要在网关和各个服务中配置以下参数:

服务鉴权方式Token有效期刷新机制
API网关JWT2小时自动刷新
用户服务JWT + Redis2小时手动刷新
订单服务JWT1小时自动刷新

架构示意

如图4-3所示,鉴权流程为:客户端 -> API网关(验证JWT) -> 业务服务(解析JWT) -> 返回结果

鉴权流程图

代码示例

# API网关配置(gateway-config.yaml)
security:
  jwt:
    secret: ${JWT_SECRET}
    expiration: 7200  # 2小时
    refreshable: true

注意事项

  1. 确保所有服务的JWT secret一致
  2. 使用Redis存储Token黑名单(支持登出)
  3. 敏感操作需要二次验证(见第6章)

七、总结与展望

7.1 RAG-Anything的核心价值

1. 真正的"全模态"理解

  • 不只是OCR文字,而是理解图片的语义、表格的结构、公式的数学含义
  • 跨模态关联:文字描述的图表、表格数据的来源、公式的上下文

2. 知识图谱带来的推理能力

  • 多跳推理:从"A提到B"和"B参见图C",推断出"A和C的关系"
  • 跨文档关联:不同文档的相同概念会自动合并

3. 生产可用的性能优化

  • 分层检索、缓存、异步并行:让420ms的延迟降到200ms以内
  • 向量压缩、图片降采样:存储占用减少75%

7.2 适用场景

非常适合

  • 技术文档问答(API文档、架构设计文档)
  • 学术论文检索(包含大量公式、图表)
  • 法律合同分析(条款引用、表格数据)
  • 医疗报告理解(医学影像 + 文字描述)

不太适合

  • 纯文本对话(用传统RAG更轻量)
  • 实时性要求极高的场景(检索延迟仍然较高)
  • 计算资源极度受限的环境(需要GPU运行多模态模型)

7.3 未来发展方向

1. 更多模态的支持

  • 视频理解:提取关键帧 + 字幕 + 音频转文本
  • 3D模型理解:CAD文件、BIM模型的结构化检索

2. 更高效的检索算法

  • 基于强化学习的检索策略(根据用户反馈动态调整)
  • 边缘计算部署(在本地设备运行轻量级多模态模型)

3. 与其他技术的融合

  • Agent + RAG-Anything:让AI Agent能自主检索多模态知识库
  • Fine-tuning:用领域数据微调多模态嵌入模型(提升专业术语理解)

八、参考资料

  1. RAG-Anything GitHub仓库:https://github.com/HKUDS/RAG-Anything
  2. LightRAG论文:"LightRAG: Simple and Fast Retrieval-Augmented Generation"
  3. 多模态嵌入模型
    • 文本:BAAI/bge-large-zh-v1.5
    • 图像:openai/clip-vit-base-patch32
    • 表格:tanbaojun/TaBERT
    • 公式:allenai/mathbert
  4. 知识图谱数据库:Neo4j, Nebula Graph
  5. 向量数据库:Milvus, Qdrant, Weaviate

写在最后:RAG-Anything不是银弹,但它确实解决了传统RAG在处理多模态文档时的核心痛点。如果你正在构建技术文档问答系统、学术论文检索平台,或者任何需要处理"文字+图片+表格+公式"的场景,RAG-Anything值得一试。

项目热度:截至目前,RAG-Anything在GitHub上已获得17,968 Star,是2026年GitHub Trending的热门项目之一。港大HKUDS实验室还在持续更新,值得关注!


本文作者:程序员茄子
发布时间:2026年5月16日
标签:RAG | 多模态 | 知识图谱 | PDF处理 | GitHub Trending

推荐文章

Go语言中的mysql数据库操作指南
2024-11-19 03:00:22 +0800 CST
Vue 中如何处理跨组件通信?
2024-11-17 15:59:54 +0800 CST
使用Vue 3实现无刷新数据加载
2024-11-18 17:48:20 +0800 CST
IP地址获取函数
2024-11-19 00:03:29 +0800 CST
总结出30个代码前端代码规范
2024-11-19 07:59:43 +0800 CST
Vue中的`key`属性有什么作用?
2024-11-17 11:49:45 +0800 CST
Vue3中如何进行性能优化?
2024-11-17 22:52:59 +0800 CST
go错误处理
2024-11-18 18:17:38 +0800 CST
程序员茄子在线接单