Neo4j graphrag skill
Neo4j Skills for Coding and other Agents including Cypher
npx -y skills add neo4j-contrib/neo4j-skills --skill neo4j-graphrag-skillAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Build GraphRAG retrieval pipelines on Neo4j using the neo4j-graphrag Python package (v1.16.0+). Covers retriever selection (VectorRetriever, HybridRetriever, VectorCypherRetriever, HybridCypherRetriever, Text2CypherRetriever, ToolsRetriever), external vector DB retrievers (Weaviate, Pinecone, Qdrant), retrieval_query Cypher fragments, query_params, filters, GraphRAG pipeline wiring (GraphRAG + LLM + prompt), all LLM providers (OpenAI, Anthropic, VertexAI, Bedrock, Cohere, Mistral, Ollama), embedder setup, index creation, token usage tracking, Cypher 25 SEARCH clause, and LangChain/LlamaIndex integration. Does NOT handle KG construction — 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.
SKILL.md
18.2 KB, as published. Nobody here has run it
Neo4j GraphRAG Skill
When to Use
- Building GraphRAG retrieval pipelines with
neo4j-graphragPython package - Choosing between VectorRetriever, HybridRetriever, VectorCypherRetriever, HybridCypherRetriever
- Writing
retrieval_queryCypher fragments for graph-augmented context - Wiring retriever + LLM into a
GraphRAGpipeline - Using LLM-routed multi-retriever with
ToolsRetriever - Debugging low retrieval quality
- Integrating Neo4j with LangChain, 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
Retriever Selection
Has fulltext index?
YES → Hybrid variants (HybridRetriever / HybridCypherRetriever)
NO → Vector variants (VectorRetriever / VectorCypherRetriever)
Need graph traversal after vector lookup?
YES → Cypher variants (VectorCypherRetriever / HybridCypherRetriever)
NO → plain variants
Natural-language-to-Cypher? → Text2CypherRetriever (no embedder needed)
LLM should route between retrievers? → ToolsRetriever
Vectors stored in external DB? → WeaviateNeo4jRetriever / PineconeNeo4jRetriever / QdrantNeo4jRetriever
| Retriever | Vector | Fulltext | Graph | Best For |
|---|---|---|---|---|
VectorRetriever | ✓ | — | — | Baseline semantic search |
HybridRetriever | ✓ | ✓ | — | Better recall, no graph expansion |
VectorCypherRetriever | ✓ | — | ✓ | GraphRAG without fulltext |
HybridCypherRetriever | ✓ | ✓ | ✓ | Production GraphRAG — default |
Text2CypherRetriever | — | — | ✓ | NL→Cypher, no embedder |
ToolsRetriever | varies | varies | varies | LLM-routed multi-retriever |
WeaviateNeo4jRetriever | ✓ | — | ✓ | Vectors in Weaviate |
PineconeNeo4jRetriever | ✓ | — | ✓ | Vectors in Pinecone |
QdrantNeo4jRetriever | ✓ | — | ✓ | Vectors in Qdrant |
Install
pip install neo4j-graphrag[openai] # OpenAI LLM + embeddings
pip install neo4j-graphrag[anthropic] # Anthropic Claude
pip install neo4j-graphrag[google] # Vertex AI / Gemini
pip install neo4j-graphrag[bedrock] # Amazon Bedrock (boto3)
pip install neo4j-graphrag[cohere] # Cohere
pip install neo4j-graphrag[mistralai] # MistralAI
pip install neo4j-graphrag[ollama] # Ollama (local)
pip install neo4j-graphrag[weaviate] # Weaviate external retriever
pip install neo4j-graphrag[pinecone] # Pinecone external retriever
pip install neo4j-graphrag[qdrant] # Qdrant external retriever
Requires: Python >= 3.10, neo4j >= 5.17.0 (driver 6.x supported).
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)
// 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)
from neo4j import GraphDatabase
from neo4j_graphrag.embeddings import OpenAIEmbeddings
from neo4j_graphrag.generation import GraphRAG
from neo4j_graphrag.llm import OpenAILLM
from neo4j_graphrag.retrievers import HybridCypherRetriever
driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USERNAME, NEO4J_PASSWORD))
embedder = OpenAIEmbeddings(model="text-embedding-3-large") # OPENAI_API_KEY from env
# retrieval_query: Cypher fragment executed after the vector/fulltext lookup.
# Auto-injected variables: node (matched node) score (similarity float)
# MUST include a RETURN clause. score must appear in RETURN.
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-4.1", 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)
driver.close()
VectorCypherRetriever
from neo4j_graphrag.retrievers import VectorCypherRetriever
retriever = VectorCypherRetriever(
driver=driver,
index_name="chunk_embedding",
retrieval_query=retrieval_query,
embedder=embedder,
)
response = rag.search(
query_text="What happened at Apple?",
retriever_config={"top_k": 10},
)
Text2CypherRetriever
Translates natural language to Cypher using an LLM. No embedder required.
Security (v1.16.0+): Every LLM-generated Cypher is run through
EXPLAINfirst. Any statement classified as write/destructive raisesText2CypherRetrievalErrorinstead of executing — prevents prompt-injection attacks.
from neo4j_graphrag.retrievers import Text2CypherRetriever
retriever = Text2CypherRetriever(
driver=driver,
llm=OpenAILLM(model_name="gpt-4.1"),
neo4j_schema=None, # None = auto-fetch schema from DB; pass string to trim
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?")
ToolsRetriever (LLM-routed multi-retriever)
from neo4j_graphrag.retrievers import ToolsRetriever
tools_retriever = ToolsRetriever(
llm=llm,
retrievers=[vector_retriever, text2cypher_retriever],
)
# LLM decides which retriever(s) to invoke per query
# Convert any retriever to a standalone Tool:
tool = vector_retriever.convert_to_tool()
Filters (pre-filter before vector search)
results = retriever.search(
query_text="quarterly earnings",
top_k=5,
filters={
"date": {"$gte": "2024-01-01"},
"source": {"$eq": "10-K"},
},
)
# Operators: $eq $ne $lt $lte $gt $gte $between $in $like $ilike
query_params (parameterized retrieval_query)
retrieval_query = """
MATCH (node)<-[:HAS_CHUNK]-(a:Article)-[:MENTIONS]->(org:Organization {name: $entity_name})
RETURN node.text, a.title, score
"""
# Pass via retriever.search directly:
results = retriever.search(
query_text="What happened at Apple?",
top_k=10,
query_params={"entity_name": "Apple"},
)
# Or via GraphRAG.search:
response = rag.search(
query_text="What happened at Apple?",
retriever_config={"top_k": 10, "query_params": {"entity_name": "Apple"}},
)
Cypher 25 SEARCH Clause (v1.16.0, Neo4j 2026.x+)
# Enable SEARCH clause syntax in vector/hybrid retrievers (requires Neo4j 2026+)
retriever = VectorRetriever(
driver=driver,
index_name="chunk_embedding",
embedder=embedder,
use_search_clause=True,
)
ORDER BY on Cypher Retrievers (v1.16.0)
results = retriever.search(
query_text="...",
top_k=10,
order_by="score DESC",
)
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.
Custom Prompt Template
from neo4j_graphrag.generation.prompts import RagTemplate
template = RagTemplate(
template="""Answer using ONLY the context below.
Context: {context}
Question: {query_text}
Answer:""",
expected_inputs=["context", "query_text"],
)
rag = GraphRAG(retriever=retriever, llm=llm, prompt_template=template)
return_context and response_fallback
response = rag.search(
query_text="...",
retriever_config={"top_k": 5},
return_context=True, # include raw retrieved chunks
response_fallback="No relevant context.", # skip LLM call if retriever returns nothing
)
print(response.answer)
print(response.retriever_result) # RawSearchResult when return_context=True
Message History (multi-turn)
from neo4j_graphrag.message_history import InMemoryMessageHistory
history = InMemoryMessageHistory()
r1 = rag.search(query_text="Who is Alice?", message_history=history)
r2 = rag.search(query_text="Where does she work?", message_history=history)
External Retrievers
# --- Weaviate ---
from neo4j_graphrag.retrievers import WeaviateNeo4jRetriever
import weaviate
weaviate_client = weaviate.connect_to_local()
retriever = WeaviateNeo4jRetriever(
driver=driver,
client=weaviate_client,
collection="Chunk",
id_property_external="neo4j_id",
id_property_neo4j="id",
retrieval_query=retrieval_query,
node_label_neo4j="Chunk", # optional: speeds up Neo4j lookup
)
# --- Pinecone ---
from neo4j_graphrag.retrievers import PineconeNeo4jRetriever
from pinecone import Pinecone
pc = Pinecone(api_key=os.environ["PINECONE_API_KEY"])
retriever = PineconeNeo4jRetriever(
driver=driver,
client=pc,
index_name="my-index",
id_property_neo4j="id",
retrieval_query=retrieval_query,
)
# --- Qdrant ---
from neo4j_graphrag.retrievers import QdrantNeo4jRetriever
from qdrant_client import QdrantClient
retriever = QdrantNeo4jRetriever(
driver=driver,
client=QdrantClient(url="http://localhost:6333"),
collection_name="Chunk",
id_property_external="neo4j_id",
id_property_neo4j="id",
id_property_getter=lambda hit: hit.payload["neo4j_id"], # custom ID extraction
retrieval_query=retrieval_query,
)
LLM Providers
All implement LLMBase. All support sync + async, tool calling, and automatic rate limiting.
| Class | Extra | Notes |
|---|---|---|
OpenAILLM | openai | Structured output; tool calling |
AzureOpenAILLM | openai | Azure-hosted OpenAI |
AnthropicLLM | anthropic | Tool calling |
VertexAILLM | google | Structured output; tool calling |
MistralAILLM | mistralai | Tool calling |
CohereLLM | cohere | |
OllamaLLM | ollama | Local; tool calling |
BedrockLLM | bedrock | Boto3 Converse API; added v1.15.0 |
from neo4j_graphrag.llm import (
OpenAILLM, AzureOpenAILLM, AnthropicLLM, VertexAILLM,
MistralAILLM, CohereLLM, OllamaLLM, BedrockLLM,
)
llm = OpenAILLM(model_name="gpt-4.1", model_params={"temperature": 0})
llm = AnthropicLLM(model_name="claude-3-5-sonnet-20241022")
llm = VertexAILLM(model_name="gemini-2.0-flash")
llm = OllamaLLM(model_name="llama3") # no API key needed
llm = BedrockLLM(model_id="anthropic.claude-3-5-sonnet-20241022-v2:0")
# Token usage tracking (v1.15.0+)
response = llm.invoke("Hello")
# response.usage → LLMUsage(request_tokens=N, response_tokens=M, total_tokens=T)
# Graceful resource cleanup (v1.16.0+)
llm.close() # sync
await llm.aclose() # async
Embedder Providers
All include automatic rate limiting with tenacity exponential backoff.
| Class | Extra | Dims |
|---|---|---|
OpenAIEmbeddings | openai | 3072 / 1536 |
AzureOpenAIEmbeddings | openai | varies |
VertexAIEmbeddings | google | 768 |
MistralAIEmbeddings | mistralai | 1024 |
CohereEmbeddings | cohere | 1024 |
OllamaEmbeddings | ollama | varies |
SentenceTransformerEmbeddings | sentence-transformers | 384+ |
BedrockEmbeddings | bedrock | varies; added v1.15.0 |
from neo4j_graphrag.embeddings import (
OpenAIEmbeddings, VertexAIEmbeddings, CohereEmbeddings,
OllamaEmbeddings, SentenceTransformerEmbeddings, BedrockEmbeddings,
)
embedder = OpenAIEmbeddings(model="text-embedding-3-large") # 3072 dims
embedder = OpenAIEmbeddings(model="text-embedding-3-small") # 1536 dims
embedder = SentenceTransformerEmbeddings(model="all-MiniLM-L6-v2") # 384 dims, local
embedder = BedrockEmbeddings(model_id="amazon.titan-embed-text-v2:0")
Index Setup
from neo4j_graphrag.indexes import create_vector_index
# Vector index — adjust dimensions to match your embedding model
create_vector_index(
driver,
name="chunk_embedding",
label="Chunk",
embedding_property="embedding",
dimensions=1536,
similarity_fn="cosine", # or "euclidean"
)
# Fulltext index (run as Cypher)
# CREATE FULLTEXT INDEX chunk_fulltext IF NOT EXISTS
# FOR (c:Chunk) ON EACH [c.text]
Schema Inspection
from neo4j_graphrag.schema import get_schema, get_structured_schema
schema_str = get_schema(driver, sample=1000) # human-readable string
schema_dict = get_structured_schema(driver, sample=1000) # dict with labels/rels/props
Common Errors
| Error | Cause | Fix |
|---|---|---|
ModuleNotFoundError: neo4j_genai | Old package name | pip uninstall neo4j-genai && pip install neo4j-graphrag |
retrieval_query returns 0 rows | Missing MATCH or wrong rel direction | EXPLAIN the fragment; check CALL db.schema.visualization() |
KeyError: 'score' in results | retrieval_query RETURN missing score | Add score to every retrieval_query RETURN clause |
score variable not found | score re-declared in retrieval_query | Do not re-declare score — it is auto-injected |
Text2CypherRetrievalError | LLM generated a write statement | Expected security behavior (v1.16.0+); refine prompt or schema |
TypeError: coroutine | Missing await / asyncio.run() | Wrap async calls: asyncio.run(pipeline.run_async(...)) |
| Empty results from HybridRetriever | Fulltext index not ONLINE | SHOW INDEXES YIELD name, state WHERE state <> 'ONLINE' |
| Embedding dimension mismatch | Index dims ≠ model dims | Recreate index with correct dimensions= value |
Verification Checklist
-
neo4j-graphrag(notneo4j-genai) installed;neo4j >= 5.17.0driver - Vector index ONLINE before ingesting embeddings or running retriever
- Fulltext index ONLINE if using Hybrid variants
- Embedding dims in
create_vector_indexmatch the embedder output -
retrieval_queryreturnsnodeandscorein RETURN (not re-declared) -
query_paramspassed viaretriever_configonrag.search()(not on retriever constructor) - API keys in env vars; never hardcoded
-
llm.close()called when done to release resources
References
Load on demand: