neo4j-vector-index-skill
Create and manage Neo4j vector indexes, run vector similarity search (ANN/kNN), store embeddings on nodes or relationships, use SEARCH clause (Neo4j 2026.01+, preferred) or db.index.vector.queryNodes() procedure (deprecated 2026.04, still works on 2025.x), configure HNSW and quantization options, pi
By neo4j-contrib · 606 installs
npx skills add neo4j-contrib/neo4j-skills --skill neo4j-vector-index-skill
Source repository · Upstream listing
When to Use
Creating a vector index ( CREATE VECTOR INDEX ) on nodes or relationships
Running vector similarity / nearest neighbor search
Storing embeddings on graph nodes during ingestion
Indexing/querying embeddings already written by GDS algorithms
Choosing similarity function, dimensions, HNSW params, or quantization
Using SEARCH clause (2026.01+) or db.index.vector.queryNodes() (2025.x)
Batch updating embeddings after model change
Combining vector results with immediate graph neighborhood (full retrieval query pipelines → neo4j graphrag skill )
Hybrid search that combines vector results with fulltext or other ranked sources
When NOT to Use
GraphRAG pipelines (VectorCypherRetriever, HybridCypherRetriever, retrieval query) → neo4j graphrag skill
Fulltext only / keyword only search (FULLTEXT INDEX, db.index.fulltext.queryNodes ) → neo4j cypher skill
Computing GDS graph embeddings (FastRP, Node2Vec, GraphSAGE) → neo4j gds skill
Index admin (list all indexes, drop range/text/lookup indexes) → neo4j cypher skill
Pre flight — Determine Version
Drives syntax choice:
Version Use
2026.01 or higher SEARCH clause (in index filtering, preferred)
2025.x db.index.vector.queryNodes() procedure ( deprecated 2026.04 — use SEARCH when on 2026.x)
Step 1 — Create Vector Index
Node index (single label):
Node index with filterable properties [2026.01+] — WITH declares which properties can be used in SEARCH ... WHERE :
Multi label index with filterable properties [2026.01+]:
Relationship index:
WITH property types — only scalar types allowed: INTEGER , FLOAT , STRING , BOOLEAN , DATE , ZONED DATETIME , LOCAL DATETIME , ZONED TIME , LOCAL TIME , DURATION . Not allowed: LIST , POINT , or the vector property itself.
Index config reference:
Parameter Type Default Notes
vector.dimensions INTEGER 1–4096 none Required; must match embedding model exactly
vector.similarity function STRING 'cosine' 'cosine' or 'euclidean'
vector.quantization.type STRING 'scalar' 'none' , 'scalar' , 'binary' [2026.06+, GA 2026.07]; reduces storage; binary smallest (1 bit per dimension), most aggressive; needs vector 2.0+ (5.18+)
vector.quantization.enabled BOOLEAN true Deprecated 2026.06 — use vector.quantization.type ; false without vector.quantization.type: 'none' fails index creation before 2026.07
vector.default search expansion factor FLOAT 1.0–10000.0 1.0 none / 1.5 scalar / 3.0 binary (was 2.0 before 2026.07) [2026.06+, GA 2026.07]; value 1.0 on quantized vectors enables automatic rescoring with full precision vectors (High Fidelity Quantized search, HFQ); not settable at query time; existing indexes keep their stored value until rebuilt
vector.hnsw.m INTEGER 1–512 16 HNSW graph connections; higher = better recall, more memory
vector.hnsw.ef construction INTEGER 1–3200 100 Build time candidates; higher = better recall, slower build
Provider is not settable in Cypher 25 — Neo4j picks the most feature rich one ( vector 2026.06 on 2026.06+, required for binary quantization + rescoring). Check with SHOW VECTOR INDEXES YIELD name, indexProvider . Changing quantization requires dropping and recreating the index.
Unquantized vectors: set vector.quantization.type: 'none' alone — on 2026.06, vector.quantization.enabled: false without it errors (fixed 2026.07).
Similarity function choice:
Use case Function
Normalized embeddings (OpenAI, Cohere, Voyage, Google) 'cosine'
Unnormalized / raw distance matters 'euclidean'
Index providers — latest selected automatically; not specifiable in Cypher 25. Check with SHOW VECTOR INDEXES YIELD name, indexProvider :
Provider Quantization support
vector 2026.07 High Fidelity Quantized search for scalar and binary
vector 2026.06 scalar and binary
vector 2.0 (5.18+) scalar
Changing quantization type or expansion factor requires index re create + re population.
Step 2 — Wait for Index ONLINE
Index builds asynchronously — do NOT query until ONLINE:
Poll every 5s until state = 'ONLINE' and populationPercent = 100.0 . If state = 'FAILED' → stop, check logs.
Shell poll (cypher shell):
Step 3 — Ingest Embeddings
Batch UNWIND pattern (use for 100 nodes — never one node per transaction):
❌ Never create index after embeddings are already stored — always create index first.
✅ Create index → poll ONLINE → ingest embeddings.
Step 4 — Run Vector Search
SEARCH clause (2026.01+, preferred)
With in index filter [2026.01+] — properties must be declared in WITH at index creation:
Filtering strategy — choose one:
Strategy When to use Tradeoff
In index WHERE [2026.01+] Filters on pre declared WITH properties; known at index design time Fast, consistent latency; properties must be declared upfront
Post filter (MATCH + procedure) Arbitrary Cypher predicates, graph traversal, OR/NOT Full flexibility; may over fetch then discard
Pre filter (MATCH first, then SEARCH) Small known candidate set; exact nearest neighbor within subset Deterministic; slow on large candidate sets
In index WHERE hard limits [2026.01+]:
Property must be listed in WITH [...] at index creation — undeclared properties silently fall back to post filtering
AND predicates only — no OR, NOT, string ops. IN list membership allowed [2026.06+]
Scalar types only: INTEGER , FLOAT , STRING , BOOLEAN , temporal types — not VECTOR/LIST/POINT
Post filter pattern (2025.x or arbitrary predicates)
Relationship index procedure:
SEARCH clause hard limits (all versions):
Index name cannot be a parameter ( $indexName not allowed — use literal string)
Binding variable must come from the enclosing MATCH pattern
Query vector cannot reference the binding variable
Step 5 — Combine with Graph Traversal (simple cases)
Vector search as entry point, then graph hop:
For full retrieval query pipelines, HybridCypherRetriever, or neo4j graphrag library → delegate to neo4j graphrag skill .
Step 6 — Hybrid Search
Use hybrid search when one signal misses useful candidates: semantic vectors miss exact terms, lexical fulltext misses paraphrases, structural graph signals find topology not present in text.
The common pattern is vector + fulltext, but the same approach works for several vector indexes, GDS written embeddings, graph traversal scores, or any two+ ranked/scored sources.
Load [references/hybrid search.md](references/hybrid search.md) and apply its query shape.
Rules:
Run each source independently; rank each by score DESC, stable id ASC .
Combine by rank, not raw scores; fulltext and vector scores are not comparable.
Every UNION ALL branch returns same columns: matched node + contribution.
Use sourceK finalK ; combine before final limiting.
Sum contributions per node; order final rows by wrrf DESC, stable id ASC .
Add more sources with extra UNION ALL branches and new sourceWeights keys.
Embedding Provider Quick Reference
Provider / Model Dimensions Similarity Notes
OpenAI text embedding 3 small 1536 cosine Default; reducible to 256–1536 via dimensions= param
OpenAI text embedding 3 large 3072 cosine Reducible to 256–3072
OpenAI text embedding ada 002 1536 cosine Legacy; prefer 3 small
Cohere embed v3 (English) 1024 cosine Use input type='search document' at ingest, 'search query' at query
Voyage voyage 3 large 1024 cosine High quality; needs voyage ai package
Google text embedding 004 768 cosine Via Vertex AI
Ollama nomic embed text 768 cosine Local dev/testing
Ollama mxbai embed large 1024 cosine Local; production quality
vector.dimensions must exactly match model output — no auto truncation.
Vector Functions
Ad hoc similarity (not for kNN search — use index for that):
Convert LIST to typed VECTOR:
Index Management
Common Errors
Error Cause Fix
IllegalArgumentException: Index dimension mismatch Stored embedding dim ≠ vector.dimensions Fix embed generation; drop + recreate index with correct dim
Search returns incomplete results Index still POPULATING Poll until state = 'ONLINE'
Unknown procedure db.index.vector.queryNodes Neo4j < 5.11 No vector index support below 5.11; upgrade
SEARCH clause not available Neo4j < 2026.01 Use queryNodes() procedure
OR/NOT not allowed in SEARCH WHERE SEARCH in index filter restriction Move complex predicates to outer WHERE after SEARCH
Zero results from correct query Wrong similarity function or all zeros embedding Verify with vector.similarity.cosine() ; check embed call succeeded
Score always 1.0 All zeros or identical vectors Embedding generation failed; add dimension assertion before ingest
vector.quantization.enabled / .type option rejected provider vector 1.0 (Neo4j < 5.18) Omit quantization option or upgrade to 5.18+
BINARY quantization rejected provider older than vector 2026.06 Upgrade to 2026.06+; SHOW VECTOR INDEXES YIELD name, indexProvider to check ( vector 2026.07 adds High Fidelity Quantized search)
Checklist
[ ] vector.dimensions matches embedding model output exactly
[ ] Vector index created before ingesting embeddings
[ ] Similarity function chosen explicitly ( cosine for normalized, euclidean for distance based)
[ ] Index polled to state = 'ONLINE' before first query
[ ] Dimension validated on every embedding before ingest
[ ] SEARCH clause on Neo4j = 2026.01 (preferred); procedure fallback only on 2025.x (deprecated 2026.04)
[ ] SEARCH WHERE uses AND only predicates with scalar types
[ ] Batch UNWIND pattern used for 100 nodes
[ ] If model changes: drop index → recreate with new dimensions → re generate all embeddings
In Cypher Embedding Generation — ai.text.embed() [2025.12]
Generate embeddings at query time without external Python code. Use ai.text.embed() — the current API since [2025.12]:
Provider strings are lowercase ( 'openai' , 'vertexai' , 'bedrock titan' , 'azure openai' ). Full provider config → neo4j genai plugin skill .
Full query pattern — embed at query time, search immediately (procedure fallback for 2025.x):
With SEARCH clause (2026.01+):
❌ Never pass API key as literal string in production — use $param or apoc.static.get() .
✅ Use $openaiKey parameter; inject via driver params dict.
Rule : Use same model at ingest time and query time — embeddings from different models are not comparable.
Deprecated (still works but do not use in new code):
genai.vector.encode() [deprecated] → use ai.text.embed() [2025.12]
genai.vector.encodeBatch() [deprecated] → use CALL ai.text.embedBatch() [2025.12]
genai.vector.listEncodingProviders() [deprecated] → use CALL ai.text.embed.providers() [2025.12]
For full ai.text. reference (completion, structured output, chat, tokenization) → neo4j genai plugin skill .
Cypher Based Embedding Ingestion — db.create.setNodeVectorProperty
Set vector property via Cypher (e.g. during LOAD CSV or MERGE pipeline):
Use when embedding is already in CSV/JSON form as a string — apoc.convert.fromJsonList() converts "[0.1,0.2,...]" to LIST<FLOAT .
For Python generated embeddings, use the Python UNWIND batch pattern (Step 3) instead.
Similarity Function — Extended Guidance
Existing table (Step 1) gives the basic rule. Additional guidance from course patterns:
Choose based on training loss function:
Check embedding model docs — models trained with cosine loss → use 'cosine'
Models trained with L2/Euclidean loss → use 'euclidea