Overview
Knowledge Graph RAG enriches standard vector retrieval with a graph of named entities and their relationships. When a query requires reasoning across multiple entities — "Who manages the team that built product X?" — a knowledge graph can traverse the path product X → built_by → team → managed_by → person in a single hop, something dense retrieval cannot do reliably.
Pattern #2 in the 37 RAG Patterns Collection
The Problem
Standard RAG retrieves documents by semantic similarity. This works well for single-entity lookups but breaks on relational queries:
Query: "Which VP oversees the team responsible for the Falcon payment gateway?"
Standard RAG retrieval:
Top doc 1: "Falcon Payment Gateway product brief" (mentions features, not org)
Top doc 2: "Engineering team structure 2024" (lists teams, not reporting lines)
Top doc 3: "Falcon integration guide" (technical docs, no org info)
Result: LLM hallucinates an answer because no single doc has the full chain.
The answer requires traversing: Falcon → owned_by → Payments Team → reports_to → Sarah Chen (VP Eng) — a two-hop graph path that dense retrieval cannot surface from isolated document embeddings.
Architecture
graph TB
A[User Query] --> B[Entity Extraction]
B --> C{Query Type?}
C -->|Relational| D[Graph Traversal]
C -->|Semantic| E[Vector Search]
D --> F[Multi-hop Path]
F --> G[Reached Nodes]
G --> E
E --> H[Relevant Chunks]
H --> I[Merge Context]
I --> J[LLM Generation]
style A fill:#f9f,stroke:#333
style C fill:#ff9,stroke:#333
style J fill:#9f9,stroke:#333Graph Schema
Nodes represent entities; edges represent typed relationships:
Nodes: Person, Product, Team, Organization, Document
Edges: manages, reports_to, built_by, owns, acquired, references
A small example graph fragment:
(Sarah Chen) --manages--> (Payments Team)
(Payments Team) --built--> (Falcon Gateway)
(Falcon Gateway) --acquired_by--> (Acme Corp)
(Falcon Gateway) --documented_in--> [doc_chunk_42, doc_chunk_87]
Implementation
Step 1 — Build the Knowledge Graph
import networkx as nx
from sentence_transformers import SentenceTransformer
graph = nx.DiGraph()
embedder = SentenceTransformer("all-MiniLM-L6-v2")
def add_entity(name: str, entity_type: str, chunk_ids: list[str]):
"""Add a node with its type and associated document chunks."""
graph.add_node(name, type=entity_type, chunks=chunk_ids)
def add_relation(source: str, relation: str, target: str):
"""Add a directed edge between two entities."""
graph.add_edge(source, target, relation=relation)
# Populate from structured data or NER extraction
add_entity("Sarah Chen", "Person", ["chunk_12", "chunk_45"])
add_entity("Payments Team", "Team", ["chunk_88", "chunk_91"])
add_entity("Falcon Gateway", "Product", ["chunk_42", "chunk_87"])
add_relation("Sarah Chen", "manages", "Payments Team")
add_relation("Payments Team", "built", "Falcon Gateway")Step 2 — Extract Entities from Query
def extract_entities(query: str, llm) -> list[str]:
"""Use an LLM to identify named entities in the query."""
prompt = f"""Extract all named entities (people, products, teams, orgs)
from the following query. Return a JSON list of strings.
Query: {query}
Entities:"""
result = llm.invoke(prompt, schema={"type": "array", "items": {"type": "string"}})
return resultStep 3 — Multi-Hop Graph Traversal
def traverse_graph(seed_entities: list[str], max_hops: int = 2) -> set[str]:
"""
Starting from seed entities, collect all reachable nodes within max_hops.
Returns a set of chunk IDs associated with reached nodes.
"""
visited_nodes = set()
chunk_ids = set()
frontier = set(seed_entities)
for _ in range(max_hops):
next_frontier = set()
for node in frontier:
if node not in graph or node in visited_nodes:
continue
visited_nodes.add(node)
# Collect document chunks linked to this node
chunk_ids.update(graph.nodes[node].get("chunks", []))
# Expand to neighbours
next_frontier.update(graph.successors(node))
next_frontier.update(graph.predecessors(node))
frontier = next_frontier - visited_nodes
return chunk_ids
def knowledge_graph_rag(query: str) -> tuple[str, dict]:
# Step 1: Extract entities mentioned in the query
entities = extract_entities(query, llm)
# Step 2: Traverse graph from those entities
graph_chunk_ids = traverse_graph(entities, max_hops=2)
# Step 3: Vector search for semantic similarity
semantic_chunks = vector_store.search(query, top_k=5)
# Step 4: Fetch graph-referenced chunks from the document store
graph_chunks = [doc_store.get(cid) for cid in graph_chunk_ids if doc_store.has(cid)]
# Step 5: Merge and deduplicate
seen = set()
merged = []
for chunk in graph_chunks + semantic_chunks:
if chunk["id"] not in seen:
seen.add(chunk["id"])
merged.append(chunk)
context = "\n\n".join(c["content"] for c in merged[:8])
response = llm.generate(f"Context: {context}\n\nQuestion: {query}")
return response, {
"entities_found": entities,
"graph_chunks": len(graph_chunk_ids),
"semantic_chunks": len(semantic_chunks),
"merged_total": len(merged),
}Complete Notebook
Run the full implementation:
Knowledge Graph RAG AWS Notebook
Includes:
- ▸Graph construction from unstructured text with NER
- ▸Neo4j and NetworkX backend options
- ▸Multi-hop traversal with path explanations
- ▸AWS Bedrock integration
- ▸Evaluation against standard RAG on multi-hop benchmarks
Performance
| Query Type | Standard RAG | KG-RAG |
|---|---|---|
| Single-entity lookup | 79% | 81% |
| Two-hop relational | 44% | 81% |
| Three-hop relational | 21% | 68% |
| Latency (avg) | ~300ms | ~650ms |
KG-RAG nearly doubles accuracy on multi-hop queries.
When to Use
Best For
- ▸Enterprise knowledge bases with clear entity relationships (org charts, product catalogs, project ownership)
- ▸Multi-hop reasoning — queries that require chaining two or more relationships
- ▸Explainability requirements — graph paths make reasoning traceable
- ▸Structured + unstructured hybrid — when you have both database records and documents
Combine With
- ▸Hierarchical RAG (#22) — use the graph for cross-document entity links and hierarchical chunking for within-document structure
- ▸Self-RAG (#14) — after graph traversal, validate that the retrieved chunks actually answer the question before generating
Related Papers
- ▸GraphRAG (Microsoft, 2024): Community-based graph construction and summarisation for RAG — arxiv.org/abs/2404.16130
- ▸KGRAG (2023): Entity-centric knowledge graph augmentation for open-domain QA — arxiv.org/abs/2310.11220
Next Steps
- ▸Try it: Run the notebook
- ▸Scale up: Move from NetworkX to Neo4j for graphs with millions of nodes
- ▸Automate construction: Use an NER pipeline to extract entities and relations automatically from your document corpus
- ▸Add validation: Layer Self-RAG (#14) to verify graph-retrieved chunks before generation
Part of: 37 RAG Patterns Collection See also: Complete Pattern Index