@llama_index: 大多数自主检索演示假设文档干净、结构良好。企业现实往往不同,包含…

X AI KOLs Following 工具

摘要

LlamaIndex 和 LanceDB 合作构建了一个管道,使用 LiteParse 进行 PDF 解析,并使用 LanceDB 进行多模态存储,从而能够从复杂的企业 PDF 中实现更好的检索,以支持自主工作流。

大多数自主检索演示假设文档干净、结构良好。企业现实往往不同,包含混乱的 PDF,其中关键信息被埋藏在表格、图形和复杂布局中。 这就是为什么我们与 @lancedb 合作,探索如何将 LiteParse(我们闪电般快速的解析器)与 LanceDB 的原生多模态存储相结合,以提高检索质量和代理响应准确性。 通过将 PDF 分离为多个信息层——页面(文本 + 截图 + 嵌入)、块和提取的资产——并将它们存储在 LanceDB 中以实现快速多模态检索,我们构建了一个混合管道,解锁了传统 RAG 系统经常遗漏的信息。 代理不再仅仅依赖于块级检索,而是可以跨页面、块和视觉资产进行检索和推理,使得复杂的企业 PDF 变得更加易于访问。 结果是,为自主工作流奠定了一个更强大的检索基础。 阅读博客文章中的完整分析:https://lancedb.com/blog/from-messy-pdfs-to-verifiable-answers-with-liteparse-and-lancedb… 在 GitHub 上探索完整实现:https://github.com/lancedb/liteparse-lancedb-pdf-qa…
查看原文
查看缓存全文

缓存时间: 2026/07/07 12:19

大多数基于智能体的检索演示都假设文档是干净且结构良好的。但企业现实往往不同,充斥着杂乱的PDF文件,其中关键信息被埋藏在表格、图形和复杂的布局之中。

因此,我们与 @lancedb 合作,探索如何将 LiteParse(我们超快的解析器)与 LanceDB 的原生多模态存储相结合,以提升检索质量和智能体响应准确性。

通过将PDF分离成多个信息层——页面(文本 + 截图 + 嵌入向量)、文本块和提取的资产——并将它们存储在LanceDB中,实现快速的多模态检索,我们构建了一个混合管道,能够解锁传统RAG系统经常遗漏的信息。

智能体不再仅仅依赖文本块级检索,而是可以在页面、文本块和视觉资产之间进行检索和推理,使复杂的企业PDF更易于访问。

结果是,为智能体工作流程提供了显著更强的检索基础。

完整解析请阅读博客文章:https://lancedb.com/blog/from-messy-pdfs-to-verifiable-answers-with-liteparse-and-lancedb… 在GitHub上探索完整实现:https://github.com/lancedb/liteparse-lancedb-pdf-qa…


从杂乱的PDF到可验证的答案:LiteParse与LanceDB

来源:https://www.lancedb.com/blog/from-messy-pdfs-to-verifiable-answers-with-liteparse-and-lancedb 企业PDF,如年度ESG和可持续发展报告,是信息最丰富的文档之一:数百页的叙述,夹杂着表格、图表和图形。分析师和审计师仔细审查它们以寻找具体事实,每个数字都要追溯到其来源。那个来源往往是一份二百页报告中的某一页。对我们来说,这些报告读起来像普通文档,但对检索器而言,它们是文本、表格和图形杂乱缠绕的集合,而人们所需的事实就遗落在这些元素的边界之间。

如果我们过早地扁平化所有结构,就会丢失智能体稍后需要的确切证据。我们也失去了检查问题出在哪里的能力。是页面解析错误?文本块切分破坏了表格?检索器找到了正确的页面但遗漏了相关的图形?没有结构化的证据层,回答这些问题就非常痛苦。

在构建复杂的智能体管道之前,值得思考如何正确构建证据层——毕竟,俗话说“上下文为王”。PDF应以保留文本、页面截图、提取的图形、元数据、嵌入向量、二进制对象和其他实体之间连接性的方式进行解析。同样重要的是,存储这些片段的方式应使得检索能够结合页面级、文本块级和资产级的信号,而不丢失原始页面标识。

在这篇文章中,我们将使用LlamaIndex的LiteParse (https://github.com/run-llama/liteparse)库进行提取,以及LanceDB (https://github.com/lancedb/lancedb)进行多模态存储和检索,来构建这样一个本地化、可检查的证据存储。我们将描述端到端管道的工作原理,并评估来自智能体的检索结果。

我们构建的管道如下所示:

一个单一的开源、进程内管道:LiteParse负责解析,LanceDB负责存储和检索。## 数据集:六份ESG报告,五十个带标注的问题

为了使后续内容具体化,我们将使用Climate Finance Bench (https://github.com/Pladifes/climate_finance_bench)的一个小子集,这是一个开放基准,包含企业可持续发展报告以及每个问题都标注了答案所在页面的问题。我们从知名公司中选取了六份报告,总共五十个问题。这些报告是很好的压力测试:报告页数从41到200页不等,混合了长篇叙述与密集的表格和图表,并且问题要求具体事实而非宽泛总结。

公司报告页数问题数大小
阿里巴巴集团2024年ESG报告2001012.9 MB
Google2024年环境报告86914.2 MB
NVIDIAFY2024企业可持续发展报告4184.4 MB
Nestle2023年创造共享价值与可持续发展89818.9 MB
Samsung2024年可持续发展报告8374.0 MB
TotalEnergies2024年可持续发展与气候进展112810.0 MB

每个问题还标记了所需证据的单一模态:文本、表格或图形。在这五十个问题中,29个可以仅通过文本回答,14个需要阅读表格,7个需要图形。这种划分很重要:表格和图形问题正是纯文本检索容易失败的地方。以下是一个图形问题及其对应到单一页面的答案:

{
  "question_id": "NVIDIA_Q7",
  "question": "该公司在FY2024年的总碳足迹(范围1、2和3排放,以吨CO2当量计)是多少?",
  "expected_pages": [12],
  "required_modality": "figure",
  "difficulty": "medium"
}

为简化本文,我们只解析基准中标注的页面:六份报告中共70页,而非全部611页。这使我们能够在调整解析、分块和检索时快速迭代。然而,如果你想复现这项工作,管道对于完整文档集是相同的:只需运行--pages all而不是--pages labeled,这正是你在生产环境中会做的。

使用LiteParse解析报告

在之前的一篇关于LiteParse的文章 (https://www.lancedb.com/blog/smart-parsing-meets-sharp-retrieval-combining-liteparse-and-lancedb?__hstc=181257784.0d87e83222ddb888a68c98f5df77ad73.1777392160926.1782837640811.1783014848725.34&__hssc=181257784.3.1783014848725&__hsfp=3516596829bad893f2b764fc25d7b61d) 中,我们通过TypeScript SDK将其与LanceDB配对。LiteParse现在在相同的Rust核心上提供了一个原生Python SDK,这就是我们在这里将使用的,因为管道的其余部分(嵌入、存储、检索)通过LanceDB在Python中运行。

LiteParse是一个开源文档解析库,它解析带有空间布局信息和边界框的文本。它构建在Rust核心之上,完全在本地机器上运行,无需云依赖、无需LLM、无需API密钥。它确定性地读取PDF自身的文本,并且可以直接读取页面几何结构,并根据每个片段的位置重建阅读顺序。

从Python中使用它非常简单。我们配置一个解析器,然后进行两次调用(一次用于解析,另一次用于提取页面截图):

from liteparse import LiteParse

parser = LiteParse(
    ocr_enabled=False,       # 数字原生PDF已有文本层
    dpi=150,                 # 足以保持截图清晰而不使二进制对象体积过大
    image_mode="embed",      # 将嵌入的图形提取为原始字节
    target_pages="2,4-6,8",
)

result = parser.parse("report.pdf")        # 文本、带位置信息的text_items、图形字节
screenshots = parser.screenshot("report.pdf", page_numbers=[2, 4, 5, 6, 8])

在这个高层接口之下,LiteParse返回一组类型化的基本元素。parse()返回一个ParseResult:一个ParsedPage对象列表,每个对象包含页面的完整阅读顺序文本以及保持其字符串、边界框和字体的TextItem。请求嵌入图像(image_mode="embed")会添加包含原始图形字节的ExtractedImage,而单独的screenshot()调用返回包含全页PNG字节的ScreenshotResult

由于我们的ESG报告是数字原生的,我们可以关闭OCR(ocr_enabled=False),因此LiteParse通过PDFium读取现有文本层,而不是渲染每个页面并运行Tesseract进行OCR。它还为每个TextItem记录一个边界框,即该文本片段在页面上的确切区域。这种位置细节支持下游的视觉引用用例:直接在渲染页面上高亮匹配的区域。在本文中,我们不基于边界框进行检索,因此不将其引入LanceDB表;我们的检索使用文本、图形和截图。不过,LiteParse在每次解析时都会返回它们,并且我们将原始解析输出保存在磁盘上(位于liteparse.json中),因此如果需要,以后还可以使用。

下图展示了这一点,具体为一个视觉引用的例子。我们选取了Google 2024年环境报告中回答基准问题“公司评估了哪些主题为实质性主题?”的页面,并用橙色高亮了实际回答该问题的四个文本块,这些高亮直接来自它们的TextItem边界框。

来自Google 2024年环境报告的一页,用橙色高亮显示了“公司评估了哪些主题为实质性主题?”的答案,这些高亮来自LiteParse的边界框,作为视觉引用。

生成所有这些结构(文本、边界框、图形和截图)成本很低。在我们的70个标注页面上运行parse()screenshot()两次调用大约在2秒内完成:

阶段时间吞吐量范围
parse()0.51 秒136.9 页/秒文本 + 嵌入图像
screenshot()1.74 秒40.2 页/秒全页截图
端到端2.35 秒29.7 页/秒解析 + 截图 + 记录写入

这些时间数字显示了LiteParse在实际使用中的速度:parse()仅用了半秒就从所有70页中提取了文本、边界框和嵌入图形,而渲染截图用了不到两秒,全部在笔记本电脑上完成,无需LLM API调用。对于每份报告,解析步骤将一个小的、可检查的数据包写入磁盘:结构化的解析结果JSON(包含页面及其文本和边界框)、提取的图形和页面截图作为PNG文件,以及一组将所有内容关联回其页面的归一化记录。

这些解析后的记录是我们接下来使用的原材料,我们将把它们转换为LanceDB表。

在LanceDB中存储证据

报告解析完成后,重点转向存储:将文本、图像、元数据和嵌入向量统一放入一个地方,这样我们就可以无需拼接多个系统来进行检索——我们将在LanceDB中实现这一点。三个决策决定了其布局方式:连接每条记录的关键字、同时包含文本、二进制对象和向量的表模式,以及我们如何对其进行索引和查询。

将页面标识作为连接键

LiteParse将文本、图形和截图作为松散的部分交给我们。将它们连接在一起的决策是将页面标识作为公共键:我们创建的每条记录都标记有它所属的页面,以及它的文档和来源。我们在归一化过程中构建每个页面的id,将文档标识(来自报告的公司、年份和文件名)与LiteParse报告的实际PDF页码配对,其他所有记录都依附于它:

page_id  = f"{doc_id}:p{page_num}"      # nvidia_fy2024:p2  /  google_2024:p6
chunk_id = f"{page_id}:c{chunk_index}"  # nvidia_fy2024:p2:c0  /  google_2024:p6:c0
asset_id = f"{page_id}:asset:{name}"    # nvidia_fy2024:p13:asset:image_p13_0  /  google_2024:p6:asset:image_p6_0

证据以三种粒度存储,每条记录都携带其所属的page_id

  1. 页面 保存完整的页面文本及其截图。
  2. 文本块 是该文本的页面边界切片(约1200字符,少量重叠),因此一个文本块永远不会跨越两页。
  3. 资产 是视觉部分:LiteParse提取的图形和页面截图。

这些粒度分别位于LanceDB的不同表中。一个问题的最佳证据通常分布在多个表中:文本块中的确切句子、其在页面中的上下文、资产中的图形。由于每条记录都带有page_id,我们可以单独搜索每个表,然后在我们自己的代码中,将落在同一页面上的命中结果合并为一个带有该页面完整证据的结果。这就是上面构建页面标识键的主要原因。

模式:文本、向量、元数据和二进制对象在一个存储中

LanceDB非常适合这类数据和当前任务。单个表保存结构化列(id、公司、页码)、完整页面文本、原始图像字节和嵌入向量,并且它同时携带着索引和版本历史。没有单独的对象存储位置来存放截图,没有元数据数据库来存放来源,也没有向量数据库来存放嵌入向量:所有内容都放在一个地方,以不同方式查询。

LanceDB存储共有五个表。上一节中的三种证据粒度各自有一个表(pageschunksassets),并由两个辅助表连接:documents保存报告级元数据,eval_questions保存我们评估的基准。

行数内容
documents6报告元数据、校验和、解析配置、计时
pages70完整页面文本、页面截图、文本+图像向量
chunks252页面边界的文本段及其文本向量
assets77提取的图形、其字节、文本+图像向量
eval_questions50归一化的问题及其期望页面

在底层,Lance使用Apache Arrow的类型系统,我们使用PyArrow声明每个表的模式。我们使用OpenAI的text-embedding-3-small嵌入文本,使用OpenCLIP ViT-B-32嵌入图像,这决定了下面代码片段中两个向量的宽度(1536和512)。

pages_schema = pa.schema([
    # ... ids, company, page_num, text ...
    pa.field("screenshot_blob", pa.large_binary(),           # 渲染的页面图像
             metadata={b"lance-encoding:blob": b"true"}),
    pa.field("text_vector",  pa.list_(pa.float32(), 1536)),  # 文本嵌入
    pa.field("image_vector", pa.list_(pa.float32(), 512)),   # CLIP图像嵌入
])

lance-encoding:blob标记告诉Lance将PNG字节存储在行外,放在正常列页之外,只留下一个小的(位置, 大小)描述符在行中。因此,搜索只扫描这些轻量级描述符,完整的图像字节则按需单独获取,仅针对我们实际检索的少数页面。

两个包含图像的表是特意分开的。pages是我们在本文后面基准测试的检索器所针对的表:每行将一个页面的文本与其截图以及文本+图像嵌入配对。assets更侧重于图像,保存提取的图形及其图像嵌入——我们的基准测试仅轻度使用它,但之所以保留它,是因为如果以后需要直接基于图形进行推理的VLM智能体,它正是所需的内容。

真正的要点是,模式设计取决于你计划在下游运行的任何检索逻辑,因此手边保留你可能需要的形状是值得的:不同的智能体可能受益于不同的表集。

由于LanceDB构建在Lance格式之上,我们构建的索引(下文介绍)与数据位于同一存储中,并且每次写入都会产生表的一个新版本,因此我们可以检查或重现证据的早期状态。

数据摄取、索引与存储占用

数据摄取和索引速度很快,约0.2秒完成。在此示例中,我们在每次搜索都需要过滤的列(companysource_pdf)上创建了标量BTree索引,并在text上创建了全文搜索(FTS)索引。对于小于10万行的小型数据集,LanceDB中不需要向量索引——精确最近邻搜索在这里同样快速。

值得注意的部分是页面图像的大小及其最终位置。在传统设置中,它们会存放在单独的对象存储中,与文本和元数据分离,且无版本控制。在这里,同样约100 MB的页面截图(PNG文件)被存储在同一约101 MB的pages表中,与它们的文本和向量一起,几乎没有存储开销。所有数据和索引都一起进行版本控制,页面的文本及其图像都来自同一个存储,无需往返于单独的对象存储或元数据服务,这降低了智能体需要它们时的延迟。

你可以通过我们的

相似文章

@llama_index: 大多数AI管道的质量取决于我们提供的数据,而这些数据通常意味着PDF或其他非结构化文档…

X AI KOLs Timeline

Parse-Flow 是 LlamaIndex 构建的一个开源可视化工作流设计器,它将四个文档处理原语——Parse(解析)、Classify(分类)、Split(分割)和 Extract(提取)——串联到一个由 LlamaAgents 工作流驱动的拖拽画布中,能够从非结构化企业文档(如PDF、合同和发票)中可靠地提取结构化数据。