Zucchetti LLM Course — Lecture 6
When vector search isn’t enough — follow the relationships
Kamyar Zeinalipour
Section 1 — When Vector Search Isn’t Enough
Section 2 — Knowledge Graph Fundamentals
Section 3 — Building a KG from Documents (LLM-Powered)
☕ Break
Section 4 — Graph RAG — Retrieval That Follows Relationships
Section 5 — The Graph RAG Landscape
Section 1 of 5
The relationship problem
Some questions can’t be answered by finding similar documents
“Which employees in the Rome office report to managers hired before 2020?”
4 hops across different entities. No single chunk contains this.
| Query Type | Vector Search | Graph Search |
|---|---|---|
| “What is the sick leave policy?” | ✅ Finds the document | Overkill |
| “Who reports to Mario?” | ❌ Finds docs mentioning Mario | ✅ Follows reports_to edge |
| “Which drugs interact with Patient X’s medications?” | ❌ Can’t chain: Patient → Meds → Interactions | ✅ Multi-hop traversal |
| “What changed in the policy since last year?” | ❌ Finds both versions | ✅ Temporal edges |
Where Graph RAG fits in the techniques you’ve learned
L4 Simple factual → Vector Search
L5 Vocabulary mismatch → HyDE / Query Rewriting
L5 Multi-source → Semantic Routing
L6 Relationship reasoning → Knowledge Graphs YOU ARE HERE
L7 Multi-step autonomous → Agentic RAG
Section 2 of 5
Nodes, edges, and triples
Everything is a triple: (Subject, Predicate, Object)
$\mathcal{G} = (\mathcal{V}, \mathcal{E}, \mathcal{R})$ where $\mathcal{V}$ = entities, $\mathcal{R}$ = relation types, $\mathcal{E} \subseteq \mathcal{V} \times \mathcal{R} \times \mathcal{V}$
Each edge is a triple: $(h, r, t) \in \mathcal{E}$ where $h$ = head, $r$ = relation, $t$ = tail
works_in — employmentreports_to — hierarchyauthored_by — document ownershipeligible_for — policy access| Subject | Predicate | Object |
|---|---|---|
| Mario Rossi | works_in | Engineering |
| Mario Rossi | reports_to | Luigi Bianchi |
| Mario Rossi | hired_in | 2019 |
| Engineering | located_in | Rome Office |
| Mario Rossi | eligible_for | Remote Work Policy 2024 |
| Luigi Bianchi | manages | Engineering |
“Who is Mario’s manager?” → Mario reports_to → Luigi Bianchi
“Where does Mario work?” → Mario works_in → Engineering located_in → Rome
| Type | Structure | Best For | Example |
|---|---|---|---|
| RDF / Triples | (Subject, Predicate, Object) | Standards-based, semantic web | Wikidata, DBpedia |
| Property Graph | Nodes + edges with properties | Enterprise, flexible schemas | Neo4j, Neptune |
| LLM-Extracted OUR FOCUS | Triples from unstructured text | Quick prototyping, document KGs | What we’ll build |
| Tool | Type | When to Use | Query Language |
|---|---|---|---|
| Neo4j | Full graph DB | Production, complex queries | Cypher |
| Amazon Neptune | Managed cloud | AWS shops, no infra management | openCypher / Gremlin / SPARQL |
| NetworkX START HERE | Python library | Prototyping, small graphs | Python API |
| In-memory dict | Custom | Tiny graphs, embedded in pipeline | Python |
Section 3 of 5
LLM-powered entity and relation extraction
You have 10,000 documents. How do you turn them into a graph?
Mario Rossi, hired in 2019, works in the Engineering department
under the supervision of Luigi Bianchi. He is eligible for the
2024 remote work policy. The Engineering department is located
in the Rome office.
Ask the LLM to find all entities — using structured JSON output from L3
Extract all entities from this text.
Return JSON: [{"entity": "...", "type": "PERSON|ORG|POLICY|DATE",
"description": "brief description"}]
Text: "Mario Rossi, hired in 2019, works in Engineering..."
| Entity | Type | Description |
|---|---|---|
| Mario Rossi | PERSON | Employee |
| Luigi Bianchi | PERSON | Supervisor |
| Engineering | ORG | Department |
| Remote Work Policy 2024 | POLICY | Policy document |
| 2019 | DATE | Hire year |
Ask the LLM to find how entities connect
Given these entities and text, extract relationships as triples:
[{"subject": "...", "predicate": "...", "object": "..."}]
Entities: Mario Rossi, Luigi Bianchi, Engineering, ...
Text: "Mario Rossi, hired in 2019, works in Engineering..."
| Subject | Predicate | Object |
|---|---|---|
| Mario Rossi | works_in | Engineering |
| Mario Rossi | reports_to | Luigi Bianchi |
| Mario Rossi | eligible_for | Remote Work Policy 2024 |
| Mario Rossi | hired_in | 2019 |
| Engineering | located_in | Rome Office |
Extraction is easy. Cleaning is where projects fail.
| Problem | Doc A Says | Doc B Says | Solution |
|---|---|---|---|
| Different names | “Mario Rossi” | “M. Rossi” | Fuzzy matching + LLM |
| Conflicting facts | Mario in Engineering | Mario in Sales (old) | Use timestamps |
| Ambiguous refs | “the employee” | “he” | Coreference resolution |
“The employee” / “he” / “Mr. Rossi” / “the engineer” → all = Mario Rossi — LLMs handle this with overlapping chunks (L4)
$\text{sim}(e_1, e_2) = \frac{\text{embed}(e_1) \cdot \text{embed}(e_2)}{\|\text{embed}(e_1)\| \times \|\text{embed}(e_2)\|}$ If $\text{sim} > \tau$ → merge ($\tau \approx 0.85$)
In practice, combine entity + relation extraction in a single prompt
You are a knowledge graph builder.
Given this text, extract:
1. All entities with their types
2. All relationships between entities as triples
Return JSON:
{
"entities": [{"name": "...", "type": "...", "description": "..."}],
"triples": [{"subject": "...", "predicate": "...", "object": "..."}]
}
Rules:
- Use consistent entity names (full names, not abbreviations)
- Predicate should be a short verb phrase (works_in, reports_to)
- Include temporal relationships (hired_in, valid_from)
Text: """[CHUNK]"""
Before extracting anything, define what you’re looking for
| Type | Examples |
|---|---|
| PERSON | Employees, managers, clients |
| DEPARTMENT | Engineering, HR, Sales |
| POLICY | Remote work, sick leave |
| LOCATION | Rome office, Milan HQ |
| ROLE | Senior Engineer, Team Lead |
| Predicate | Between |
|---|---|
works_in | PERSON → DEPARTMENT |
reports_to | PERSON → PERSON |
located_in | DEPARTMENT → LOCATION |
eligible_for | PERSON → POLICY |
has_role | PERSON → ROLE |
From text to graph in ~15 lines of code
import json, networkx as nx
from google import genai
client = genai.Client()
graph = nx.DiGraph()
def extract_and_add(chunk: str):
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=SCHEMA_PROMPT + chunk, # our one-call prompt
config={"response_mime_type": "application/json"}
)
data = json.loads(response.text)
for e in data["entities"]:
graph.add_node(e["name"], type=e["type"],
desc=e["description"])
for t in data["triples"]:
graph.add_edge(t["subject"], t["object"],
relation=t["predicate"])
# Process all chunks
for chunk in chunks:
extract_and_add(chunk)
response_mime_type: "application/json" forces the LLM to return valid JSON — no parsing errors. The graph grows as you process each chunk.| # | Pitfall | Example | Fix |
|---|---|---|---|
| 1 | Over-extraction | LLM extracts “2024” as an entity everywhere | Restrict entity types in schema |
| 2 | Inconsistent predicates | works_at vs works_in vs employed_by |
Provide allowed predicate list in prompt |
| 3 | Hallucinated relations | LLM invents “Mario manages Sales” (not in text) | Add: “only extract facts explicitly stated” |
| 4 | Missing chunk overlap | Entity in chunk 1, relationship in chunk 2 | Use overlapping chunks (L4) |
| 5 | No validation loop | Silently building a bad graph | Sample 50 triples → human review → fix prompt |
☕
10 minutes
Section 4 of 5
Retrieval that follows relationships
“What training courses are required for Mario’s role?”
Searches for “training courses” + “Mario”
Finds general training documents
Doesn’t know Mario is in Engineering
Misses role-specific courses
1. Find “Mario” node
2. Mario → works_in → Engineering
3. Engineering → requires → [Java, Cloud, Security]
Finds role-specific courses
| Step | What Happens | Example |
|---|---|---|
| Entity Linking | Find mentioned entity in the graph | “Mario” → node Mario Rossi |
| Graph Traversal | Walk N hops to collect related entities | Mario → Engineering → [Java, Cloud, Security] |
| Context Enrichment | Retrieve documents for discovered entities | Fetch training policy docs for Java, Cloud, Security |
| LLM Synthesis | Generate answer from graph facts + documents | “Mario needs Java Cert, Cloud Training, and Security Basics” |
“Who is Mario’s manager?”
Start at Mario → look at direct edges → find Luigi Bianchi
“Who else reports to Mario’s manager?”
Hop 1: Mario → Luigi | Hop 2: Luigi ← Anna, Paolo, Sara
reports_to edges, not all edges.”“Tell me everything about Mario’s team”
Extract the full connected neighborhood of Mario — all nodes, all edges, all types
nx.ego_graph(G, "Mario", radius=2)MATCH (n)-[*..2]-(m) WHERE n.name = "Mario"“How is Mario connected to the CEO?”
Find the shortest path between two entities — reveals the chain of connections
nx.shortest_path(G, "Mario", "CEO")MATCH p=shortestPath((a)-[*]-(b))The real architecture — run both in parallel, then merge
Vector Search (always runs)
Embed query → find top-K similar chunks
Catches broad semantic matches
Graph Traversal (if entity detected)
Detect entity → traverse N hops → gather facts
Catches relationship-based context
| Step | What Happens |
|---|---|
| 2. Merge | Combine vector chunks + graph facts → deduplicate overlapping context |
| 3. Re-rank | Cross-encoder (L5) scores all results together → best evidence to the top |
| 4. LLM | Generate answer from enriched context → final answer |
Section 5 of 5
GraphRAG, RAPTOR, LightRAG, HippoRAG & beyond
“What are the main themes across all 10,000 HR policy documents?”
Returns 5 individual chunks
Each chunk covers one topic. No global view.
No single entity to start from
“Main themes” isn’t an entity in the graph.
Basic Graph RAG (Section 4) can’t handle these — each framework below was designed to solve one
“What are the main themes across 10,000 documents?”
No starting entity. Need to summarize the entire corpus.
→ Solved by Microsoft GraphRAG (community detection) and RAPTOR (tree summarization)
New documents arrive daily. GraphRAG requires full rebuild — hours of reprocessing.
→ Solved by LightRAG (incremental updates)
Iterative retrieval needs multiple LLM calls per query: retrieve → reason → retrieve again → reason…
→ Solved by HippoRAG (single-step PageRank retrieval)
From Local to Global: the 2024 breakthrough
The Leiden algorithm maximizes modularity $Q$:
$Q = \frac{1}{2m} \sum_{ij} \left[ A_{ij} - \frac{k_i k_j}{2m} \right] \delta(c_i, c_j)$
$A_{ij}$ = adjacency, $k_i$ = degree of node $i$, $m$ = total edges, $\delta(c_i, c_j) = 1$ if same community
Community A
Mario, Luigi, Engineering, Rome — “Engineering Team”
Community B
Sick Leave, Annual Leave, HR Policy — “Leave Policies”
Community C
Training, Certification, Java — “Employee Development”
Edge et al. (2024) — “From Local to Global: A Graph RAG Approach to Query-Focused Summarization”
Step-by-step: from communities to answers
Each community gets an LLM-generated summary describing its members and relationships:
Community A Summary:
“The Engineering team is based in Rome, led by Luigi Bianchi. Mario Rossi is a senior developer hired in 2019.”
Community B Summary:
“HR policies cover sick leave (10 days/year), annual leave (25 days), and parental leave. Updated in 2024.”
“What are the main themes?” → vector search over community summaries → returns top communities → LLM synthesizes global answer
| Query Type | Method | Example |
|---|---|---|
| Local ENTITY | Entity match + graph traversal | “What is Mario’s role?” |
| Global THEME | Search community summaries | “What are the main HR policy themes?” |
| Hybrid BOTH | Community + entity traversal | “How does remote work affect Engineering?” |
Same goal as GraphRAG (global queries) — but without a graph
Build a tree of summaries. Search at all levels. Detailed answers come from leaves, overview answers from higher levels.
| Level | Content | Answers |
|---|---|---|
| Root | Corpus summary | “What are the main themes?” |
| L2 | Topic summaries | “Summarize leave policies” |
| L1 | Cluster summaries | “What types of sick leave exist?” |
| Leaves | Original chunks | “How many sick days for FT employees?” |
Sarthi et al. (2024) — “RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval” — ICLR 2024
Both solve global queries — but through very different architectures
| GraphRAG | RAPTOR | |
|---|---|---|
| Builds on | Knowledge graph + communities | Chunk clustering + summaries |
| Best for | Entity-rich domains (HR, medical) | Document-heavy domains |
| Global queries | ✅ Community summaries | ✅ Tree-level summaries |
| Relationship queries | ✅ Graph traversal | ❌ No relationship awareness |
| Incremental updates | ⚠️ Possible but complex | ❌ Full tree rebuild |
| Setup complexity | High (graph DB + extraction) | Medium (clustering + LLM calls) |
| Cost | High (many LLM calls) | Medium (fewer LLM calls) |
Each level summarizes the level below. Small errors at L1 get baked into L2, then L3. The “telephone game” problem.
Add one document? Rebuild the entire tree. No way to update a single branch.
Diverse corpora (HR + IT + Finance) produce vague clusters. Works best with thematically coherent collections.
Clusters by semantic similarity, not by entity connections. “Mario works in Engineering” and “Engineering is in Rome” may end up in different clusters.
GraphRAG’s biggest weakness: you must rebuild everything when documents change
⏱ Hours of reprocessing. Hundreds of dollars in LLM calls.
⏱ Seconds per document. Pennies in cost.
Guo et al. (2024) — “LightRAG: Simple and Fast Retrieval-Augmented Generation”
One graph, two retrieval strategies — no community detection needed
Entity-specific queries
“What is Mario’s hire date?”
→ Find entity → follow edges → exact answer
Same as basic Graph RAG (Section 4)
Theme-based queries
“What are Engineering’s goals?”
→ Search key-value pairs of conceptual relationships
Replaces community detection with indexed KV pairs
| Mode | Uses | Best For |
|---|---|---|
| Naive | Vector search only | Simple factual queries |
| Local | Low-level KG retrieval | Entity-specific questions |
| Global | High-level KG retrieval | Theme/trend questions |
| Hybrid DEFAULT | Low + High combined | Most production queries |
| Mix | Vector + KG combined | Maximum coverage |
Multi-hop retrieval is expensive. HippoRAG does it in one step.
“What drugs interact with Patient X’s medications?”
3 retrieval steps × 3 LLM calls = slow and expensive
Indexing (offline) — Open IE
No predefined schema. The LLM extracts whatever triples it finds:
“Aspirin interacts with Warfarin, increasing bleeding risk.”
→ (Aspirin, interacts_with, Warfarin)
→ (Aspirin, increases, bleeding_risk)
vs. Section 3: you define entity types (PERSON, ORG) and predicates (works_in, reports_to). Open IE skips all that.
Query (online) — PageRank
Extract query entities → run Personalized PageRank → all relevant nodes found in 1 step.
Query: “What interacts with Patient X’s meds?”
→ Extract Patient_X
→ PPR finds: Aspirin, Warfarin, bleeding_risk…
1 step, 1 LLM call → 6–13× faster, 10–30× cheaper
Gutiérrez et al. (2024) — “HippoRAG: Neurobiologically Inspired Long-Term Memory for LLMs” — NeurIPS 2024
Mimics how the human hippocampus indexes long-term memory
| Brain Component | HippoRAG Component | Role |
|---|---|---|
| Neocortex | LLM | Processes and understands text |
| Parahippocampal regions | Knowledge Graph | Stores entities and relationships |
| Hippocampus | Personalized PageRank | Retrieves by association, not just similarity |
$\text{PPR}(v) = (1 - \alpha) \cdot \mathbf{e}_q + \alpha \sum_{u \in \mathcal{N}(v)} \frac{\text{PPR}(u)}{\deg(u)}$
$\alpha$ = damping factor, $\mathbf{e}_q$ = query entity indicator vector
PPR scores all nodes by how strongly they associate with the query entity. More paths = stronger association = higher rank.
HippoRAG cannot answer global “what are the themes?” queries (no community detection). But for multi-hop entity queries — fastest and cheapest option.
Decision guide: start with your primary problem
| Your Primary Problem | Best Framework | Why |
|---|---|---|
| “I need global summarization” | Microsoft GraphRAG | Community detection + summaries = only system for “what are the themes?” |
| “I need global queries without a graph” | RAPTOR | Tree of summaries, works with any vector store |
| “My documents change frequently” | LightRAG | Incremental updates — no full rebuild |
| “I need fast, cheap multi-hop” | HippoRAG | One-shot PageRank — 6–13× faster than iterative |
| “I want the simplest setup” | LightRAG | Easiest to set up, five query modes, good enough for most cases |
Chen et al. (2025) — “PathRAG: Pruning Graph-based RAG with Relational Paths”
1. Vector search finds similar documents. Graphs find connected entities. Different tools for different questions.
2. LLMs are your extraction engine. No NER model training needed — prompt the LLM to extract triples.
3. Entity resolution is the hard part. Extraction is easy. Deduplication and merging is where projects fail.
4. Graph RAG augments vector RAG, doesn’t replace it. Most queries still use vector search.
5. Microsoft GraphRAG handles global queries through community detection and hierarchical summarization.
6. Start simple: NetworkX + LLM extraction. Scale to Neo4j when you need it.
❓
Thank you!
Kamyar Zeinalipour — Zucchetti LLM Course
Next → Lecture 7: Agentic RAG & Autonomous Systems
Theory: Function calling · Tool use · Multi-agent workflows