RAG Doesn't Know Who You Are: How Knowledge Graphs Fix Personalized Retrieval
Standard RAG retrieves documents relevant to a query — not to a person. This post explains how knowledge graphs encode user identity into the retrieval loop, and the three integration patterns that make personalized RAG actually work at production latencies.
RAG Doesn't Know Who You Are: How Knowledge Graphs Fix Personalized Retrieval
TL;DR.
- Standard RAG is query-scoped, not person-scoped: two users asking the same question retrieve the same documents and get the same answer.
- A
knowledge graphencodes the user as a typed entity with edges to interests, behaviors, and content — making the user a first-class participant in retrieval, not an afterthought.- Pre-retrieval enrichment, in-graph path traversal, and post-retrieval KG reranking are three distinct integration patterns with different latency and precision trade-offs; most teams start with enrichment and graduate to traversal.
- Microsoft's GraphRAG (2024) demonstrated that graph-structured context outperforms naive chunking for global queries — but that's corpus-side; user-side KG is an orthogonal and underexplored lever with equivalent upside.
- If you only remember one thing: retrieval relevance has two dimensions — topical fit to the query and personal fit to the user — and the second dimension only exists if you've modeled the user structurally.
Every team building LLM-powered features eventually hits the same wall. The retrieval is working. The generation is coherent. But the output feels like it was written for nobody in particular, because it was. Standard RAG systems are query-answering machines, not user-understanding machines. They retrieve documents that match a query string; they have no concept of who typed it, what they care about, or what they've already seen. Adding a knowledge graph to the retrieval loop is how you close that gap — not by making retrieval smarter about the corpus, but by making it smarter about the person.
This post is for engineers who've already shipped RAG and are asking what's next. We'll cover why the user model is structurally absent from standard retrieval architectures, how a knowledge graph fills that absence, and the three patterns for wiring graph context into retrieval without blowing your latency budget.
What Is Graph-Augmented RAG for Personalization?
Graph-augmented RAG for personalization is a retrieval architecture in which a user's structured representation in a knowledge graph — their interests, interaction history, inferred preferences, and relationships to content entities — is used to modify, filter, or rerank retrieved documents before they enter the LLM context window. The graph is not a replacement for a vector index; it is a user model that makes vector retrieval person-aware.
The original RAG formulation from Lewis et al. (2020) treats retrieval as a function of the query alone: given query q, retrieve top-k documents D by embedding similarity, then condition generation on (q, D) (Lewis et al., 2020). This is correct for knowledge-intensive tasks where the goal is factual accuracy. It is incomplete for personalization tasks where the goal is relevance to a specific user. Graph-augmented RAG extends the retrieval function to f(q, u) where u is a user representation derived by traversing the user's node in the knowledge graph.
Why RAG's Retrieval Is Query-Scoped, Not Person-Scoped
The framing of RAG as "retrieval + generation" obscures a category error. Retrieval in standard RAG is document retrieval — finding passages that are similar to the query vector. That is a corpus operation. It knows nothing about the consumer of the answer.
Consider two users on a content platform: a jazz musician asking "what should I listen to tonight?" and a first-year medical student asking the same question. Identical query. Identical retrieval. Identical response. This is not a model failure; it is an architecture failure. The model was never given any information that would let it distinguish the two users.
Vector embeddings encode semantic content. They do not encode user identity. Even if you append user history to the query before embedding — "[listened to Coltrane, Kind of Blue] what should I listen to tonight?" — you're now stuffing behavioral context into a single embedding that collapses all relational structure into one point in space. The information is there but it's flattened. A knowledge graph keeps the structure: the user is a node, "Kind of Blue" is a node, "jazz" is a genre node, Miles Davis is an artist node, and the edges between them carry type and weight. Traversing from the user node returns a structured neighborhood that can be used to condition retrieval in ways that are precise, auditable, and not subject to embedding drift.
This is the core argument for graphs in personalized RAG: they preserve relational structure that embeddings destroy during compression.
How a Knowledge Graph Encodes the User
In a personalization knowledge graph, the user is not a feature vector — they are an entity node with outgoing and incoming typed edges (Hogan et al., 2021). A minimal user subgraph looks like this:
User(id=u42)
--[LISTENED_TO, count=14, last=2026-05-20]--> Track(id=t_blue_train)
--[PREFERS_GENRE, weight=0.91]--> Genre(jazz)
--[SIMILAR_TO]--> User(id=u17) ← collaborative edge
--[INFERRED_INTEREST]--> Artist(id=a_coltrane)
--[COMPLETED_ONBOARDING_STEP]--> OntologyNode(bebop)
Each edge type carries different semantics and different retrieval implications. LISTENED_TO with a high count is strong behavioral signal. INFERRED_INTEREST is a model-generated edge — weaker, but available from day zero via onboarding. SIMILAR_TO is a collaborative filtering edge, computed offline, that lets a new user inherit the retrieval context of behaviorally similar users.
The depth of traversal matters. Depth-1 from the user node (direct edges) gives you explicit preferences. Depth-2 gives you interest neighborhoods — genres the user hasn't explored but their graph neighbors have. Depth-3 starts connecting to content nodes directly, enabling path-based retrieval without a vector index at all.
What makes this a knowledge graph rather than a feature store is the typed, traversable edge structure. A feature store answers "what are user u42's top genres?" A graph answers "what is the shortest path between user u42 and content node c_miles_davis_quintet, and through which interest nodes does that path pass?" The second question is what enables explanation: you can tell the user exactly why a piece of content was retrieved.
Three Patterns for Integrating KG into RAG
Pattern 1 — Pre-Retrieval Query Enrichment
The lowest-complexity integration. Before embedding the user's query, traverse the user's subgraph to extract a context summary, then concatenate it to the query string or use it to construct a hybrid query.
def enrich_query(user_id: str, raw_query: str, graph: KnowledgeGraph) -> str:
prefs = graph.traverse(user_id, depth=1, edge_types=["PREFERS_GENRE", "INFERRED_INTEREST"])
top_interests = [n.label for n in prefs[:5]]
return f"[User interests: {', '.join(top_interests)}] {raw_query}"
This is fast — a depth-1 graph lookup is 1-5ms against an in-memory graph — and it meaningfully shifts the embedding. The enriched query vector now sits closer to personalized content in the embedding space. The downside is that you're still constrained by whatever the embedding can compress. If the user's interest set is complex or multi-dimensional, some structure is lost.
When to use it: Early in your personalization stack, when you have a vector index already deployed and want to add personalization without restructuring retrieval. This approach is drop-in compatible with any RAG framework (LangChain, LlamaIndex, custom) — you're only modifying the query construction step.
Pattern 2 — In-Graph Path Retrieval
Instead of using the graph only to enrich the query, you use the graph as a first-class retrieval index. Content nodes live in the graph alongside user nodes. Retrieval becomes graph traversal: start from the user node, traverse through interest and preference edges, and return content nodes reachable within a depth or score budget.
This is the approach that Microsoft's GraphRAG work points toward — not for user personalization specifically, but for corpus-level structure (Edge et al., 2024). Their finding: representing document relationships as a graph and summarizing community structures produces dramatically better answers for queries that require synthesizing across documents. The user-side analog is equivalent: representing user-content relationships as a graph and traversing them produces dramatically better personalization for queries that require understanding the whole user.
In-graph retrieval has two flavors:
Graph-only retrieval. Skip the vector index. Retrieve content nodes solely by traversal from the user node. This works well when the graph is dense (every content item is linked to the relevant taxonomy nodes) and when the query is facet-driven rather than semantic ("show me jazz from the 1960s" → traverse via Genre and Decade nodes). It breaks down when the query has long-tail semantic content that isn't captured by the graph's entity types.
Hybrid graph + vector retrieval. Use the graph to generate a candidate filter or a set of anchor nodes, then use vector similarity within that filtered candidate set. For example: traverse from user → Genre(jazz) → Artist nodes → Track nodes to get a filtered candidate pool of 500 jazz tracks the user's graph is connected to, then run embedding similarity within that pool for the specific query. This combines the precision of graph traversal with the semantic flexibility of embeddings.
Hybrid is where most production systems land. The graph provides the personalization scaffold; the vector index provides the semantic fine-tuning. Neither alone is sufficient.
Pattern 3 — Post-Retrieval KG Reranking
Retrieve with your existing vector system (query-scoped, no personalization). Then, after retrieving top-k candidates, score each candidate against the user's graph subgraph and rerank. The reranking score is a function of graph proximity: how many hops from the user node to the content node, through how many high-weight edges?
def kg_rerank(user_id: str, candidates: list[Content], graph: KnowledgeGraph) -> list[Content]:
scores = []
for c in candidates:
path = graph.shortest_path(user_id, c.entity_id)
graph_score = 1.0 / (1.0 + path.hop_count) * path.edge_weight_product
final_score = 0.6 * c.vector_score + 0.4 * graph_score
scores.append((c, final_score))
return [c for c, _ in sorted(scores, key=lambda x: -x[1])]
This is the highest-compatibility pattern — zero changes to the retrieval pipeline, personalization applied at the reranking layer. It's also the most interpretable: the reranking signal is a path in the graph, auditable edge by edge. HippoRAG (2024) demonstrates a related insight: structuring retrieved passages as graph associations before reranking significantly improves multi-hop reasoning (Gutiérrez et al., 2024).
When to use it: When your retrieval pipeline is locked (managed service, heavy operational cost to change) and you want to layer personalization on top. Also when auditability is a hard requirement — GDPR Article 22 right-to-explanation is much easier to satisfy when personalization is a graph path than when it's an embedding delta.
Cold Start: Why KGs Work Before Embeddings Have Signal
This is where the graph's structural advantage is most pronounced. Embeddings require behavioral data to learn meaningful user representations. A new user has no behavioral data. Collaborative filtering has the same problem. Both fail at day zero.
A knowledge graph can be populated at day zero through three mechanisms:
Onboarding-as-graph-construction. Each onboarding question maps to a graph edge. "I like jazz and classical" creates PREFERS_GENRE edges before the user has listened to a single track. These are weak-signal edges — lower weight than behavioral edges — but they're structurally valid and immediately traversable for retrieval. The graph is ready to serve personalized retrieval on the first session.
Ontology-driven priors. A well-designed ontology for your domain lets you infer interests from stated preferences. If a user says they like "bebop jazz," the ontology knows that bebop implies interest in Charlie Parker, Dizzy Gillespie, fast tempos, and complex harmonics. These inferences are encoded as graph edges at onboarding time. Retrieval on the first session is already more personalized than pure popularity ranking.
Synthetic user cloning. A synthetic clone is a proxy user node created by averaging the graph neighborhoods of the N most similar users (by onboarding attributes) to a new user. The clone's interest and content edges are inherited by the new user's node, giving retrieval a starting point grounded in observed behavior from real users. As the new user generates behavioral signal, clone-derived edges are progressively down-weighted and replaced by observed edges. The cold-start problem becomes a warm-start — p50 < 30ms retrieval latency from session one.
The graph doesn't solve cold start by predicting what a specific new user wants. It solves it by making structural knowledge (ontology, user similarity, stated preference) immediately traversable in the same system that will later serve behavioral signal. The retrieval mechanism doesn't change; the graph just has more edges over time.
Latency Budget: What KG Traversal Actually Costs in Production
The common objection to graph-augmented retrieval is latency. The concern is well-founded but often overstated. Let's disaggregate.
Graph store options. An in-memory graph store (Neo4j in-memory mode, Tigergraph, RedisGraph) returns depth-1 to depth-2 traversal results in 1-10ms for user subgraphs of typical size (hundreds of edges). A disk-backed graph database (Neo4j Community, JanusGraph) adds 10-30ms for the same query. Distributed graph systems (AWS Neptune, Azure Cosmos DB Gremlin) add 20-50ms but scale horizontally. For a personalization system, the right choice depends on your graph size and write frequency, not on RAG latency requirements alone.
The LLM dominates. A GPT-4-class generation call costs 400-2000ms. A Claude Sonnet-class call costs 200-800ms. Streaming reduces perceived latency but not total latency. Against this baseline, adding 5-15ms for graph traversal is rounding error. The retrieval step that graph enrichment is modifying — the vector index query — costs 20-80ms on most hosted vector stores (Pinecone, Weaviate, Qdrant). The KG overhead is less than the vector index overhead in most configurations.
Where it gets expensive. Multi-hop path queries (depth 3+) on large graphs (millions of nodes) can take 50-200ms. Graph shortest-path algorithms across an unfiltered graph are O(V + E) in the worst case. The mitigation is scope: you are not running global graph queries. You are traversing a user's local neighborhood, which is bounded by the number of edges per user node (typically 10-500). Pre-compute the user's depth-2 neighborhood as a cached subgraph and refresh it on interaction events. Traversal of a cached subgraph is a memory operation — sub-millisecond.
A practical production budget: 5ms for KG subgraph lookup (cached), 40ms for vector retrieval, 10ms for KG reranking (if post-retrieval pattern), 600ms for generation. Total: ~655ms, well within user expectation for an LLM feature. The graph adds 15ms total against a 600ms generation call. That is not a performance problem.
What to Build First
If you're starting from an existing RAG system and want to add knowledge graph personalization, the sequence matters.
Week 1-2: Build the user subgraph. Start with explicit behavioral data you already have: clicks, views, completions, ratings, purchases. Map these to typed edges in a simple graph schema. User → entity relationships only, no corpus-side graph yet. A property graph (Neo4j, Amazon Neptune) or even a graph stored in Redis as adjacency lists works fine at this stage. Don't over-engineer the ontology yet.
Week 3-4: Implement pre-retrieval enrichment. Take depth-1 traversal from the user node (top interests by edge weight), serialize to a natural language prefix, prepend to every query before embedding. A/B test against your baseline. Measure NDCG@10, CTR, and session depth. You will see lift on returning users immediately. New users will show no difference yet — that's the cold start problem you haven't addressed.
Week 5-6: Add ontology edges for cold start. Define your domain ontology: genre → artist → era → mood, or product category → brand → price tier → use case, or whatever fits your domain. Map onboarding questions to graph edges. Now new users have a populated subgraph from session one, and your enrichment logic applies to them immediately. Measure day-zero session depth and first-session return rate.
Month 2: Evaluate hybrid retrieval. Once your graph is populated for >20% of your active users, run an experiment with in-graph candidate filtering before vector retrieval (Pattern 2). Compare retrieval precision at k=10, k=50. If your content taxonomy is well-structured, you will see significant improvement in retrieval precision for returning users — the graph is filtering out off-preference candidates that vector similarity would otherwise surface.
Month 3: Post-retrieval reranking for auditability. If you're in a regulated domain or need right-to-explanation capability, implement the KG reranking layer. This gives you an auditable retrieval path for every result — critical for compliance and for building user trust features ("Here's why we retrieved this for you").
The knowledge graph is not a complete replacement for the vector index. It is the layer that makes the vector index person-aware. Start simple — enrichment is a one-week implementation with measurable day-one lift. Grow the graph's role as your ontology matures and your behavioral data accumulates. The architecture is designed to add precision incrementally, not require a full rewrite to show value.
The teams we've seen fail at this build the graph first and retrieval second. Build retrieval first, make it work, then add the graph as the personalization layer on top of working infrastructure. The graph is a user model, not a database migration.
×marble is the personalization graph.
One API. A living knowledge graph per user. Day-zero ready, explainable by construction. We built it so you don't have to.