Skill v1.0.1
currentAutomated scan100/100+5 new
name: neo4j-graphrag-skill description: 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. version: 1.0.1 status: active allowed-tools: Bash WebFetch
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 variantsNatural-language-to-Cypher? → Text2CypherRetriever (no embedder needed)LLM should route between retrievers? → ToolsRetrieverVectors 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 + embeddingspip install neo4j-graphrag[anthropic] # Anthropic Claudepip install neo4j-graphrag[google] # Vertex AI / Geminipip install neo4j-graphrag[bedrock] # Amazon Bedrock (boto3)pip install neo4j-graphrag[cohere] # Coherepip install neo4j-graphrag[mistralai] # MistralAIpip install neo4j-graphrag[ollama] # Ollama (local)pip install neo4j-graphrag[weaviate] # Weaviate external retrieverpip install neo4j-graphrag[pinecone] # Pinecone external retrieverpip 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 variantsNO → plain variantsFor natural-language-to-Cypher? → Text2CypherRetriever (no embedder needed)For multi-tool LLM routing? → ToolsRetrieverUsing 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 EXISTSFOR (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 EXISTSFOR (c:Chunk) ON EACH [c.text];// Confirm ONLINE before ingesting:SHOW INDEXES YIELD name, stateWHERE 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 GraphDatabasefrom neo4j_graphrag.embeddings import OpenAIEmbeddingsfrom neo4j_graphrag.generation import GraphRAGfrom neo4j_graphrag.llm import OpenAILLMfrom neo4j_graphrag.retrievers import HybridCypherRetrieverdriver = 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 VectorCypherRetrieverretriever = 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 throughEXPLAINfirst.Any statement classified as write/destructive raisesText2CypherRetrievalErrorinsteadof executing — prevents prompt-injection attacks.
from neo4j_graphrag.retrievers import Text2CypherRetrieverretriever = Text2CypherRetriever(driver=driver,llm=OpenAILLM(model_name="gpt-4.1"),neo4j_schema=None, # None = auto-fetch schema from DB; pass string to trimexamples=["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 ToolsRetrievertools_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 RagTemplatetemplate = 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 chunksresponse_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 InMemoryMessageHistoryhistory = 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 WeaviateNeo4jRetrieverimport weaviateweaviate_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 PineconeNeo4jRetrieverfrom pinecone import Pineconepc = 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 QdrantNeo4jRetrieverfrom qdrant_client import QdrantClientretriever = 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 extractionretrieval_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 neededllm = 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() # syncawait 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 dimsembedder = OpenAIEmbeddings(model="text-embedding-3-small") # 1536 dimsembedder = SentenceTransformerEmbeddings(model="all-MiniLM-L6-v2") # 384 dims, localembedder = 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 modelcreate_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_schemaschema_str = get_schema(driver, sample=1000) # human-readable stringschema_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: