Conversation
The three similarity searches fetched every candidate row *with its
embedding* over Bolt, then computed cosine in Python with numpy, sorted, and
truncated to the limit. For a 384-dimension embedder that ships ~1.5KB per
candidate row across the wire to discard almost all of it, and it grows with
the tenant's node count rather than with the limit.
ArcadeDB supports the same `vector.similarity.cosine()` that the Neo4j path
uses, so `get_vector_cosine_func_query()` already returns the correct
expression for this provider through its fallthrough — nothing there needed
changing. Scoring, `min_score` filtering, ordering and the limit all move
into the query, which is what the Neo4j, FalkorDB and Kuzu drivers already
do. The Python-side `_cosine_similarity()` helper and the numpy import go
with it.
Measured on ArcadeDB 26.9.1 (digest 02a1a74f), 1800 nodes, fastembed
BGE-small (384-dim), graphiti's own load test:
vector search p50 330.3ms -> 79.9ms
vector search p95 354.9ms -> 133.5ms
Results are unchanged: the same exact cosine over the same candidate set,
computed one hop earlier. The functional suite is 15/15 on ArcadeDB with
this applied.
This also folds the embedding guard into the filter list rather than
emitting it as a second WHERE, because the rewritten queries build one
WHERE. That happens to fix the `WHERE ... WHERE ...` syntax error that made
every filtered similarity search fail, which getzep#2 fixes separately and for
which getzep#2 should get the credit.
6 tasks
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.
The three similarity searches (node, edge, community) fetch every candidate row with its embedding over Bolt, then compute cosine in Python with numpy, sort, and truncate to the limit.
For a 384-dimension embedder that is ~1.5KB per candidate row shipped across the wire to be discarded, and the cost grows with the tenant's node count rather than with the limit.
ArcadeDB supports the same
vector.similarity.cosine()the Neo4j path uses — I verified it over Bolt against 26.9.1 — soget_vector_cosine_func_query()already returns the right expression for this provider through its fallthrough. Nothing ingraph_queries.pyneeded changing. Scoring,min_score, ordering and the limit all move into the query, matching what the Neo4j, FalkorDB and Kuzu drivers already do. The Python-side_cosine_similarity()and the numpy import go with it.Measured
ArcadeDB 26.9.1 (
arcadedata/arcadedb@sha256:02a1a74f…), 1800 nodes, fastembed BGE-small (384-dim), graphiti's own load test:Results are unchanged — the same exact cosine over the same candidate set, computed one hop earlier. Functional suite 15/15 on ArcadeDB with this applied.
On the LSM_VECTOR index — deliberately not in this PR
The obvious next step is
db.index.vector.queryNodesagainst anLSM_VECTORindex, which does work over Bolt on 26.9.1. I benchmarked it rather than assuming, 1800 nodes across 3 tenants, querying one tenant:It is roughly 3x faster again — but the index returns the global top-k before the tenant filter is applied, so the natural
k = limitsilently returns 2 rows where 10 were asked for. Not an error, just a short result, which is the failure mode hardest to notice in a hybrid search that fuses three legs.Making that safe needs an over-fetch factor tied to tenant selectivity, and with enough tenants the required
kgrows without a good bound. That is a design decision for this driver's owners rather than something to slip into a perf patch, so this PR keeps exact semantics and leaves the index alone. Happy to implement the over-fetch version — behind a flag, or with a fallback to the exact path when the filtered result comes up short — if you want it.🤖 Generated with Claude Code