Introduction
For the last few years, “RAG” has meant essentially one architecture: chunk a document, embed the chunks, store them in a vector database, do similarity search at query time, stuff the top-K results into a prompt. It works—and it still works for a huge class of problems—but it has well-known failure modes when documents are long and structured: chunking destroys context, embeddings match the wrong things confidently, and cross-section reasoning falls apart.
Enter vectorless RAG: a newer pattern (popularized by libraries like PageIndex) that throws away chunking and embeddings entirely. Instead of vector similarity, it builds an LLM-readable tree of the document’s structure and lets the model navigate it like a human would navigate a book—table of contents first, then section, then paragraph.
This guide is a side-by-side comparison and a hands-on tutorial: how each approach works, where each shines, where each breaks, a full PageIndex code walkthrough you can copy into a notebook, and the hybrid pattern that production RAG teams are increasingly converging on in 2026.
📚 Table of contents
- A 30-second refresher: what is RAG?
- How traditional vector RAG actually works
- How vectorless RAG works (LLM tree builder + JSON tree index)
- What happens when the PDF has no table of contents?
- Where to store the JSON tree
- Traditional RAG: strengths and weaknesses
- Vectorless RAG: strengths and weaknesses
- Side-by-side comparison table
- Hands-on: build a vectorless RAG pipeline with PageIndex
- Try it without code: the chat.pageindex.ai playground
- When to use traditional RAG
- When to use vectorless RAG
- The rise of hybrid RAG
- Common mistakes & pro tips
- Best practices for choosing an approach
- Frequently asked questions
🧠 A 30-second refresher: what is RAG?
Retrieval-Augmented Generation is the pattern where a language model doesn’t answer from its weights alone—it answers using retrieved context fetched from an external knowledge source at query time. The model becomes a reasoning engine, and your documents become the source of truth.
The two big design questions in any RAG system:
- How do you index the corpus so the right context is retrievable?
- How do you find that context when a user asks a question?
Traditional RAG answers both with vector similarity. Vectorless RAG answers both with tree navigation. That single shift cascades into everything else.
🧮 How traditional vector RAG works
The classic pipeline is two-phase: build-time indexing and query-time retrieval.
🏗️ Build phase
- Take a large PDF (or website, ticket, transcript…) and split it into fixed-size chunks.
- Pass each chunk through an embedding model. You now have a vector per chunk.
- Store those vectors in a vector database—Pinecone, Weaviate, Qdrant, Chroma, pgvector, FAISS, etc.
- Optionally add metadata (source, page number, section title) for filtering.
🔎 Query phase
- The user query is embedded with the same model.
- The vector DB returns the top-K nearest chunks via cosine similarity.
- Those chunks are concatenated into a prompt with the question.
- The LLM generates an answer grounded in the retrieved chunks.
The core algorithm is similarity search: find the nearest vectors. That’s fast, cheap, and works at internet scale—but the metric is “closeness in embedding space,” not “relevance to the question.” And the chunking step is the silent killer: a single logical section can be split across three chunks, and the one chunk that actually answers the query may not make the top-K cutoff.
🌳 How vectorless RAG works
Vectorless RAG takes a different bet: large documents already have structure (table of contents, chapters, sections, sub-sections). If you preserve that structure, you don’t need a vector database—you can let the LLM walk the tree directly, exactly the way a human expert would skim a book before reading the relevant chapter.
🏗️ Build phase (with PageIndex or equivalent)
- Table-of-contents detection. Scan the first and last pages, parse the TOC if it exists.
- Section-aware splitting. Respect logical boundaries, not token counts. One section per node, never split mid-thought.
- Summarize each section with an LLM. The summary is what the navigator reads at decision time.
- Assemble a hierarchy: root → chapter → section → sub-section. Each node gets a stable node ID like
0011. - Persist as JSON: each node carries title, page range, summary, and a pointer to the full text.
🔎 Query phase
- The LLM receives the user query plus the full JSON tree of titles and summaries as context.
- It reasons over the tree and returns a list of node IDs most likely to contain the answer.
- You pull the full text of those nodes (not chunks, not approximations—the actual section).
- A second LLM call synthesizes the final answer with section + page citations.
- If the first pass wasn’t sufficient, you loop back and let the model pick more nodes.
👉 No chunking. No embeddings. No vector database. The cost is multiple LLM calls per query instead of one, in exchange for far better section-level reasoning and explainable citations.
📭 What if the PDF has no table of contents?
The first reaction most people have when they hear “tree-based RAG” is: my documents don’t have a clean TOC, so this won’t work for me. That’s a fair worry, but PageIndex (and most tree builders) handle the no-TOC case explicitly.
🔧 The fallback flow
- TOC detection pass. Scan the first and last pages for an existing index. If one is found, use it.
- LLM-driven heading detection. If no TOC exists, an LLM reads every page and infers headings, sections, and structural boundaries from typography, repetition, and context.
- Section-aware splitting. The splitter respects those inferred logical boundaries instead of token counts. Crucially, an entire concept stays in a single node—unlike vector chunking where the same paragraph might be sliced three ways.
- LLM summarization per section. Each section gets a title, a node ID, a page range, and a tight summary.
- Hierarchical assembly. The flat list of sections is folded into a tree using heading levels.
📌 The key win over vector chunking: splits follow logical boundaries, not token counts. That single difference is why the resulting tree is reason-able by an LLM.
💾 Where do you store the JSON tree?
This is the most-asked question after the first vectorless RAG tutorial: the tree is just JSON, so—where does it live? The honest answer: anywhere that handles structured documents well.
📁 Filesystem or S3
One JSON file per document. Easiest possible setup. Great for static corpora that update rarely.
🍃 MongoDB
Native JSON storage with rich querying. Ideal when you want to query metadata across documents.
🐘 PostgreSQL (JSONB)
Combine the tree with relational metadata, indexes, and access control. The pragmatic production choice for many teams.
🔑 Redis or DynamoDB
Key-value stores with low latency—handy when you want sub-millisecond retrieval of the root-level summary for a known document ID.
📌 Tree size scales with document length and section count, not with chunk count. A 500-page annual report typically produces a JSON tree in the low hundreds of kilobytes—tiny compared to the vectors a traditional RAG would need.
⚖️ Traditional RAG: strengths and weaknesses
✅ Strengths
- Scales to millions of documents. Vector DBs are designed for it.
- Mature ecosystem. Pinecone, Weaviate, Qdrant, Chroma, FAISS, pgvector—all production-hardened.
- Cheap retrieval. One embedding + one similarity search per query.
- Low latency. Tens to hundreds of milliseconds end-to-end.
- Great for factoid queries. “Who is the CEO?” “What was Q3 revenue?”
- Domain-agnostic. Works on any text—blogs, tickets, chats, PDFs, transcripts.
❌ Weaknesses
- Chunking destroys context. Concepts that span chunks get split; the relevant piece may not appear in the top-K.
- Similarity ≠ relevance. Embeddings can match the wrong things confidently.
- No cross-section reasoning. “Compare risk vs. mitigation” questions struggle.
- Hard to explain retrieval. A cosine score isn’t a reason.
- Embedding drift. Switch models → re-embed the entire corpus.
- Tuning burden. Chunk size, overlap, hybrid BM25, rerankers—there’s a lot to dial in.
⚖️ Vectorless RAG: strengths and weaknesses
✅ Strengths
- Preserves document context. Whole sections stay intact, no broken references.
- Cross-section reasoning. The LLM can compare, contrast, and synthesize across sections.
- Explainable retrieval. Returns a navigation path (Chapter 3 → Section 3.2), not a cosine score.
- No embedding pipeline. No re-embedding when you switch models.
- No vector DB. Lower infra footprint for the right kind of corpus.
- Plays well with structure. Reports, contracts, filings, textbooks, course syllabi.
❌ Weaknesses
- Multiple LLM calls per query. Higher latency—hundreds of milliseconds to a few seconds.
- Higher per-query cost. Multiple LLM invocations beat one cosine lookup on price.
- Doesn’t scale to millions. Realistic range: tens to thousands of documents.
- Needs structured documents. Random blog posts add little value.
- Less mature tooling. PageIndex and a handful of others; ecosystem still emerging.
📊 Side-by-side comparison
| Dimension | Traditional RAG | Vectorless RAG |
|---|---|---|
| Index unit | Fixed-size chunks + embeddings | Section nodes + summaries |
| Retrieval method | Cosine similarity | LLM tree reasoning |
| Scale | Millions of documents | 10s to 1,000s |
| Latency | 10–200 ms | Hundreds of ms to seconds |
| Cost per query | Cheap (1 embedding + 1 search) | Higher (multiple LLM calls) |
| Cross-section reasoning | Weak | Strong |
| Explainability | Cosine scores | Navigation path + page citations |
| Best for | Factoid Q&A, mixed corpora | Long structured documents |
| Infra complexity | Embedding pipeline + vector DB | Tree builder + JSON store |
| Ecosystem maturity | Very mature | Emerging |
🛠️ Hands-on: build a vectorless RAG pipeline with PageIndex
Enough theory. Here’s the shortest path from a fresh notebook to a working vectorless RAG over your own PDF, using the open-source PageIndex SDK and OpenAI as the reasoning model. The free tier is generous (around 1,000 documents at the time of writing), which is plenty to prototype.
📦 Step 1 — Install and configure
You need three packages: the PageIndex SDK, the OpenAI client, and python-dotenv
so you can keep API keys out of source. Grab a PageIndex API key from pageindex.ai
and an OpenAI key from platform.openai.com.
pip3 install pageindex openai python-dotenv
# .env
PAGEINDEX_API_KEY=your_pageindex_key
OPENAI_API_KEY=your_openai_key
🔌 Step 2 — Initialize the clients
import os, json, time
from dotenv import load_dotenv
from pageindex import PageIndexClient
from openai import OpenAI
load_dotenv()
pipeline = PageIndexClient(api_key=os.getenv("PAGEINDEX_API_KEY"))
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
📤 Step 3 — Upload and index a PDF
One call uploads the PDF, kicks off async tree building, and returns a document ID. A 50-page PDF typically takes 30–90 seconds to build. Save the document ID—you’ll reuse it throughout the notebook.
pdf_path = "./sample_document.pdf"
result = pipeline.submit_documents(pdf_path)
doc_id = result["doc_id"]
# Poll until the tree is ready.
while True:
status = pipeline.get_document(doc_id)["status"]
if status == "completed":
break
print("Building tree index...")
time.sleep(5)
🌲 Step 4 — Inspect the tree
Every node carries a title, a stable node ID, a page index, and a summary. Walk the tree once to get a feel for what the LLM will see at query time.
tree = pipeline.get_tree(doc_id, node_summary=True)
raw_tree = tree["result"]["tree"]
def walk(nodes, depth=0):
for n in nodes:
print(" " * depth + f"{n['title']} (p.{n.get('page_index')})")
walk(n.get("nodes", []), depth + 1)
walk(raw_tree)
print(f"Total nodes: {sum(1 for _ in walk(raw_tree))}")
For a 48-page course syllabus PDF, this typically produces something like 40 nodes—preface, module headers, sub-topics, and leaf sections—each with a one-paragraph summary the LLM can reason over without ever loading the full PDF.
🧭 Step 5 — LLM-driven tree search (retrieval)
This is the vectorless equivalent of “top-K cosine lookup.” You hand the LLM the query and a compressed view of the tree, and ask it to return the node IDs most likely to answer the question.
def compress_nodes(nodes):
out = []
for n in nodes:
out.append({
"node_id": n["node_id"],
"title": n["title"],
"summary": n.get("summary", ""),
"children": compress_nodes(n.get("nodes", [])),
})
return out
TREE_SEARCH_PROMPT = """You are given a query and a document tree (title + summary per node).
Identify which nodes most likely contain the answer. Think step by step.
Query: {query}
Document tree:
{tree}
Return JSON: {{"thinking": "...", "node_ids": ["..."]}}"""
def llm_tree_search(query, tree_nodes):
prompt = TREE_SEARCH_PROMPT.format(
query=query,
tree=json.dumps(compress_nodes(tree_nodes), indent=2),
)
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
response_format={"type": "json_object"},
)
return json.loads(resp.choices[0].message.content)
✍️ Step 6 — Generate the cited answer
With the selected node IDs in hand, pull the full text of each matched section and pass it to a second LLM call. The answer prompt explicitly demands section + page citations—the navigation path is the whole point of going vectorless.
def find_nodes_by_id(nodes, target_ids):
found = []
for n in nodes:
if n["node_id"] in target_ids:
found.append(n)
found.extend(find_nodes_by_id(n.get("nodes", []), target_ids))
return found
ANSWER_PROMPT = """You are an expert document analyst. Answer the question using
ONLY the provided context. For every claim, cite the section title and page number
in parentheses.
Question: {query}
Context:
{context}"""
def generate_answer(query, nodes):
if not nodes:
return "No relevant section found in the document."
context = "\n\n".join(
f"[{n['title']} (p.{n.get('page_index')})]\n{n.get('text', '')}"
for n in nodes
)
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": ANSWER_PROMPT.format(
query=query, context=context
)}],
)
return resp.choices[0].message.content
def vectorless_rag(query):
search = llm_tree_search(query, raw_tree)
nodes = find_nodes_by_id(raw_tree, search["node_ids"])
return generate_answer(query, nodes)
print(vectorless_rag("What is the syllabus covered in modern LLM fine-tuning?"))
📌 Notice what’s missing: no vector database, no embedding model, no chunk-size tuning, no reranker. Two LLM calls per query and a JSON file on disk. That’s the entire retrieval stack.
🧪 Try it without code: the chat.pageindex.ai playground
If you want to feel the difference before writing any code, the PageIndex team hosts a free chat
playground at chat.pageindex.ai. Upload a
PDF, watch the tree get built in seconds, and then ask cross-section questions like “what are the
main challenges in pattern recognition?” The UI surfaces the tree-walk explicitly: you can see the
model identify relevant section titles, descend into the right subtree, pull page summaries, and only
then generate the answer.
Two things tend to stand out the first time you try it. The retrieval is visibly faster than spinning up a vector store on the same PDF, and the citations are real—each answer comes with the section title and page number it came from, not a cosine score you have to take on faith.
📥 When to use traditional RAG
- Massive, heterogeneous corpora. Millions of mixed-format documents—blog posts, tickets, transcripts, knowledge-base articles.
- Latency-critical apps. Real-time chatbots, search, autocomplete.
- Short, factoid queries. “Who is the CEO?” “What was revenue last quarter?”
- Cost-sensitive workloads at scale. Thousands of queries per minute where pennies matter.
- Heterogeneous content. When documents don’t share consistent structure.
📥 When to use vectorless RAG
- Long, highly structured documents. Annual reports, 10-Ks, legal contracts, textbooks, regulatory filings, course syllabi.
- Reasoning > similarity. When relevance and synthesis matter more than vector match.
- Compliance, audit, legal, financial advisory. Anywhere you need to show your work and cite the navigation path.
- When chunking destroys meaning. Cross-section comparisons, multi-step analyses, framework-aware Q&A.
- Smaller corpora. A library of dozens to a few thousand structured documents.
🔀 The rise of hybrid RAG
The smartest production teams aren’t picking one architecture and forcing every document through it. They’re running both in parallel, with a router that decides per query (or per document type) which engine to use.
🧭 A common hybrid pattern
- Classify the query. Factoid lookup? Cross-section analysis? Comparative reasoning?
- Classify the corpus. Heterogeneous tickets/blogs? Or a curated library of filings and contracts?
- Route accordingly. Factoid + heterogeneous → vector RAG. Reasoning + structured → vectorless RAG.
- Optionally combine. Use vector RAG to find candidate documents, then vectorless to navigate within the chosen document.
📌 Traditional RAG ≈ scale. Vectorless RAG ≈ reasoning for structure. They are complementary, not competitors.
Common mistakes & pro tips
❌ Common mistakes
- Defaulting to vector RAG because it’s familiar, not because it fits
- Using vectorless RAG on millions of unstructured blog posts
- Cranking up
top_kto fix bad retrieval instead of fixing chunking - Forgetting that switching embedding models means re-embedding everything
- Treating “hybrid” as “run both and concat results”—without a router
- Skipping evals; both approaches need a quality measurement loop
- Rebuilding the tree on every query instead of caching the JSON
✅ Pro tips
- Profile your documents first—structure decides architecture
- Cache vectorless tree summaries aggressively; they’re expensive to regenerate
- For traditional RAG, add a reranker (Cohere, BGE) before betting on top-K
- Show users the retrieval trail—“Chapter 3 → Section 3.2”—builds trust fast
- Measure cross-section reasoning explicitly in your eval set
- Start vectorless if your corpus is < 1,000 long structured docs; scale up if needed
- For very deep trees, let the LLM loop: pick nodes, check sufficiency, descend if not
🎯 Best practices for choosing an approach
- Start with the document, not the architecture. Is it structured? How long? How many of them?
- Match retrieval to the question type. Factoids want vectors; analyses want trees.
- Build an eval set early. Include factoid, cross-section, and synthesis questions.
- Don’t over-index on cost. Vectorless is more expensive per query but eliminates an entire infra layer.
- Prepare for hybrid. Architect with a router from day one even if you only ship one engine first.
Conclusion
Vectorless RAG isn’t a replacement for traditional RAG—it’s a sharper tool for a different job. If your corpus is long, structured, and reasoning-heavy, tree navigation will routinely beat similarity search. If your corpus is huge, heterogeneous, and latency-sensitive, vectors still win.
The PageIndex code path above is short enough that you can stand up a working prototype in an afternoon. That’s the real takeaway: you don’t need a vector DB, an embedding pipeline, or a reranker to ship a credible RAG system over structured documents in 2026. You need a tree builder, a JSON file, and two LLM calls.
The honest answer for most production teams is “both, intelligently routed.” The architectures are complementary, not competitive, and the right pick depends on the document, not the hype.
Related reading: MCP explained: build your own server — how AI actually works (tokens & context engineering) — LangChain review
Explore More on DevShelf
-
Learn Agentic AI in 7 Steps
Step 4 of this path is entirely RAG — where the vector vs. vectorless decision sits in the broader agentic AI stack.
-
PG Text Search: BM25 to Replace Elasticsearch
The keyword-ranking layer that pairs with vector search in hybrid RAG — how BM25 inside Postgres closes the gap.