Vector Embeddings vs. Knowledge Graphs for Code Intelligence
The debate over how to represent a codebase for LLM context usually splits into two camps: the "vector-everything" approach and the "graph-based" model. After benchmarking these against several real-world repositories using Claude Code and Cursor-style agent workflows, it's clear that neither is a silver bullet. Instead, they solve fundamentally different retrieval problems.
Vector-first tools operate by embedding files, functions, and docstrings into a semantic space. This is the "RAG 101" approach: you convert your code into chunks, store them in a vector database, and use cosine similarity to pull relevant snippets based on a natural language query. The primary advantage here is speed of implementation. You can have a working prototype running in an afternoon.
However, vector search is inherently "fuzzy." It excels at finding where a concept is discussed but struggles with how things are connected. For example, if you ask an LLM to find all calls to a specific method across a 100k-line codebase, a vector search might return the method definition and a few common usage sites, but it will almost certainly miss the long tail of calls. This is because semantic similarity doesn't equal structural dependency.
This is where Graph-based representations shine. By modeling code as a graph—where nodes are symbols (classes, functions, variables) and edges are relationships (inherits from, calls, imports)—you move from semantic guessing to deterministic lookup.
When building custom LLM pipelines, I've found that the "graph" approach is non-negotiable for complex refactoring tasks. If you are using a tool that relies solely on pgvector or ChromaDB, you'll notice a significant drop in accuracy when the LLM needs to trace a data flow across five different files. A graph allows the agent to perform a precise traversal: "Find the definition of X, see who implements X, and then find all references to those implementations."
The trade-off is the "indexing tax." Vectorizing a repo is a linear process of embedding and storing. Graph construction requires a full AST (Abstract Syntax Tree) parse of the entire project. If you're working with a language like TypeScript, you're dealing with complex dependency trees that make the initial indexing phase significantly heavier in terms of CPU and memory.
In my benchmarks, the "Hybrid" approach is the only one that holds up at scale. You use vector search for the initial "discovery" phase (e.g., "Where is the authentication logic handled?") and then switch to graph traversal to map out the actual call stack before feeding the context to the LLM.
If you are building your own tool, don't start by picking a database; start by defining your query patterns. If your users are asking "What does this module do?", vectors are sufficient. If they are asking "What happens if I change this function signature in api/v1/user.ts?", you need a graph. Without structural awareness, your LLM is just guessing based on keywords, and in a production codebase, "close enough" usually means a broken build.
All Replies (3)
Shocked by 500 false positives using vectors. Did the graph solve this for you immediately?
Curious if using a graph to prune vector search results actually improves accuracy or just slows things down?
Annoyed that vector stores miss exact references. Is anyone actually successfully running a hybrid setup?