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.”
HybridRetriever for the best coverage.
Architecture
CypherStore
Low-level interface for any graph DB that speaks the Cypher / Bolt protocol.
Neo4jCypherStore
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:
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[]:
getSchema()
Returns the live schema by calling db.labels(), db.relationshipTypes(), db.propertyKeys():
GraphRAGRetriever reads to ground the LLM’s Cypher generation.
GraphRAGRetriever
LLM-to-Cypher with schema-aware prompting.
retrieve(query) return shape
Default system prompt
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 incypher ... 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
- Parallel fan-out: vector search + graph retrieval run concurrently.
- Score normalization: vector cosine scores (0–1) and graph row-rank scores (1 - rank/N) are unioned.
- 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. - Optional rerank: if a
Rerankeris configured, the toptopK * 3fused results are passed to it for a final reorder.
HybridResult
Config
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
| Step | Latency |
|---|---|
store.getSchema() (cached at the DB level) | ~5ms |
| LLM Cypher generation (GPT-4o-mini) | ~600ms |
runCypher (small Neo4j) | ~10ms |
| RRF fusion | < 1ms |
| Rerank (Cohere, 30 candidates) | ~200ms |
Best practices
- Cache the schema at startup if your graph schema is stable. Read it once and pass it into
GraphRAGRetrievervia a customsystemPrompt. - Use a read-only DB user. The LLM should never have credentials that can DROP nodes.
- Cap costs. Set
maxRecordslow (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.cypherto 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
- Embeddings — the vector half
- Reranking — the optional final step
- Vector Stores — what to plug into
vector.store