GraphRAG Overview
Graph-aware retrieval that adds entity and relation reasoning on top of Knowledge RAG. When to reach for it and how it differs from plain RAG.
GraphRAG is a retrieval mode for the Knowledge module that augments vector search with a knowledge graph built from the same chunks. Where plain RAG retrieves the k chunks closest to a query, GraphRAG additionally traverses entity-relation paths — letting it answer questions whose evidence is scattered across multiple documents.
It is not a separate product. GraphRAG runs over the same KnowledgeChunk table as Knowledge RAG, with two extra tables — GraphNode and GraphEdge — populated by an LLM pass at ingest time.
Status
| Capability | Status |
|---|---|
LLM-based entity resolution at ingest (resolveEntitiesFromChunks) | ✅ Available |
LLM-based relation typing (extractRelationsFromChunks) | ✅ Available |
| Confidence-weighted edges with evidence chunk reference | ✅ Available |
| Community detection (Louvain) for clustering | ✅ Available |
graphragExplain — answer with traversal path | ✅ Available |
Mixed retrieval (vector + graph hop) in ask | 🟡 Beta — includeGraph: true option |
| Per-space graph rebuild without re-embedding | ⏳ Roadmap |
| Multi-hop reasoning beyond 2 hops | ⏳ Roadmap |
| Visual graph explorer in the dashboard | ⏳ Roadmap |
When includeGraph is off, the ask endpoint behaves exactly like Knowledge RAG. When it's on, the system blends vector retrieval with a graph hop and concatenates the evidence.
When to use GraphRAG vs plain RAG
| Question shape | Pick |
|---|---|
| "What is the password reset policy?" | Plain RAG — single document, single chunk. |
| "List all integrations our SDK supports." | Plain RAG — usually one canonical doc. |
| "How does our auth system handle SAML, and which teams own it?" | GraphRAG — joins auth docs with team-roster docs. |
| "Which customer cases used the wallet API, and what payment provider were they on?" | GraphRAG — multi-doc, multi-entity. |
| "Summarise everything about our PrestaShop connector." | Plain RAG with topK: 10. |
| "What changed between v1 and v2 of the documents API?" | GraphRAG with the changelog source. |
The rule of thumb: if the answer requires joining facts from different documents, GraphRAG is worth the extra ingest cost. If the answer is already in one document, plain RAG is faster, cheaper, and just as accurate.
How it's built at ingest
For every IngestionJob, after chunking and embedding, buildGraphFromChunks (in packages/api/modules/knowledge/lib/graphrag.ts) does the following:
- Entity resolution. An LLM pass over each chunk extracts named entities with a canonical name and a semantic type ("Product", "Person", "API", "Concept", …). Implementation:
resolveEntitiesFromChunksinentity-resolution.ts. - Filter. Chunks with fewer than 2 entities are dropped — they cannot form a relation.
- Relation typing. A second LLM pass over the same chunks, given the resolved entities, extracts directed relations between them with a confidence score. Implementation:
extractRelationsFromChunksinrelation-typing.ts. - Graph write. For each entity,
upsertGraphNodeeither reuses an existingGraphNode(matched by(spaceId, canonicalName, nodeType)) or creates a new one. For each relation,createGraphEdgewrites a new edge keyed by(fromNodeId, toNodeId, relationType)with the chunk id asevidenceChunkIdso the answer can cite the originating passage.
The graph is append-only at ingest time — re-ingesting the same source adds new evidence edges but does not remove old ones. Per-source rebuild without re-embedding is on the roadmap.
Community detection
After enough edges accumulate, the Louvain algorithm clusters dense neighbourhoods into communities (listCommunities, getCommunity). Each community is a soft cluster of related concepts — useful for "what topics are in this space" overviews and for narrowing GraphRAG retrieval to a subgraph.
Communities are recomputed by the scheduled graphragCommunities job. See community-detection.ts for the Louvain implementation and the LLM-based naming pass that labels each cluster.
How a GraphRAG query runs
const result = await orpc.knowledge.graphragExplain.call({
spaceId: "ks_…",
question: "How does the wallet ledger handle promo balances?",
});The query path:
- Anchor extraction. The question is embedded and the top-k chunks identified. The entities in those chunks become the anchor nodes.
- Graph hop. From each anchor, the system walks outgoing and incoming edges with
relationTypematching the question intent (typed by the LLM). The hop is currently 1 hop by default; 2 hops is opt-in. - Evidence gather. For each visited edge,
evidenceChunkIdresolves back to the originating chunk text. - Answer synthesis. Anchor chunks + traversed chunks are concatenated and passed to the LLM. The answer cites the originating documents.
- Path output. The response includes a
path— the ordered list of(fromNode, relation, toNode)tuples — so the UI can render a "why this answer" explanation.
The response shape is { answer, sources, chunks, path: GraphPath[] }. path is what differentiates graphragExplain from ask.
When GraphRAG is the wrong tool
- Tiny spaces. Fewer than ~20 documents — there aren't enough relations for the graph to add signal over plain retrieval.
- Highly structured content. If every document is a self-contained policy or product card, the graph adds little.
- Adversarial / noisy text. The LLM entity-resolution pass picks up junk entities from machine-translated text or low-quality OCR. Plain RAG is more robust.
- Cost-sensitive workflows. The ingest-time graph build runs two extra LLM passes per chunk — meaningful at scale. Budget accordingly.
Cost shape at ingest
Per chunk, ingest cost is roughly:
embedding cost (always) + entity-resolution cost + relation-typing cost
1× 1× 0.5–1× (only for chunks with ≥ 2 entities)So a GraphRAG-enabled ingest is 2–3× the cost of a plain Knowledge ingest. Reserve credits accordingly; see CREDIT_RATES.knowledge_graph_build in entitlements/credit-rates.ts.
If cost is a concern, enable GraphRAG only on the spaces where multi-document questions actually arrive. A per-space flag (KnowledgeSpace.ragConfig.graphragEnabled) controls this; off by default.
Tenant isolation
The graph is namespaced by knowledgeSpaceId. GraphNode.canonicalName is unique within a space; "OpenAI" in space A and "OpenAI" in space B are distinct rows. Cross-space traversal is not allowed (Invariant 5).
Related pages
- GraphRAG entity model —
GraphNode,GraphEdge, schema, evidence - GraphRAG use cases — concrete patterns where graph beats vector
- Knowledge RAG overview — the underlying chunk + ask layer
- Knowledge evaluation — measuring whether GraphRAG actually helps your eval set
Evaluating Knowledge RAG Quality
A pragmatic workflow for measuring and improving answer quality — retrieval, faithfulness, coverage — without a research lab.
GraphRAG Entity Model
The data shape of the knowledge graph — GraphNode, GraphEdge, evidence chunks, and the constraints that make multi-tenant graph queries safe.