feat(neo4j): route similarity search through HNSW vector indexes - #1796
Draft
xiaoyaner0201 wants to merge 6 commits into
Draft
xiaoyaner0201 wants to merge 6 commits into
xiaoyaner0201 wants to merge 6 commits into
Conversation
Neo4j similarity search computes vector.similarity.cosine() against every matching node/edge, with no vector index. On a 76k-edge graph with 2560-dim embeddings a single edge_similarity_search takes ~41s; the same top-20 result set via an HNSW index returns in ~82ms. HNSW support existed briefly: getzep#859 added a USE_HNSW flag and a Neo4j vector-index branch, and getzep#894 removed it during cleanup. This reintroduces it without the parts that made the original fragile. - get_vector_indices() emits CREATE VECTOR INDEX DDL for Entity.name_embedding, Community.name_embedding and RELATES_TO.fact_embedding. The dimension is supplied by the caller rather than hardcoded (getzep#859 pinned 1024, which breaks any non-1024 embedder). - get_vector_similarity_query() emits db.index.vector.queryNodes / queryRelationships, over-fetching limit*3 candidates so group_id and SearchFilters post-filtering cannot truncate below the caller's limit. - Neo4jDriver gains embedding_dim and use_vector_index constructor arguments. Both default to preserving current behaviour: index DDL is only emitted when use_vector_index=True, so existing deployments keep the exact-recall brute-force path and are not surprised by a one-time background index build. - edge_similarity_search routes through the index when enabled and falls back to the brute-force query otherwise. Recall was verified against the brute-force path on a real 76,333-edge graph: top-20 result sets were identical (20/20 intersection, no misses). Refs getzep#1793
Deployments that construct Graphiti indirectly (the MCP server passes uri/user/password to Graphiti and never touches Neo4jDriver) cannot supply use_vector_index=True. Read GRAPHITI_NEO4J_USE_VECTOR_INDEX and GRAPHITI_NEO4J_EMBEDDING_DIM as fallbacks so the feature is reachable from configuration; explicit constructor arguments still take precedence.
Route Neo4j entity similarity searches through the native entity_name_embedding vector index when opt-in is enabled. Apply group and search filters after a bounded over-fetch, preserving the exact cosine path when the candidate window is sparse or the index is unavailable. Share dimension and lifecycle fallback handling with edge searches and cover both paths with contract tests.
xiaoyaner0201
force-pushed
the
feat/neo4j-hnsw-vector-index
branch
from
September 2, 2026 19:21
7b00896 to
eaea277
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #1793.
Problem
Neo4j similarity search computes
vector.similarity.cosine()against every matching node/edge. On larger graphs this makes dedup/search take minutes and can leave long-running transactions behind.What this does
Entity.name_embedding,Community.name_embedding, andRELATES_TO.fact_embedding, using the configured embedding dimension rather than a hard-coded dimension.node_similarity_searchandedge_similarity_searchthroughdb.index.vector.queryNodes/queryRelationshipswhenuse_vector_indexis enabled.group_id, search, and endpoint filters after a bounded ANN over-fetch. If the filtered candidate window is sparse, it reruns the exact query so the old result-count contract is preserved.Neo4jDriverand the publicGraphiticonstructor; indirect MCP construction can useGRAPHITI_NEO4J_USE_VECTOR_INDEX.group_idswasNone.community_similarity_searchand non-Neo4j providers unchanged.Why this is not a blind #859 revert
#859 previously added an HNSW path but used a hard-coded dimension and a simpler opt-in. This implementation keeps indexing disabled by default, couples the index dimension to the configured embedder, preserves exact fallback, validates query dimensions, bounds lifecycle fallback to known index errors, and covers both node and edge search paths.
Evidence
A live local Neo4j 5.26 graph with approximately 52k
Entitynodes and 2560-dimensional embeddings was used for an independent node canary. For the same real embedding vector:queryNodestop-10: approximately 4.9 seconds under host loadA second live filtered probe used 12 random
xiaoyaner-coreseeds. The HNSW candidate path returned 10 rows for every seed and reduced the measured query time from 490.5 seconds total to 6.6 seconds total. Six of twelve had identical set and order; the remaining results had the same row count but ANN ranking/set differences, which is expected from approximate search and is why exact fallback remains available for sparse windows and the feature is opt-in.A sparse-group probe showed the bounded candidate window can produce fewer than the requested limit. The implementation detects that case and reruns the exact filtered query; a focused regression test covers this fallback.
Testing
85 passedfocused tests covering node/edge routing, filters, over-fetch, sparse-window exact fallback, lifecycle fallback, dimension validation, result shape, public wiring, and provider isolation.ruff checkclean.ruff format --checkclean.git diff --checkclean.The repository-wide non-integration suite was attempted but cannot be used as a green gate in this environment: unrelated integration-marked tests try to connect to
localhost:7687, while the live Neo4j instance is isolated inside Docker and is not exposed on that host port. The run reached existing database-dependent errors; no code failure from this patch was established.Scope / trade-off
HNSW is approximate. Results may differ from exact cosine even when the candidate window is full. Operators who require exact recall can leave the opt-in disabled. Operators enabling it should create/verify the indexes and compare their workload before making it the default in their deployment.
Index creation remains opt-in to avoid silently starting a potentially expensive index population during upgrade. The current local deployment has the indexes online and has separately verified the live node path, but this PR does not change upstream defaults.