A few weeks ago I wanted to search my personal document library — a folder of PDFs sitting in Google Drive — using plain language questions instead of Drive's keyword search. I'd heard about PageIndex, a "vectorless, reasoning-based" alternative to the usual embeddings-and-vector-database RAG stack, and I wanted an excuse to actually use it rather than just read about it.
So that's what I did: I picked a real, personally useful project, added a constraint that mattered to me (no OpenAI key, nothing running in someone else's cloud unless I chose to), and built the whole thing with Claude, end to end, over one long working session. I've started calling this Vibe Learning — the learning equivalent of vibe coding: you don't read the manual first, you describe what you want, let the AI drive the actual building, and pick up the framework as a side effect of watching it get used and occasionally break in front of you.
This post is three things at once: how Vibe Learning actually works in practice, what PageIndex is and why it's a genuinely different approach to RAG, and a walkthrough of the app that came out of it.
Vibe Learning: Building With AI Instead of Reading About It
The old way of picking up a new library looks something like: read the docs top to bottom, follow the quickstart, maybe get through a toy tutorial, and file away a vague mental model for "later" — which often means never actually using it for real.
The way I did this instead: I described what I actually wanted (search my Drive library, no vector DB, no paid API key), and let the agent do the parts that used to be the friction — reading the actual source code of the library instead of trusting a possibly-stale mental model of it, writing the integration code, and running it against my real Drive folder and my real documents.
The valuable part wasn't that the AI produced working code. It's that when things broke — and they did — I got to see why, in enough detail to actually learn something about how the library works under the hood, not just a patched-over error message. A few examples from this exact build:
- Every single indexing call started failing with a cryptic
Failed to extract JSON: Expecting value: line 1 column 1error. It looked like a broken API key or a bad model choice. It turned out to be a genuinely interesting fact about how modern "reasoning" LLMs behave: they can spend their entire token budget on hidden chain-of-thought before ever writing the actual answer, if nothing caps it — and PageIndex's own code never sets amax_tokenslimit on its calls. That's a real, transferable lesson about working with reasoning models, not just a bug I happened to hit. - I discovered, by actually trying to query my indexed documents, that PageIndex's open-source package builds the tree and gives you read tools for it — but doesn't ship the retrieval loop itself. That's a load-bearing detail for anyone evaluating it, and not something the README leads with.
- OpenRouter's free-tier model lineup turned out to change constantly — a model id that worked one day can be delisted days later. Small thing, but the kind of operational reality you only notice when you're actually running against a live provider instead of reading a static list of "supported models."
None of that shows up if you skim a README and move on. It shows up when you build something real and something breaks, with someone (or something) alongside you that can explain the actual mechanism instead of just supplying a fix. That's the shift: the AI doesn't replace learning the framework, it removes the friction that used to stop me from ever getting far enough in to learn it properly.
With that said, let's get into what PageIndex actually is.
What Is PageIndex, and Why Is It Different From Classical RAG?
The classical RAG recipe, and its problem
Standard RAG (retrieval-augmented generation) usually looks like this: split your documents into fixed-size chunks, embed each chunk into a vector, store the vectors in a vector database, and at query time, embed the question and pull back the top-k chunks by cosine similarity. It works, and it's become the default architecture almost by inertia.
The problem PageIndex's authors point at is a simple but important one: similarity is not the same thing as relevance. A vector search finds text that sounds like the question, not necessarily the text that actually answers it. For casual, single-fact lookups over short documents that's often good enough. For long, professional documents — financial filings, regulatory text, technical manuals — where answering correctly requires multi-step reasoning and an understanding of where something sits in the document's structure, similarity search alone tends to fall short.
Chunking makes this worse in a subtler way: it flattens a document's structure. A fixed-size chunk boundary doesn't know or care where a section, a table, or a clause actually ends — it can slice straight through the middle of the thing you needed, discarding the surrounding context a human reader would use without even thinking about it.
PageIndex's approach: reasoning over a tree, not similarity over vectors
PageIndex, built by VectifyAI and released in September 2025, throws out vectors and chunking entirely. Its own framing is explicit about the inspiration: it's modeled on how AlphaGo uses tree search, and on how a human expert actually navigates a long document — by using its table of contents, not by speed-reading every page.
Concretely, it works in two phases:
- Build a tree. PageIndex parses the document and produces a hierarchical structure that looks like an extended, machine-usable table of contents: every node has a title, a page range, and an LLM-generated summary of what that section actually covers. Nodes nest into their natural sections — chapters, sub-sections, and so on — following the document's own structure rather than an arbitrary token-count boundary.
- Reason over the tree to retrieve. At query time, instead of computing similarity scores, an LLM reads the tree — starting from the top-level node summaries — and decides which branches are actually worth descending into, narrowing down step by step until it lands on the specific section(s) relevant to the question. This is the "tree search": genuinely reasoning about relevance, informed by a summary of what each part of the document contains, rather than pattern-matching on surface similarity.
The practical upshot is that every answer is traceable to an exact page and section — no more "vibe retrieval" where you're trusting an opaque similarity score. Retrieval can also incorporate context a fixed vector index can't easily use, like conversation history, since it's a reasoning step rather than a static index lookup.
PageIndex's own benchmark result is a strong argument for the approach: a reasoning-based RAG system built on top of it (VectifyAI's "Mafin 2.5") scored 98.7% on FinanceBench — a benchmark built specifically around financial document question-answering — against roughly 30–50% for typical vector-based RAG systems on the same benchmark. Financial filings are exactly the kind of long, structurally dense, professional document where "similarity" and "relevance" diverge the most, so it's a fair stress test for the idea.
How it's actually built, under the hood
PageIndex is open source (MIT license) and, refreshingly, doesn't lock you into a single LLM provider. Every model call goes through LiteLLM, so under the hood it's just:
response = litellm.completion(model=model, messages=messages, temperature=0)
— where model is any LiteLLM-formatted string: gpt-4o, anthropic/claude-..., ollama/qwen2.5:14b, openrouter/<provider>/<model>, whatever you want. This one design choice is what made it possible to build the whole thing below with no OpenAI key at all.
A config.yaml sets sane defaults — which model to use, how many pages to scan for an existing table of contents, how big a tree node is allowed to get, whether to attach node summaries or a whole-document description — all overridable via CLI flags or, if you're using it as a library, a small options object.
The actual entry point for using it as a library is PageIndexClient, which is genuinely pleasant to work with:
client = PageIndexClient(model=model, retrieve_model=model, workspace="./my_workspace")
doc_id = client.index("some_document.pdf")
client.get_document(doc_id) # metadata: name, description, page count
client.get_document_structure(doc_id) # the tree, without the full text (cheap to hand to an LLM)
client.get_page_content(doc_id, "10-15") # the actual text for a page range
That last trio is clearly designed to be used as tools an agent calls — PageIndex's own examples wire this up with the OpenAI Agents SDK for a small demo. Which brings me to the one honest caveat worth knowing before you adopt it: the open-source package builds the tree and hands you the read primitives, but it does not ship the retrieval loop itself. Deciding which document to search, which section of it to read, and how to turn the fetched text into an answer — that's on you, unless you use VectifyAI's hosted cloud API, which does include it. That's not a criticism so much as a fact you only really absorb by trying to query your own indexed documents and finding there's no search() method waiting for you.
Building that missing piece myself is where a lot of the actual learning happened.
Building the Thing: A Local, Vectorless Search Engine for My Google Drive
The goal was concrete: search my own PDF library, living in Google Drive, in plain language, with no vector database and no data going to a paid provider unless I explicitly chose to. Here's the pipeline that came out of it:
Google Drive (OAuth, read-only)
→ download / export changed files
→ PageIndex tree generation (title, sections, summaries)
→ local JSON storage (no database)
→ hand-rolled tree-search retrieval agent
→ FastAPI backend
→ a small static frontend
Getting into Drive without over-scoping
Read-only OAuth against the Drive API (the standard "Desktop app" installed-app flow — one browser consent, then a cached refresh token), scoped optionally to a single folder. Sync is incremental: each file's Drive modifiedTime is tracked, so unchanged files are skipped, changed files get re-indexed, and anything removed from Drive gets dropped from the index automatically. Google Docs, Sheets, and Slides get exported to PDF on the way in, so the rest of the pipeline only ever has to deal with one format.
No API key: swapping the model string
Because PageIndex is LiteLLM-based, "no OpenAI key" turned out to be almost entirely a configuration problem, not a code problem. The app supports two backends, switchable with one environment variable:
- Ollama — fully local, private, zero cost, needs decent local compute.
- OpenRouter — hosted, with genuinely free-tier models, no local compute required.
LLM_PROVIDER=openrouter
OPENROUTER_MODEL=google/gemini-3.1-flash-lite
OPENROUTER_API_KEY=... # free, no credit card
The reasoning-token bug
This is the one worth dwelling on, because it's a genuinely useful thing to know if you're going to work with reasoning-capable LLMs at all. Every indexing call was failing identically, from the very first LLM request, with an empty response body that PageIndex's JSON parser choked on. It wasn't rate limiting, and it wasn't a bad model — a trivial test prompt worked fine. The difference was prompt complexity: on a real page of document text, the model would spend its entire token budget on hidden chain-of-thought reasoning before it ever got around to writing the actual JSON answer, because PageIndex's own code never sets a max_tokens cap.
The fix, applied once, globally, without touching the vendored library:
DEFAULT_MAX_TOKENS = 16000
DEFAULT_REASONING_EFFORT = "low"
def _completion_with_defaults(*args, **kwargs):
kwargs.setdefault("max_tokens", DEFAULT_MAX_TOKENS)
kwargs.setdefault("reasoning_effort", DEFAULT_REASONING_EFFORT)
return _original_completion(*args, **kwargs)
litellm.completion = _completion_with_defaults
Since litellm.drop_params = True is already set (by PageIndex itself), any provider or model that doesn't understand reasoning_effort just ignores it instead of erroring — so this is safe to apply unconditionally, regardless of which backend is active.
Writing the retrieval loop PageIndex doesn't ship
With the tree-building side solid, the actual search had to be hand-rolled from the get_document / get_document_structure / get_page_content primitives:
- Pick documents. The LLM sees every indexed document's title and short description, and picks which ones are worth searching for this particular question.
- Navigate the tree. For each candidate document, the LLM reads that document's flattened table of contents — titles, page ranges, and summaries — and decides which section(s) actually matter, the tree search PageIndex is named for.
- Fetch and answer. The actual text for those page ranges gets pulled, and one final LLM call answers the question using only that text, citing which document and page range each claim comes from.
The tree navigation step, in essence:
prompt = f"""You are navigating a document's table of contents to find sections
relevant to a question, the way a human expert flips to the right chapter
rather than reading everything.
Question: {query}
Table of contents:
{listing}
Return JSON only: {{"node_ids": ["<id>", ...]}}, most relevant first."""
Simple, but it's the whole idea in one prompt: reason over structure, not similarity over vectors.
Citations that actually point somewhere
Since the goal was searching my own Drive library, not a copy of it, citations link straight back to the original file. Google Drive's PDF viewer happens to honor a #page=N URL fragment, so a citation link like https://drive.google.com/file/d/<id>/view#page=10 opens the real file at the exact cited page — no local copy of the document needs to live in the app itself. A "Sources" footer lists each unique document referenced, alongside its title and author.
Which raises one more small, very "real-world-data-is-messy" detail: a lot of PDFs — scans, Drive-exported Google Docs — simply don't have reliable title/author metadata embedded. So the app tries the embedded PDF metadata first, and falls back to asking the LLM to read the title and byline off the document's own opening pages when that metadata is missing or unreliable.
The frontend, deliberately boring
A single static HTML file — vanilla JS, no build step, no framework — served directly by FastAPI so the whole thing is one app on one URL. It renders the LLM's markdown answer (via marked, sanitized with DOMPurify, both loaded straight from a CDN), shows clickable citation chips, the sources footer, and a library panel listing everything currently indexed. A "Sync Drive" button kicks off an incremental sync in the background and polls until it's done.
Nothing about the frontend needed to be clever — the interesting engineering was entirely in the retrieval pipeline behind it.
Vibe Learning still needs a paper trail
Vibe coding has a well-known failure mode: you end up with something that works, but that nobody — including you, a week later — can actually explain. I didn't want Vibe Learning to end the same way, with the understanding scattered across a long chat transcript I'd never reread.
So once the app actually worked end to end, I had one last step: I asked Claude to go back over everything we'd built and write it up properly in /docs — a functional spec, a description of the technical stack, and an architecture doc, plus a top-level README tying it together. Not as an afterthought, but as the step that turns "I vibed my way to something that works" into something I could hand to someone else, or come back to myself in six months, and actually understand. The docs became the artifact that proves the learning happened, not just the code.
Closing Thoughts
This is what learning a new framework looks like for me now: not reading the whole doc site first, but picking something I actually wanted to exist, adding a real constraint that forced genuine engineering decisions, building it end to end with an AI doing the driving — and then closing the loop by writing down what actually happened, in plain documentation, once it worked. Vibe Learning gets you through the friction that used to stop me from ever getting far enough in to learn a framework properly; the documentation step is what makes sure the learning sticks instead of evaporating back into a chat log.
I came out the other side able to explain how PageIndex's tree search actually works, why it beats similarity search on structurally dense documents, and exactly where its open-source package stops and your own code has to begin. That's a very different, and much stickier, kind of understanding than skimming a README ever gave me.
If you're evaluating PageIndex for your own project: the tree-search idea is genuinely compelling for long, structured documents, the LiteLLM foundation means you're never locked into one provider, and the open-source package is honest about being a building block rather than a finished retrieval system — plan for writing that last piece yourself.



Top comments (0)