Skip to main content

Agentic GraphRAG: When Similarity Search Isn't Enough

In the RAG post I flagged one failure I deliberately didn't fix: vanilla RAG can't reason across documents. Ask my digital twin "What did you build that combines security and machine learning?" and plain similarity search hands the model a pile of loosely related chunks and hopes one of them connects the dots. It usually doesn't.

This is the fix. Instead of treating my knowledge as a bag of disconnected passages, I store it as a graph — facts and the relationships between them — and let an agent walk that graph to assemble an answer. This is the hero of the backend: agentic GraphRAG.


The limitation we're actually fixing

Plain RAG retrieves chunks independently. Each chunk is judged only by how similar it is to the question. That works when the answer sits inside one passage. It falls apart when the answer is a connection between facts that live in different places.

"Security and machine learning" → there's no single sentence in my notes that says both. The answer is a path: I built an APT-detection system → it uses Graph Neural Networks → that is machine learning → applied to cybersecurity. Similarity search can't follow that chain. A graph can.


The idea: facts as a graph, not a pile

A knowledge graph stores information as nodes (entities) and edges (relationships). I build mine with NetworkX:

import networkx as nx

G = nx.DiGraph()
G.add_node("PayRue", type="company", location="London")
G.add_node("APT Detection", type="project")
G.add_node("GNN", type="method")

G.add_edge("Ahmed", "PayRue", rel="worked_at")
G.add_edge("Ahmed", "APT Detection", rel="built")
G.add_edge("APT Detection", "GNN", rel="uses")
G.add_edge("APT Detection", "Cybersecurity", rel="domain")

Now "security and machine learning" isn't a keyword match — it's a traversable structure. The model doesn't have to guess that GNNs connect to security; the edge says so explicitly.


The architecture: three retrieval moves

GraphRAG keeps the two-phase shape from plain RAG (index once, retrieve per question) but changes how retrieval works. Every query runs three moves:

  1. Seed — find the entry points. I embed the question and use Pinecone to match it against the graph's nodes. "Security and machine learning" lands on the APT Detection and GNN nodes. (This is the one place plain vector search still earns its keep — finding where to start.)
  2. Traverse — from those seeds, walk the graph with NetworkX to pull in connected facts. This is the step similarity search can't do: it follows relationships, not just resemblance.
  3. Generate — hand the walked subgraph + the question to an open-source Llama model on Groq, which writes a grounded answer from the assembled facts.

The actual interface mirrors that exactly:

from kg.graph_store import get_graph

kg = get_graph()
res = kg.retrieve("What did you build that combines security and machine learning?")

res["seeds"] # entry-point nodes matched from the query
res["graph_path"] # {"nodes": [...], "edges": [...]} — the subgraph we walked
res["facts"] # human-readable facts along that path
res["images"] # optional media tied to those nodes

Notice graph_path: the answer comes with its reasoning trail. You can see why the model said what it said — which nodes, which edges. Plain RAG gives you a citation; GraphRAG gives you the path.


Why "agentic"

The three moves aren't a fixed pipeline — they're a decision flow, orchestrated with LangGraph. The agent decides what to do based on what it finds:

from langgraph.graph import StateGraph, END

g = StateGraph(State)
g.add_node("seed", seed_from_query) # vector search → entry nodes
g.add_node("traverse", walk_graph) # NetworkX neighbors → facts
g.add_node("generate", answer_with_groq) # grounded generation

g.add_conditional_edges("seed", has_seeds, {True: "traverse", False: "generate"})
g.add_edge("traverse", "generate")
g.add_edge("generate", END)

app = g.compile()

That conditional edge is the whole point. Ask "the weather today" and the seed step finds no matching nodes — nothing in my knowledge base is about weather. Instead of hallucinating, the agent routes straight to a graceful "I don't have that." The graph is also a boundary: if a fact isn't in it, the system knows it doesn't know.


Where it shines — and where it doesn't (the honest part)

Shines when:

  • The answer is a connection across topics (the security + ML case).
  • You need traceable, explainable answers — the graph_path is the audit trail.
  • You need a hard knowledge boundary — no seeds, no hallucinated answer.

Costs you:

  • Graph construction is the real work. Garbage edges produce confident-but-wrong paths. Extracting clean entities and relationships from raw text is the hard part, not the retrieval.
  • More moving parts than plain RAG — a graph store and a vector index and an orchestrator. Don't reach for it until similarity search actually fails you. For a single-passage FAQ, plain RAG is the right tool.
  • Traversal needs limits. Walk too far from the seeds and you drag in noise. Bounded hops keep facts relevant.

How this looks in the real build

The digital twin on bargady.online runs exactly this: my CV, projects, and notes parsed into a NetworkX knowledge graph, seeded via Pinecone, traversed per question, and answered by Llama on Groq — all orchestrated by LangGraph. Ask it about London and it walks to the PayRue node. Ask it to connect security and ML and it follows the edge through GNNs. Ask it about the weather and it admits it doesn't know.


The one-sentence takeaway

Plain RAG retrieves similar facts; GraphRAG retrieves connected ones — and the connection is exactly what reasoning is made of.


Part of the build-in-public series on docs.bargady.online. I build real AI systems and explain how they actually work — no hype.