Series: RAG 系統架構 (4/5)
- PageIndex's counter-thesis: similarity ≠ relevance. Instead of using embeddings to find the passage that looks 'most alike,' let an LLM reason over the document's tree-shaped table of contents to figure out 'where the answer lives.'
- No vector DB, no chunking — it retrieves via a section tree plus LLM tree search, hits 98.7% SOTA on FinanceBench, and is traceable and explainable.
- This site takes the opposite tradeoff: bge-m3 vectors + Cloudflare Vectorize + a keyword fallback, running on the edge with low latency — a fit for short blog posts rather than long structured documents.
Table of Contents
RAG (Retrieval-Augmented Generation) has almost become synonymous with “vector database + semantic search,” but VectifyAI/PageIndex proposes a counter-thesis: vector similarity is not the same as relevance. Rather than using embeddings to find the “most alike” passage, let the LLM reason directly about “where the answer is.” This article takes apart PageIndex’s architecture in depth and fully compares it against the Hybrid RAG (bge-m3 + Cloudflare Vectorize) this site actually runs.
PageIndex: Tree Index + Agent Reasoning
PageIndex’s core idea is to index a document into a hierarchical tree (much like a table of contents), then let an LLM Agent navigate that tree through tool calls — instead of dumping every chunk into embedding space all at once.
Building the Index (Index Phase)
Given a PDF or Markdown document, PageIndex produces a JSON tree structure like this:
{
"title": "財務報表分析",
"node_id": "0001",
"start_index": 1,
"end_index": 80,
"summary": "本文件涵蓋 2023 年度損益表、資產負債表與現金流量表...",
"nodes": [
{
"title": "損益表",
"node_id": "0002",
"start_index": 5,
"end_index": 22,
"summary": "營收 42億、毛利率 38%、淨利 6.1億..."
},
{
"title": "資產負債表",
"node_id": "0003",
"start_index": 23,
"end_index": 41,
"summary": "..."
}
]
}
Each node records the section title, the page range, an LLM-generated summary, and its child nodes. By default each node is capped at 10 pages / 20,000 tokens, with support for automatically detecting an existing table of contents from the document’s first 20 pages.
Reasoning-Based Retrieval (Retrieval Phase)
When a query comes in, the LLM Agent has three tools:
get_document()— fetch basic document info (page count, description)get_document_structure()— fetch the full tree structure (summaries only, no full text)get_page_content(pages='5-7')— fetch the actual content of the specified pages
The Agent’s system prompt forces it to proceed in order: first confirm the document structure → locate the relevant nodes → fetch only the content of the necessary pages → answer. This mimics how a human expert flips through a book.
sequenceDiagram
participant User
participant Agent as "LLM Agent"
participant Index as "Page Index Tree"
User->>Agent: Question: What was the 2023 gross margin?
Agent->>Index: get_document_structure()
Index->>Agent: Tree summary (with each section's summary)
Agent->>Index: get_page_content(pages='5-10')
Index->>Agent: Income statement page content
Agent->>User: Answer: Gross margin 38%, from the income statement on p.7
Note right of User: get_document_structure() only
Note over User,Agent,Index: LLM Agent answers the question
Performance and Positioning
PageIndex reaches 98.7% accuracy on FinanceBench (a financial-document QA benchmark), far ahead of traditional vector RAG. The defining traits of that scenario: the documents have a fixed structure (financial-report format), answers require precise numbers, and a bad chunk split directly causes errors. The project flies the banner of “vectorless, reasoning-based RAG,” has 33k stars on GitHub with active development, and has launched the PageIndex File System (a file-level tree index) to extend the same reasoning-based retrieval to “an entire corpus” rather than a single document.
This Site’s Hybrid RAG
This site (Engineer News) takes a different route: vector search as the primary path, keyword search as the backup, all running on the Cloudflare edge.
Building the Index (sync-to-d1.ts)
Markdown article
→ split into paragraph chunks on double newlines (max 1000 chars)
→ bge-m3 embed each chunk (1024 dim)
→ store in Cloudflare Vectorize (cosine similarity)
→ store chunk metadata in D1 SQLite (doc_chunks table)
The splitting is straightforward:
function chunkText(text: string, maxLength = 1000): string[] {
const paragraphs = text.split(/\n\n+/);
const chunks: string[] = [];
let current = '';
for (const p of paragraphs) {
if ((current + p).length > maxLength) {
if (current) chunks.push(current.trim());
current = p;
} else {
current += (current ? '\n\n' : '') + p;
}
}
if (current) chunks.push(current.trim());
return chunks;
}
Query Flow (api/search.ts)
flowchart LR
Q[User query] --> E[bge-m3 embed query]
E --> V[Vectorize.query topK=8]
V --> D[D1 JOIN posts for metadata]
D --> DD{Any hits?}
DD -- Yes --> S[Dedupe to 5 posts]
DD -- No --> K[SQLite LIKE keyword search]
K --> S
S --> P[Assemble prompt + sources]
P --> L[qwen1.5-14b streaming]
L --> R[Return SSE + x-rag-sources header]
The chunk IDs found by Vectorize are used to fetch the full content back from D1, keeping at most one chunk per article (deduped within an article) for a total of 5 sources. When the vector path returns nothing, it falls back to a SQLite LIKE query, ranked with weighting across title / tldr / content.
The Core Difference Between the Two Routes
graph TB
subgraph PageIndex["PageIndex (reasoning-based)"]
PI1[Document] --> PI2[Build tree: sections + summaries]
PI2 --> PI3[LLM Agent reasons and navigates]
PI3 --> PI4[Fetch specified page content]
PI4 --> PI5[Generate answer]
end
subgraph VectorRAG["This site's Hybrid RAG (vector-based)"]
VR1[Markdown] --> VR2[paragraph chunks]
VR2 --> VR3[bge-m3 embed]
VR3 --> VR4[Vectorize similarity query]
VR4 --> VR5[keyword fallback]
VR5 --> VR6[qwen1.5-14b generation]
end
| Aspect | PageIndex | This site’s Hybrid RAG |
|---|---|---|
| Index structure | Hierarchical tree (sections + summaries) | Flat paragraph chunks |
| Vector DB | Not needed | Cloudflare Vectorize |
| Retrieval mechanism | LLM Agent tool calls | Vector cosine similarity |
| Fallback | None (reasoning is the primary path) | SQLite LIKE keyword search |
| Embedding | None | bge-m3 1024-dim |
| Generation model | Any (OpenAI Agents SDK, swappable to any LLM) | qwen1.5-14b-chat-awq |
| Document structure preserved | Section hierarchy fully preserved | Lost after chunking |
| Long-document support | Core design (financial reports, etc.) | Mainly short blog posts |
| Multi-turn conversation | Full history supported | Single turn |
| Explainability | Reasoning path is traceable | Vector scores aren’t intuitive |
| Inference cost | High (multiple LLM reasoning hops for navigation) | Low (Workers AI) |
| Deployment environment | Python + OpenAI API | Cloudflare edge |
The “Similarity ≠ Relevance” Thesis
PageIndex’s central claim deserves to be taken seriously. Vector similarity is essentially asking “how semantically close is this text to the query?” — but the real question is “can this text answer the question?”
The most classic failure case: querying “the company’s 2023 EBITDA,” vectors might recall every passage that mentions EBITDA — methodology introductions, historical comparisons, accounting-standard explanations — but the only one that actually holds the answer is that single line of numbers on page 12 of the financial report. If that line happens to be split into a different chunk from its surrounding context, vector search simply fails.
PageIndex’s LLM reasoning understands that “the EBITDA calculation will only appear in the income-statement section,” and navigates straight there.
But this advantage has a prerequisite: the document has structure. For documents with clear sections — financial reports, legal documents, technical manuals — a tree index makes a noticeable difference. For short blog posts like this site’s, the structural differences between paragraphs aren’t that large, and vector similarity as a proxy is already good enough.
Overall
The two architectures answer different questions:
PageIndex suits: long documents that need precise numbers or specific facts, documents with clear structure (financial reports / legal / manuals), use cases that can absorb GPT-4o-level inference cost, and applications that need to trace “which page the answer came from.”
Hybrid vector RAG suits: semantic search over large collections of short documents, low-latency needs, deployment on the edge or in resource-constrained environments, and documents that are semantically rich but structurally flat (blogs, news, notes).
This site’s current implementation is a reasonable tradeoff for the blog-search scenario. If it ever needs to support “searching PDF reports” or “searching long technical documents,” PageIndex’s reasoning route is worth serious consideration — or at minimum, its idea of embedding a “title + tldr + chunk” combination, rather than embedding the chunk content alone, is worth borrowing.
References
Answers come from this article only. Click any prompt below or open the chat at the bottom right.
🇺🇸 English
RAG — retrieval-augmented generation — has practically become a synonym for one specific recipe: throw your documents into a vector database, and search by semantic similarity. But there's a project called PageIndex, from VectifyAI, that pushes back hard on that assumption. Its core argument is deceptively simple: similarity is not the same thing as relevance. Just because a passage *sounds* like your question doesn't mean it actually *answers* it. So instead of using embeddings to find the most look-alike chunk of text, PageIndex lets a language model reason directly about where the answer actually lives.
Let me walk you through how it works, and then contrast it with the very different setup running on this blog.
PageIndex has two phases. The first is building the index. Now, most RAG systems chop a document into chunks and scatter them across embedding space. PageIndex does something closer to how a human reads — it builds a hierarchical tree, basically a table of contents. Picture a financial report. The root node is the whole document. Underneath it sit child nodes: the income statement, the balance sheet, the cash flow statement. And each node carries four things — the section title, the page range it spans, a short LLM-generated summary of what's in it, and pointers to its children. So the income statement node might say "pages five through twenty-two, revenue four-point-two billion, gross margin thirty-eight percent." By default each node caps out around ten pages or twenty thousand tokens, and it can even auto-detect an existing table of contents by scanning the first twenty pages.
The second phase is retrieval — and this is where it gets interesting. There's no vector search at all. Instead, an LLM agent gets three tools. One tool fetches basic document info, like page count. A second fetches the full tree structure — but *only the summaries*, not the actual text. And the third fetches the real content of specific pages you name.
The agent is forced to work in order, like an expert flipping through a book. Say you ask, "What was the 2023 gross margin?" The agent first pulls the tree structure, reads the summaries, and reasons: gross margin? That belongs in the income statement. It sees the income statement lives on pages five through ten. So it requests *only* those pages, reads them, and answers — gross margin was thirty-eight percent, found on page seven. It never touched the balance sheet or the cash flow section. It navigated straight to the answer.
And the payoff is dramatic. On FinanceBench, a financial-document question-answering benchmark, PageIndex hits ninety-eight-point-seven percent accuracy — way ahead of traditional vector RAG. Now, notice *why* that scenario suits it so well: the documents have a fixed, predictable structure, the answers demand precise numbers, and a sloppy chunk split would directly produce a wrong answer. The project's banner is "vectorless, reasoning-based RAG," it's pulling thirty-three thousand stars on GitHub, and it's now extending the same idea from single documents up to an entire corpus.
Okay — so that's PageIndex. Let me switch to what this blog, Engineer News, actually runs, because it's a completely different animal: vector search as the main path, keyword search as the backup, all living on the Cloudflare edge.
The indexing side is refreshingly plain. Take a Markdown article, split it into paragraph-sized chunks on blank lines — capped around a thousand characters each. Embed every chunk with the bge-m3 model into a thousand-and-twenty-four-dimension vector. Store those vectors in Cloudflare Vectorize using cosine similarity, and keep the chunk metadata in a D1 SQLite table. The splitting logic is nothing fancy — it just accumulates paragraphs until adding the next one would blow past the size limit, then starts a fresh chunk.
The query flow goes like this. A user question comes in, gets embedded by the same bge-m3 model, and gets thrown at Vectorize to pull back the top eight matches. Those chunk IDs are used to fetch full content from D1, joined against the posts table for metadata. Then it dedupes — at most one chunk per article — down to five sources total. Now here's the fallback: if vector search comes back empty-handed, it drops to a plain SQLite keyword search, a LIKE query, weighted across title, summary, and content. Finally, everything gets assembled into a prompt and streamed through qwen-14b back to the user.
So how do these two routes really differ? Let me lay it out as a head-to-head.
On index structure: PageIndex builds a hierarchical tree of sections and summaries; this blog uses flat paragraph chunks. On the vector database: PageIndex needs none at all; the blog leans entirely on Cloudflare Vectorize. On the retrieval mechanism: PageIndex uses an LLM agent making tool calls; the blog uses raw cosine similarity. PageIndex has no fallback — reasoning *is* the whole path — whereas the blog falls back to keyword search. PageIndex preserves the document's section hierarchy completely; the blog loses all structure the moment it chunks. PageIndex is built for long documents like financial reports; the blog is tuned for short posts. PageIndex supports full multi-turn conversation and gives you a traceable reasoning path you can audit; the blog is single-turn, and honestly, vector scores aren't very intuitive to explain. And the big tradeoff — cost. PageIndex is expensive, because every navigation step is another round of LLM reasoning. The blog's approach is cheap, running on Workers AI at the edge.
Now, the heart of all this — the claim that deserves real respect — is "similarity is not relevance." When you do vector search, you're really asking, "how semantically close is this text to my query?" But that's not the question you care about. The question you care about is, "can this text actually answer me?"
Here's the killer example. You ask for a company's 2023 EBITDA. Vector search happily recalls every passage that *mentions* EBITDA — the methodology section, historical comparisons, accounting-standard explanations. All semantically close. But the one thing you need is a single line of numbers buried on page twelve of the report. And if that line got split into a different chunk from its surrounding context, vector search just... misses it. PageIndex's reasoning, on the other hand, knows the EBITDA figure only lives in the income statement, and it walks straight there.
But — and this is the crucial caveat — that advantage has a prerequisite: the document needs structure. Financial reports, legal contracts, technical manuals — clear sections, clear hierarchy. A tree index shines there. For short blog posts like the ones on this site, the paragraphs aren't structurally that different from each other, and plain vector similarity is already a perfectly good proxy.
So the takeaways. First: these two architectures aren't competing — they're answering different questions. PageIndex is for long, structured documents where you need exact facts and you can afford GPT-4o-level inference cost and you want to trace which page an answer came from. Vector RAG is for semantic search across big piles of short, structurally-flat documents where you need low latency and cheap edge deployment — blogs, news, notes.
Second: for this blog's actual job — searching short posts — the simple vector approach is a sound, sensible tradeoff. No need to over-engineer it.
And third, the idea worth stealing even if you never adopt the full architecture: PageIndex suggests embedding a combination of *title plus summary plus chunk*, rather than embedding the bare chunk text alone. That small change gives each vector more context to anchor on — and that's a borrowable win for almost any RAG system, including this one.
🇹🇼 中文
RAG 這個詞,現在幾乎已經跟「向量資料庫加語意搜尋」劃上等號了。但有一個叫 PageIndex 的專案,提出了一個很有意思的反命題:向量的相似度,其實不等於相關性。與其用 embedding 去找跟問題「最像」的段落,不如讓大型語言模型直接去推理——答案到底藏在哪裡。今天就來拆解 PageIndex 的架構,順便跟我們這個站實際在用的 Hybrid RAG 做個完整對照。
先講 PageIndex 的核心想法。它做的事情,是把一份文件索引成一棵「階層樹」,你可以想像成一份目錄。然後讓一個 LLM Agent 透過工具呼叫,去導航這棵樹,而不是一口氣把所有切碎的片段全部丟進向量空間。
建索引的階段是這樣的。你給它一份 PDF 或 Markdown,它會吐出一棵 JSON 樹狀結構。樹上的每一個節點,都記錄了幾樣東西:這一章的標題、它對應的頁碼範圍、由 LLM 生成的一段摘要,還有底下的子節點。舉個例子,最頂層可能是「財務報表分析」,底下掛著「損益表」「資產負債表」這些子節點,每個都標好了從第幾頁到第幾頁。預設每個節點上限是十頁、或兩萬個 token,而且它還能自動從文件前二十頁去偵測有沒有現成的目錄。
到了查詢階段,這個 LLM Agent 手上有三把工具。第一把,拿文件的基本資訊,像是頁數、描述。第二把,拿完整的樹狀結構——注意,這裡只有各章節的摘要,沒有全文。第三把,才是去拿指定頁碼的實際內容,比如說「給我第五到第七頁」。Agent 的系統提示會強制它照順序走:先看文件結構、再定位相關的節點、然後只拿必要頁碼的內容、最後回答。這整套,其實就是在模擬一個人類專家翻書找答案的模式。
舉個實際的流程:使用者問「2023 年的毛利率是多少?」Agent 先呼叫拿結構,看到各章節的摘要,判斷答案應該在損益表那一段,於是去拿第五到第十頁的內容,最後回答「毛利率 38%,出自第七頁損益表」。它甚至能告訴你答案出自第幾頁。
效果怎麼樣?PageIndex 在 FinanceBench——這是一個財務文件問答的基準——拿到了 98.7% 的準確率,大幅超越傳統的向量 RAG。這個場景有幾個特性很關鍵:文件結構固定、答案需要精確數字、而且片段一旦切壞就直接導致錯誤。這專案打著「無向量、推理式 RAG」的旗號,GitHub 上有三萬三千顆星,更新很活躍,還推出了檔案系統版本,把同樣的推理檢索從單一文件擴展到整個語料庫。
好,那我們這個站是怎麼做的?Engineer News 走的是另一條路:以向量搜尋為主、關鍵字搜尋為輔,而且全部跑在 Cloudflare 的邊緣節點上。
建索引的流程很直接。一篇 Markdown 文章進來,先按照雙換行切成段落片段,每段上限一千個字元;然後用 bge-m3 這個模型把每個片段做成 1024 維的向量;存進 Cloudflare Vectorize,用餘弦相似度;片段的中介資料則存在 D1 這個 SQLite 裡。切法本身沒什麼花俏,就是逐段累加,超過長度就斷開、開新的一塊。
查詢的時候,使用者的問題先被 embed 成向量,丟進 Vectorize 撈出最相近的前八個片段,再回 D1 去 JOIN 文章資料拿完整內容。這裡有個去重邏輯:同一篇文章最多只留一個片段,最後總共保留五篇來源。如果向量這條路一個結果都沒撈到,就會 fallback 到 SQLite 的 LIKE 關鍵字查詢,按照標題、摘要、內文加權排序。最後組好 prompt 跟來源,丟給 qwen1.5-14b 串流生成回答。
那這兩條路線,核心差異到底在哪?我幫你整理幾個最關鍵的對照。索引結構上,PageIndex 是階層樹,保留了完整的章節層級;我們是平坦的段落片段,切碎之後結構就丟失了。向量資料庫,PageIndex 根本不需要,我們則重度依賴 Vectorize。檢索機制,一邊是 LLM Agent 工具呼叫,一邊是向量餘弦相似度。Fallback 部分,PageIndex 沒有,因為推理本身就是主路徑;我們則有 SQLite 關鍵字兜底。長文件支援,那是 PageIndex 的設計核心,財報那種;我們主打的是短篇 blog。可解釋性上,PageIndex 的推理路徑可以一步步追蹤,我們的向量分數說實話不太直觀。代價呢?PageIndex 推理成本高,要多次 LLM 來回導航;我們跑在 Workers AI 上,成本低、延遲低。
接下來聊聊那個最值得認真對待的命題:「相似度不等於相關性」。向量相似度,本質上是在問「這段文字,語意上跟查詢有多接近?」但你真正想問的,其實是「這段文字,到底能不能回答這個問題?」這兩件事不一樣。
最典型的失敗案例:你查「公司 2023 年的 EBITDA」。向量可能把所有提到 EBITDA 的段落全召回來——方法論介紹、歷史比較、會計準則說明,通通有份。但真正有答案的,只有財報第十二頁那一行數字。更糟的是,如果那一行剛好跟前後文被切在不同的片段裡,向量搜尋就直接失手了。而 PageIndex 的 LLM 推理能理解「損益表那一章才會有 EBITDA 的計算結果」,然後直接導航過去。
但是,這個優勢有個前提——文件要有結構。對財報、法律文件、技術手冊這類章節分明的東西,樹狀索引效果非常顯著。可是對我們這種短篇 blog,段落之間的結構差異沒那麼大,向量相似度作為一個近似的代理,其實已經夠用了。
所以結論是,這兩種架構,根本是在回答不同的問題。PageIndex 適合的是:需要精確數字或特定事實的長文件、結構清晰的財報法律手冊、可以接受 GPT-4o 等級推理成本、而且需要追蹤「答案出自第幾頁」的場景。Hybrid 向量 RAG 適合的是:大量短文件的語意搜尋、需要低延遲、部署在邊緣或資源受限的環境、文件語意豐富但結構扁平,像 blog、新聞、筆記這種。
我們站上現在的實作,在 blog 搜尋這個場景下,是個合理的取捨。但如果未來要支援搜尋 PDF 報告、或者長篇技術文件,PageIndex 的推理路線就真的值得認真考慮了。
最後幫你收斂三個重點。第一,PageIndex 賭的是「LLM 推理導航勝過向量相似度」,靠的是階層樹加 Agent 工具呼叫,在結構化長文件上拿到了 98.7% 這種亮眼數字。第二,相似度不等於相關性,這個命題是真的成立,但它的威力高度依賴文件本身有沒有清楚的結構。第三,沒有哪一種架構是絕對的贏家——長文件、要精確、要可追蹤,就走推理式;短文件、要低延遲、跑在邊緣,向量式反而更務實。選型的關鍵,從來不是哪個技術比較潮,而是你的文件長什麼樣子。
Tags
Related Articles
RAG's Five Stages: From Pipeline to Reasoning Retrieval, and the Naive RAG on My Own Site
Over the past two years RAG evolved from a 'linear pipeline' to 'loop-based reasoning'. It maps cleanly to five stages: Naive, Advanced, Modular, Graph, Agentic. The real inflection point is control moving from pipeline to agent — a System 1 → System 2 shift. Looking back at engineer-news's own RAG stack, it's stuck at the Naive edge — so this post also lays out what to fix next.
Building a Real RAG: 5 Infra Lessons from InfiniFlow's 2024 Year-in-Review
The previous post zoomed out for a five-stage panorama of RAG. This one zooms in on the five infra lessons any real RAG has to face: document ingestion, contextualized chunking, three-lane hybrid search, tensor reranker, and GraphRAG's semantic gap. Each lesson is checked against engineer-news's current stack, ending with a priority list for a personal site.
Is Claude Code's On-Demand Loading of Skills/Tools a Form of RAG? Unpacking Agentic Retrieval
When an agent loads tools on demand, it's essentially applying RAG's 'retrieve-then-inject' pattern to tool schemas — the only difference is the retriever is the LLM's own reasoning instead of vector similarity.