跳至正文
-
关注
IT极限技术分享汇

ITmax

IT极限技术分享汇

ITmax

  • 首页
  • 商店
  • 我的帐户
  • 结账
  • 购物车
  • 首页
  • 商店
  • 我的帐户
  • 结账
  • 购物车
关

搜索

首图:PageIndex 项目名,标语「不建向量库,走目录树找答案」,下方三格数据为 39,794 星标、v0.4.7、近四周 77 次提交
技术分享

向量库救不了长文档|PageIndex 的树索引,我装完试到摘要那步就停了

mumupudding 的头像
作者 mumupudding
2026年10月27日 3 分钟阅读
0

业务同事问我的问题,来来回回就那么几句:「对方逾期付款,我们的违约金怎么算」「这条排他条款是不是把整个华东都圈进去了」「验收不合格的补救期限写的几天」。

这些问题的答案,全在合同 PDF 里,而且全在某一节的某一段。我做过的事很自然:把合同灌进知识库,切块、转向量、检索、塞给模型。然后就卡在这里了——问违约金,召回来的八段里有七段讲的是付款节点和账期,真正那句「逾期三十日以上,按未付款项日万分之五计」压根没进候选集。原因不复杂:那句话的上一句是「违约责任」,再上一句是小标题「第九条 违约与救济」。切片按字数硬切,「第九条」被切到了上一块,「逾期三十日以上」被切到了下一块,中间那块单独看,语义上跟「付款方式」没什么区别。

这不是某一次的意外,是每个月都会碰上的事。我试过的补丁也都试过:把 chunk 从八百字加到一千五、重叠两百字、加 reranker、加关键词召回一路混检。每加一层,召回率是会上去一点,代价是噪声也一起上去,模型开始在七八段似是而非的条款之间编答案。混检调到能用的程度,我已经在维护一套比我业务代码还复杂的检索链路了。

所以我看到 PageIndex 那句 slogan 的时候,是被戳了一下的:similarity ≠ relevance。相似不等于相关,这话在合同、财报、招股书这类文档上是真的。

它把文档目录当成索引用,这条不是噱头

思路本身很朴素,朴素到有点反常:既然人有目录,为什么不拿目录当索引。

建树是一步,检索是另一步。建树的时候,它从 PDF 的版面里把标题层级抽出来,做成一个带 node_id、页码区间、父子和兄弟关系的 JSON;检索的时候,模型拿着问题在这棵树上走,判断该进哪个分支,走到叶子节点再去读那几页的正文。整个过程跟人翻一本厚报告没区别:先看目录,再翻到第九条,再读那三页。

真正能跑的入口有三个,我挨个看过:

# 仓库自带脚本,PDF 走 flash 模式(默认)
python run_pageindex.py --pdf_path ./contract.pdf

# Markdown 直接进,不需要模型参与就能出树
python run_pageindex.py --md_path ./notes.md

# SDK:本地建树,自己的模型回答
python -c "
from pageindex import PageIndexClient
c = PageIndexClient(index='gpt-6-luna', chat='gpt-6-sol')
doc_id = c.submit_document('contract.pdf')['doc_id']
print(c.chat('逾期付款的违约金怎么算', doc_id=doc_id))
"

工具层是四个:browse_documents 列清单、get_document_structure 取树、get_page_content 按页取正文、remove_document 删。SDK 里还有 as_openai_tools() 和 as_anthropic_tools() 把这一组包装成主流框架认的工具定义。这几个接口是实打实写在代码里的,不是宣传页 PPT。

建树的粗细程度是可以拧的,旋钮都在 config.yaml 里,默认值挺保守:

toc_check_page_num: 20        # 只在前 20 页里找目录
max_page_num_each_node: 10    # 一个节点最多 10 页
max_token_num_each_node: 20000
if_add_node_summary: "yes"    # 每个节点让模型写一段摘要

前两个数字决定了树的分辨率。一份两百页的文档按「每节点最多十页」切,最多分出二十个叶子;合同这种九条十二款的文档,叶子会粗到一条一节一层都带着,跟我想要的「精确定位到那句违约条款」还差一截。要更细就得把 max_page_num_each_node 往下调,代价是节点变多、摘要调用变多、检索时模型要看的树也变长。这条没有标准答案,只能拿自己的文档试。

摘要那块还有个细节:每个节点的摘要默认压到 150 词(SUMMARY_MAX_WORDS = 150),发给模型的 reasoning effort 是 none。官方在 README 里的说法是「建树用基础模型就够,真正吃能力的是检索时的 chat 模型」。我认同这个判断的动机——树的结构来自版面,模型只负责归纳——但 150 词的摘要去描述一份财报里一节五页的正文,信息损耗是免不掉的,这也是我怀疑检索准确率会掉的一个点,只是没 key,验不了。

哪些是真能用,哪些还只是话术,我分开说:

能力 我的判断
离线建树(PDF、Markdown) 真能用,我自己跑通了,不需要任何 key
SDK 本地模式(建树 + 检索) 能用,但摘要和检索两步必须有模型 key
页码级引用 本地模式有,能追到第几页
块级引用、OCR、图片理解 只在云端,本地版没有
MCP server 只有云端版,本地不起
98.7% FinanceBench 那是 Mafin 2.5 的成绩单,不是 pip install 完就有的能力

最后一行我想多说一句。那个 98.7% 出自他们自家的 Mafin 2.5 项目,跑的是完整 FinanceBench 题库,属于自家系统在自家基准上的成绩。真正对开源版有参考价值的是另一个仓库——PageIndex-OSS-Benchmark,用 34 份 PDF、1,945 页、62 道题的本地 flash 索引来跑,他们还写明了一件事:索引不进去的文档被排除在外,而不是算作失败。这句话很关键,它意味着那个准确率只描述「PageIndex 能吃进去的文档」,不描述你的文档。

省掉向量库,账单挪到了检索那一刻

这是我最想掰扯的地方,因为大部分推文只讲前半句。

向量检索 树索引 + 推理
建索引 一次性 embedding,快、便宜 遍历版面 + 每个节点一次模型调用
单次检索 一次向量比对,毫秒级 模型沿树走,几次到十几次调用
出错形态 召回不相关但相似的片段 走错分支、读错节点、摘要漏信息
可追溯性 相似度分数,说不清为什么 节点 id + 页码,能画路径
扩展性 百万级向量很成熟 单文档很强,跨文档要额外一层

省掉向量库省掉的是运维,不是钱。原来你付的是 Pinecone 或者 Milvus 的账单,现在付的是模型 token 的账单。官方开源基准里 12 组配置的数字摆得很清楚,我挑四行:

chat 模型 reasoning effort 62 题答对 单次查询均价
gpt-5.6-luna none 53 $0.0031
gpt-5.6-luna high 60 $0.0036
gpt-5.6-terra medium 61 $0.0303
gpt-5.6-sol medium 62 $0.0810

便宜模型八五折的准确率,一次三分钱;贵模型全对,一次八分钱,中间差了二十六倍。而同一次查询用向量库,成本几乎可以忽略不计。所以这笔交易的本质是:用延迟和 token 换准确率和可解释性。它不是免费午餐,它是另一种付费方式。

代价在四种情况下会变得不可接受,我一条条划出来:

高并发在线问答不行。 每次检索都要模型来回走树,社区里实测的量级是单次查询几十秒到一两分钟,没有缓存的情况。这对一个聊天窗口是灾难,对一个分析师自己用的工具就完全可以接受。看的是场景,不是数字。

碎片化语料不行。 它的树来自文档本身的层级。聊天记录、零散笔记、几百字的 FAQ,没有目录可走,树会退化成一堆等长的块,那时候它跟向量检索比没有任何优势,反而更贵。issue #518 就是这个问题的极端版本:有人拿 flash 模式跑 FinanceBench,发现不少 PDF 的前几页根本没被抽进索引,覆盖率只有一半。索引里没有的页,后面推理再聪明也找不到。

多文档不行,至少不能直接上。 开箱的状态是一棵树对应一份文档。全部语料上做检索,要么自己写一层路由,要么用他们云版的文件系统层。这个能力我没看到开源版里有。

要毫秒级首字的不行。 这个不用解释。

还有一个我个人的保留意见:它把不确定性从「嵌入模型选得对不对」挪到了「模型这一次走的路对不对」。错误没消失,只是换了个形状——而且新形状比旧形状更难测。向量检索召回错了,你能看着相似度分数骂它;树搜索走错了,你得把整条推理路径翻出来看,才知道它是从哪一步开始跑偏的。

装它两分钟,找入口花了十分钟

python3 -m venv /tmp/pi
/tmp/pi/bin/pip install -U pageindex

装完是 0.4.7,PyPI 上 10 月 24 日发的版。Python 要求 >=3.10,我这台是 3.13.12,没问题。

第一个意外:装完没有 pageindex 这个命令。pip 只装了库,命令行脚本 run_pageindex.py 还躺在仓库根目录,得自己 clone 下来或者单独下这个文件。文档的快速上手写的是 pip install 然后在 Python 里调 PageIndexClient,脚本用法写在仓库 README 里,两处口径不一样,我第一次就找错地方了。

第二个意外是体积。litellm、pypdfium2、PyPDF2、Pillow,再加上一串跟工具调用和协议相关的依赖,一路装下来虚拟环境里 81 个包,site-packages 占 339 MB。对一个「就是个文档索引器」的东西来说不算小,但都在依赖树里,没有偷偷下模型权重。litellm 还被钉在 1.105.x(<1.106),说明它跟着这条线走,升级节奏得看上游脸色。

硬件门槛官方没写,README 和文档站都没给最低配置。这块我只能报我自己量到的数:

档位 配置 依据
最低可用 2 核 4 GB,纯 CPU,20 GB 磁盘 我实测一份 12 页 PDF 离线建树:1.65 秒,峰值内存 148 MB
建议配置 4 核 8 GB 以上 摘要并发默认 64 路(SUMMARY_CONCURRENCY = 64),树优化展开默认 32 路,并发打满时内存和供应商限流都会难受
GPU 不需要 推理全在远端模型,本地只做版面解析

那个 148 MB 是 --no-summary 的裸建树;开着摘要跑,内存会随并发上去,官方没给数字,我也没有 key 去量,只能说到这儿。

一份迪士尼季报,29 个节点

端到端我只跑通了前半段,后半段缺 key。前半段的输入输出都是真的。

输入用仓库自带的样例,迪士尼 2025 财年一季度财报,12 页:

python run_pageindex.py --pdf_path q1.pdf --no-summary --optimize off

预期输出就两行:

Parsing done, saving to file...
Tree structure saved to: ./results/q1_structure.json

出来这个文件 4,572 字节,29 个节点,顶层 12 个,doc_title 是从版面里抽出来的,一个字没差:

{"doc_name": "q1.pdf",
 "doc_title": "THE WALT DISNEY COMPANY REPORTS FIRST QUARTER EARNINGS FOR FISCAL 2025",
 "structure": [
   {"title": "Financial Results for the Quarter:", "node_id": "0000", "start_index": 1, "end_index": 1},
   {"title": "DISCUSSION OF FIRST QUARTER SEGMENT RESULTS", "node_id": "0006", "start_index": 4, "end_index": 9,
    "nodes": [
      {"title": "Star India", "node_id": "0007", "start_index": 4, "end_index": 4},
      {"title": "Entertainment", "node_id": "0008", "start_index": 4, "end_index": 7}
    ]}]}

start_index 和 end_index 是页码区间。看到这个结构我就明白它为什么敢说「不做 chunking」了——它确实不按字数切,它按章节切。只是这句话的另一面是:它换了个地方切。章节切得好不好,取决于你的 PDF 有没有像样的标题层级。

把 --optimize off 换成 --optimize full,多打一行:

Optimize: merges=1 expands=0, worst-case search cost 6 -> 6 pages

翻译成人话:它把一个小节点合进了父节点,然后算出最坏情况下一次搜索要翻 6 页。这个「worst-case search cost」是我在这套东西里看到的最诚实的一个指标——它自己承认,从根走到叶子,最坏要付出多少页的阅读量。

再往下就走不动了。去掉 --no-summary、用默认的 flash 模式,日志开始刷同一条错误,第十次之后抛出来:

pageindex.utils.LLMRetriesExhausted: LLM completion failed after 10 retries:
InternalServerError ... Missing credentials

SDK 那边同样卡住,抛的是 PageIndexAPIError,12.3 秒之后放弃,storage_path 默认落在当前目录的 .pageindex。

我这儿没有可用的模型 key,也没有把公司合同往外部接口上发的打算,所以摘要和检索这两步我没跑。那个 98.7% 我验不了,也不替它背书。能替它背书的只有一件事:离线建树这一步是真的,而且不需要任何外部服务。

翻 issue 翻出来的东西,比文档里写得多

按说这种东西的坑都藏在 issue 里,我翻了一遍,按份量排:

#518,覆盖率只有一半。 一位用户在 FinanceBench 数据集上跑 flash 模式,发现不少 PDF 的前几页没被抽出来,比如 3M_2108k.pdf,索引覆盖率报的是 50%。这条到现在还是 open。它戳的正是整个方案的地基——索引里没有的页,推理再准也没用。

#316,索引只能整体重建(8 条讨论)。 一份 500 页的文档改了一节,现在得整份重跑一遍,建树成本和 token 成本一起重付。提需求的人要的是增量更新,官方还没给。这个坑对静态文档无所谓,对经常改版的合同模板就很疼。

#268,输出路径写死。 ./results/{name}_structure.json 是硬编码的,想批量处理就只能事后搬文件。写脚本的人会先撞上这个。

#456,Windows 上的锁是假的。 DocStore.lock() 在 Windows 上退化成一个什么都不做的 yield,因为 fcntl.flock 在那边不存在。并发提交同名文档时,那个「先查重再写」的窗口是敞开的。

#103,装不上过。 早期仓库里没有 pyproject.toml,pip install 直接失败。这条已经关了,现在装得上——但翻 issue 列表能看出这个项目的工程化是后来补的。

外部的讨论我也翻了几处。Hacker News 上那次 Show HN 是 2025 年 8 月 27 日发的,192 分、128 条评论,标题很直白:Show HN: PageIndex – Vectorless RAG。评论区最响的一句质疑是「这不就是换了个地方的 vibe retrieval 吗」——原来靠向量算近似,现在靠模型猜路径,都是近似,凭什么说后者更准。这话我认同一半:它确实把确定性计算换成了随机调用,只不过换来的东西是「可解释」,而不是「更准」。

还有人贴了延迟的实测,单次查询几十秒到两分钟,取决于文档长度和模型;也有人指出它本质上是「拿模型给每一节写摘要,再把树塞进上下文让模型指节点」,一位自己实现简化版的用户说用更少的调用在 FinanceBench 上拿到过八成出头。

中文圈的实测提到两个具体的坑:一是没有标题的文档(聊天记录、零散笔记)根本生成不了像样的树;二是走云版 API 时 submit_document() 会把整份 PDF 传上去,公开论文无所谓,合同、财报这种就得先看数据政策。

谁该上,谁别上

我的结论分三层。

适合上的:语料是长、结构化、专业文档(财报、招股书、合同、法规、技术手册);查询低频但单次价值高;对溯源有硬要求,出了答案要能指到第几页第几节;或者你已经把混检加重排调到了瓶颈,长文档的多跳问题还是答不准。这几种情况它给你的东西,向量检索确实给不了。

别上的:海量碎片语料;高并发在线问答;预算按 QPS 算而不是按次算;文档天天改版。这四种情况硬上,你会得到一个又慢又贵的向量库替代品。

替代方案,我拿两个比:

RAGFlow(infiniflow/ragflow,92,919 star,Apache-2.0)。走的是完全相反的路线——把解析、切块、向量、重排、对话全链路打包成一套 Docker Compose,工程完整度是它的强项。维度差别在你要不要自己造:想开箱有个能用的知识库系统,选它;想在自己代码里换掉检索这一层,PageIndex 更轻,一个 pip 包,没有服务要运维。

Docling(docling-project/docling,69,640 star,MIT)。它只做前半段——把 PDF 变成结构化 Markdown,标题层级、表格、阅读顺序都给你保住。维度差别在边界:Docling 到文档结构为止,检索得你自己接;PageIndex 把结构和检索一起给了,但结构那一段没有 Docling 细。我现在的想法是这俩可以叠着用,解析用 Docling,检索这一层再决定用树还是用向量。

顺带提一句 MinerU(82,538 star),定位跟 Docling 很近,中文 PDF 的版面还原做得不错,也是只做解析这一段。

一句话:PageIndex 治的是「相似但不是要找的那一段」,不治「模型读不懂那一段」。 前者是我天天遇到的,后者得靠别的办法。

总而言之,这东西的方向我认,工程成熟度我打个折。顺手看了下仓库,现在 39,794 个 star、3,432 个 fork,最近四周 77 次提交,最新发版 v0.4.7 是 10 月 24 日,release note 的标题很自信——”Better retrieval than Vector RAG, at lower cost”。

最后说句实在的,我这次的结论只走了一半路:树建出来了,检索那一段因为没 key 停在那儿。等哪天手上有合适的模型额度、又拿到一份可以外发的公开财报,我会把它和现在这套混检摆在一起,用同一批问题跑一遍,看谁答得准。在那之前,它对我来说是一个方向对、地基还没验收完的东西。

mumupudding 的头像
作者

mumupudding

关注我
其他文章
EdgeEver 开源笔记项目:把笔记的服务器成本干到零,2844 star,1409 fork,321 个版本
上一个

笔记软件还在按月收费|EdgeEver 把服务器成本干到 0,2.8K star 的国产项目我把源码翻了一遍

FunASR 封面:中文语音转写工具包,20845 star,v1.4.16,近四周 43 次提交
下一个

三个小时的会我只想要一份能用的文字|FunASR 装完,我卡在断句上

暂无评论!成为第一个。

发表回复 取消回复

您的邮箱地址不会被公开。 必填项已用 * 标注

Copyright 2026 — IT极限技术分享汇. 备案号:浙ICP备17006560号-2