传统 RAG 系统遇到 PDF 里的柱状图、LaTeX 公式、嵌套表格时,基本上就废了。OCR 强转文字丢掉视觉语义,表格结构全乱,公式变成一堆乱码。我最近在项目里需要处理一批混合了大量图表的金融研报,试了三四个 RAG 方案,最后停在了 RAG-Anything 上。
这是港大黄超团队开源的框架,GitHub 上 16k+ 星。它基于 LightRAG,核心卖点是把文本、图片、表格、公式统一扔进一个知识图谱里做检索。听起来很理想,实际用下来有惊喜也有坑。这篇文章拆一下它的架构,贴实际能跑的代码,然后说说我踩过的问题。
先说问题有多严重。拿一份典型的券商研报举例:30 页 PDF,里面有 15 个数据表格、8 张趋势图、若干段内嵌公式。传统 RAG 的处理流程是:
- PyPDF2 提取文字 → 表格变成一行一行的碎片文本,列对不上
- 图片直接跳过或者 OCR 提取图上的文字 → "2024" "营收" "增长" 这些散碎词,完全丢失了"2024 年营收同比增长 23%"这个语义
- 公式用 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 以上,之前的接口不太稳定。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/279986.html