01 · The basicsThe two questions plain RAG can't answer

Normal RAG (Retrieval-Augmented Generation) is by now a well-worn recipe: chop your documents into flat chunks, embed each one into a vector, and at query time fetch the handful of chunks that sit closest to the question. It's cheap, fast, and genuinely good at one thing — "find me the passage that answers this."

But it quietly falls apart on two kinds of questions. The first is the big-picture question — "what are the main themes across all of these reports?" — where no single chunk holds the answer. The second is the connect-the-dots question, where the answer is scattered across pieces that don't individually look like the query. RAPTOR and GraphRAG are two well-known answers to exactly this, and they fix it in very different ways. This post walks through both — plainly, and step by step.

I'll assume you know the basics of chunking and retrieval already — if not, I wrote separate posts on chunking and retrieval accuracy first.

02 · Where it breaksFlat chunks lose the forest for the trees

Here's the whole problem in one picture. Plain vector search embeds each chunk once and returns the ones nearest the query. Ask it something specific and it shines. Ask it something that spans the corpus, and it hands back a few disconnected fragments.

a big-picture query
"What are the main themes across Nimbus's incident reports?"
embed →
top-k by cosine
5 nearest chunks — all from one report
1"…the March outage lasted 3 hours…"
2"…affected EU customers only…"
3"…incident postmortem, page 4…"
4"…the March outage, again…"
5"…March, one more time…"

The word "themes" doesn't point at any one chunk, so cosine similarity just grabs the five that happen to say "incident" the loudest — likely all from the same noisy report — and the model answers as if that one report were the whole story. Two failure modes are hiding here, and each technique targets one:

1 · Big-picture / summarization. "What are the themes?", "summarize everything about billing." The answer is a synthesis of many chunks, but top-k only returns a few — so you get a keyhole view. RAPTOR tackles this by pre-computing summaries at every level.

2 · Connect-the-dots / multi-hop. "Which incidents share a root cause?" needs to link facts from different documents that never mention each other. Similarity can't follow a chain of relationships. GraphRAG solves this by making those relationships explicit as a graph.

Let's start with the smaller leap.

Technique 1 · the smaller leap
RAPTOR — a tree of summaries

RAPTOR stays inside the world you already know — chunks and embeddings — and adds one thing on top: a tree of summaries. The insight is simple. If big-picture questions fail because there's no chunk that summarizes the whole, then build those summaries ahead of time — and not just one, but a whole hierarchy, from tight local summaries up to a bird's-eye view of everything.

The name spells out the recipe: Recursive Abstractive Processing for Tree-Organized Retrieval. You recursively embed, cluster, and summarize your chunks into a tree, then retrieve from every level of it at once. It's from Sarthi et al. at Stanford (2024), with an official implementation. Here's how the tree gets built:

RAPTOR · BUILDING THE TREE
the corpus
Nimbus — reports & docs
Incident postmortems, billing policy, security whitepaper, product manuals…
chunk +
embed
leaf nodes · one vector each
X-200 boot fix X-200 specs billing = usage
refund policy encryption SSO & roles

1Start exactly like normal RAG: cut the corpus into small chunks (~100 tokens each) and embed them. These become the leaf nodes — the bottom layer of the tree. Nothing new yet.

leaf vectors
hundreds of numbers each
UMAP ·
shrink the
dimensions
GMM ·
soft-cluster
clusters — a leaf can join two
cluster 1 cluster 2 in both

2Cluster the leaves — the careful part. Embeddings have hundreds of dimensions, where distances get unreliable, so RAPTOR first shrinks them with UMAP, then runs a Gaussian Mixture Model for soft clustering: a leaf can belong to more than one cluster (the ringed dot), and the number of clusters is chosen automatically. Related passages group up no matter which document they came from.

the clusters from step 2
X-200 boot fix X-200 specs
billing = usage refund policy
encryption SSO & roles
LLM
Summarize this group of related passages into one short paragraph.
one summary per cluster → parent nodes
Hardware: X-200 specs & boot troubleshooting
Billing: usage-based, with refunds on early cancel
Security: encryption + access control

3Summarize each cluster. Hand every cluster to an LLM to write one short summary. Each summary becomes a new parent node — a single node that stands in for its whole cluster of leaves.

Level 2 · root
Nimbus at a glance: a cloud product with its billing model and security posture
re-embed & summarize again
Level 1 · summaries
Hardware: X-200 specs & boot fixes Billing: usage-based + refunds Security: encryption + access
cluster & summarize
Level 0 · leaves (original chunks)
X-200 boot fix X-200 specs billing = usage refund policy encryption SSO & roles

4Now recurse. Treat those summaries as a new layer, re-embed them, cluster and summarize again — and keep going, layer by layer, until the top is down to a handful of high-level summaries (RAPTOR caps this at a few layers). The result is a tree: detailed at the bottom, ever more abstract toward the top.

query
"Summarize how Nimbus billing works"
collapse the tree → one flat pool of every node
rank all nodes
by similarity
retrieve top nodes up to a token budget
Billing: usage-based + refunds refund policy Nimbus at a glance X-200 boot fix encryption X-200 specs

5Retrieval — the "collapsed tree". Flatten every node (leaves and summaries) into one pool and rank them all by cosine similarity to the query, taking the top ones up to a token budget. A billing question pulls the billing summary directly — one node that already synthesizes the whole topic — instead of scattered fragments. RAPTOR tested this against a top-down tree traversal (walk the tree layer by layer), and the simple collapsed pool won — so it's the default.

The payoff: because summaries live right alongside the raw chunks in the same index, a broad question retrieves a ready-made synthesis, while a narrow question still finds its exact leaf. It's an elegant fix for the big-picture problem, and it's cheap to bolt onto an existing vector pipeline — same embeddings, same retrieval, just extra summary nodes. In the paper, paired with GPT-4, it pushed the best QuALITY benchmark score up by about 20 absolute points (to 82.6%).

A related idea worth a link. The LLM wiki shares RAPTOR's core move. There, a model compiles wiki pages that synthesize your sources — and if you drop those generated pages into the same vector DB as the original source chunks and retrieve flat over both, you get something in RAPTOR's spirit: a synthesized, higher-level layer sitting right next to the raw detail in one flat index, so a broad question lands on a wiki page while a narrow one still finds its source chunk. The difference is how that layer is organized — RAPTOR builds a strict bottom-up hierarchy by recursive clustering, whereas a wiki is arranged by entity and topic with cross-links and updated incrementally rather than rebuilt.

The catches are real, though. Every summary is one more LLM call at indexing time, and summaries can smooth over or drop the very detail you needed — a game of telephone up the tree. And it's still fundamentally similarity retrieval: RAPTOR can summarize a topic beautifully, but it doesn't model how two specific entities relate. That's exactly the gap the next technique fills.

Technique 2 · the bigger leap
GraphRAG — a map of who relates to whom

GraphRAG (from Microsoft Research, 2024, with a widely-used open-source library) makes a bigger bet. Instead of storing text and matching on similarity, it reads the whole corpus and builds an explicit knowledge graph: the entities (people, products, places, events) become nodes, and the relationships between them become edges. Once the facts are wired together, you can answer questions by traversing the connections, not just by fuzzy-matching words.

The subtitle of the paper — "From Local to Global" — is the whole idea. There are two phases: an expensive indexing phase that builds the graph and pre-summarizes it, and a query phase that reads from it. Let's build it first.

Phase 1 · Building the graph

GRAPHRAG · INDEXING
the corpus · reports & docs
March postmortem
On Tuesday the X-200 service in the EU region went down for 3 hours after a TLS certificate expired. The Payments team later issued refunds to affected accounts…
split into
chunks
chunks · a few hundred tokens each
"…X-200 in the EU region went down 3 hours after a TLS cert expired…"
"…Payments team issued refunds to affected accounts…"
"…usage-based billing, refunds on early cancel…"

1Chunk the corpus. Exactly like any RAG pipeline, split every document into bite-size chunks. GraphRAG keeps these around — they're the raw text the next step reads, and later what local search retrieves — but on their own they're still just flat text, with no structure yet.

one chunk of a report
March postmortem
On Tuesday the X-200 service in the EU region went down for 3 hours after a TLS certificate expired.
LLM
Extract entities and the relationships between them. Return them as a list.
extracted entities + relationships
entities: X-200 (product), EU region (location), TLS cert (component), Tue outage (event) rels: outage —hit→ EU region outage —took down→ X-200 outage —caused by→ TLS cert from: chunk #5 ← link kept on each

2Extract entities & relationships. An LLM reads every chunk and pulls out the entities and the relationships connecting them — turning free text into structured (node)—[relationship]→(node) triples. It also records the chunk each item came from (a text_unit_ids link), so local search can fetch the raw text back later. This is the expensive step: one LLM pass over the whole corpus.

w:2 w:3 X-200 EU region Tue outage TLS cert Payments team Billing Refunds

3Merge into one graph. Stitch every chunk's triples together: the same entity across ten chunks collapses to one node, and repeated links between the same pair collapse to one edge. Two things happen on merge — the edge's weight is simply a count of how many times that pair co-occurred (no LLM strength score; each co-occurrence just adds 1), and however differently each mention described the link, all those descriptions are handed to the LLM and summarized into one. So the whole corpus becomes a single connected map of facts.

entity "X-200" · a description from each mention
chunk 3: "Nimbus's flagship compute service"
chunk 7: "runs in the EU region"
chunk 12: "hit by the March outage"
LLM
Merge these into one description of the entity.
one entity · consolidated, then embedded
X-200 · product
Nimbus's flagship compute service, running in the EU region; hit by the March outage.
embed name + description →

4Describe & embed each entity. The extractor didn't just name each entity — it also wrote a short description of it from every chunk it appeared in. On merge, those descriptions are fused into one, and then the entity's name + description is embedded into a vector. That vector is exactly what local search matches the query against — this is where each entity gets its embedding.

X-200 EU region Tue outage TLS cert Payments team Billing Refunds
Community A · Infrastructure Community B · Billing

5Find communities. A clustering algorithm (GraphRAG uses Leiden) partitions the graph into communities — clumps of nodes densely wired to each other but loosely to the rest. Here the infrastructure facts fall into one community, the billing facts into another. On a real corpus this is hierarchical: communities nest inside bigger communities.

input: the community's element descriptions
entity · Tue outage: "a 3-hour EU outage…"
entity · TLS cert: "certificate that expired…"
rel · outage —caused by→ TLS cert
✗ not the raw source chunks
LLM
Write a report from these entity & relationship descriptions.
community reports
REPORT · INFRASTRUCTURE
The X-200 service runs in the EU region. An expired TLS certificate caused a 3-hour outage there.
REPORT · BILLING
The Payments team owns usage-based billing, which includes a refund policy.

6Summarize each community. An LLM writes a community report for each one — but note what it's fed: the descriptions of that community's entities and relationships (plus any claims), added in order of prominence until a token budget, not the original source chunks. If a community is too big to fit, GraphRAG substitutes its sub-community reports for the detail — that's the bottom-up part. These reports become the pre-computed "global" knowledge the query phase reads from.

That's the index: a graph of entities and relationships, carved into communities, each with its own summary. It's a lot of machinery — but notice what you now have that flat chunks never did: an explicit statement that the outage was caused by the TLS cert and hit the EU region, ready to be traversed.

One honest limitation: entity resolution. That merge back in step 3 is only an exact-name match, so the same real thing written slightly differently across documents — "X-200", "Nimbus X-200", "the X-200 service" — can end up as separate nodes. The paper leans on resilience rather than fixing it: duplicates usually sit close in the graph and fall into the same community, so the community report co-summarizes them anyway (global search barely notices; local search, which matches a single node, is more exposed). Solving it properly means adding an entity-resolution step — cluster near-identical names by embedding or fuzzy similarity, or let an LLM judge which nodes are the same, then merge them — which GraphRAG treats as an optional layer to bolt on, not part of the base pipeline.

Phase 2 · Answering a global question

This is where "from local to global" pays off. For a broad, whole-corpus question, GraphRAG doesn't search for chunks at all — it runs a map-reduce over the community reports.

GRAPHRAG · GLOBAL SEARCH (MAP-REDUCE)
the question
"What are the main themes across Nimbus's reports?"
LLM
Using this one community report, list points that help answer the question. Score each 0–100 for relevance.
run once per community → rated key points
INFRASTRUCTURE report
90reliability — an EU outage from an expired TLS cert
10X-200 hardware spec detail
BILLING report
85revenue ops — usage billing & refunds
15Payments team headcount

1Map — every community answers separately. The exact same question is run against each community report on its own, in parallel. Each run lists the points from its slice of the graph that help answer the question, and scores every point 0–100 for relevance — so off-topic bits (a hardware spec, a headcount) score near zero. Because every community is asked, nothing in the corpus is skipped.

all key points pooled, sorted by score
90reliability — EU outage from expired TLS cert
85revenue ops — usage billing & refunds
15Payments team headcount
10X-200 hardware spec detail
low scorers dropped ✗
LLM
Write one answer to the question using only these top-ranked points.
final answer
"Two themes dominate: service reliability — notably an EU outage from an expired TLS cert — and revenue operations, i.e. usage-based billing and refunds."

2Reduce — keep the best, write one answer. Pool the key points from all communities into a single list, sort by the map-step scores, and drop the low ones. Whatever survives (trimmed to fit the context window) goes to a final LLM call that writes it up as one coherent answer. Since the points came from every community, distinct themes all make it in — exactly what flat top-k missed.

Global search is GraphRAG's headline mode, but it's not the only one — and the other mode matters just as much. For a specific question, GraphRAG runs local search, and this is where the connect-the-dots, multi-hop reasoning actually happens.

Local search · connecting the dots

Global search sweeps every community; local search does the opposite — it zooms in. It finds the entities your question is about, gathers their immediate graph neighborhood plus the original text those entities came from, and answers from that tight bundle. Take "what caused Tuesday's outage?"

GRAPHRAG · LOCAL SEARCH
the question
"What caused Tuesday's outage?"
embed it
cosine similarity
vs. every entity's
embedding
all entities in the graph, ranked
0.91Tue outage
0.56X-200
0.52EU region
0.13Billing
0.09Payments team
top match → entry point ✓

1Find the entry entity. How does it know where to start? Every entity in the graph was stored with an embedding of its name and description. So local search just embeds the question and runs a nearest-neighbor search across the whole entity list — plain vector similarity, exactly like normal RAG, but over entities instead of chunks. The top match (or few) becomes the entry point; here Tue outage wins and the billing entities score too low to matter.

took down hit caused by Tue outage X-200 EU region TLS cert Payments Billing Refunds
its 1-hop neighbors + relationships, ranked
outage —caused by→ TLS cert
outage —hit→ EU region
outage —took down→ X-200
capped at ≈ top-k × #entry entities

2Walk one hop out — and stop. From each entry entity, local search grabs its directly connected neighbors and the relationships between them. That's the whole traversal — a single hop, not a deep crawl. The relationships are ranked (ones joining two entry entities first, then by how many entry entities they touch and their weight) and capped at a budget of about top-k × the number of entry entities, so the neighborhood stays bounded instead of exploding outward.

each selected entity
Tue outage
its stored link
text_unit_ids → chunk #5
follow the
provenance link
the raw, unstructured context
source chunk #5
"On Tuesday the X-200 service in the EU region went down for 3 hours after a TLS certificate expired."
+ INFRASTRUCTURE community report · claims

3Pull the original text back in. This is what separates local search from global: it doesn't answer from the graph alone. Using the text_unit_ids link saved at build time, it follows each selected entity back to the original source chunks it was extracted from and adds them to the context — along with the community reports those entities sit in, and any recorded claims. Structured graph facts and unstructured raw text, side by side.

one token budget (~12k), ranked & split by context type
source chunks · 50% — the raw text units, e.g. "…a TLS certificate expired…"
graph facts · 35% — matched entities + descriptions ("Tue outage: a 3-hr EU outage"), relationships ("outage —caused by→ TLS cert"), and any claims ("breached the 99.9% SLA")
community reports · 15% — the infrastructure report
LLM
"Tuesday's outage was caused by an expired TLS certificate. It hit the EU region and took the X-200 service down for 3 hours."

4Fit it to the window, then answer. Everything gathered is ranked and packed into one fixed token budget, split by type: the raw source chunks (the biggest slice), the graph facts — the matched entities with their descriptions, the relationships, and any claims — and the community reports. That bundle goes to the LLM, which answers by connecting facts the graph made explicit — something flat similarity search would never have retrieved together.

So which mode runs for a given question? The paper doesn't say — you just pick the method per call. But it's easy to automate with a simple intent-classification router (the same trick from the retrieval post): a small LLM classifier reads the question and sends whole-corpus themes to global, specific-entity questions to local. There's also DRIFT search, a hybrid that blends both so you needn't choose.

Vs. the LLM wiki, again. A quick contrast with the LLM wiki. On the build side, GraphRAG leans on vectors — it embeds entities, their descriptions, and community reports — whereas a wiki is just linked markdown, with no embedding of entities or communities required. On retrieval, GraphRAG follows a fixed recipe (entity match → 1-hop traversal → budgeted context), while the wiki leans on agentic reasoning: the model navigates pages and follows cross-links itself, deciding what to read next rather than walking the graph mechanically.

The strengths are unique: GraphRAG genuinely shines on global sensemaking and multi-hop questions, and its answers are traceable — you can point at the exact entities and edges behind them. Microsoft's paper reports clear wins over vanilla RAG on the comprehensiveness and diversity of answers to whole-corpus questions.

But the cost is the real catch, and it comes in a few flavors:

Which brings us to the honest comparison.

05 · Head to headThree ways to answer the same question

Let's put all three on the same big-picture question and watch them diverge — "What are the main themes across Nimbus's reports?"

SAME QUERY · THREE APPROACHES
Normal RAG
Embeds the query, grabs the 5 nearest chunks — all from the loudest report. Answers about the March outage as if it were the whole story.
keyhole view ✗

1Normal RAG. Top-k similarity has no notion of "the whole corpus." It returns a few near chunks, which cluster around one loud topic — so the summary is narrow and misses everything else.

Normal RAG
5 nearest chunks, one topic.
keyhole ✗
RAPTOR
The collapsed tree surfaces a mid-level summary node that already aggregates several reports — so the answer covers more ground, drawn from pre-built summaries.
decent synthesis ✓

2RAPTOR. Because summary nodes sit in the same index, the query lands on one that already blends multiple reports. A solid big-picture answer — though it's still whichever summary is most similar, so a theme with no close-matching summary can slip through.

Normal RAG
5 nearest chunks, one topic.
keyhole ✗
RAPTOR
One best-matching summary node.
decent ✓
GraphRAG
Map-reduce over every community report — reliability and revenue ops both surface, each traceable to its entities. Nothing is left unconsulted.
comprehensive ✓

3GraphRAG. Global search touches every community by construction, so distinct themes all make it into the answer — the most complete and diverse of the three. The price is the expensive graph you had to build first.

Normal RAG
Best at: pinpoint fact lookup. Weak at: anything that spans the corpus.
cheapest
RAPTOR
Best at: summarizing & multi-level questions over documents. Weak at: linking specific entities.
mid cost
GraphRAG
Best at: global sensemaking & multi-hop across sources. Weak at: cost, updates, simple lookups.
priciest

4The takeaway. There's no winner — they climb a ladder of power vs. cost. More structure buys better answers on hard, holistic questions, and charges you for it at indexing time. Match the tool to the question you actually get.

They stack, too. These aren't mutually exclusive with the basics — or each other. GraphRAG still chunks and embeds the source text underneath (its local search retrieves those chunks), and plenty of production systems run hybrid search as the default and route only the genuinely global questions to a RAPTOR or GraphRAG index. Start flat; add structure where you can name the question it's failing on.

06 · Wrapping upSo what should I actually reach for?

Both techniques exist for the same reason — flat chunks lose the big picture — but they buy it back differently: RAPTOR pre-computes summaries so no question lacks a synthesis; GraphRAG pre-computes relationships so no connection is invisible. Here's the cheat sheet:

ApproachAddsProsConsReach for it when…
Normal RAG flat chunks Cheap, fast, simple; instant to update; strong on pinpoint lookups. No big-picture; can't connect dots across documents. Questions are answerable by a single passage — most FAQ/support/doc search.
RAPTOR summary tree Big-picture answers at every abstraction level; cheap add-on to a vector pipeline; keeps detail leaves too. An LLM summary per cluster at index time; summaries can lose detail; still pure similarity, no explicit relations. Long documents and thematic/summarization questions — books, papers, reports.
GraphRAG knowledge graph Global sensemaking + multi-hop reasoning; traceable, explainable answers; consults the whole corpus. Very expensive to build (LLM per chunk + per community); slow to update; overkill & noisy on simple lookups; quality hinges on extraction. Whole-corpus "what are the themes / how does it all connect" questions over a large, static corpus.

My honest advice mirrors the last two posts: start with the cheapest thing that works. Hybrid search plus a reranker answers most questions, and it costs almost nothing. Only when you can point at the exact questions it's failing on — the sweeping "summarize everything" ones, or the "how do these connect" ones — is it worth paying for a summary tree or a knowledge graph. The structure is powerful, but you pay for it up front, every time the corpus changes.

The through-line across all of this: RAG quality is mostly about giving the model the right shape of context. Flat chunks are one shape. A tree of summaries is another. A graph of relationships is a third. Pick the shape your questions actually need.

Thanks for reading, and hope you got something out of it.