Immutable. This exact content is served forever at /api/v1/blob/139a258b4f57ad38.
---
name: neo4j-graphrag-skill
description: Build GraphRAG retrieval pipelines on Neo4j using the neo4j-graphrag Python
package (formerly neo4j-genai). Covers retriever selection (VectorRetriever,
HybridRetriever, VectorCypherRetriever, HybridCypherRetriever, Text2CypherRetriever),
retrieval_query Cypher fragments, query_params, pipeline wiring (GraphRAG + LLM),
embedder setup, index creation, and LangChain/LlamaIndex integration. Does NOT handle
KG construction from documents — use neo4j-document-import-skill. Does NOT handle
plain vector search — use neo4j-vector-index-skill. Does NOT handle GDS analytics —
use neo4j-gds-skill. Does NOT handle agent memory — use neo4j-agent-memory-skill.
version: 1.0.0
status: active
allowed-tools: Bash WebFetch
---
# Neo4j GraphRAG Skill
## When to Use
- Building GraphRAG retrieval pipelines with `neo4j-graphrag` Python package
- Choosing between VectorRetriever, HybridRetriever, VectorCypherRetriever, HybridCypherRetriever
- Writing `retrieval_query` Cypher fragments that traverse the graph after vector lookup
- Wiring retriever + LLM into a `GraphRAG` pipeline
- Debugging low retrieval quality (when to use graph traversal vs plain vector)
- Integrating Neo4j with LangChain (`langchain-neo4j`), LlamaIndex, or Haystack
## When NOT to Use
- **KG construction from documents** → `neo4j-document-import-skill`
- **Plain vector/semantic search without graph traversal** → `neo4j-vector-index-skill`
- **Hybrid search that combines vector with fulltext or other ranked sources** → `neo4j-vector-index-skill`
- **GDS algorithms (PageRank, Louvain, node embeddings)** → `neo4j-gds-skill`
- **Agent long-term memory** → `neo4j-agent-memory-skill`
- **Writing raw Cypher queries** → `neo4j-cypher-skill`
---
## Step 1 — Install
```bash
pip install neo4j-graphrag
# LLM/embedder extras (choose one or more):
pip install neo4j-graphrag[openai] # OpenAI + AzureOpenAI
pip install neo4j-graphrag[google] # VertexAI
pip install neo4j-graphrag[anthropic] # Anthropic
pip install neo4j-graphrag[ollama] # Ollama (local)
pip install neo4j-graphrag[cohere] # Cohere
pip install neo4j-graphrag[sentence-transformers] # local embeddings
# BREAKING: old package `neo4j-genai` is deprecated — imports also changed:
pip uninstall neo4j-genai
# neo4j_genai.retrievers → neo4j_graphrag.retrievers
# neo4j_genai.generation → neo4j_graphrag.generation
```
Requires: Python ≥ 3.10, Neo4j ≥ 5.18.1 or Aura ≥ 5.18.0.
---
## Step 2 — Choose Retriever
```
Has fulltext index? YES → Hybrid variants (better recall)
NO → Vector variants (baseline)
Needs graph context after vector lookup? YES → Cypher variants
NO → plain variants
For natural-language-to-Cypher? → Text2CypherRetriever (no embedder needed)
For multi-tool LLM routing? → ToolsRetriever
Using external vector DB? → WeaviateNeo4jRetriever / PineconeNeo4jRetriever / QdrantNeo4jRetriever
```
| Retriever | Vector | Fulltext | Graph | When to use |
|---|:---:|:---:|:---:|---|
| `VectorRetriever` | ✓ | — | — | Baseline; quick start |
| `HybridRetriever` | ✓ | ✓ | — | Better recall; no graph context |
| `VectorCypherRetriever` | ✓ | — | ✓ | GraphRAG without fulltext |
| `HybridCypherRetriever` | ✓ | ✓ | ✓ | **Production GraphRAG — default choice** |
| `Text2CypherRetriever` | — | — | ✓ | LLM generates Cypher; no embedder |
| `ToolsRetriever` | varies | varies | varies | Multi-retriever LLM routing |
For custom Cypher hybrid search outside the `neo4j-graphrag` retriever APIs, use `neo4j-vector-index-skill`.
**Vector backend selection [v1.16+, auto]**: on Neo4j 2026.01+ all four vector/hybrid retrievers auto-route through the Cypher 25 `SEARCH ... WHERE` clause when filters are SEARCH-compatible (simple AND comparisons) and all filter props are declared in the index `WITH [n.prop]` list. `$or`, `$in`, `$like`, or undeclared props → automatic fallback to `db.index.vector.queryNodes()` procedure path (with warning log). Declare filterable properties via `filterable_properties=[...]` on `create_vector_index()`.
---
## Step 3 — Create Indexes (run once)
```cypher
// Vector index (all retrievers need this)
CREATE VECTOR INDEX chunk_embedding IF NOT EXISTS
FOR (c:Chunk) ON (c.embedding)
OPTIONS { indexConfig: {
`vector.dimensions`: 1536,
`vector.similarity_function`: 'cosine'
} };
// Fulltext index (Hybrid retrievers only)
CREATE FULLTEXT INDEX chunk_fulltext IF NOT EXISTS
FOR (c:Chunk) ON EACH [c.text];
// Confirm ONLINE before ingesting:
SHOW INDEXES YIELD name, state
WHERE name IN ['chunk_embedding', 'chunk_fulltext']
RETURN name, state;
// Both must show state = 'ONLINE'
```
If index not ONLINE: wait, poll every 5s. Do NOT start ingestion until ONLINE.
---
## Step 4 — Core Pattern (HybridCypherRetriever)
```python
from neo4j import GraphDatabase
from neo4j_graphrag.retrievers import HybridCypherRetriever
from neo4j_graphrag.embeddings import OpenAIEmbeddings
from neo4j_graphrag.generation import GraphRAG
from neo4j_graphrag.llm import OpenAILLM
driver = GraphDatabase.driver("neo4j+s://<host>:7687", auth=("neo4j", "<password>"))
embedder = OpenAIEmbeddings(model="text-embedding-3-small") # 1536 dims — match index
# retrieval_query: Cypher fragment executed after vector lookup.
# `node` = matched node from vector index (AUTO-INJECTED — do NOT declare)
# `score` = similarity float (AUTO-INJECTED — do NOT declare)
# MUST include RETURN clause. MUST return `score` column.
retrieval_query = """
MATCH (node)<-[:HAS_CHUNK]-(article:Article)
OPTIONAL MATCH (article)-[:MENTIONS]->(org:Organization)
RETURN node.text AS chunk_text,
article.title AS article_title,
collect(DISTINCT org.name) AS mentioned_organizations,
score
"""
retriever = HybridCypherRetriever(
driver=driver,
vector_index_name="chunk_embedding",
fulltext_index_name="chunk_fulltext",
retrieval_query=retrieval_query,
embedder=embedder,
)
llm = OpenAILLM(model_name="gpt-4o", model_params={"temperature": 0})
rag = GraphRAG(retriever=retriever, llm=llm)
response = rag.search(query_text="Who does Alice work for?", retriever_config={"top_k": 5})
print(response.answer)
```
---
## Step 5 — query_params (Parameterized retrieval_query)
Pass runtime parameters into `retrieval_query` via `retriever_config`:
```python
retrieval_query = """
MATCH (node)<-[:HAS_CHUNK]-(article:Article)-[:MENTIONS]->(org:Organization)
WHERE org.name = $entity_name
RETURN node.text AS chunk_text, article.title AS title, score
"""
retriever = VectorCypherRetriever(
driver=driver,
index_name="chunk_embedding",
retrieval_query=retrieval_query,
embedder=embedder,
)
# Pass query_params inside retriever_config on each search:
response = rag.search(
query_text="What happened at Apple?",
retriever_config={"top_k": 10, "query_params": {"entity_name": "Apple"}},
)
# Direct retriever call (without GraphRAG wrapper):
results = retriever.search(
query_text="What happened at Apple?",
top_k=10,
query_params={"entity_name": "Apple"},
)
```
---
## Step 6 — Filters (Pre-filter before vector search)
```python
# Filter reduces candidate pool BEFORE vector similarity ranking
results = retriever.search(
query_text="quarterly results",
top_k=5,
filters={"date": {"$gte": "2024-01-01"}},
)
# Supported operators: $eq $ne $lt $lte $gt $gte $between $in $like $ilike
```
---
## Step 7 — VectorRetriever (return_properties)
```python
from neo4j_graphrag.retrievers import VectorRetriever
retriever = VectorRetriever(
driver=driver,
index_name="chunk_embedding",
embedder=embedder,
return_properties=["text", "source", "page_number"], # subset of node props
)
# No retrieval_query needed — returns node properties directly
```
---
## Step 8 — Text2CypherRetriever (no embedder)
```python
from neo4j_graphrag.retrievers import Text2CypherRetriever
# LLM generates Cypher from natural language; no vector index needed
retriever = Text2CypherRetriever(
driver=driver,
llm=OpenAILLM(model_name="gpt-4o"),
neo4j_schema=None, # auto-fetched from db; or pass string
examples=["Q: Who works at Neo4j? A: MATCH (p:Person)-[:WORKS_AT]->(c:Company {name:'Neo4j'}) RETURN p.name"],
)
results = retriever.search(query_text="Which people work at Neo4j?")
```
If `neo4j_schema=None`: retriever fetches schema automatically. For large schemas, pass a trimmed string to reduce LLM prompt size.
**Destructive-query guard [v1.16+]**: `Text2CypherRetriever` runs `EXPLAIN` on the generated Cypher before execution and rejects queries that produce writes (`CREATE`, `MERGE`, `DELETE`, `SET`, `REMOVE`, etc.). LLM-generated writes are never executed against the graph.
---
## Step 9 — Custom Prompt Template
```python
from neo4j_graphrag.generation.prompts import RagTemplate
custom_template = RagTemplate(
template="""Answer the question using ONLY the context below.
Context: {context}
Question: {query_text}
Answer:""",
expected_inputs=["context", "query_text"],
)
rag = GraphRAG(retriever=retriever, llm=llm, prompt_template=custom_template)
```
---
## Common Errors
| Error | Cause | Fix |
|---|---|---|
| `ModuleNotFoundError: neo4j_genai` | Old package installed | `pip uninstall neo4j-genai && pip install neo4j-graphrag` |
| `retrieval_query` returns 0 rows | Missing `MATCH` or wrong rel direction | Add `EXPLAIN` prefix; verify node/rel names with `CALL db.schema.visualization()` |
| `KeyError: 'score'` in results | `retrieval_query` missing `score` in RETURN | Add `score` to every `retrieval_query` RETURN clause |
| `score` variable not found | Declared `score` as Cypher variable | Remove it — `score` is auto-injected; never re-declare |
| `node` variable not found | Wrong variable name in retrieval_query | Use exactly `node` (lowercase); auto-injected by retriever |
| Embedding dimension mismatch | Index created with different dims | Drop index, recreate with correct `vector.dimensions`, re-embed all chunks |
| `IndexNotFoundError` | Index name typo or index not ONLINE | `SHOW INDEXES YIELD name, state` — verify name and state=ONLINE |
| Low recall on hybrid search | Fulltext index not on right property | Fulltext index must cover same property as `node.text` in retrieval_query |
| `perform_entity_resolution` slow | Large corpus with many entities | Set `perform_entity_resolution=False` for initial testing; enable in production |
| `TypeError: coroutine` | Calling `pipeline.run_async()` without `await`/`asyncio.run()` | Wrap in `asyncio.run(pipeline.run_async(...))` |
| Empty KG after pipeline run | `on_error="IGNORE"` masks extraction failures | Temporarily set `on_error="RAISE"` to see LLM extraction errors |
---
## Embedder Quick Reference
```python
from neo4j_graphrag.embeddings import (
OpenAIEmbeddings, # OpenAI text-embedding-3-*
AzureOpenAIEmbeddings, # Azure-hosted OpenAI
VertexAIEmbeddings, # Google Vertex AI
MistralAIEmbeddings, # Mistral
CohereEmbeddings, # Cohere embed-v3
OllamaEmbeddings, # Local via Ollama
SentenceTransformerEmbeddings, # Local HuggingFace
)
# Dimension mapping (must match vector index):
# text-embedding-3-small → 1536
# text-embedding-3-large → 3072
# text-embedding-ada-002 → 1536
# all-MiniLM-L6-v2 → 384
```
All embedders include automatic rate limiting with exponential backoff.
---
## LLM Quick Reference
```python
from neo4j_graphrag.llm import (
OpenAILLM,
AzureOpenAILLM,
AnthropicLLM,
VertexAILLM,
MistralAILLM,
CohereLLM,
OllamaLLM,
)
# Any LangChain chat model also accepted by GraphRAG
```
---
## GraphRAG.search() Full Signature
```python
response = rag.search(
query_text="...",
retriever_config={
"top_k": 5, # candidates per search (default 5)
"query_params": {...}, # passed to retrieval_query Cypher
"filters": {...}, # pre-filter before vector search
},
return_context=False, # True: include retrieved chunks in response
response_fallback="No context found.", # returned when retriever yields nothing
)
# response.answer → str
# response.retriever_result → RawSearchResult (if return_context=True)
```
---
## Failure Recovery
- 0 results from retrieval: run `retriever.search()` directly (skip LLM); check `top_k`, index name, embedding dims
- LLM hallucinating: reduce `top_k`, improve `retrieval_query` to return more specific context
- Slow queries: add `LIMIT` inside `retrieval_query` on expensive expansions; use `filters` to pre-reduce candidates
- Embedding dimension mismatch: `SHOW INDEXES YIELD name, options` — check `vector.dimensions`
---
## References
- [references/retrievers.md](references/retrievers.md) — full retriever API, all constructor params, result_formatter, ToolsRetriever, external DB retrievers
- [GraphRAG Python Docs](https://neo4j.com/docs/neo4j-graphrag-python/current/)
- [neo4j-graphrag GitHub](https://github.com/neo4j/neo4j-graphrag-python)
---
## Checklist
- [ ] `neo4j-genai` uninstalled; `neo4j-graphrag` installed; import paths updated
- [ ] Vector index ONLINE before ingesting or querying
- [ ] Fulltext index ONLINE if using Hybrid retriever
- [ ] Embedding dims match `vector.dimensions` in index config
- [ ] `retrieval_query` includes `node` and `score` in RETURN clause (both required)
- [ ] `node` and `score` NOT re-declared in `retrieval_query` — auto-injected
- [ ] `query_params` passed via `retriever_config` or direct `retriever.search()` arg
- [ ] `retriever_config={"top_k": N}` set on `rag.search()` (default 5)
- [ ] Credentials in env vars; never hardcoded