PageIndex:用结构感知索引突破RAG检索瓶颈
发布时间:2026/10/31 5:43:46 锦皓数字建站

1. 从向量数据库的困境说起PageIndex 到底想解决什么问题做过 RAG 项目的人大概都有过这种体验文档切了一堆 chunkembedding 也算了向量数据库也搭好了检索出来的东西看着相似度挺高但丢给 LLM 之后回答就是不对味。问题出在哪很多时候不是模型不行而是检索这一环从根上就偏了。传统 RAG 的套路是“切块 向量化 相似度召回”这套流程默认了一个假设语义相似的文本块就是相关的。但真实文档里一个关键结论可能分散在三个段落一个表格的上下文藏在标题和脚注里一份合同的核心条款跟它的例外条件隔了十几页。向量相似度只看到了局部语义看不到文档本身的结构和层级关系。PageIndex 这个项目核心思路就是把“页”和“索引”这两个概念重新拉回 RAG 的主链路。它不满足于把文档打碎成无结构的 chunk而是尝试在检索之前先建立一层带有页面级、章节级结构信息的索引让检索过程能够感知“这段内容在文档的什么位置、属于哪个层级、跟哪些页面存在引用关系”。说白了它想让 RAG 从“语义匹配”进化到“结构感知 语义匹配”的双通道模式。这个方向为什么值得关注因为当前 RAG 的瓶颈已经很明显了。热词里频繁出现的“rag瓶颈”“rag检索增强”“rag知识库能存储图片嘛”这些搜索词反映的正是大家在实战中遇到的真实困惑检索不准、多模态内容处理不了、知识库更新成本高。PageIndex 切入的正是“检索不准”这个最痛的环节而且它的思路不是换一个更强的 embedding 模型而是从文档组织方式入手这个角度本身就很有启发性。适合谁来参考如果你正在做企业知识库、技术文档问答、法律合同检索、学术论文助手这类对结构敏感的 RAG 应用PageIndex 的思路值得仔细拆解。如果你只是拿 RAG 做个玩具 demo那可能感受不深。但只要你处理过超过 50 页的 PDF、带复杂表格的财报、或者有严格章节层级的规范文档你就会明白“结构感知检索”有多重要。2. PageIndex 的核心设计思路拆解2.1 为什么“页”是一个被低估的检索单元传统 chunk 策略通常是按固定 token 数切分比如 512 token 一块重叠 50 token。这种切法的问题在于它完全无视了文档的物理边界和逻辑边界。一个段落被拦腰截断一个表格被拆成两半标题和正文分离这些都是家常便饭。PageIndex 把“页”作为一级检索单元背后有几个很实际的考量。第一页是文档最自然的物理边界。PDF、Word、PPT 这些格式本身就有明确的页面概念用户在阅读时也是按页来定位信息的。第二页的粒度足够大能保留完整的上下文。一页内容通常包含一个完整的段落群、一个表格、或者一组相关的图表语义完整性比 512 token 的 chunk 好得多。第三页的粒度又足够小不会引入太多噪声。相比把整章整节作为一个检索单元页的精度更高召回时更容易定位到具体位置。当然单纯按页检索也有问题一页里可能只有一句话是相关的其他都是无关内容。所以 PageIndex 的做法是在页的基础上再叠加一层页内索引把每页的关键信息标题、摘要、实体、表格结构提取出来形成一个轻量级的页级索引。检索时先定位到页再在页内做细粒度的匹配。2.2 索引结构的设计从扁平到层级PageIndex 的索引不是简单的“文档 ID → 页列表”这种扁平结构而是带有层级关系的。具体来说它至少包含三层信息文档级元数据文档标题、作者、创建时间、总页数、章节结构树。这一层帮助系统快速判断“这份文档大概讲什么”在跨文档检索时尤其有用。页级索引每页的页码、所属章节、页内标题、关键实体、内容摘要、与其他页的引用关系。这一层是检索的主力决定了召回哪些页。页内块索引页内的段落、表格、图片说明等细粒度单元带有位置信息和类型标记。这一层用于在召回页之后做精确的内容提取。这种层级结构的好处是检索可以分阶段进行先用文档级信息做粗筛再用页级索引做召回最后用页内块索引做精排。每一层的计算成本不同可以根据场景灵活调整。比如对延迟敏感的场景可以只用到页级对精度要求高的场景可以下钻到页内块。2.3 与向量数据库的关系互补而非替代很多人看到 PageIndex 的第一反应是这跟向量数据库什么关系是不是要替代它我的理解是PageIndex 不是向量数据库的替代品而是它的前置层和补充层。向量数据库擅长的是“给定一个 query找出语义最相似的向量”。但它不擅长处理“这个 query 应该在哪份文档的哪一页找”这种结构性问题。PageIndex 恰好补上了这一环它先通过结构信息缩小检索范围把候选文档和候选页圈出来然后再交给向量数据库做语义精排。这样既发挥了向量检索的语义优势又避免了它在长文档中“大海捞针”式的盲目搜索。实际部署时可以把 PageIndex 的页级索引也向量化存到同一个向量数据库里但检索时先用结构过滤条件比如“只搜第 3 章”“只搜包含表格的页”做预过滤再做向量相似度计算。这种“结构预过滤 语义精排”的组合在实测中比纯向量检索的准确率有明显提升。2.4 对多模态内容的处理思路热词里有人问“rag知识库能存储图片嘛”这其实是个很典型的痛点。传统 RAG 对图片的处理要么是 OCR 提取文字要么是直接用多模态模型生成描述两种方式都有信息损失。PageIndex 的思路是把图片作为页级索引的一个属性来记录而不是强行把它转成文本。具体来说每页的索引里会标记“本页包含 2 张图表、1 个流程图”并记录这些图片的位置和简要说明。检索时如果 query 涉及图表内容系统可以定位到包含相关图表的页然后把整页内容包括图片一起送给多模态 LLM 处理。这样既保留了图片的原始信息又不需要在索引阶段做复杂的多模态对齐。3. 核心细节解析与实操要点3.1 文档解析PageIndex 的地基PageIndex 的效果好不好七成取决于文档解析的质量。如果解析出来的页边界是乱的、表格是散的、标题层级是错的后面的索引和检索全是空中楼阁。实操中文档解析要重点关注几个点。第一是页边界识别。PDF 的页边界通常比较明确但有些扫描件或者排版复杂的文档会出现跨页表格、跨页段落。这时候需要做跨页合并处理把被物理分页切断的逻辑单元重新拼起来。第二是标题层级提取。通过字体大小、加粗、编号模式如“1.1”“第一章”来识别标题层级构建章节树。这个步骤的准确率直接影响后续按章节过滤的能力。第三是表格结构化。表格不能只提取文字还要保留行列关系。常见的做法是把表格转成 Markdown 或 HTML 格式保留表头和单元格对应关系。注意文档解析阶段不要过度清洗。有些团队为了“干净”把页眉页脚、页码、脚注全删了结果丢失了重要的定位信息。页码恰恰是 PageIndex 定位的关键脚注里可能藏着关键解释。3.2 页级索引的构建提取什么、怎么存页级索引是 PageIndex 的核心资产。每页需要提取的信息包括索引字段说明提取方式页码物理页码和逻辑页码解析器直接获取所属章节该页属于哪一章哪一节根据章节树映射页内标题该页出现的小标题字体/编号规则识别关键实体人名、机构名、术语、数值NER 模型 规则内容摘要该页内容的简要概括LLM 生成或抽取式摘要内容类型正文/表格/图表/混合版面分析引用关系该页引用了哪些页/被哪些页引用交叉引用检测这些字段里内容摘要和关键实体是检索时最常用的。摘要的生成可以用小模型如 7B 级别的 LLM批量处理成本可控。关键实体则建议用专门的 NER 模型准确率比通用 LLM 更稳定。存储方面页级索引可以放在关系型数据库里如 PostgreSQL也可以放在文档数据库里如 MongoDB。如果团队已经有向量数据库也可以把摘要和实体向量化后存进去但结构字段页码、章节、类型建议单独存方便做过滤查询。3.3 检索流程从 query 到页的映射PageIndex 的检索流程大致分四步Query 解析分析用户 query 的意图判断它是在问事实、找定义、对比信息还是做推理。同时提取 query 中的结构线索比如“第 3 章”“表格里”“关于 XX 的那一页”。文档级粗筛如果有多个文档先用文档级元数据标题、摘要、章节树做粗筛排除明显不相关的文档。页级召回在候选文档内用页级索引做召回。召回策略可以是关键词匹配 向量相似度的混合也可以加上结构过滤条件。页内精排对召回的页做细粒度排序选出最相关的 top-k 页提取页内具体内容送给 LLM。这个流程里Query 解析这一步很关键但容易被忽略。很多 RAG 系统直接把用户 query 扔给向量数据库不做任何解析结果就是检索结果飘忽不定。PageIndex 的做法是先理解 query 的结构意图再决定检索策略这个思路值得借鉴。3.4 与 LLM 的配合什么时候用、怎么用PageIndex 本身不生成答案它负责的是“找到正确的页”。找到之后怎么用这些页内容取决于具体的 LLM 应用场景。一种常见做法是页内容 query 一起送给 LLM让 LLM 基于页内容生成答案。这时候要注意页内容的长度控制如果召回 5 页每页 2000 字加起来就是 1 万字对 LLM 的上下文窗口是个考验。建议的做法是先对页内容做一次压缩保留关键句和表格再送给 LLM。另一种做法是多轮检索。第一轮先召回一批页让 LLM 判断哪些页真正相关然后针对相关页做第二轮更细粒度的检索。这种方式适合复杂 query但延迟会高一些。实操心得PageIndex 的页级索引可以缓存 LLM 生成的页摘要这样每次检索不需要重新生成摘要响应速度会快很多。摘要的更新频率取决于文档的更新频率一般文档不变就不需要重新生成。4. 实操过程与核心环节实现4.1 环境准备与依赖选型要跑通一个 PageIndex 的最小可用版本需要准备以下几类工具文档解析PDF 用 PyMuPDF 或 pdfplumberWord 用 python-docxPPT 用 python-pptx。如果文档格式混杂可以考虑用 Apache Tika 做统一解析。版面分析如果文档排版复杂需要识别标题、正文、表格、图片区域可以用 LayoutParser 或 PaddleOCR 的版面分析模块。NER 与摘要NER 可以用 spaCy 或 HanLP中文场景摘要可以用本地部署的小模型如 Qwen2.5-7B或调用 API。存储页级索引存 PostgreSQL 或 SQLite向量存 FAISS 或 Milvus。LLM本地可以用 Ollama 跑 Llama 3 或 Qwen线上可以用各家 API。这套组合的优点是全部可本地部署不依赖外部服务适合对数据隐私有要求的场景。缺点是初始搭建成本较高需要一定的工程能力。4.2 文档解析与页边界处理的具体步骤以 PDF 为例解析流程可以这样设计import fitz # PyMuPDF def parse_pdf(pdf_path): doc fitz.open(pdf_path) pages [] for page_num, page in enumerate(doc): # 提取文本块保留位置信息 blocks page.get_text(dict)[blocks] # 提取表格需要额外用 pdfplumber 或 camelot # 提取图片位置 images page.get_images() pages.append({ page_num: page_num 1, blocks: blocks, images: images, text: page.get_text() }) return pages这段代码只是最基础的提取。实际项目中还需要做几件事第一识别页眉页脚并标记但不删除因为页眉里可能包含章节名页脚里有页码。第二检测跨页表格如果一页的最后一个表格没有闭合需要跟下一页的第一个表格合并。第三提取标题层级通过字体大小和加粗信息判断哪些文本是标题。4.3 页级索引的生成与存储页级索引的生成可以批量处理每页独立生成互不依赖。具体步骤对每页的文本做清洗去掉多余空白和乱码。用 NER 模型提取实体记录实体类型和位置。用 LLM 生成页摘要prompt 可以设计为“用 2-3 句话概括本页主要内容保留关键数据和结论”。根据章节树映射确定该页所属的章节路径。检测页内是否有表格、图片记录类型和数量。将以上信息组装成 JSON存入数据库。存储表结构可以这样设计CREATE TABLE page_index ( id SERIAL PRIMARY KEY, doc_id VARCHAR(64), page_num INT, chapter_path VARCHAR(256), summary TEXT, entities JSONB, content_type VARCHAR(32), has_table BOOLEAN, has_image BOOLEAN, raw_text TEXT );这个表结构支持按文档、按章节、按内容类型做过滤查询也支持对摘要和实体做全文检索。4.4 检索接口的实现与参数调优检索接口的核心逻辑是接收 query返回 top-k 页。实现时可以分两步def retrieve(query, doc_idsNone, top_k5): # 第一步结构过滤 filters parse_query_structure(query) # 提取章节、类型等条件 candidates query_page_index(filters, doc_ids) # 第二步语义排序 query_vec embed(query) scored [] for page in candidates: page_vec embed(page[summary] page[raw_text][:500]) score cosine_sim(query_vec, page_vec) scored.append((page, score)) scored.sort(keylambda x: x[1], reverseTrue) return [page for page, _ in scored[:top_k]]参数调优方面top_k 的选择很关键。太小会漏掉相关页太大会引入噪声。建议从 5 开始试根据召回率和准确率的平衡来调整。摘要和原文的权重也需要调如果摘要质量高可以给摘要更高权重如果摘要太泛就多用原文。4.5 与现有 RAG 框架的集成方式PageIndex 可以跟 LangChain、LlamaIndex 这些框架集成。集成的思路是把 PageIndex 作为一个 Retriever 组件替换掉默认的向量检索器。以 LangChain 为例可以实现一个自定义 Retrieverfrom langchain.schema import BaseRetriever, Document class PageIndexRetriever(BaseRetriever): def _get_relevant_documents(self, query): pages retrieve(query, top_k5) return [Document(page_contentp[raw_text], metadata{page: p[page_num]}) for p in pages]这样就能把 PageIndex 的检索能力接入现有的 RAG 链路上层的 LLM 调用逻辑不需要改动。5. 常见问题与排查技巧实录5.1 检索结果不相关从哪几个方向排查这是最常见的问题。排查顺序建议是先看解析质量再看索引质量最后看检索策略。解析质量方面检查页边界是否正确、标题层级是否准确、表格是否完整。如果解析出来的页是乱的后面全白搭。索引质量方面检查摘要是否准确、实体是否漏提、章节映射是否正确。检索策略方面检查 query 解析是否合理、过滤条件是否过严、相似度阈值是否合适。一个实用的调试方法是把检索过程可视化。打印出 query 解析结果、候选页列表、每页的相似度分数逐条检查为什么某些相关页没有被召回。5.2 页摘要质量差原因与改进页摘要质量差通常有三个原因LLM 能力不足、prompt 设计不好、页内容本身太杂。LLM 能力不足的解决办法是换更大的模型或者用专门微调过的摘要模型。prompt 设计方面建议明确要求“保留关键数据、结论和实体”并给出示例。页内容太杂的情况可以先做页内分段对每段分别摘要再合并成页摘要。避坑技巧不要用同一个 LLM 既做摘要又做问答。摘要任务和问答任务对模型的要求不同分开用不同的模型效果更好。摘要可以用小模型批量跑问答用大模型保证质量。5.3 多文档场景下的检索混乱当知识库里有几十上百份文档时检索容易变得混乱明明问的是 A 文档的内容却召回了 B 文档的页。解决办法是加强文档级粗筛。在页级检索之前先用文档标题、文档摘要、文档级实体做一次粗筛把候选文档缩小到 3-5 份再做页级检索。文档级粗筛可以用 BM25 向量混合的方式准确率比纯向量高。另外给每页索引加上文档 ID 和文档标题检索结果里带上这些信息方便排查问题。5.4 性能瓶颈延迟太高怎么办PageIndex 的检索链路比纯向量检索长延迟自然会高一些。优化方向有几个缓存页摘要和实体不要每次检索都重新生成提前算好存起来。向量预计算页摘要和原文的向量提前算好检索时直接查。分级检索先用轻量级索引做粗筛再用重量级模型做精排。并行处理多页的相似度计算可以并行用多线程或向量化计算加速。实测下来如果页级索引建得好检索延迟可以控制在 200-500ms 以内跟纯向量检索的差距不大。5.5 常见问题速查表问题现象可能原因排查方向解决建议召回页不相关query 解析错误检查结构线索提取调整解析规则相关页没召回过滤条件过严检查章节/类型过滤放宽过滤条件摘要太泛prompt 不具体检查摘要 prompt增加关键信息要求延迟太高索引未缓存检查缓存命中率预计算并缓存多文档混乱文档粗筛缺失检查文档级过滤增加粗筛步骤表格内容丢失解析不完整检查表格提取用专门表格解析器6. 一些实战中的个人体会PageIndex 这个思路我最初是在处理一批技术规范文档时接触到的。那批文档有 200 多页章节层级很深表格特别多。用传统 RAG 做出来的问答系统回答经常张冠李戴把第 3 章的内容安到第 5 章头上。后来加了页级索引和章节过滤准确率提升非常明显。我的体会是RAG 的瓶颈往往不在模型而在检索。大家花很多时间调 prompt、换模型但检索这一环如果没做好后面怎么调都是事倍功半。PageIndex 的价值在于它把检索从“纯语义”拉回到了“语义 结构”的双通道这个思路对处理结构化文档特别有效。另外一点页级索引的构建成本比想象中低。用本地小模型批量跑摘要和 NER一台带 24G 显存的机器就能处理几万页文档成本完全可控。关键是索引结构要设计好字段要选对不然后面检索时才发现缺信息就麻烦了。最后分享一个小技巧页摘要里一定要保留页码和章节信息。这样检索结果出来之后你可以直接告诉用户“答案在第 3 章第 12 页”用户体验会好很多。而且当用户反馈答案不对时你可以快速定位到具体页去排查调试效率高很多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。