在大模型与RAG技术蓬勃发展的今天,PDF文档解析已成为构建知识库的核心痛点。由于 PDF 在跨平台兼容性和格式固定性方面的优势,企业通常选择 PDF 作为知识资产的主要存储形式。然而,这些文档中的复杂排版(如多栏布局、嵌套表格、公式与图表混排)往往让传统解析工具难以应对。尤其在金融、法律、科研等专业领域,解析失误可能导致语义断层、数据错位,进而引发RAG系统"幻觉式"回答。

针对PDF格式文档版式多样、解析难度大等难题,上海人工智能实验室推出了一款究极武器——MinerU,各位开发者在以往的开发过程中可能听说过这个名字,但这玩意儿究竟是个啥呢?本文将带你一同探索它的奇妙之处,并带大家使用LazyLLM,结合MinerU打造PDF解析与RAG应用的无缝链路。

当RAG遇上PDF,一场AI的"阅读理解噩梦"

"这PDF怎么像俄罗斯套娃?"每个RAG开发者在深夜都会发出的灵魂拷问...

你永远不知道一份专业PDF里藏着多少"反AI陷阱":
🔹 金融报告里嵌套的九层表格
🔹 法律文书里突然出现的竖排注释
🔹 科研论文里公式和图表的花式排列组合
🔹 更别提那些扫描件里堪比抽象画的OCR结果......

ef7293820d6126f0b04edfb6ee61fefe.png

在MagicPDF诞生之前,市面上已经有了很多PDF解析工具,比如pypdf、llama-parse,然而都存在一些能力缺陷。我们调研了市面上n种PDF解析工具后得出一个结论——某些工具处理复杂文档时,像极了用汤勺拆快递的憨憨!(小编真的笑得很大声哈哈哈哈哈哈哈~)

a87bb8d9b46969aff78b6e6e474a165f.png

有人会说了:“解析组件只要基本够用就行,至于这么折腾不?”,你以为解析不准顶多让AI犯傻?太天真了!PDF拆包失误轻则社会性死亡,重则引发行业地震!

image.png

这些啼笑皆非的案例背后,暴露出RAG对于传统PDF解析技术面对复杂文档的困境。接下来为大家介绍破局利器。

技术CP出道,当"瑞士军刀"遇上"变形金刚"

MinerU——PDF解析界的扫地僧

MinerU是由上海人工智能实验室(上海AI实验室)大模型数据基座OpenDataLab团队开源的全新的智能数据提取工具(官网:https://github.com/opendatalab/MinerU)。MinerU 能够快速识别PDF版面元素,将文档转化为清晰、通顺、易读的Markdown格式。

其核心能力在于:
保留原文档的结构和格式,包括标题、段落、列表等;
自动删除页眉、页脚、脚注、页码等元素;
准确提取图片、表格和公式等多模态内容;
符合人类阅读顺序的排版格式。

MinerU代码公开之后,凭借精准、快速的SOTA效果,媲美甚至超过商业软件的性能,获国内外多个技术大V点赞,GitHub Star累计飙升29K+,登顶GitHub Python Trending(2024年7月28日-29日),成为AI数据清洗中一个亮眼的开源工具。

业界反馈确实不错:
11033d3999db031aef7afd3a9188e0c7.png

精准解析只是开始,如何把解析能力融入到RAG框架,提升知识提取与问答能力,协同解决复杂文件数据抽取与智能问答的瓶颈?

解决方案来了!

LazyLLM——RAG框架里的乐高大师

LazyLLM是一个开源大模型应用开发框架,可以让我们像搭建积木一样,快速构建出具有生产力的AI大模型应用(官网:https://github.com/LazyAGI/LazyLLM )。LazyLLM旨在以最简单的方法和最少的代码,快速构建复杂、强大的多Agent AI应用原型,即使没有大模型应用开发背景也能轻松上手。

LazyLLM架构图:
efd139deaef0446db62119be75d9295c.png

-以数据流为核心的应用开发范式,集成丰富的标准组件,像搭积木一样开发大模型应用,低至10行代码搭建多路召回的RAG;
-为同一模块的不同技术选型提供一致的使用体验,用户可以零成本切换基模型、数据库、训推理框架等组件;
-复杂应用一键部署:利用轻量网关实现分布式应用一键部署,助力用户产品落地;
-多Agent编排:封装FunctionCall、React、ReWOO、Plan-and-Solve等多种业界主流的Agent;
-跨平台:兼容多个操作系统(如Windows、MacOS或Linux)和多种IaaS平台(如裸金属、k8s、slurm、公有云)。

让RAG变为阅读理解大师:LazyLLM × MinerU 联合解决方案

如果说PDF解析是RAG的“眼睛”,知识编排则是它的“大脑”。本节将深入解析两大开源利器如何从技术架构协同、应用场景适配、部署流程贯通三个维度,打造“能读会想”的RAG系统。通过实测案例,展示从复杂PDF文档到精准问答的完整技术链路。

快速部署方案——从安装、模型下载配置到无缝接入LazyLLM

1.1 安装配置

1.LazyLLM安装
可参考LazyLLM官方项目进行安装,或直接在命令行中运行以下命令:

pip install lazyllm

2.MinerU安装配置及模型选择
参考MinerU官方项目进行安装配置

a.magic-pdf安装

pip install -U "magic-pdf[full]" --extra-index-url https://wheels.myhloli.com -i https://mirrors.aliyun.com/pypi/simple

b.模型和配置文件下载
可参考官方文档通过脚本一键下载

pip install modelscope
wget  https://gcore.jsdelivr.net/gh/opendatalab/MinerU@master/scripts/download_models.py -O download_models.py 
python download_models.py

1.2 通过自定义reader接入LazyLLM代码示例

LazyLLM 提供了一套默认的文档解析器,开箱即用,同时也支持用户自定义解析器,用户只需将封装好的可调用对象注册为 Document 类型解析器,即可实现个性化解析方案。

基于此,我们首先利用MinerU构建PDF解析器,处理其输出的解析结果为LazyLLM的标准节点DocNode。       

class MagicPDFReader:
    """
    PDF 文档解析器,支持文本、图片、表格的解析,返回结构化文档节点。
    """
    def __call__(self, file: Path, split_documents: Optional[bool] = True, **kwargs) -> List[DocNode]:
        ...

    def _load_data(self, file: Path, split_documents: Optional[bool] = True) -> List[DocNode]:
        """节点转化"""

    def _parse_pdf_elements(self, pdf_path: Path) -> List[dict]:
        """MinerU调用"""

(喜欢动手的朋友们可以在文档后面找到详细的代码~)

接下来用 LazyLLM 搭建 RAG 流程的 Demo。仅需不到 20 行代码!
只要把刚刚定义的MagicPDFReader注册为pdf文档的解析器就能轻松搞定!

import lazyllm
from lazyllm import pipeline, parallel, bind, Document, Retriever, Reranker, SentenceSplitter

prompt = 'You will play the role of an AI Q&A assistant and complete a dialogue task. In this task, you need to provide your answer based on the given context and question.'

documents = Document(dataset_path="rag_master", embed=OnlineEmbeddingModule(), manager=False)
documents.create_node_group(name="sentences", transform=SentenceSplitter, chunk_size=1024, chunk_overlap=100)
documents.add_reader("**/*.pdf", MagicPDFReader)

with pipeline() as ppl:
    with parallel().sum as ppl.prl:
        prl.retriever1 = Retriever(documents, group_name="sentences", similarity="cosine", topk=3)
        prl.retriever2 = Retriever(documents, "CoarseChunk", "bm25_chinese", 0.003, topk=3)
    ppl.reranker = Reranker("ModuleReranker", model=OnlineEmbeddingModule(type="rerank"), topk=1, output_format='content', join=True) | bind(query=ppl.input)
    ppl.formatter = (lambda nodes, query: dict(context_str=nodes, query=query)) | bind(query=ppl.input)
    ppl.llm = lazyllm.OnlineChatModule(stream=False).prompt(lazyllm.ChatPrompter(prompt, extra_keys=["context_str"]))

if __name__ == "__main__":
    query = "your query"
    answer = ppl(query)

以下为rag框架的流程图,接收到用户query后,通过多路召回(retriever)得到节点,然后通过rerank重排序到输入大模型得到最终回答。每个retriever可以绑定多个document,自定义的解析器仅需通过documents.add_reader("*/.pdf", MagicPDFReader)注册到流程中。

fedf334f2b508fa281fe02a27a5dc19b.png

⚒️实验室暴击现场⚒️
当MinerU遇上PyPDF,降维打击太残忍了!
有了以上启动代码,我们直接运行并测试一下,看看MagicPDF到底给我们的RAG应用带来了什么改变。对比效果如下:

MinerU解析结果:
21c917f473cdd1aa171ebb0da0e53639.png

PyPDF解析结果:
4c916a8e40e0f6afe1077c8c998f12a2.png

结果不出所料,MagicPDF直接把PyPDF按在地上摩擦...通过更多的测试,我们也总结了二者在PDF文档解析中备受关注的多个角度对比结论:
b7d19d1f3c74863777f0c5f8bf44581f.png

单独的文档解析环节比拼,MagicPDF胜出——1:0。

问答效果
我们为本次评测选择了两种较为复杂的场景,分别是基于复杂表格问答和基于复杂问题的问答。

案例1:基于复杂表格提问
"3月15日政府发行国债的净融资量是多少?"
-MinerU:提取表格结构 → 表头文字精准对应 →大模型秒答"2595.70亿元"
-PyPDF菜鸟:把表格拆成散装文字 → 对不上 → 回答"融资量为2220.7.00亿元“(幻觉模式)

这一案例较为考验大模型对于表格结构的理解能力,但由于表格解析成先前那个样子,复杂表格这一环节PyPDF理所当然的指望不上了... MagicPDF胜出——2:0。
c19e8099773d8befbbdcb8de2ec08489.png

案例2:基于复杂结构的提问
"关于珀莱雅的投资建议?"
-MinerU:跨页拼接文本+分离穿插表格 → 语义完整性MAX → 生成专业建议
-PyPDF:丢失关键段落+表格文字粘连 → 大模型东拼西凑 → 回答当场翻车
3ae503dff88b097de6f9a0203e16f21c.png

3:0啊...除了“残忍”二字,已经找不到别的词能够形容这场PK了。同时,能够这么快完成效果验证,除了小编的聪明才智(不是)MagicPDF的强力支持,还有懒懒猫(LazyLLM中文名)短短几行代码就能把这个复杂应用构建出来,简直是妙上加喵,喵喵妙~

进阶优化方案——充分利用MinerU输出的多元信息,让RAG更懂你的文章

除了以上最基本的使用,我们还带来了一些使用技巧,助力开发者更好的玩转MagicPDF。

坐标追踪模式:给每行文字装上GPS

在某些讲求严谨性的专业场景中,RAG 系统不仅需要 "知道答案",更要 "解释答案的来龙去脉"。这意味着 AI 不仅要能检索到正确的答案,还要能够提供强有力的证据链,指向原文中的具体位置。例如,在法律、财务、医学等领域,AI 需要做到 "可溯源",让用户不仅能获取答案,还能直接看到它是如何得出的。

想象一个法律检索场景,律师问 AI 某个合同条款是否允许提前终止,RAG 系统不仅要回答 "允许",还需要指着合同第8页第3段第5行说:"就是这里!" 这极大增强了 AI 生成内容的可信度,使其更加符合专业场景的严谨性和透明性要求。

对此,MinerU 已经支持行级别的位置信息,但当前版本默认不输出这些细节信息。不过,这并不妨碍我们自己挖掘和利用它!通过重写部分函数,我们可以深入解析 MinerU 的底层数据结构,精确提取 block 级别和 line 级别的位置信息,并以结构化的方式返回。这一能力将极大提升 RAG 系统的可解释性,使其能够支持精准文本定位、高亮展示、证据链构建、可视化分析等高级应用。

# add patchs to magic-pdf
from magic_pdf.dict2md import ocr_mkcontent

def para_to_standard_format_v2(para_block, img_buket_path, page_idx, drop_reason=None):
    """修改输出bbox信息"""

ocr_mkcontent.para_to_standard_format_v2 = para_to_standard_format_v2

【部分修改】:

57315978916406f510a56518a43b83a0.png

接下来介绍一种简单利用坐标信息的方式,在RAG pipeline中插入一个模块实现:根据目标文档的召回块画出box并保存,待问答后查看,就可以精准定位到你的问答依据!

def draw_pdf_bbox(nodes, query, **kwargs):
    """在召回PDF文档中绘制box并标注query"""
    for node in nodes:
        bbox_list = node.metadata['bbox']
        output_path = os.path.join(get_pdf_output_path(), f"query[{query}] -- {node.metadata['file_name']}")
        draw_bboxes_on_pdf(node.metadata['file_path'], bbox_list, output_path, query)
    return "\n".join([node.get_content() for node in nodes])

def draw_bboxes_on_pdf(input_pdf_path, bbox_list, output_pdf_path, query):
    # 打开PDF文档
    pdf_document = fitz.open(input_pdf_path)

    # 遍历每一页
    for page_num in range(len(pdf_document)):
        page = pdf_document.load_page(page_num) 

        # 遍历bbox列表
        for bbox in bbox_list:
            if bbox['page'] == page_num:  # 检查bbox是否属于当前页
                rect = fitz.Rect(*bbox['bbox'])  # 创建矩形框
                page.draw_rect(rect, color=(1, 0, 0), width=2)  # 绘制红色矩形框

    # 保存带有bbox的PDF文档
    pdf_document.save(output_pdf_path)
    pdf_document.close()

接下来将其插入到pipeline流程中:

1dbb32f9d3ea69edda4066eeedafe19b.png

📌定制精细化的本文分块策略:告别"切香肠式"文本处理

MinerU 能够输出丰富的元信息,这为 RAG应用提供了极大的灵活性。传统的文本切分方法通常采用固定长度或简单的句子切割,这种方式容易破坏语义完整性,导致信息割裂,影响 RAG 问答的内容质量。而 MinerU 的元信息可以用于构建更加智能的 内容结构化分块策略,确保每个分块都保持完整的语义单元,提升检索与生成的准确性。

在 MinerU 中,我们可以自定义更加灵活的分块方式,以适应不同的业务场景。例如,项目中提供了一种基于章节标题进行分块的通用算法,在实际应用中,可以结合业务需求进一步优化分块策略,确保信息的完整性。例如:

  1. 招股书处理传统切分:固定字数切分,可能导致"业务概览"和"财务数据"混杂,信息断裂。优化方案:按 “业务概览 → 财务数据 → 风险因素” 进行模块化分块,保证业务逻辑清晰,不破坏上下文连贯性。
  2. 学术论文分析传统切分:可能导致假设、实验和数据分析混在不同块中,使得推理链断裂。优化方案:按照 “假设 → 实验 → 数据 → 结论” 进行切分,确保逻辑链条完整,提高生成答案的可靠性。
  3. 医疗报告解析传统切分:可能会将影像描述与诊断意见分离,使得问答系统难以精准匹配。优化方案:按 “检查项目 → 影像描述 → 诊断意见” 切割或组合,确保影像结论与描述保持一致,提高问答准确度。

假设有一份 CT 影像报告,内容如下:

患者姓名:张三  
检查项目:胸部 CT  
影像描述:双肺下叶可见多发小结节,边界清晰,未见明显钙化。  
诊断意见:考虑良性病变,建议 3 个月后复查。
  1. 传统固定长度切分(错误示例)
分块 1:患者姓名:张三 检查项目:胸部 CT 影像描述:双肺下叶可见多发小结节
分块 2:边界清晰,未见明显钙化。 诊断意见:考虑良性病变,建议 3 个月后复查。

❌问题:影像描述和诊断意见被拆开,导致问答系统在生成答案时可能无法准确关联病变特征和医生建议。

  1. Mineru 元信息辅助的结构化分块(优化示例)
患者姓名:张三  
检查项目:胸部 CT  
影像描述:双肺下叶可见多发小结节,边界清晰,未见明显钙化。  
诊断意见:考虑良性病变,建议 3 个月后复查。

✅优化点:每个块都是完整的语义单元,不会因固定长度切割导致信息断裂。

在LazyLLM搭建的rag框架中自定义切分算法只需继承NodeTransform并且将该类注册到document的分组中:

class MagicPDFTransform(NodeTransform):
    def __init__(self, **kwargs):
        super().__init__()

    def transform(self, document: DocNode, **kwargs) -> List[Union[str, DocNode]]:
        """自定义切分算法"""

# 在你的rag代码中:
documents.create_node_group(name="magic-pdf", transform=MagicPDFTransform)

总结

以上就是基于LazyLLM融合MinerU实现PDF解析与RAG的应用与进阶,让PDF解析从 “机械拆分” 迈向 “智能重构”,更多高级玩法有待探索。(以上代码请参考项目 👉https://github.com/jisyST/LazyRAG-Mineru#

更多信息请移步“LazyLLM”gzh!


商汤万象开发者
1 声望0 粉丝