给财务报告、法务合同这类长文档做问答,向量检索有个绕不过去的问题:它按语义相似度召回,可相似不等于相关。你问「2024 年经营现金流是多少」,它可能把「现金流」相关的描述段落都给你,却漏掉真正写数字的那一页;反过来又会把「相似但答非所问」的段落塞进来。PageIndex 的出发点就是这个:检索真正需要的是相关性,而相关性要靠推理,不是靠向量距离。

它用一句话概括自己的路线——不做向量库、不做切块(No Vector DB, No Chunking),换成生成树状索引,再让 LLM 像人翻报告一样,一路推理到该读的那一节。

树索引与两段式检索

判断写进第一句:PageIndex 的核心是把「一次性语义召回」拆成「先建树、再走树」两步,用 LLM 的推理替代向量相似度。

所谓树索引,是把一篇文档的层级结构(章节、小节、段落)整理成一棵树。它和 AlphaGo 的思路相通:不是全局扫一遍,而是从根节点开始,每一步推理「该往哪个分支走」,最终定位到几页。对比一下两种做法:

向量 RAG PageIndex
索引 向量索引 树索引
检索 语义相似度搜索 LLM 沿树推理
结果 不透明,像「感觉对」 可回溯到明确引用
上下文 只有 query 的 embedding 完整上下文(对话历史、领域知识)

索引和检索这两步,对模型的要求是相反的。树的结构本身是从文档版式里提取出来的,不需要 LLM 参与;索引模型只是做摘要和精修,所以用基础模型就够。检索模型才需要花大钱——它要沿树推理,官方建议「用你能负担的最强模型」。

本地跑起来

先装包,再用两个参数把「建树」和「走树」的模型分开指定:

import os
from pageindex import PageIndexClient

os.environ["OPENAI_API_KEY"] = "sk-xxx"

client = PageIndexClient(
    index="gpt-6-luna",   # 建树索引用基础模型,够用
    chat="gpt-6-sol",     # 沿树检索用最强的模型
)
doc_id = client.submit_document("annual_report.pdf")["doc_id"]
answer = client.chat("2024 年的经营现金流是多少?", doc_id=doc_id)
print(answer)

index 和 chat 分开,是这个 API 设计里最值得注意的一点。很多人一开始会把两个都设成最强模型,结果是索引阶段白白烧钱——树结构早就从版式里抽好了,索引模型只做润色。

云端模式

开源版擅长的是纯文本 PDF 和本地流程。遇到扫描件、图多的文档,本地就吃力了。云端模式把这部分搬走:解析、OCR、图像理解、树索引构建、存储全在云端,检索层仍用你自己的模型。切换只多一个 key:

import os
from pageindex import PageIndexClient

os.environ["PAGEINDEX_API_KEY"] = "pi-xxx"
os.environ["OPENAI_API_KEY"] = "sk-xxx"

client = PageIndexClient(
    index="cloud",        # 索引和存储在 PageIndex Cloud
    chat="gpt-6-sol",     # 检索仍走你自己的模型
)
doc_id = client.submit_document("contracts/nd_2025.pdf", wait=True)["doc_id"]
print(client.chat("违约金的计算基数是什么?", doc_id=doc_id))

两边的能力边界也清楚:本地只有页级引用(citation),云端有块级引用,还多出 OCR、元数据、文件夹和 MCP server。

索引一次、查询多次

成本结构是它相对向量 RAG 的一个实打实优势。建索引是一次性开销:官方测下来本地建树约 0.001 美元/页(用 gpt-5.6-luna),1000 页的教材也就一美元多一点、几分钟,之后每个问题都复用。查一次的成本不随文档变长而涨,因为它只读推理走到的那几个节点。

把「建索引」和「查问题」拆开写,就是下面这样:

# 建一次索引,之后每个问题只花检索成本,不再重复索引
from pageindex import PageIndexClient

client = PageIndexClient(index="gpt-6-luna", chat="gpt-6-sol")
docs = {
    "fy2024": client.submit_document("fy2024_10k.pdf")["doc_id"],
    "fy2023": client.submit_document("fy2023_10k.pdf")["doc_id"],
}

def ask(doc_key, question):
    return client.chat(question, doc_id=docs[doc_key])

作为对照,另一种做法是每次把整份 PDF 塞给模型。官方测下来,同样答对的情况下,原生 PDF 输入在 52 页时贵 2.1 倍,420 页时贵 16.6 倍,到 805 页直接塞不进上下文窗口。

精度和边界

它在 FinanceBench(财务文档问答基准)上拿到 98.7% 的准确率,远高于向量 RAG 的 50%。这个数字来自它自己的评测,方向可信,但要记住基准场景是长而复杂的专业文档——财报、法条、监管文件、技术手册、医学文献、教材这类。

反过来看它的适用边界。如果你的文档短、语义单一、用户问法多变,向量检索仍然更成熟、生态更全,PageIndex 的树索引优势不明显。而且它和 LLM 强绑定——检索质量取决于 chat 模型强不强,选个弱模型,树走得再对,答案也还是不行。它也不是向量库的替代品,而是针对「长文档、要上下文理解、要多步推理」这类场景的另一种选择。

判断标准一句话:文档越长、越结构化、越要求答案可回溯到原文,PageIndex 越划算;文档短、追求快和稳,向量 RAG 仍够用。