一个 RAG 框架处理 PDF 里的图表和公式——RAG-Anything 架构拆解与踩坑实录

一个 RAG 框架处理 PDF 里的图表和公式——RAG-Anything 架构拆解与踩坑实录传统 RAG 系统遇到 PDF 里的柱状图 LaTeX 公式 嵌套表格时 基本上就废了 OCR 强转文字丢掉视觉语义 表格结构全乱 公式变成一堆乱码 我最近在项目里需要处理一批混合了大量图表的金融研报 试了三四个 RAG 方案 最后停在了 RAG Anything 上 这是港大黄超团队开源的框架 GitHub 上 16k 星 它基于 LightRAG 核心卖点是把文本 图片 表格

大家好,我是讯享网,很高兴认识大家。这里提供最前沿的Ai技术和互联网信息。



传统 RAG 系统遇到 PDF 里的柱状图、LaTeX 公式、嵌套表格时,基本上就废了。OCR 强转文字丢掉视觉语义,表格结构全乱,公式变成一堆乱码。我最近在项目里需要处理一批混合了大量图表的金融研报,试了三四个 RAG 方案,最后停在了 RAG-Anything 上。

这是港大黄超团队开源的框架,GitHub 上 16k+ 星。它基于 LightRAG,核心卖点是把文本、图片、表格、公式统一扔进一个知识图谱里做检索。听起来很理想,实际用下来有惊喜也有坑。这篇文章拆一下它的架构,贴实际能跑的代码,然后说说我踩过的问题。

先说问题有多严重。拿一份典型的券商研报举例:30 页 PDF,里面有 15 个数据表格、8 张趋势图、若干段内嵌公式。传统 RAG 的处理流程是:

  1. PyPDF2 提取文字 → 表格变成一行一行的碎片文本,列对不上
  2. 图片直接跳过或者 OCR 提取图上的文字 → "2024" "营收" "增长" 这些散碎词,完全丢失了"2024 年营收同比增长 23%"这个语义
  3. 公式用 OCR 识别 → frac{P}{E} 变成 "P E" 或者乱码

问个简单问题:"这份报告里哪个季度毛利率最高?"传统 RAG 大概率答不上来,因为毛利率数据在表格里,表格被切碎了。

RAG-Anything 用三个阶段解决这个问题:文档解析 → 跨模态知识构建 → 检索生成。

第一阶段:文档解析(MinerU 干的活)

RAG-Anything 没有自己造解析轮子,它用的是 MinerU(上海 AI Lab 开源的文档解析引擎)。MinerU 做的事情是把 PDF 拆成结构化的 block:

  • 文本块:正文段落、标题、脚注
  • 图片块:截图 + 位置坐标
  • 表格块:识别出行列结构,输出 HTML 或 Markdown 表格
  • 公式块:LaTeX 格式输出

这一步很关键。MinerU 不只是 OCR,它有布局分析模型(用的 PaddleOCR),能区分"这一块是表格"和"这一块是正文"。

 
  
    
    
from raganything import RAGAnything 

rag = RAGAnything(

working_dir="./my_rag_storage", llm_model_func=your_llm_func, vision_model_func=your_vlm_func, embedding_func=your_embed_func, 

)

一行代码触发整个管线

await rag.insert_document(

file_path="research_report.pdf", lang="ch", # 中文文档用 "ch" device="cuda:0", # MinerU 推理设备 

)

 

insert_document 内部调用 MinerU 解析 PDF,拿到结构化输出后进入第二阶段。

第二阶段:跨模态内容理解与知识图谱构建

解析完拿到不同类型的 block 后,RAG-Anything 按类型走不同的处理路径。

文本块最简单,直接送进 LLM 做实体提取和关系抽取,生成知识图谱的节点和边。表格块稍复杂一些,先用表格专用处理器把行列结构转成自然语言——比如一个收入表格会被转成"2024年Q1营收为85.3亿元,同比增长12.7%;Q2营收为91.6亿元…",然后再做实体提取。

图片块是最费钱的环节。RAG-Anything 把截图发给 VLM(视觉语言模型),让它用文字描述图片内容。一张柱状图会被描述成"图表显示2022年至2024年的营收趋势,2022年为310亿元,2023年为356亿元,2024年为412亿元"。公式块走 LaTeX 解析器,把 ext{ROE} = frac{ ext{净利润}}{ ext{净资产}} 这类表达式转成文字。

这四条路径的输出最后都汇入同一个 LightRAG 知识图谱。图表里的数据点和正文里的分析结论,通过实体关系连在一起。

来看实际的知识图谱构建代码:

 
  
    
    
# RAG-Anything 内部的多模态路由逻辑(简化版) 

async def process_block(block):

if block.type == "text":
    # 直接走 LightRAG 的文本处理
    entities, relations = await extract_from_text(block.content)
elif block.type == "table":
    # 表格先转自然语言
    table_desc = await table_processor.describe(block.html_content)
    entities, relations = await extract_from_text(table_desc)
elif block.type == "image":
    # 图片送 VLM
    image_desc = await vlm.describe(block.screenshot_path)
    entities, relations = await extract_from_text(image_desc)
elif block.type == "equation":
    # 公式转文本描述
    eq_desc = latex_parser.to_text(block.latex)
    entities, relations = await extract_from_text(eq_desc)

# 统一写入知识图谱
await knowledge_graph.insert(entities, relations)
第三阶段:检索与生成

查询时 RAG-Anything 组合两种检索方式:向量检索走 embedding 相似度匹配,适合语义模糊的问题;图检索沿着知识图谱的边做跳转,适合需要跨文档关联的问题。

 
  
    
    
# 三种检索模式 

result = await rag.aquery(

"2024年Q3的毛利率是多少?", param=QueryParam(mode="hybrid") # naive / local / global / hybrid 

) print(result)

 

hybrid 模式同时跑向量检索和图检索,合并结果后送进 LLM 生成回答。对于"毛利率"这种问题,图检索能沿着"Q3 → 毛利润 → 营收"的路径找到表格里的具体数字,比纯向量检索准确不少。

坑 1:MinerU 3.0 的 API 变更

今年 3 月底 MinerU 升级到 3.0.0,改成了服务化架构。以前是直接调二进制文件解析,现在变成 mineru-api 服务模式。如果你不传 –api-url,它会自动启动一个本地临时服务。

问题在于:旧版教程(包括很多博客文章)里的代码是按 2.x 写的,直接跑会报错。要么锁定 MinerU 2.x 版本,要么改成服务调用方式:

 
  
    
    
# 方法1:锁版本 

pip install magic-pdf==0.9.3

方法2:用 3.0 的服务模式

mineru-api serve –port 8765

然后在 RAG-Anything 配置里指定 api_url

 

我选的是方法 1,因为项目里不想多维护一个服务。

坑 2:embedding 双重包装 bug

RAG-Anything 的 examples 目录里有个已知 bug:embedding 函数被多包了一层,导致推理时报维度不匹配。官方已经在 GitHub commit 里修了,但如果你 clone 的时间不对,可能还会碰到。

修复方式很直接:

 
  
    
    
# 错误写法(旧 example 里的) 

async def embedding_func(texts):

return await openai_embed(texts) # 外面又包了一层 

正确写法

embedding_func = openai_embed.func # 直接用内部函数

 
坑 3:VLM 调用的成本控制

RAG-Anything 默认对每个图片块都调 VLM。一份 50 页的 PDF 可能有几十张图,每张图一次 VLM 调用。如果用的是 GPT-4o,成本会很可观。

我的做法是加了一个过滤层:先对图片做简单分类(用一个轻量的 CLIP 模型),只对信息密度高的图片(数据图表、流程图)调 VLM,纯装饰性的图片直接跳过。

 
  
    
    
import clip 

import torch from PIL import Image

model, preprocess = clip.load("ViT-B/32")

def should_process_image(image_path):

"""判断图片是否值得送 VLM 分析""" image = preprocess(Image.open(image_path)).unsqueeze(0) text = clip.tokenize(["data chart", "table", "diagram", "decorative image", "logo"]) with torch.no_grad(): image_features = model.encode_image(image) text_features = model.encode_text(text) similarity = (image_features @ text_features.T).softmax(dim=-1) # 前三类(图表/表格/流程图)相似度高就处理 info_score = similarity[0][:3].sum().item() return info_score > 0.5

加上这个过滤后,VLM 调用量降了大约 60%,成本和速度都改善明显。

坑 4:中文文档的 PaddleOCR 依赖链

MinerU 依赖 PaddleOCR 做 OCR。PaddleOCR 有 C++ 组件,在 macOS 上编译偶尔会出问题(特别是 M 系列芯片)。如果遇到安装失败,可以试:

 
  
    
    
# 先装 paddlepaddle 

pip install paddlepaddle==3.0.0

再装 paddleocr

pip install paddleocr==2.9.1

如果还不行,用 conda 环境

conda install -c conda-forge paddlepaddle

 

中文文档解析时记得设 lang="ch",不然 OCR 模型加载的是英文的,中文识别率会很差。

用同一份 30 页中文研报测试,问 10 个涉及图表和表格数据的问题:

  • 纯 LangChain + PyPDF:10 个里答对 2 个,基本只能回答纯文本段落的问题
  • Unstructured.io + Chroma:答对 5 个,表格处理比纯 PyPDF 好,但图表还是不行
  • RAG-Anything:答对 8 个,图表和表格数据都能检索到,错的 2 个是公式相关的(LaTeX 解析偶尔会丢符号)

速度方面有代价。RAG-Anything 建索引时要跑 MinerU 解析加 VLM 调用,单文档 3-5 分钟(GPU 环境),比纯文本方案慢好几倍。查询速度差不多,都是在知识图谱和向量库上走。

RAG-Anything 适合图文混合的专业文档:金融研报、学术论文、技术手册。如果你的文档主要是纯文本,LightRAG 就够了。

有个许可证的问题值得提一下。MinerU 用 AGPL-3.0,商业闭源项目用它需要评估合规风险,这在实际落地时可能是个阻碍。

代码在 github.com/HKUDS/RAG-Anything,pip install raganything 装。版本用 1.2.10 以上,之前的接口不太稳定。

小讯
上一篇 2026-04-27 12:08
下一篇 2026-04-27 12:06

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/279986.html