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不是简单地"把图片存下来",而是:
- 理解图片的语义内容(图表、公式、表格的结构化信息)
- 关联图片与文字的上下文关系
- 检索时同时考虑文本和视觉语义
- 生成时能把多模态信息融合进答案
二、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集成两种方案:
- 基于深度学习的表格检测:使用TableTransformer(微软开源)
- 基于规则的后处理:处理合并单元格、多级表头
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网关,形成单点瓶颈。

## 优化建议
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 对比表格
| 特性 | 传统RAG | LightRAG | RAG-Anything |
|---|---|---|---|
| 处理的模态 | 仅文本 | 仅文本 | 文本+图像+表格+公式 |
| 检索单位 | 文本块(chunk) | 实体-关系(子图) | 多模态实体-关系(子图) |
| 跨文档关联 | ❌ | ✅ | ✅ |
| 多跳推理 | ❌ | ✅ | ✅ |
| 表格理解 | ❌(OCR成文本) | ❌ | ✅(保留结构化数据) |
| 公式理解 | ❌(LaTeX丢失) | ❌ | ✅(LaTeX嵌入) |
| 图片理解 | ❌(忽略或仅caption) | ❌ | ✅(视觉语义嵌入) |
| 存储方式 | 向量数据库 | 知识图谱+向量 | 多模态知识图谱+混合检索 |
| 适用场景 | 纯文本问答 | 复杂文本推理 | 技术文档、学术论文、医疗报告 |
3.2 性能对比(基于HKUDS的实验数据)
数据集:200份技术PDF文档(共约5万页),包含大量架构图、数据表格、数学公式。
测试query类型:
- 纯文本问题:"什么是Saga模式?"
- 表格相关问题:"各模块的性能参数对比?"
- 图片相关问题:"系统架构图中的数据流向?"
- 多模态综合问题:"如何优化API网关的性能?(需要结合文本说明+表格数据+架构图)"
实验结果:
| 指标 | 传统RAG | LightRAG | RAG-Anything |
|---|---|---|---|
| 纯文本问题准确率 | 78% | 89% | 91% |
| 表格问题准确率 | 12% | 15% | 94% |
| 图片问题准确率 | 3% | 5% | 87% |
| 多模态综合准确率 | 8% | 11% | 92% |
| 平均检索延迟 | 120ms | 350ms | 420ms |
| 存储占用 | 10GB | 25GB | 40GB |
结论:
- 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)高,主要原因:
- 需要检索多种模态的向量
- 需要遍历知识图谱的关系
- 多模态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'',
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网关 JWT 2小时 自动刷新 用户服务 JWT + Redis 2小时 手动刷新 订单服务 JWT 1小时 自动刷新
架构示意
如图4-3所示,鉴权流程为:客户端 -> API网关(验证JWT) -> 业务服务(解析JWT) -> 返回结果
代码示例
# API网关配置(gateway-config.yaml) security: jwt: secret: ${JWT_SECRET} expiration: 7200 # 2小时 refreshable: true
注意事项
- 确保所有服务的JWT secret一致
- 使用Redis存储Token黑名单(支持登出)
- 敏感操作需要二次验证(见第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:用领域数据微调多模态嵌入模型(提升专业术语理解)
八、参考资料
- RAG-Anything GitHub仓库:https://github.com/HKUDS/RAG-Anything
- LightRAG论文:"LightRAG: Simple and Fast Retrieval-Augmented Generation"
- 多模态嵌入模型:
- 文本:BAAI/bge-large-zh-v1.5
- 图像:openai/clip-vit-base-patch32
- 表格:tanbaojun/TaBERT
- 公式:allenai/mathbert
- 知识图谱数据库:Neo4j, Nebula Graph
- 向量数据库: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
