Skip to main content

GraphRAG

When to use a graph instead of vectors

Vector RAG is great for semantic similarity: “find documents about X”. It struggles with relationship reasoning:
  • “Which projects does Alice’s direct reports work on?”
  • “How many bugs has team B closed in the last 30 days?”
  • “Trace this transaction’s path through the ledger.”
Those questions need a graph database. GraphRAG generates a Cypher query from the natural-language question, runs it against Neo4j or Memgraph, and returns the rows. Combine the two via HybridRetriever for the best coverage.

Architecture

CypherStore

Low-level interface for any graph DB that speaks the Cypher / Bolt protocol.

Neo4jCypherStore

Requires: npm install neo4j-driver (already an optional peer dep of @agentium/core). Defaults pull from NEO4J_URI, NEO4J_USER, NEO4J_PASSWORD environment variables.

MemgraphCypherStore

Memgraph speaks the same Bolt protocol as Neo4j, so the adapter just sub-classes:
Differences from Neo4j: Memgraph defaults to no auth (username: ""). It’s faster for streaming analytics and ships some unique procedures (e.g. mgps.*).

runCypher(cypher, params?)

Execute any Cypher query, return rows as CypherRecord[]:
Always parameterize. Never string-interpolate user input into Cypher.

getSchema()

Returns the live schema by calling db.labels(), db.relationshipTypes(), db.propertyKeys():
This is what GraphRAGRetriever reads to ground the LLM’s Cypher generation.

GraphRAGRetriever

LLM-to-Cypher with schema-aware prompting.

retrieve(query) return shape

Default system prompt

Pass systemPrompt to override for domain-specific instructions (e.g. “use the German names for nodes” or “always project full names + emails”).

Safety: automatic LIMIT

If the model omits LIMIT, GraphRAGRetriever appends one with the configured maxRecords. This is the difference between a runaway query and a useful one. For destructive operations (CREATE, DELETE, MERGE, SET), the default system prompt says “prefer MATCH … RETURN”. For an extra hard fence, run a Cypher parser yourself or use a read-only Neo4j user.

Markdown fence stripping

Models sometimes wrap their Cypher in cypher ... fences despite being told not to. The retriever strips that automatically.

HybridRetriever

Compose vector search + graph search + optional rerank into a single retrieval pipeline.

Algorithm

  1. Parallel fan-out: vector search + graph retrieval run concurrently.
  2. Score normalization: vector cosine scores (0–1) and graph row-rank scores (1 - rank/N) are unioned.
  3. Reciprocal Rank Fusion: combine the two ranked lists with k=60 (the standard RRF constant). Each result keeps the better of its vector or graph rank, plus a small bonus for appearing in both.
  4. Optional rerank: if a Reranker is configured, the top topK * 3 fused results are passed to it for a final reorder.

HybridResult

Config

Either vector or graph may be omitted — HybridRetriever becomes a one-side pipeline. Useful when you want to keep the rerank-and-fuse boilerplate but only have one source.

Performance characteristics

Total Hybrid retrieve(): ~800ms for typical queries.

Best practices

  • Cache the schema at startup if your graph schema is stable. Read it once and pass it into GraphRAGRetriever via a custom systemPrompt.
  • Use a read-only DB user. The LLM should never have credentials that can DROP nodes.
  • Cap costs. Set maxRecords low (10-25) — the LLM can ask for more by paginating in subsequent calls if it really needs them.
  • Log all generated Cypher. Add a hook that emits result.cypher to your observability stack so you can spot prompt regressions.
  • Pair with vector for breadth. Pure GraphRAG misses semantic matches that aren’t in the graph. Pure vector misses relationship answers. Hybrid is almost always the right choice in production.

See also