← Back to Technical Library

Graph RAG & Knowledge Graphs

When vector similarity is not enough: how knowledge graphs add relationship-aware retrieval to sovereign, on-premise RAG -- traversing edges instead of measuring distances, answering multi-hop questions that vector databases cannot.

Graph RAG Knowledge Graph Neo4j Apache AGE Microsoft GraphRAG Sovereign RAG Multi-Hop Reasoning

What Is Graph RAG?

Graph RAG is a retrieval architecture that uses a knowledge graph instead of (or in addition to) a vector database to find relevant information. Where standard RAG measures the semantic similarity between a query embedding and document embeddings, Graph RAG traverses the edges of a graph -- hopping from entity to entity across explicit, labeled relationships -- to retrieve facts that are structurally connected rather than merely topically similar.

A knowledge graph stores three things: nodes (entities such as people, organizations, drugs, statutes, products), edges (typed relationships such as WORKS_AT, ACQUIRED, INTERACTS_WITH, CITES), and properties (attributes on either, such as a date, a confidence score, or a source chunk ID). Retrieval becomes a graph traversal problem: starting from a seed entity mentioned in the question, you follow edges outward to collect connected entities and the document chunks that mention them.

This is the core distinction. Vector RAG asks: “Which chunks are closest to my question in embedding space?” Graph RAG asks: “Which entities are connected to the entities in my question, and how?” The two answer fundamentally different questions, and the most effective production systems combine them.

Definition Graph RAG augments retrieval with a knowledge graph so that the system can reason over relationships between entities, not just over the lexical or semantic similarity of text. Retrieval is a traversal, not a nearest-neighbor lookup.

Why Graph RAG Matters: The Multi-Hop Problem

Vector retrieval has a known, well-documented weakness: it finds documents that look similar to the query but has no concept of how the entities inside those documents relate to one another. This breaks down on multi-hop questions -- questions whose answer requires chaining facts across two, three, or more documents that may not even be semantically similar to the question.

Consider this question:

The canonical multi-hop query “Who is the CEO of the company that acquired the startup that built the tool we use?”

The answer requires four hops:

  1. Identify the tool we use (a product entity in your inventory).
  2. Find the startup that built that tool (a BUILT_BY edge).
  3. Find the company that acquired that startup (an ACQUIRED edge).
  4. Find the CEO of that company (a HAS_CEO edge).

A vector database will return chunks that mention tools, startups, acquisitions, and CEOs -- but it cannot guarantee that the specific tool, startup, acquirer, and CEO are the same connected chain. It returns topical hits, not a traversed path. The LLM is then left to reconstruct the chain from context, which frequently hallucinates or picks the wrong entity when several candidates are present.

A knowledge graph answers this directly. Each hop is a single edge traversal. In Cypher (Neo4j's query language) the whole question is a four-line pattern match that returns the exact person, with the exact chain of evidence, in one query. No guessing, no hallucination, no reliance on the model to stitch fragments together.

The vector blind spot Vector similarity is a distance metric over the surface text of chunks. It has no idea that “AcmeCorp” in one chunk and “the parent company” in another chunk are the same entity, and no idea that an acquisition is a directed relationship. It cannot traverse. It can only rank by closeness.

The practical consequence is that Graph RAG dramatically improves accuracy on questions that involve entities, relationships, timelines, and chains of inference -- which in domains like medicine, law, finance, and enterprise intelligence is most of the valuable questions.

Knowledge Graph Basics

A knowledge graph is a data structure that represents the world as entities and the relationships between them. Three primitives make up the entire model:

Primitive Represents Example
Node An entity: a person, organization, document, drug, statute, product, event. Person:Steve, Company:Avondale.AI, Service:RAG
Edge A typed, directed relationship connecting two nodes. WORKS_AT, OFFERS, ACQUIRED, CITES
Property A key-value attribute attached to a node or edge. since: 2024-03-15, confidence: 0.93, source_chunk: 842

A single fact is expressed as a triple -- a subject node, a predicate (edge type), and an object node -- optionally decorated with properties. The triple [Person:Steve] -[WORKS_AT]-> [Company:Avondale.AI] asserts that Steve works at Avondale.AI. Add the property since: 2024-03-15 and you know when he started. Add source_chunk: 842 and you can trace the claim back to the exact document chunk the extractor read it from.

A small knowledge graph built from a few sentences might look like this:

[Person:Steve] | | WORKS_AT (since: 2024-03-15) v [Company:Avondale.AI] | | OFFERS +------------------------+ | | v v [Service:RAG] [Service:Knowledge Graphs] | | | USES | USES v v [Tool:Neo4j] [Tool:Apache AGE] | | HOSTED_ON v [Server:On-Prem Phoenix]

Five facts, five edges. From Person:Steve you can traverse to every tool the company uses, find where it is hosted, and enumerate every service -- all by following edges. This is what a vector database fundamentally cannot do, because it has no edges.

Why properties matter for RAG The source_chunk property is what ties the graph back to your document store. When a traversal returns Company:Avondale.AI, you also get source_chunk: 842, which lets you fetch the original text chunk and hand it to the LLM as grounding context. The graph finds what is related; the source chunks provide what to say about it.

Building Knowledge Graphs From Documents

Your documents are unstructured text. Your knowledge graph is structured triples. The gap between them is bridged by a four-stage pipeline: extract entities, extract relations between them, resolve duplicates into single canonical nodes, and persist the result in a graph database.

Documents --> [NER] --> Entities | v [Relation Extraction] --> Triples (Entity -RELATION-> Entity) | v [Entity Resolution] --> Canonical graph (no duplicates) | v [Graph Construction] --> Neo4j / ArangoDB / Apache AGE

Stage 1 -- Named Entity Recognition (NER)

NER scans text and identifies spans that name entities, classifying each into a type. Given “Avondale.AI deployed a Neo4j graph at their Phoenix office in March 2024,” a NER model returns: Avondale.AI (ORG), Neo4j (PRODUCT), Phoenix (LOC), March 2024 (DATE).

Three families of extractors dominate on-premise RAG builds:

  • spaCy with a trained ner pipe -- fast, deterministic, fully local, no GPU required. Trained on standard types (PERSON, ORG, GPE, DATE, MONEY). Best for high-throughput extraction over large corpora where precision matters more than novel entity types.
  • GLiNER -- a zero-shot NER model. You pass candidate labels at inference time (“drug, dosage, ICD-10 code, adverse event”) and it extracts spans matching those types without retraining. Excellent for domain-specific graphs where the entity types are not in spaCy's defaults.
  • LLM-based extraction -- prompt a local model (Llama 3, Qwen, Mistral) to return JSON entities from each chunk. Most flexible, slowest, most expensive in compute, but handles novel types and nuanced definitions out of the box. Pair with a schema (Pydantic, JSON Schema) so the model returns structured, validated output.
# spaCy NER -- local, fast, no GPU required import spacy nlp = spacy.load("en_core_web_lg") doc = nlp("Avondale.AI deployed a Neo4j graph at their Phoenix office.") for ent in doc.ents: print(ent.text, ent.label_) # Avondale.AI ORG, Neo4j PRODUCT, Phoenix GPE # GLiNER -- zero-shot, pass custom labels at runtime from gliner import GLiNER model = GLiNER.from_pretrained("urchade/gliner_medium") entities = model.predict_entities( text, labels=["drug", "dosage", "ICD-10", "adverse_event"], ) for e in entities: print(e["text"], e["label"], e["score"]) # LLM-based -- prompt a local model for structured JSON prompt = """Extract entities from the text as JSON with keys { "text": str, "type": str, "start": int, "end": int }[]. Text: {chunk}""" # call ollama, vllm, or llama.cpp server; validate with Pydantic

Stage 2 -- Relation Extraction

NER gives you nodes. Relation extraction gives you edges: given a pair of entities in a sentence or window, what typed relationship connects them? Two approaches dominate:

  • Rule-based -- dependency-parse the sentence and match patterns. “X was acquired by Y” always yields Y -[ACQUIRED]-> X. Deterministic, auditable, zero compute cost beyond parsing, but brittle and limited to patterns you hand-wrote. Strong baseline for closed domains (legal citations, SEC filings) where phrasing is formulaic.
  • LLM-based -- pass the chunk and a schema of allowed edge types to a local model and ask it to return triples as JSON. Captures nuance, novel relationships, and implicit links the rule engine would miss. Slower and costlier, but the only practical option for free-form prose like clinical notes or contracts.

The output of either is a set of triples: (Person:Steve, WORKS_AT, Company:Avondale.AI), (Company:Avondale.AI, OFFERS, Service:RAG), each tagged with the source chunk so the relationship is traceable to its origin.

Schema discipline pays off Constrain relation extraction to a fixed, domain-specific ontology. A medical graph should allow PRESCRIBED, INTERACTS_WITH, CONTRAINDICATED_FOR -- not a generic RELATED_TO that collapses every edge into an ambiguous blob. The tighter the schema, the more useful the traversal.

Stage 3 -- Entity Resolution

The same entity appears under many surface forms. “Avondale.AI” in one chunk, “Steve” in another, “S. Sargent” in a signature block, “Mr. Sargent” in a memo. Without resolution, your graph ends up with four nodes for one person, and traversal breaks -- the path from Steve to Avondale.AI exists, but the path from S. Sargent does not, because they are stored as different nodes.

Entity resolution (also called deduplication or record linkage) merges these into a single canonical node. Techniques, in order of sophistication:

  • String similarity -- Levenshtein distance, Jaro-Winkler, or cosine over character n-grams. Cheap, catches typos and abbreviations, misses semantic aliasing.
  • Embedding similarity -- embed each entity mention and cluster by cosine distance. Catches “the CEO” = “Avondale.AI” when both appear in the same context window.
  • Contextual resolution -- pass candidate pairs to an LLM with surrounding context and ask whether they refer to the same entity. Highest accuracy, highest cost.
  • Rules + dictionaries -- a lookup of known aliases (“IBM” = “International Business Machines”) for deterministic merges.

The merged node keeps all surface forms as aliases and all edges from all duplicates. Traversal now works regardless of which form the question uses.

Resolution is the difference between a toy and a tool An unresolved graph looks impressive in a demo and falls apart on real questions. Resolve aggressively. The investment pays back the first time a user asks about “the CFO” and the system still finds the right person and their connections.

Stage 4 -- Graph Construction & Persistence

The final stage persists the resolved triples in a graph database. Each node and edge becomes a row (or its native equivalent) with its properties, including the all-important source_chunk back-reference. The graph database provides the query language and traversal engine that Graph RAG retrieves against.

For sovereign RAG, the database runs on your hardware. The options are covered in detail in the graph databases section below, but the short version: Neo4j Community Edition (self-hosted, Cypher), ArangoDB (multi-model, self-hosted), Postgres with the Apache AGE extension (no new database -- add graph to your existing Postgres), NebulaGraph (distributed), or even DuckDB with graph extensions for a fully embedded, serverless option.

Graph + Vector Hybrid: Two-Phase Retrieval

Neither pure vector RAG nor pure graph RAG is the right answer for most real systems. Vector retrieval is unbeatable at “find me documents about X” -- open-ended, semantic, exploratory. Graph retrieval is unbeatable at “how are X and Y connected?” -- structured, relational, multi-hop. Production systems run both, in a two-phase pipeline that uses each for what it does best.

QUERY | v +----------------+----------------+ | PHASE 1: VECTOR RETRIEVAL | | Embed query, search vector DB, | | return top-K semantically | | similar chunks and the | | entities they mention. | +----------------+----------------+ | v seed entities (from Phase 1) | v +----------------+----------------+ | PHASE 2: GRAPH EXPANSION | | For each seed entity, traverse | | N hops out in the knowledge | | graph, collecting connected | | entities and their source | | chunks. | +----------------+----------------+ | v merged, re-ranked chunk set | v LLM context window | v Generated answer

Phase 1 (vector) finds the starting point. You embed the question, search the vector database, and get back the chunks that are semantically closest -- and the entities those chunks mention. This gives the system the entry points into the graph without requiring the user to name exact entities.

Phase 2 (graph) expands from those seed entities. For each one, traverse N hops outward along typed edges, collecting every connected entity and the source chunks that mention the traversed edges. This brings in documents that are structurally relevant even if they are not semantically similar to the query -- the missing acquisition filing, the contract clause referenced by a precedent, the drug-interaction warning that only appears when you traverse from the prescribed drug to its interaction class.

The two chunk sets are merged, de-duplicated, and re-ranked (by a cross-encoder, by graph-distance weighting, or by an LLM judge), then handed to the LLM. The LLM now sees both the topically relevant passages and the structurally connected ones, and can answer multi-hop questions correctly.

Why this beats either alone Vector-only misses connections. Graph-only requires you to already know which entities to start from. The hybrid uses vectors to find the seeds and the graph to walk outward from them -- the best of both, with neither blind spot.

Re-ranking is where the two signals are fused. A common weighting scheme:

# Hybrid re-rank: combine vector similarity with graph proximity def hybrid_score(chunk, query_emb, seed_entities, graph): vec_sim = cosine(embed(chunk.text), query_emb) # 0..1 graph_dist = min_hop_distance(chunk.entities, seed_entities, graph) graph_sim = exp_decay(graph_dist) # 1 hop ~= 1.0, 2 hops ~= 0.5, 3 hops ~= 0.25 ... return 0.6 * vec_sim + 0.4 * graph_sim # tune to taste

The weights are a tunable knob. Entity-heavy domains (legal, medical) skew toward the graph term; exploratory Q&A over a knowledge base skews toward the vector term. There is no universal optimum -- measure recall on your own evaluation set.

Microsoft GraphRAG (2024): Community Detection & Hierarchical Summarization

In 2024 Microsoft Research released GraphRAG, an open-source framework that introduced a now-influential idea: do not just store entities and edges -- cluster them into communities, summarize each community, and retrieve against the summaries for global questions. It splits retrieval into two distinct modes that answer different question types.

Mode Question Type How It Retrieves
Local Search Specific, entity-focused. “What drugs does Patient 4471 take?” Find seed entities in the question, traverse their local neighborhood, pull connected chunks and community context.
Global Search Thematic, corpus-wide. “What are the main themes across all 10,000 patient charts?” Iterate over community summaries at the top level of the hierarchy, map-reduce an answer across them.

The pipeline has four steps:

Step 1 -- Extract entities and relationships

Each text chunk is passed to an LLM (locally, in a sovereign build) that extracts entities and the relationships between them, emitting typed triples. Same as the general pipeline above.

Step 2 -- Build the graph

Triples are assembled into a knowledge graph, with entity resolution applied so the same entity from different chunks becomes one node.

Step 3 -- Community detection (Leiden algorithm)

The graph is partitioned into communities -- clusters of densely interconnected nodes that are only loosely connected to other clusters. The Leiden algorithm (a refinement of Louvain) finds these groupings, and the result is organized into a hierarchy: top-level communities contain sub-communities, which contain sub-sub-communities, down to individual entities. This hierarchy is what makes global questions tractable.

Step 4 -- Hierarchical summarization

An LLM summarizes each community at each level of the hierarchy. The bottom-level communities get summaries of their constituent entities and edges; the next level up gets summaries of those summaries; the top level gets a handful of summaries that each cover a large thematic cluster of the entire corpus. This produces a multi-resolution map of what the documents are about.

Retrieval then splits:

  • Global search walks the top-level community summaries, asks each for a partial answer to the question, and map-reduces those into a final synthesized answer. This is how GraphRAG answers “what are the main themes across the whole corpus” -- a question that vector RAG cannot answer at all, because vector retrieval has no concept of corpus-level structure.
  • Local search finds the entities in the question, pulls the chunks connected to those entities, and also pulls the community summaries for context -- giving the LLM both the specific facts and the surrounding thematic frame.
Sovereign note GraphRAG is open source (MIT) and runs fully on local hardware with a local LLM doing extraction and summarization. The community-detection step (Leiden) is pure CPU graph math -- no model call needed. The expensive steps are the LLM extraction and the per-community summarization passes, which scale with corpus size. Budget GPU time accordingly, or run extraction on a smaller local model and reserve a larger model for final summarization.
Indexing cost is real GraphRAG's indexing is an upfront, offline cost: extract triples from every chunk, build the graph, run Leiden, summarize every community at every level. For a 100K-chunk corpus this is hours of local GPU time. It is not a per-query cost -- you pay it once, then queries are cheap. Plan the indexing pass as a batch job, not an interactive flow.

Graph Databases For Sovereign RAG

The knowledge graph needs a home. For sovereign RAG that home is on your hardware, under your control, with no telemetry phoning home. Six credible options, ranging from industry-standard server databases to embedded engines that need no server at all.

Database Type Query Language Sovereign Option Best For
Neo4j Native graph (property graph) Cypher The default choice. Mature, huge ecosystem, every tutorial uses it. Cypher is the de-facto graph query standard.
ArangoDB Multi-model: graph + document + search AQL (ArangoDB Query Language) One database for graph, documents, and full-text search. Eliminates the “do I need three databases?” question.
Postgres + Apache AGE Relational + graph extension SQL + openCypher (via AGE) You already run Postgres. AGE adds Cypher graph queries as a Postgres extension. Reuse your backups, replication, and ops.
NebulaGraph Distributed native graph nGQL Very large graphs (billions of edges) that exceed a single machine. Sharding built in.
RedisGraph In-memory graph (Redis module) Cypher Redis Source Available License (check terms for your use case) Low-latency, small-to-medium graphs where speed dominates. Pairs with Redis for caching.
DuckDB + graph extensions Embedded analytical DB + graph SQL (with recursive CTEs for traversal) Single-node, single-process RAG where you want no server at all. DuckDB's recursive CTEs do bounded-depth traversals natively.

A few practical notes on each:

Neo4j is the safe default. Cypher is readable -- MATCH (p:Person)-[:WORKS_AT]->(c:Company) RETURN p, c is self-explanatory -- and the ecosystem (drivers, visualizers, LLM integrations) is the deepest. The Community Edition covers everything a single-node sovereign RAG deployment needs. The Enterprise Edition's extras (clustering, RBAC, backups) matter at scale, but you can run a long time on Community first.

ArangoDB is the “one database to rule them all” option. It stores graph edges and document nodes in the same engine, plus it has a built-in full-text and (experimentally) vector search. If you want to avoid running separate vector, document, and graph stores, ArangoDB collapses them into one process. The cost is that each individual capability is slightly less polished than a single-purpose database.

Postgres + Apache AGE is the “no new database” option and the one we highlight for teams already running Postgres. AGE (A Graph Extension) adds openCypher support as a Postgres extension -- LOAD 'age'; SET graph_path = 'mygraph'; SELECT * FROM cypher('mygraph', $$ MATCH (p:Person) RETURN p $$) AS (p agtype); You get graph queries, and your existing Postgres backups, replication, monitoring, and pgvector embeddings all keep working. One database, three retrieval modes (SQL, Cypher, pgvector), zero new ops burden.

NebulaGraph is the scale-out pick. When your graph exceeds a single machine's memory -- tens of billions of edges, social-scale or enterprise-wide graphs -- NebulaGraph shards the graph across a cluster. The trade-off is operational complexity: you are now running a distributed system with multiple services (graphd, storaged, metad).

RedisGraph is the speed pick. In-memory, sub-millisecond traversals, Cypher interface. Check the license terms (Redis moved modules to RSALv2 / SSPLv1 in recent versions) against your deployment model. Best when the graph is small, the query volume is high, and you are already running Redis for caching.

DuckDB is the embedded pick. No server process -- your Python application opens a file and queries it. DuckDB does not have a native graph type, but its recursive CTEs perform bounded-depth traversals over an edge table efficiently, and for single-process sovereign RAG (a desktop app, a single-user research tool, an on-device medical assistant) it is hard to beat the operational simplicity. Pair with DuckDB's native vector similarity (via the vss extension) and you have vector + graph in one embedded file with no services to run.

Sovereign stack recommendation For most on-premise RAG deployments the pragmatic stack is Postgres + pgvector + Apache AGE: one database, vector embeddings and graph traversal in the same process, your existing Postgres ops carry over, and everything is open source. If you are starting clean and want the deepest graph ecosystem, pick Neo4j Community and run pgvector or a separate vector store alongside it.

When To Use Graph RAG vs Vector RAG vs Hybrid

Not every workload needs a graph. The decision is driven by the shape of the questions your users actually ask.

Approach Question Shape Example Retrieval Mechanism When To Choose
Vector RAG “Find documents about X” “What does the manual say about calibrating the sensor?” Embedding cosine similarity over chunks Open-ended semantic search over a document corpus. No entities, no relationships, just “find me text about this topic.”
Graph RAG “How are X and Y related?” “Which suppliers are affected by the strike at Plant 4?” Graph traversal from seed entities Questions that require multi-hop reasoning over named entities and their relationships. The answer is a path, not a passage.
Hybrid “Find documents about X and everything connected to X” “Summarize our exposure to the supplier involved in the recall, and pull every related contract.” Vector retrieval to find seeds, then graph expansion, then re-rank The real-world default. Use when users ask both topical and relational questions, or when you do not know in advance which type a query will be.
Decision shortcut If the answer is a passage, use vectors. If the answer is a path, use a graph. If you are not sure which the user will ask, use the hybrid. The hybrid subsumes the other two -- its only cost is the graph-building pipeline, which is a one-time indexing investment.

A few heuristics that make the call concrete:

  • Skip the graph if your documents are a flat knowledge base with no named entities worth modeling (a product manual, an FAQ, a set of marketing pages). Vectors are simpler and answer every question you have.
  • Build the graph if your users ask about people, organizations, products, contracts, cases, or drugs -- and especially if they ask how those things connect, depend on, cite, or affect each other.
  • Use the hybrid if you have both question types, or if your graph is sparse enough that pure graph retrieval sometimes returns nothing for a reasonable question that vectors would catch.
  • Use Microsoft GraphRAG-style community summarization if users ask corpus-wide thematic questions (“what are the main themes?”) that neither vectors nor local graph traversal can answer.

Graph RAG For Medical: Patient Journeys, Pathways, and Drug Interactions

Medicine is the canonical relationship-heavy domain. A patient is not a document; a patient is a timeline of encounters, each with diagnoses, prescriptions, lab results, procedures, and providers, all of which relate to each other in clinically meaningful ways. A vector database can find notes about a condition. A knowledge graph can answer “given this patient's medication list, which of these proposed drugs is contraindicated?” -- because it can traverse from each prescribed drug to its interaction class and check for overlap.

Three patterns where graph RAG earns its keep in clinical settings:

1. Patient Journeys

A patient journey is a temporal graph: [Patient] -[HAD_ENCOUNTER]-> [Encounter] -[DIAGNOSED_WITH]-> [Condition] -[TREATED_WITH]-> [Medication] -[CAUSED]-> [AdverseEvent]. Traversing this graph reconstructs the full clinical narrative -- every diagnosis, every intervention, every outcome -- in chronological and causal order. A query like “show me every patient who started Drug A, later developed Condition B, and was then switched to Drug C” is a single graph traversal. In a vector store it is effectively impossible.

2. Treatment Pathways & Guidelines

Clinical guidelines are themselves graphs: first-line therapy, second-line if first-line fails or is contraindicated, alternatives for specific comorbidities. Modeling the guideline as a graph and the patient's current state as a subgraph lets the system traverse from “where this patient is now” to “what the guideline recommends next,” with the contraindication checks expressed as edges to avoid.

3. Drug-Drug Interactions

Drug interaction knowledge bases (DrugBank, RxNorm) are natively graph-structured. Each drug has metabolism, targets, and interaction edges to other drugs. The question “is Drug X safe alongside this patient's current regimen?” is a traversal from Drug X through its interaction edges to each drug the patient already takes, checking severity and mechanism. This is the textbook case where graph retrieval is not just better than vector retrieval -- it is the only correct tool.

[Patient:4471] | |-- HAS_ENCOUNTER --> [Encounter:2025-11-03] | | | |-- DIAGNOSED_WITH --> [Condition:Type2 Diabetes] | | | | | |-- TREATED_WITH --> [Drug:Metformin] | | | | |-- LAB_RESULT --> [Lab:HbA1c 9.2%] |-- INTERACTS_WITH --> [Drug:Contrast Dye] | (severity: major) |-- HAS_ENCOUNTER --> [Encounter:2026-01-15] | |-- SCHEDULED_FOR --> [Procedure:CT with contrast] | +-- RAG TRAVERSAL FINDS: Metformin + contrast = lactic acidosis risk. ALERT: hold metformin 48h pre-procedure.
Sovereignty is non-negotiable here Patient-identified graph data must never leave the premises. The entire pipeline -- NER over clinical notes, relation extraction, graph storage, and LLM inference for final answer generation -- runs on hardware you control, behind your firewall, with no third-party API in the path. This is why the self-hosted graph databases (Neo4j Community, Postgres + AGE, DuckDB embedded) matter: they keep the graph on your side of the wall.

Comparison Table: Approaches at a Glance

A consolidated comparison of the three retrieval approaches, the tools that support them, and how each maps to a sovereign, on-premise deployment.

Approach Best For Complexity Tools (Open Source, Self-Hosted) Sovereign Option
Vector RAG Semantic similarity search. “Find documents about X.” Open-ended, topical, exploratory questions over a document corpus. Low. Embed chunks, store in a vector DB, query by cosine similarity. No entity modeling. pgvector (Postgres), Chroma, Qdrant, Milvus, LanceDB, DuckDB + vss extension
Graph RAG Multi-hop relational questions. “How are X and Y connected?” Entity and relationship traversal, path-based answers. High. Requires NER, relation extraction, entity resolution, schema design, and a graph database. Upfront indexing pipeline. Neo4j Community, ArangoDB, Postgres + Apache AGE, NebulaGraph, RedisGraph, DuckDB (recursive CTEs)
Microsoft GraphRAG Corpus-wide thematic questions answered via community summaries. Global and local search modes. High, plus offline indexing cost (Leiden + per-community LLM summarization across a hierarchy). Microsoft GraphRAG (MIT), runs with a local LLM for extraction and summarization
Graph + Vector Hybrid Both question types. The real-world default for systems that serve mixed query patterns. High. Both pipelines, plus a re-ranking fusion step that combines vector similarity and graph distance. Any vector DB + any graph DB, fused in application code. Postgres + pgvector + AGE collapses all three into one process.

Architecture: A Sovereign Hybrid Graph RAG System

The end-to-end architecture of a sovereign, on-premise hybrid Graph RAG system. Everything in the diagram runs on hardware you control. No external API calls. No data leaving the premises.

=== INDEXING PIPELINE (offline, batch) === Documents (PDF, DOCX, EHR, contracts, statutes) | v [Chunker] --> text chunks | +-----------------------------+ | | v v [Embedding Model] [NER + Relation Extraction] (local: bge, e5, nomic) (spaCy / GLiNER / local LLM) | | v v [Vector Store] Triples (entity -REL-> entity) (pgvector / Qdrant / | Chroma / Milvus) v [Entity Resolution] (string + embedding + LLM) | v [Graph Database] (Neo4j / AGE / ArangoDB) | +-- (optional) [Community Detection] | (Leiden) --> community summaries | v indexed knowledge graph === RETRIEVAL PIPELINE (online, per query) === User Query | v [Embedding Model] --> query vector | v +-----------------------------------+ | PHASE 1: VECTOR RETRIEVAL | | pgvector / Qdrant / Chroma | | -> top-K chunks + seed entities | +-----------------------------------+ | v seed entities | v +-----------------------------------+ | PHASE 2: GRAPH EXPANSION | | Neo4j / AGE / ArangoDB | | -> N-hop traversal, collect | | connected entities + source | | chunks (+ community summary | | if GraphRAG-style global) | +-----------------------------------+ | v [Re-Ranker] (cross-encoder / LLM judge / weighted fusion) | v merged, ranked context chunks | v [Local LLM] (Llama 3 / Qwen / Mistral -- on-prem GPU) | v Grounded answer + cited source chunks + traversed path === EVERYTHING ABOVE RUNS ON YOUR HARDWARE ===

Two pipelines, one stack. The indexing pipeline runs offline as a batch job -- chunk, embed, extract entities and relations, resolve duplicates, build the graph, optionally run community detection. The retrieval pipeline runs per query -- vector search to find seeds, graph traversal to expand, re-rank, generate. Both pipelines share the same document store, and in the Postgres + pgvector + AGE variant they share a single database process.

The sovereign promise No component in this architecture requires an external network call. The embedding model, the extraction LLM, the vector store, the graph database, and the generation LLM all run on-premise. The data -- documents, embeddings, graph, and query -- never leaves your network. This is what makes it suitable for HIPAA-regulated clinical data, attorney work product, student records under FERPA, and any other data class where cloud processing is a compliance violation waiting to happen.
Operational sizing A single mid-tier GPU server (one consumer or datacenter-class card, 24-48 GB VRAM) handles the LLM workload for both extraction and generation for a small-to-medium corpus. The vector and graph stores are CPU- and RAM-bound and co-locate comfortably on the same host for sub-million-chunk corpora. NebulaGraph or a dedicated Postgres replica enters the picture only at large scale.

Build a Sovereign Knowledge Graph

If your questions are about relationships -- patient journeys, citation chains, contract networks, drug interactions -- vector retrieval alone will not answer them. Avondale.AI designs and deploys on-premise Graph RAG systems that run entirely on your hardware, from extraction to graph to generation.

Get Started