fix: normalize vector store scores to similarity (higher = better)

The threshold parameter in Memory.search() was silently broken for 11
vector stores because they returned raw distance scores (lower = better)
while the threshold check assumed similarity scores (higher = better).
This caused threshold filtering to be inverted — good matches got
dropped and bad matches passed through.

Convert all affected vector stores to return similarity scores:
- Cosine distance stores: score = max(0.0, 1.0 - distance)
- L2 distance stores: score = 1.0 / (1.0 + distance)
- Stores computing similarity then discarding it: return similarity directly

Fixes #4453
Related: #3283

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
utkarsh240799
2026-03-28 21:57:40 +05:30
co-authored by Claude Opus 4.6
parent 431cba20e9
commit 224fd6c7d8
18 changed files with 79 additions and 48 deletions
+2 -2
View File
@@ -45,7 +45,7 @@ def test_search_vectors(chromadb_instance, mock_chromadb_client):
assert len(results) == 2
assert results[0].id == "id1"
assert results[0].score == 0.1
assert abs(results[0].score - (1.0 / (1.0 + 0.1))) < 1e-9 # L2 distance 0.1 -> similarity
assert results[0].payload == {"name": "vector1"}
@@ -150,7 +150,7 @@ def test_get_vector(chromadb_instance):
chromadb_instance.collection.get.assert_called_once_with(ids=["id1"])
assert result.id == "id1"
assert result.score == 0.1
assert abs(result.score - (1.0 / (1.0 + 0.1))) < 1e-9 # L2 distance 0.1 -> similarity
assert result.payload == {"name": "vector1"}
+3 -3
View File
@@ -129,7 +129,7 @@ class TestMilvusDB:
# Verify results are parsed correctly
assert len(results) == 1
assert results[0].id == "mem1"
assert results[0].score == 0.8
assert results[0].score == 0.8 # COSINE: Milvus returns similarity directly
def test_search_different_user_ids(self, milvus_db, mock_milvus_client):
"""Test that search works with different user_ids (reproduces reported bug)."""
@@ -228,10 +228,10 @@ class TestMilvusDB:
assert len(parsed) == 2
assert parsed[0].id == "mem1"
assert parsed[0].score == 0.9
assert parsed[0].score == 0.9 # COSINE: Milvus returns similarity directly
assert parsed[0].payload == {"user_id": "alice"}
assert parsed[1].id == "mem2"
assert parsed[1].score == 0.85
assert parsed[1].score == 0.85 # COSINE: Milvus returns similarity directly
def test_update_with_none_vector_fetches_existing(self, milvus_db, mock_milvus_client):
"""Test that update with vector=None fetches the existing vector (fixes #3708)."""
+12 -12
View File
@@ -445,9 +445,9 @@ class TestPGVector(unittest.TestCase):
# Verify results
self.assertEqual(len(results), 2)
self.assertEqual(results[0].id, self.test_ids[0])
self.assertEqual(results[0].score, 0.1)
self.assertEqual(results[0].score, 0.9)
self.assertEqual(results[1].id, self.test_ids[1])
self.assertEqual(results[1].score, 0.2)
self.assertEqual(results[1].score, 0.8)
@patch('mem0.vector_stores.pgvector.PSYCOPG_VERSION', 2)
@patch('mem0.vector_stores.pgvector.ConnectionPool')
@@ -494,9 +494,9 @@ class TestPGVector(unittest.TestCase):
# Verify results
self.assertEqual(len(results), 2)
self.assertEqual(results[0].id, self.test_ids[0])
self.assertEqual(results[0].score, 0.1)
self.assertEqual(results[0].score, 0.9)
self.assertEqual(results[1].id, self.test_ids[1])
self.assertEqual(results[1].score, 0.2)
self.assertEqual(results[1].score, 0.8)
@patch('mem0.vector_stores.pgvector.PSYCOPG_VERSION', 3)
@patch('mem0.vector_stores.pgvector.ConnectionPool')
@@ -1140,7 +1140,7 @@ class TestPGVector(unittest.TestCase):
# Verify results
self.assertEqual(len(results), 1)
self.assertEqual(results[0].id, self.test_ids[0])
self.assertEqual(results[0].score, 0.1)
self.assertEqual(results[0].score, 0.9)
self.assertEqual(results[0].payload["user_id"], "alice")
self.assertEqual(results[0].payload["agent_id"], "agent1")
self.assertEqual(results[0].payload["run_id"], "run1")
@@ -1190,7 +1190,7 @@ class TestPGVector(unittest.TestCase):
# Verify results
self.assertEqual(len(results), 1)
self.assertEqual(results[0].id, self.test_ids[0])
self.assertEqual(results[0].score, 0.1)
self.assertEqual(results[0].score, 0.9)
self.assertEqual(results[0].payload["user_id"], "alice")
self.assertEqual(results[0].payload["agent_id"], "agent1")
self.assertEqual(results[0].payload["run_id"], "run1")
@@ -1240,7 +1240,7 @@ class TestPGVector(unittest.TestCase):
# Verify results
self.assertEqual(len(results), 1)
self.assertEqual(results[0].id, self.test_ids[0])
self.assertEqual(results[0].score, 0.1)
self.assertEqual(results[0].score, 0.9)
self.assertEqual(results[0].payload["user_id"], "alice")
@patch('mem0.vector_stores.pgvector.PSYCOPG_VERSION', 2)
@@ -1288,7 +1288,7 @@ class TestPGVector(unittest.TestCase):
# Verify results
self.assertEqual(len(results), 1)
self.assertEqual(results[0].id, self.test_ids[0])
self.assertEqual(results[0].score, 0.1)
self.assertEqual(results[0].score, 0.9)
self.assertEqual(results[0].payload["user_id"], "alice")
@patch('mem0.vector_stores.pgvector.PSYCOPG_VERSION', 3)
@@ -1336,9 +1336,9 @@ class TestPGVector(unittest.TestCase):
# Verify results
self.assertEqual(len(results), 2)
self.assertEqual(results[0].id, self.test_ids[0])
self.assertEqual(results[0].score, 0.1)
self.assertEqual(results[0].score, 0.9)
self.assertEqual(results[1].id, self.test_ids[1])
self.assertEqual(results[1].score, 0.2)
self.assertEqual(results[1].score, 0.8)
@patch('mem0.vector_stores.pgvector.PSYCOPG_VERSION', 2)
@patch('mem0.vector_stores.pgvector.ConnectionPool')
@@ -1385,9 +1385,9 @@ class TestPGVector(unittest.TestCase):
# Verify results
self.assertEqual(len(results), 2)
self.assertEqual(results[0].id, self.test_ids[0])
self.assertEqual(results[0].score, 0.1)
self.assertEqual(results[0].score, 0.9)
self.assertEqual(results[1].id, self.test_ids[1])
self.assertEqual(results[1].score, 0.2)
self.assertEqual(results[1].score, 0.8)
@patch('mem0.vector_stores.pgvector.PSYCOPG_VERSION', 3)
@patch('mem0.vector_stores.pgvector.ConnectionPool')
+1 -1
View File
@@ -157,7 +157,7 @@ def test_search(mock_boto_client):
mock_boto_client.query_vectors.assert_called_once()
assert len(results) == 1
assert results[0].id == "id1"
assert results[0].score == 0.9
assert abs(results[0].score - 0.1) < 1e-9 # distance 0.9 -> similarity 0.1
def test_get(mock_boto_client):
+1 -1
View File
@@ -75,7 +75,7 @@ def test_search_vectors(supabase_instance, mock_collection):
assert len(results) == 2
assert results[0].id == "id1"
assert results[0].score == 0.9
assert abs(results[0].score - 0.1) < 1e-9 # cosine distance 0.9 -> similarity 0.1
assert results[0].payload == {"name": "vector1"}
@@ -112,7 +112,7 @@ def test_search_vectors(vector_store, mock_vertex_ai):
assert len(results) == 1
assert results[0].id == "test-id"
assert results[0].score == 0.1
assert results[0].score == 0.9 # distance 0.1 -> similarity 0.9
assert results[0].payload == {"user_id": "test_user"}