GraphRAG uses five LLM indexing stages. We use one extraction pass.
One LLM pass extracts scholarly terms and scores how informative each passage is. LLM-reconstructed semantic chunks make co-occurrences less noisy, while agents resolve meaning from evidence at query time and no glossary definition is committed.

Ask a model what "resilience" means and you get a competent paragraph that belongs to no field in particular. Ask the forty papers in a researcher's library and you get an ecology sense, a psychology sense, and a disaster-studies sense, sometimes inside the same literature review. An agent that treats a term as a stable string will blend those senses into a synthesis with honest citations and a wrong argument.
Our terminology graph spends its one LLM pass on the part where semantic judgment matters most: extracting scholarly headwords and assigning each occurrence an information score. A passage that defines or argues about a term ranks above a table that merely lists it. The graph then reduces those scored occurrences into nodes and co-occurrence edges without another model call.
Co-occurrence is only as meaningful as the window being counted. Fixed-size sliding windows can join unrelated paragraphs and split one argument in half, so the graph inherits arbitrary edges from arbitrary boundaries. It is garbage in, garbage out with a graph attached. Our graph instead inherits chunks reconstructed during PDF ingestion by an LLM for semantic cohesion, so an edge is more likely to reflect one actual unit of discussion than accidental token proximity.
We also keep meaning out of the index. At query time, an agent receives the highest-information passages for a term, its strongest neighboring terms, and the passages behind those relationships. That gives the model the corpus evidence it needs to disambiguate the term for the live question. The same evidence could support a glossary definition, but we deliberately do not commit one because a frozen definition would turn one indexing-time interpretation into the answer to every later question.
The fashionable fix for corpus-level meaning in 2026 is GraphRAG. This post is about why we built something much narrower instead. The interesting design question is not the graph itself. It is deciding which stage deserves an LLM, which structure makes co-occurrence trustworthy, and which interpretation must wait until a real question exists.
The failure mode is conflation, and it ships with honest citations
Scholarly vocabulary is local, historical, and contested. Terms drift across subfields, split into narrower concepts over time, and travel under different names in parallel communities. Polysemy is the sharp end of this. When a corpus uses one word in two senses and the retrieval layer cannot tell them apart, the result is not a visible error. It is a fluent paragraph that no author in the corpus would endorse, with real page numbers attached.
What a researcher needs against this is something like a corpus-wide glossary: for any term, what does this specific library say about it, and which concepts does it discuss it alongside.
The tempting implementation is to precompute that glossary. Extract the terms, have a model write a definition for each, store the definitions. This is wrong in a subtle way. The right sense of a contested term depends on the question being asked, and a definition frozen at index time answers a question nobody has asked yet. Whatever the indexing model believed about the term becomes the committed truth, and every later query inherits it.
So the requirement that shaped our design is: store everything needed to build a corpus-wide glossary, but never commit the glossary itself. Keep the evidence inspectable, and resolve meaning at query time in the context of an actual question.
What GraphRAG charges for, stage by stage
GraphRAG is the obvious prior art here, so it is worth being precise about what it does. Per Microsoft's own pipeline documentation, the standard method uses an LLM for five separate indexing tasks: extracting entities with written descriptions from each text unit, describing the relationship between each pair of entities in each text unit, summarizing every entity's descriptions across the corpus into one entry, doing the same for every relationship, and finally generating a narrative report for each community that the Leiden algorithm finds in the resulting graph. A sixth LLM task, claim extraction, is optional.
That machinery is genuinely capable. It answers global questions like "what are the major themes across these documents," which nothing in this post can do. But the invoice is proportional to the machinery. Microsoft's LazyGraphRAG announcement states that full GraphRAG indexing costs about 1,000 times a plain vector index. Practitioner write-ups on production Graph RAG deployments put a 500-page corpus at roughly 45 minutes and $50 to $200 at GPT-4-class pricing, and Microsoft estimates graph extraction alone at about 75 percent of indexing cost. The bill is front-loaded: you pay for the entire graph, including every edge nobody will ever query, before the first question is asked.
The maintenance profile follows from the same structure. Adding documents means re-extracting entities, resolving them against the existing graph ("Silicon Valley Bank" versus "SVB"), and re-running community detection, which cascades into regenerating the community summaries above it.
And there is a subtler property that bothered me more than the cost. The entity descriptions, relationship descriptions, and community reports are prose that the indexing model wrote. They are interpretations, committed before anyone has asked a question, and they start going stale the moment the corpus changes. When a query is answered from a community summary, it is answered from the indexing model's opinion about the corpus, not from the corpus.
Microsoft's cheap variants give up extraction quality first
Microsoft saw the cost problem and shipped two answers, and both are instructive because of what they chose to cut.
FastGraphRAG replaces LLM entity extraction with NLP noun-phrase extraction using NLTK or spaCy, and replaces described relationships with plain text-unit co-occurrence. LazyGraphRAG goes further in the same direction: noun-phrase concepts, co-occurrence edges, and all LLM work deferred to query time, for indexing costs identical to vector RAG.
Microsoft's own documentation is candid about the trade: with heuristic extraction "the graph tends to be quite a bit noisier," and they recommend the full pipeline whenever "high fidelity entities and graph exploration are important to your use case." There is also a telling implementation note. FastGraphRAG shrinks its text chunks to 50 to 100 tokens because smaller windows produce "a better co-occurrence graph." When your chunks are arbitrary slices, co-occurrence inside them means little, and the only fix available is making the slices tiny.
Lay the three pipelines side by side and the shape of the choice becomes visible:
| Indexing stage | GraphRAG standard | FastGraphRAG / Lazy | Ours |
|---|---|---|---|
| Term extraction | LLM, with written descriptions | NLP noun phrases | LLM, headwords with an information score |
| Relationship extraction | LLM, described entity pairs | chunk co-occurrence | chunk co-occurrence |
| Entity and relationship summaries | LLM | none | none |
| Community detection and reports | Leiden, LLM reports | Leiden, LLM reports | none |
| Committed generated prose | descriptions and reports | reports | none |
The market offers term quality and committed interpretation as a bundle: pay 1,000x and get both, or pay vector-index prices and get neither. Those are two independent knobs, and the useful configuration is high extraction quality with zero committed interpretation. Extraction is where the quality of everything downstream is decided. Interpretation is the part that goes stale. So we spent the LLM on the first and refused to store any of the second.
The one LLM pass extracts headwords and scores their evidence
Term extraction in our pipeline runs on gpt-5.4-mini at temperature 0 with low reasoning effort, over batches of five chunks with six batches in flight. It is the only LLM call in the entire build, so its instruction set is doing all the semantic work, and it is worth showing what an LLM buys you here that no noun-phrase extractor can.
The prompt's core test is that a term is a headword, the kind of phrase that would be alphabetized in a domain handbook's index, and that scholarship treats as a subject worth debating. Straight from the instructions:
"Bayesian inference" is a headword. "Results of Bayesian inference" is not.
A part-of-speech tagger cannot make that distinction because both phrases are syntactically valid noun phrases. TF-IDF and C-value go further: they are heuristics for semantic importance, using frequency, specificity, phrase length, and nesting as proxies for whether a candidate is a meaningful domain term. Those proxies are useful, but they remain indirect. The LLM can read the phrase in context and apply the semantic test directly: does this name a research concept that belongs in a domain handbook's index, or does it merely describe a result involving one? Its answer is still a model judgment, not ground truth, but it is based on the phrase's meaning in the passage rather than statistical signals correlated with meaning.
The same is true of the prompt's other rules. Ambiguous identifiers get disambiguated with their host context, so "Building 338" is emitted as "Megiddo Building 338," because every excavation has a Building 338 and a graph that merges them across sites is corrupted at birth. Terms are emitted in canonical singular form, in the language of the source chunk, with qualifiers split from their objects: "Neo-Assyrian cylinder seal" yields "Neo-Assyrian" and "cylinder seal" as separate headwords, while established compound names like "Type Ia supernova" stay intact. Generic academic vocabulary ("study," "method," "framework"), author surnames, and place names that are merely setting all get refused. This is canonicalization done at extraction time by a model that understands the text.
Each term also carries an information score from 0 to 1, estimating how much this chunk would contribute to a reference entry about the term. A term in a bare list or table scores near zero. A term the passage defines or argues about scores high. That one number is what later lets both the UI and the agent rank substantive discussions above passing mentions.
The pass is also incremental by construction. Extraction writes a per-document artifact next to the chunks it came from, and the build only extracts documents that lack one:
Add ten papers to a two-hundred-paper knowledge base and rebuild, and you pay for ten. There is no cascade, because nothing downstream of extraction involves a model.
An edge is only as strong as the window it was counted in
Here is the part that I think carries more of the quality than anything else, and it is decided before terminology extraction even runs.
Co-occurrence is a claim about a window: these two terms appeared within the same span of text. If the window is an arbitrary fixed-size slice, the claim is weak. Two terms can share a 1,000-token window because a section ended and another began, and FastGraphRAG's retreat to tiny chunks is the honest admission of this problem.
Our windows are not slices. During PDF ingestion, an LLM classifies every OCR block and reconstructs the document's narrative: it discards headers, footers, and bibliography, and groups blocks that form one paragraph, including paragraphs split across pages with captions wedged in between. The grouping prompt is explicit that "the goal is semantic cohesion, not larger chunks." Each reconstructed chunk then gets a short generated context that restores what the chunk lost when it was cut loose from the document, the antecedents and definitions it silently depends on.
A chunk in this pipeline is one argumentative unit, so an edge means two terms took part in the same actual discussion. When "resilience" and "socio-ecological system" share a chunk here, they shared a paragraph that an author wrote as one thought. That is a categorically better signal than sharing a window, and it is why we can keep chunks at natural paragraph size instead of shrinking them until co-occurrence stops lying.
The extraction prompt sees both the chunk text and its restored context, and is instructed to use the context only for disambiguation, never as a source of terms. Terms are extracted from where the evidence actually is, so every occurrence can later be shown to a human.
An agent resolves meaning from evidence at query time
The glossary this produces is the uncommitted kind the requirement asked for.
On the human side, an explorer renders a two-hop neighborhood around any term, because the full graph of a real corpus is an unreadable hairball. Click a node and you get its top supporting passages ranked by information score, substantive discussions first. Click an edge and you get the passages where both terms appear together. Every passage is a real citation resolving to the source document at the exact page.
On the agent side, the same artifacts back a lookup tool the research agent can call with up to four terms while classifying a request or performing retrieval. What comes back per term is evidence, not a definition: the top supporting passages, the three strongest co-occurring neighbors with few passages each, and the resolved citation bodies. No pre-baked definition exists. The agent reads the passages and resolves what the term means in the context of the question it is actually answering. The same term, the same graph, resolved differently depending on the live query, which is precisely what a committed definition can never do. Plus, as models improve, the definition in context will improve too.
This is, I will happily admit, the same insight LazyGraphRAG is built on: defer the model's meaning-making to query time, where the question exists. We planted the flag at the other end of the extraction trade-off. They deferred the LLM and accepted noisy noun-phrase terms. We deferred the interpretation and kept the LLM at the one stage where its editorial judgment is irreplaceable.
Typed edges can turn one author's claim into a corpus-level fact
Publication does not make every assertion a fact. One author may argue that X causes Y, another may challenge the evidence, and a third may reject the relationship as a category error. Materializing X causes Y as a typed edge at index time can erase that attribution and make one author's position look like a property of the corpus.
Typed relationship graphs are useful when the relation is stable and the task is to materialize it. They are a poor default across many fields in the humanities and social sciences, where the relationship itself may be disputed, historically situated, or dependent on a theoretical frame. Forcing an interpretive edge into the index creates a precision the literature does not support.
Agent Bayes keeps that interpretation open. The terminology graph records that terms occurred in the same semantic passage and keeps the route back to the source, without declaring why they are connected. Corpus-wide questions go through multi-query retrieval and agent synthesis, where claims can be attributed, compared, questioned, and interpreted in context. Sources remain views to reason over, not facts for the system to inherit.
The recipe is the whole post: extract headwords per chunk with an LLM and an opinionated instruction set, make sure the chunks are semantic units rather than slices, reduce co-occurrence deterministically while keeping chunk IDs on every edge, and never store a definition. The Terminology Graph Explorer in Agent Bayes is our version, built per knowledge base once ten documents are indexed. The test I would apply to any implementation, including ours: click the weirdest edge in the graph and see whether it can show you the sentences that put it there.
New posts, straight to your inbox
No newsletter fluff, just an email when we publish something new.
Email me when a new post is published on the Agent Bayes blog. You can unsubscribe anytime. We'll first send a confirmation email, and we only use your details for this. See our Privacy Policy.