Standard retrieval-augmented generation finds text chunks that look like the question and hands them to a language model. That works well for questions whose answer sits in one or two passages. It struggles with questions about a whole collection, such as "what are the main risks raised across these 500 audit reports?" GraphRAG addresses this by building a knowledge graph from the documents first, then retrieving from the graph.
There is a catch. When an LLM builds the graph unsupervised, it chooses its own entity types and relationships, and the result is often inconsistent. Ontology-driven GraphRAG fixes that by giving the extraction step a defined model of what can exist in the graph. By the end you should be able to decide whether GraphRAG fits your corpus, and how to give its extraction step a schema. This article is part of a series that begins with ontology in data and AI.
How GraphRAG works
Microsoft Research introduced GraphRAG in a blog post in February 2024 and described it in the paper From Local to Global, first posted in April 2024 and revised in February 2025. The indexing pipeline has four main steps:
- Split the documents into text units. The paper used chunks of 600 tokens with 100 tokens of overlap.
- Extract entities, relationships and claims from each chunk with an LLM.
- Cluster the resulting graph into hierarchical communities of closely related entities, using the Leiden algorithm, a community detection method that groups densely connected nodes.
- Summarize each community with the LLM, bottom-up, so there are summaries at several levels from the broad root level down to small groups.
At query time there are several search modes. Global search answers whole-corpus questions by asking each community summary for a partial answer, scoring those answers for helpfulness, and combining the best. Local search starts from the entities mentioned in a question and pulls in their neighbourhood in the graph. Later versions added DRIFT search, which combines local search with community context, and a basic mode that is plain vector search, as described in the GraphRAG documentation.
What the paper found
The paper evaluated GraphRAG on two datasets of roughly 1 million and 1.7 million tokens: podcast transcripts and news articles. Using an LLM as the judge, GraphRAG's global search beat plain vector RAG on comprehensiveness in 72 to 83 percent of comparisons on the podcast data and 72 to 80 percent on news. It also won on diversity of answers. Answers built from the root-level community summaries used more than 97 percent fewer context tokens than summarizing the source text directly.
Those results come with a caveat: an LLM judge is not ground truth. The paper also ran claim-based checks, and they agreed with the judge's verdicts 78 percent of the time for comprehensiveness and about 70 percent for diversity. Treat the numbers as evidence that the approach helps on whole-corpus questions, not as a guaranteed improvement on your data.
Local and global questions: a worked example
Picture an insurer with 3,000 claim adjuster notes from the past year. Two kinds of question come up.
The first kind is local: "What did the adjuster conclude about claim 88213, and which repair shop was involved?" Plain vector search handles this well. The claim number appears in a handful of chunks, retrieval finds them, and the model reads the answer straight out of the text.
The second kind is global: "What are the most common causes of disputed water-damage claims this year, and which regions see them most?" No single chunk contains that answer. Vector search returns the ten passages that look most like the question, which might all come from one unusually wordy adjuster. The model then generalizes from a sample of ten, and the answer sounds confident and is mostly wrong.
A graph-based approach answers the global question differently. During indexing, each note contributes nodes for the claim, the cause, the region, the repair shop and the outcome. Community detection groups related claims, causes and regions, and each group gets a summary. At query time, global search asks every community summary at the chosen level for a partial answer and combines the most helpful ones, so the answer draws on the whole year rather than ten passages.
This is also where a fixed schema earns its keep. If one note records the cause as "burst pipe", another as "pipe failure" and a third as "plumbing leak", and the model creates three separate cause nodes, the summaries split the same pattern three ways and the most common cause looks less common than it is. Defining Cause as a type with a reference list of values, and resolving mentions to that list, fixes that. It also makes an exact answer possible: with consistent Cause nodes, a structured query over the graph can count disputed claims per cause and region, something LLM-written summaries can't do reliably on their own.
Where free-form entity types go wrong
Left to choose freely, an LLM extracting entities from thousands of chunks will produce a graph that looks impressive and queries badly. The common problems:
- Synonymous types.
Company,Organization,Organisation,FirmandBusinessappear as separate entity types. - Inconsistent relationships.
WORKS_AT,EMPLOYED_BYandIS_EMPLOYEE_OFall mean the same thing. - Direction errors. Half the acquisitions point from acquirer to target, half the other way.
- Over-extraction. Every noun phrase becomes a node, so the graph fills with low-value entities like "the meeting" or "last quarter".
- Duplicate entities. "Acme", "Acme Corp" and "ACME Corporation" become three nodes.
Each problem degrades retrieval. Community detection groups the wrong things together, local search misses neighbours connected by an unexpected relationship name, and any structured query over the graph undercounts. The fix for the first four is to define the allowed entity types and relationships up front, which is exactly what an ontology does. The fifth needs entity resolution, covered below.
What makes GraphRAG ontology-driven
Ontology-driven GraphRAG means the extraction step works from a defined schema rather than inventing one. The ontology supplies:
- Entity types with definitions, such as
Company,PersonandProduct. - Relationship types with names, such as
WORKS_FOR,MAKESandACQUIRED. - Patterns that say which entity types each relationship may connect: a
PersonWORKS_FORaCompany, never the reverse. - Rules you can check after extraction, such as "a product has exactly one manufacturer".
If you already have an OWL or RDFS ontology, you can generate the extraction schema from it, so the GraphRAG pipeline and the rest of your data platform share one definition. The snippet below reads a small ontology with RDFLib and turns it into the lists most knowledge graph builders accept.
import re
from rdflib import Graph
from rdflib.namespace import RDF, RDFS, OWL
ontology = Graph().parse(format="turtle", data="""
@prefix ex: <https://data.example.com/ontology#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
ex:Company a owl:Class ; rdfs:comment "A legal business entity." .
ex:Person a owl:Class ; rdfs:comment "An individual human." .
ex:Product a owl:Class ; rdfs:comment "A good or service a company sells." .
ex:worksFor a owl:ObjectProperty ; rdfs:domain ex:Person ; rdfs:range ex:Company .
ex:makes a owl:ObjectProperty ; rdfs:domain ex:Company ; rdfs:range ex:Product .
ex:acquired a owl:ObjectProperty ; rdfs:domain ex:Company ; rdfs:range ex:Company .
""")
def local(iri):
return str(iri).split("#")[-1]
def rel_name(iri):
# worksFor -> WORKS_FOR, the usual relationship style in property graphs
return re.sub(r"(?<!^)(?=[A-Z])", "_", local(iri)).upper()
node_types = sorted(
({"label": local(c), "description": str(ontology.value(c, RDFS.comment))}
for c in ontology.subjects(RDF.type, OWL.Class)),
key=lambda n: n["label"])
relationship_types = sorted(rel_name(p) for p in ontology.subjects(RDF.type, OWL.ObjectProperty))
patterns = sorted(
(local(ontology.value(p, RDFS.domain)), rel_name(p), local(ontology.value(p, RDFS.range)))
for p in ontology.subjects(RDF.type, OWL.ObjectProperty))
for n in node_types:
print("node:", n["label"], "-", n["description"])
print("relationships:", relationship_types)
for p in patterns:
print("pattern:", p)
Output (Python 3.13, RDFLib 7.6.0):
node: Company - A legal business entity.
node: Person - An individual human.
node: Product - A good or service a company sells.
relationships: ['ACQUIRED', 'MAKES', 'WORKS_FOR']
pattern: ('Company', 'ACQUIRED', 'Company')
pattern: ('Company', 'MAKES', 'Product')
pattern: ('Person', 'WORKS_FOR', 'Company')
The properties use the same ex:worksFor and ex:makes IRIs as the validation example in the LLM article, and rel_name() converts them to the UPPER_SNAKE style property graphs usually expect. The domain and range of each property become the allowed patterns, and the rdfs:comment of each class becomes the description the LLM sees in its prompt. Good descriptions matter more than people expect. "A legal business entity" helps the model decide that a government agency or a product brand is not a Company. One limitation: ontology.value() assumes each property has exactly one domain and one range; a property with several, or none, needs a loop and a fallback. The modelling choices behind this file are covered in how to build an ontology in Python with RDFLib.
Feeding the schema to a GraphRAG pipeline
Different tools accept the schema in different ways:
- Microsoft GraphRAG lets you set the entity types used in its extraction prompts. Its auto prompt tuning can adapt prompts to your domain, and it can also discover entity types automatically; with an ontology you fix the types instead.
- The neo4j-graphrag Python package has a
SimpleKGPipelinewhose schema takes node types, relationship types and patterns, which maps directly onto the output above. According to its knowledge graph builder guide, the default mode lets the LLM infer a schema; you can supply your own, and flags control whether extra types and patterns outside the schema are allowed. A pruning step removes nodes and relationships that do not fit. - The Neo4j LLM Knowledge Graph Builder, a Labs application, ingests PDFs, web pages, YouTube transcripts and cloud storage files, and accepts a configurable schema.
The Neo4j documentation describes the schema as a guiding structure for the language model. That is the right expectation. A schema in the prompt reduces drift; it does not eliminate it. Turn on enforcement or pruning where the tool offers it, and validate afterwards.
Validating the extracted knowledge graph
After extraction, check the knowledge graph against the ontology before it is used for retrieval. For RDF graphs, SHACL shapes do this directly. For property graphs, the same rules can be written as queries: find every WORKS_FOR edge whose source is not a Person, every Product with no MAKES edge pointing at it, and so on. The validation pattern for LLM output, with a runnable example, is in ontologies and LLMs.
Entity resolution: the step that decides quality
Even with fixed entity types, the same real-world thing appears under different names across documents. GraphRAG quality depends heavily on merging those nodes correctly. Neo4j's package includes fuzzy-matching and spaCy-based resolvers, according to its knowledge graph builder guide; other teams use embeddings plus an LLM to confirm matches.
The ontology helps here too. Entities can only be merged within the same type, which removes a large class of false matches. Canonical identifiers from a reference list, such as company registry numbers or product SKUs from your own systems, give the resolver something definitive to match against. If your structured data is already integrated through a shared ontology, as described in ontology-based data integration, the document-derived graph can link directly to those existing entities instead of creating new ones.
Is GraphRAG worth the cost?
Indexing is the expensive part. The GraphRAG repository warns that indexing can be an expensive operation and advises starting small. Every chunk needs at least one LLM call for extraction, and every community needs a summary. On a large corpus with a strong model, that adds up quickly. The repository now also describes the project as being in maintenance mode, which is worth knowing before you build on it.
Lighter alternatives exist:
| Approach | Idea | Reported trade-off | |---|---|---| | Vector RAG | Retrieve similar chunks | Cheapest; weak on whole-corpus questions | | GraphRAG | LLM-built graph plus community summaries | Strong on global questions; costly indexing | | LazyGraphRAG | Defers LLM work to query time | Microsoft reports indexing at 0.1% of GraphRAG's cost | | LightRAG | Graph plus vector retrieval at two levels, incremental updates | Authors estimate under 100 retrieval tokens per query against about 610,000 for GraphRAG on one dataset | | Ontology-driven GraphRAG | Any graph approach with a fixed schema | Extra modelling effort; OG-RAG authors report about 55% higher recall of accurate facts |
An ontology does not make indexing cheaper per chunk. It can make it cheaper overall, because a focused schema extracts fewer low-value entities, and because a consistent graph is reusable for structured queries and other applications, not just one RAG pipeline.
Use GraphRAG when your users ask whole-corpus questions, when answers need several connected facts, or when you want the graph itself as a durable asset. Stay with vector RAG when questions are mostly local lookups and the corpus changes constantly. Add the ontology when the graph will be shared, queried directly, or combined with structured data.
Common mistakes
- Letting the LLM discover entity types in production. It is useful for exploring a new corpus, not for a graph others depend on.
- Too many entity types. A large schema dilutes the prompt. Start with the types your competency questions (the questions the system must answer) need.
- No relationship directions. Always define patterns with a source and target type.
- Skipping entity resolution. Duplicate nodes quietly break both community detection and local search.
- Ignoring cost. Index a representative sample first and measure tokens before running the full corpus.
Building your first ontology-driven GraphRAG pipeline
- Write ten competency questions that plain RAG answers badly.
- Model the entity types and relationships those questions need, reusing terms from schema.org or your domain ontology. The guide to RDF, RDFS, OWL and SHACL covers the syntax.
- Generate the extraction schema from the ontology, as in the snippet above.
- Index a sample of a few hundred documents and measure cost.
- Validate the extracted graph and fix the schema descriptions where the model goes wrong.
- Compare answers to the competency questions against your existing vector RAG.
If the graph still feels messy, check whether you need an ontology at all or just a cleaner taxonomy of topics; ontology vs taxonomy vs knowledge graph walks through that decision.
Key takeaways
- GraphRAG builds a knowledge graph from documents with an LLM, clusters it into communities and answers from community summaries or graph neighbourhoods.
- It outperforms vector RAG on whole-corpus questions in Microsoft's evaluation, at a significant indexing cost.
- Unsupervised extraction produces synonymous entity types, inconsistent relationships and duplicates.
- Ontology-driven GraphRAG fixes the entity types, relationship types and patterns up front, and validates the result.
- Entity resolution, ideally against canonical identifiers, decides how good the graph really is.
- LazyGraphRAG and LightRAG reduce cost; an ontology improves consistency with any of them.
