Skip to main content
skill CX Codex

neo4j-graphrag-skill — Claude Code skill: GraphRAG on Neo4j

A skill that walks you through building GraphRAG retrieval pipelines on Neo4j with the neo4j-graphrag package: retriever choice, Cypher fragments, LLM wiring.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add neo4j-contrib/neo4j-skills --skill neo4j-graphrag-skill -a claude-code
Open on GitHub

What neo4j-graphrag-skill does

The skill guides an agent through building retrieval pipelines on Neo4j with the neo4j-graphrag package, version 1.22.0 and up. It ships a selection table: VectorRetriever for baseline semantic search, HybridRetriever when a fulltext index exists, VectorCypherRetriever and HybridCypherRetriever for graph traversal after the vector lookup, Text2CypherRetriever for natural-language-to-Cypher without an embedder, and ToolsRetriever for LLM-routed multi-retriever setups. External vector stores get their own retrievers: Weaviate, Pinecone, Qdrant.

From there it shows how to wire a retriever and an LLM into a GraphRAG pipeline, how to write a retrieval_query fragment that must end in RETURN with score present, and how to create the vector and fulltext indexes and wait for ONLINE before ingesting anything. LLM providers are covered across the board: OpenAI, Anthropic, Gemini/VertexAI, Bedrock, Cohere, Mistral, Ollama. LangChain and LlamaIndex integration, token usage tracking and the Cypher 25 SEARCH clause are in scope too.

You need Python 3.10 or newer and the neo4j driver 5.17.0 or newer. Install the package with the extra for your provider, for instance pip install neo4j-graphrag[openai], and keep the API key in an environment variable. Knowledge-graph construction, plain vector search, GDS analytics and agent memory are out of scope — each has its own skill in the same repository, so don't expect one request to cover them.

What it helps with

  • pick a retriever for the job: vector, hybrid, or graph-augmented
  • write a retrieval_query fragment that keeps score in the RETURN clause
  • create vector and fulltext indexes and wait for ONLINE
  • wire a retriever and an LLM into a GraphRAG pipeline
  • point retrieval at an external vector store instead of Neo4j embeddings

Example prompts for the agent

  • Build a GraphRAG pipeline on Neo4j with HybridCypherRetriever and Anthropic, show the full code
  • Write a retrieval_query fragment that pulls the article and its mentioned organizations after the vector lookup
  • Switch the retriever to Ollama so nothing leaves the machine

How to install neo4j-graphrag-skill

1Skills CLI (Vercel)

npx skills add neo4j-contrib/neo4j-skills --skill neo4j-graphrag-skill -a claude-code

2Manually: put the files in place

~/.claude/skills/neo4j-graphrag-skill/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/neo4j-graphrag-skill/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about neo4j-graphrag-skill

How do I install neo4j-graphrag-skill in Claude?

Run npx skills add neo4j-contrib/neo4j-skills --skill neo4j-graphrag-skill -a claude-code. The skill file then lands at ~/.claude/skills/neo4j-graphrag-skill/SKILL.md.

What does neo4j-graphrag-skill do?

It walks you through a GraphRAG retrieval pipeline on Neo4j: choosing a retriever, writing the Cypher fragment for graph traversal, creating indexes, and connecting an LLM through the neo4j-graphrag package.

How is it different from the other Neo4j skills?

It does not build a knowledge graph from documents, does not do plain vector search without graph traversal, does not run GDS algorithms, and does not handle agent memory — the repository has a separate skill for each of those.

Source text

SKILL.md · MIT
---
name: neo4j-graphrag-skill
description: Build GraphRAG retrieval pipelines on Neo4j using the neo4j-graphrag Python
  package (v1.22.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, Gemini/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.1.1
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 for graph-augmented context
- Wiring retriever + LLM into a `GraphRAG` pipeline
- 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

```bash
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

Pick from [Retriever Selection](#retriever-selection) table above.

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.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

```python
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 `EXPLAIN` first.
> Any statement classified as write/destructive raises `Text2CypherRetrievalError` instead
> of executing — prevents prompt-injection attacks.

```python
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)

```python
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)

```python
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)

```python
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+)

```python
# 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,
)
```

Since v1.19, the vector and vector-Cypher retrievers automatically prefix SEARCH queries
with `CYPHER 25` and fall back to the procedure-based vector search when SEARCH is
unsupported or fails.

---

## Component Imports (v1.19 — breaking, preparing 2.0)

All components moved out of the `experimental` namespace. Old imports still work but emit a
DeprecationWarning and will be removed in 2.0:

```python
# v1.19+ — preferred
from neo4j_graphrag.components.text_splitters.fixed_size_splitter import FixedSizeSplitter

# deprecated (removed in 2.0)
from neo4j_graphrag.experimental.components.text_splitters.fixed_size_splitter import FixedSizeSplitter

# SimpleKGPipeline did NOT move — only valid path:
from neo4j_graphrag.experimental.pipeline.kg_builder import SimpleKGPipeline
```

`neo4j_graphrag.pipeline` (v1.21+) = new lazy dataflow DSL (`Pipeline`, `LocalInterpreter`, `Sink`), unrelated to the experimental task-graph `Pipeline` behind `SimpleKGPipeline`. `neo4j_graphrag.pipeline.kg_builder` does not exist → `ModuleNotFoundError`.

Also since v1.19: `Component` / `RunContext` / `TaskProgressNotifierProtocol` live in
`neo4j_graphrag.components.base`, and malformed components raise `ComponentDefinitionError`
(no longer `PipelineDefinitionError`).

---

## Dataflow Pipeline DSL + Observers (v1.21)

Lazy `neo4j_graphrag.pipeline.Pipeline` + `LocalInterpreter(observers=[...])`; `TextSplitter.iter_chunks()`. Full API → [references/pipeline-dsl.md](references/pipeline-dsl.md).

---

## ORDER BY on Cypher Retrievers (v1.16.0)

```python
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

```python
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

```python
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)

```python
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, LLM + Embedder Providers

- Weaviate / Pinecone / Qdrant constructors → [references/retrievers.md](references/retrievers.md#external-vector-db-retrievers)
- LLM classes (`OpenAILLM`, `AnthropicLLM`, `GeminiLLM`, `VertexAILLM`, `BedrockLLM`, …), `base_url`, token usage, `close()`; embedder classes + dims → [references/providers.md](references/providers.md)

---

## Index Setup

```python
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

```python
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'` |
| `VectorCypherRetriever` + `filters` raises "requires: node_label, embedding_node_property, …" (v1.19 name-mismatch bug) | `_fetch_index_infos` sets `self._embedding_node_property`, constructor sets `_node_embedding_property` | Shim after construction: `r._node_embedding_property = r._embedding_node_property or "embedding"`; append filter props to `r._filterable_properties` |
| Embedding dimension mismatch | Index dims ≠ model dims | Recreate index with correct `dimensions=` value |

---

## Verification Checklist

- [ ] `neo4j-graphrag` (not `neo4j-genai`) installed; `neo4j >= 5.17.0` driver
- [ ] Vector index ONLINE before ingesting embeddings or running retriever
- [ ] Fulltext index ONLINE if using Hybrid variants
- [ ] Embedding dims in `create_vector_index` match the embedder output
- [ ] `retrieval_query` returns `node` and `score` in RETURN (not re-declared)
- [ ] `query_params` passed via `retriever_config` on `rag.search()` (not on retriever constructor)
- [ ] API keys in env vars; never hardcoded
- [ ] `llm.close()` called when done to release resources

---

## References

Load on demand:
- [references/retrievers.md](references/retrievers.md) — per-retriever constructor params, external vector DB retrievers, `result_formatter`, pre-filter operators
- [references/providers.md](references/providers.md) — LLM + embedder provider classes, extras, defaults
- [references/pipeline-dsl.md](references/pipeline-dsl.md) — dataflow `Pipeline` DSL, `Source`/`Sink`, stage observers [v1.21]
- [references/kg-builder.md](references/kg-builder.md) — `SimpleKGPipeline` constructor, schema, splitters
- [references/knowledge-graph-construction.md](references/knowledge-graph-construction.md) — advanced KG pipeline customization
- [neo4j-graphrag package docs](https://neo4j.com/docs/neo4j-graphrag-python/current/)
- [RAG & GraphRAG user guide](https://neo4j.com/docs/neo4j-graphrag-python/current/user_guide_rag.html)
- [KG Builder user guide](https://neo4j.com/docs/neo4j-graphrag-python/current/user_guide_kg_builder.html)
- [GitHub — neo4j-graphrag-python](https://github.com/neo4j/neo4j-graphrag-python)
- [Examples folder](https://github.com/neo4j/neo4j-graphrag-python/tree/main/examples)

© neo4j-contrib · MIT · neo4j-contrib/neo4j-skills

Similar skills

Everything for AI and ML →

From the same set18

neo4j-cypher-skill Writes, optimizes and validates Cypher 25 queries for Neo4j 2025.x and 2026.x. Useful when a slow MATCH needs fixing or a batch write has to be assembled. CX Codex +1 1,500installs neo4j-modeling-skill Designs, reviews and refactors Neo4j data models: nodes, relationships, properties, constraints. Useful once a graph schema starts to sprawl. CX Codex +1 1,100installs neo4j-driver-python-skill Tells your agent how to write Python against the Neo4j v6 driver: driver lifecycle, transactions, async, UNWIND batching. CX Codex +1 976installs neo4j-query-tuning-skill Reads a Neo4j execution plan, spots the operators that slow a Cypher query down, and tells you what to change. Useful when a query drags and PROFILE output is hard to read. CX Codex +1 908installs neo4j-vector-index-skill A skill for building and querying Neo4j vector indexes: index creation, nearest-neighbor search, batch embedding updates. CX Codex +1 905installs neo4j-getting-started-skill Walks you from an empty folder to a running Neo4j app in eight stages, from tool checks to a notebook or service. CX Codex +1 888installs neo4j-gds-skill Guides graph projections, algorithms, and pipelines for the embedded Neo4j GDS plugin. Useful for Python and Cypher graph analytics workflows. CX Codex +1 865installs neo4j-security-skill This skill walks an agent through Neo4j user, role and privilege management in Cypher against the system database. Useful when access has to be provisioned by script instead of by hand. CX Codex +1 824installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor