PageIndex

Framework

VectifyAI/PageIndex

Vectorless, reasoning-based RAG with hierarchical tree index and human-like retrieval.

Overview

PageIndex is a vectorless, reasoning-based RAG system that builds a hierarchical tree index from long documents. It uses LLM reasoning for context-aware, human-like retrieval instead of vector similarity. Achieves state-of-the-art accuracy on professional document analysis benchmarks like FinanceBench.

README Preview

\n  \n\n  \n\n\n\n\n\n\n  \n\n\n# PageIndex: Vectorless, Reasoning-based RAG\n\nReasoning-based RAG  ◦  No Vector DB or Chunking  ◦  Context-Aware  ◦  Human-like Retrieval\n\n\n  🌐 Homepage  •  \n  🖥️ Chat Platform  •  \n  🔌 MCP & API  •  \n  📖 Docs  •  \n  💬 Discord  •  \n  ✉️ Contact \n\n  \n\n\n\n\n📢 Updates\n\n- 🔥 [**Agentic Vectorless RAG**](https://github.com/VectifyAI/PageIndex/blob/main/examples/agentic_vectorless_rag_demo.py) — A simple *agentic, vectorless RAG* [example](#agentic-vectorless-rag-an-example) with self-hosted PageIndex, using OpenAI Agents SDK.\n- [**Scale PageIndex to Millions of Documents**](https://pageindex.ai/blog/pageindex-filesystem) — *PageIndex File System* is a file-level tree layer that lets PageIndex reason over an entire corpus, not just a single document, enabling massive-scale document search.\n- [PageIndex Chat](https://chat.pageindex.ai) — Human-like document analysis agent [platform](https://chat.pageindex.ai) for professional long documents. Also available via [MCP](https://pageindex.ai/developer) or [API](https://pageindex.ai/developer).\n- [PageIndex Framework](https://pageindex.ai/blog/pageindex-intro) — Deep dive into PageIndex: an *agentic, in-context tree index* that enables LLMs to perform *reasoning-based, context-aware retrieval* over long documents.\n\n \n\n\n\n---\n\n# 📑 Introduction to PageIndex\n\nAre you frustrated with vector database retrieval accuracy for long professional documents? Traditional vector-based RAG relies on semantic *similarity* rather than true *relevance*. But **similarity ≠ relevance** — what we truly need in retrieval is **relevance**, and that requires **reasoning**. When working with professional documents that demand domain expertise and multi-step reasoning, similarity search often falls short.\n\nInspired by AlphaGo, we propose **[PageIndex](https://vectify.ai/pageindex)** — a **vectorless**,

FAQ (5)

Troubleshooting
How do I fix "KeyError: 'page'" in process_none_page_numbers during TOC processing?

Replace unsafe key deletion with pop() and a default value. In pageindex/page_index.py (or pageindex/page_index_classic.py), change:

del item_copy['page']
del item['page']

to:

item_copy.pop('page', None)
item.pop('page', None)

This removes the key when present and returns None instead of raising KeyError when a TOC item has no page field. Upgrading to the latest release may also resolve the issue because the legacy TOC path was replaced with additional validation, cleanup, retry, and error handling.

GitHub Issue #69
Troubleshooting
How do I fix KeyError: 'page' in process_none_page_numbers when a TOC item has no page number?

Guard the key removal instead of using del. In pageindex/page_index.py or pageindex/page_index_classic.py, replace:

del item['page']
del item_copy['page']

with:

item.pop('page', None)
item_copy.pop('page', None)

This safely removes page only when it exists. If running an installed release, upgrade to the latest version; the legacy TOC path was later replaced or covered by parser validation and error-handling changes. If the error persists, report the input shape, model/provider, package version, and traceback.

GitHub Issue #97
Troubleshooting
How to fix AttributeError: 'str' object has no attribute 'get' or 'dict' object has no attribute 'extend' when an LLM returns malformed JSON?

Fix the JSON fallback and add runtime type validation. In pageindex/utils.py, make extract_json() return [] instead of {} when parsing fails, because the ToC callers expect a list of dicts:

def extract_json(text):
    try:
        data = json.loads(text)
    except (json.JSONDecodeError, TypeError):
        return []

    return data if isinstance(data, list) else []

Normalize LLM output before using it:

def normalize_toc_items(data):
    if not isinstance(data, list):
        return []
    return [item for item in data if isinstance(item, dict)]

In meta_processor, filter only dict items before calling .get():

toc_with_page_number = normalize_toc_items(toc_with_page_number)
toc_with_page_number = [
    item for item in toc_with_page_number
    if item.get("physical_index") is not None
]

In process_no_toc, ensure both values are lists before calling .extend():

toc_with_page_number = normalize_toc_items(
    generate_toc_init(group_texts[0], model)
)

for group_text in group_texts[1:]:
    additional = normalize_toc_items(
        generate_toc_continue(toc_with_page_number, group_text, model)
    )
    toc_with_page_number.extend(additional)

Alternatively, update to a release containing the malformed-JSON crash guards from PRs #210, #217, or the comprehensive fix referenced in issue #199.

GitHub Issue #199
Troubleshooting
How to fix KeyError: 'physical_index' in convert_physical_index_to_int when running PageIndex?

This was caused by an early-version bug where some parsed PDF entries lacked the physical_index field. Update PageIndex to the latest version, which includes the fix and regression test:

cd PageIndex
git pull
pip install -r requirements.txt

Then rerun your original PageIndex command. Python 3.9 was not confirmed as the cause. If the error or recognition problems persist with an old or complex PDF, PyPDF may fail to parse it reliably; as a workaround, use the hosted PageIndex API, which uses OCR and may handle such PDFs better.

GitHub Issue #1
Troubleshooting
How do I fix HTTP 429 rate-limit errors followed by KeyError 'toc_detected' when generating a tree index with concurrent LLM requests?

Upgrade to the latest pageindex release, which adds client-side rate limiting, bounded concurrency, Retry-After handling, exponential backoff with jitter, and validation for malformed LLM JSON responses:

python -m pip install -U pageindex litellm

After upgrading, concurrent calls no longer burst through asyncio.gather, and an empty/failed LLM response is handled instead of accessing {}['toc_detected'].

If you cannot upgrade immediately, apply these workarounds:

1. Limit LLM concurrency with a semaphore:

sem = asyncio.Semaphore(2)  # match your provider's RPM/TPM tier

async def guarded_llm_call(coro):
    async with sem:
        return await coro

await asyncio.gather(*(guarded_llm_call(task) for task in tasks))

2. Replace the fixed 1-second retry with exponential backoff, jitter, and Retry-After support:

import asyncio, random

delay = min(60, (2 ** attempt) + random.random())
retry_after = getattr(exc, "response", None)
if retry_after is not None:
    delay = float(retry_after.headers.get("Retry-After", delay))
await asyncio.sleep(delay)

3. Validate parsed JSON before direct key access:

json_content = extract_json(response)
if not json_content or "toc_detected" not in json_content:
    raise RuntimeError("LLM returned an invalid or empty JSON response")
return json_content["toc_detected"]

Apply the same key-existence check to fields such as completed in the TOC completion checks.

GitHub Issue #283