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

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

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

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

传统 RAG 碰到多模态文档会怎样

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

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

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

RAG-Anything 的三阶段管线

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

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

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

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

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

python 复制代码
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 解析器,把 \text{ROE} = \frac{\text{净利润}}{\text{净资产}} 这类表达式转成文字。

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

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

python 复制代码
# 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 相似度匹配,适合语义模糊的问题;图检索沿着知识图谱的边做跳转,适合需要跨文档关联的问题。

python 复制代码
# 三种检索模式
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 版本,要么改成服务调用方式:

bash 复制代码
# 方法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 的时间不对,可能还会碰到。

修复方式很直接:

python 复制代码
# 错误写法(旧 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,纯装饰性的图片直接跳过。

python 复制代码
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 系列芯片)。如果遇到安装失败,可以试:

bash 复制代码
# 先装 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 以上,之前的接口不太稳定。

相关推荐
小阿鑫2 小时前
设计圈真的要变天了:ChatGPT Image 2 不只是会生图了
chatgpt·aigc·设计师·设计
程序员海军2 小时前
设计圈真的要变天了:ChatGPT Image 2 不只是会生图了
aigc·设计师·交互设计
我是无敌小恐龙3 小时前
Java SE 零基础入门Day03 数组核心详解(定义+内存+遍历+算法+实战案例)
java·开发语言·数据结构·人工智能·算法·aigc·动态规划
RE.nior4 小时前
2026 年 4 月 AI 视频生成模型实测:Wan 2.7、PixVerse V6、Sora 2、Seedance 2.0 谁能打
人工智能·aigc·音视频
FEF前端团队5 小时前
Skill 入门指南:从零开始打造你的智能编程助手
aigc·ai编程·trae
用户6757049885025 小时前
AI开发实战4、AI总是忘记项目规范?因为你缺了这份终极上下文文件
后端·aigc·ai编程
我是发哥哈5 小时前
主流AI培训机构能力横向评测:核心维度与选型要点解析
大数据·人工智能·学习·机器学习·ai·chatgpt·aigc
用户6757049885025 小时前
AI开发实战3、90%人用AI写前端都踩的坑:API层混乱!3步教你标准化
后端·aigc·ai编程
洞窝技术17 小时前
从一次性对话到自我进化:Hermes Agent 架构拆解与落地实践
aigc