fix(vector_stores): deep-copy Redis DEFAULT_FIELDS so instances keep distinct dims (#5633)

This commit is contained in:
Yash Singh
2026-06-22 11:29:25 +05:30
committed by GitHub
parent 650b734b1b
commit a48f34cf77
2 changed files with 58 additions and 4 deletions
+5 -3
View File
@@ -1,3 +1,4 @@
import copy
import json import json
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
@@ -65,7 +66,7 @@ class RedisDB(VectorStoreBase):
"prefix": f"mem0:{collection_name}", "prefix": f"mem0:{collection_name}",
} }
fields = DEFAULT_FIELDS.copy() fields = copy.deepcopy(DEFAULT_FIELDS)
fields[-1]["attrs"]["dims"] = embedding_model_dims fields[-1]["attrs"]["dims"] = embedding_model_dims
self.schema = {"index": index_schema, "fields": fields} self.schema = {"index": index_schema, "fields": fields}
@@ -98,8 +99,9 @@ class RedisDB(VectorStoreBase):
"prefix": f"mem0:{collection_name}", "prefix": f"mem0:{collection_name}",
} }
# Copy the default fields and update the vector field with the specified dimensions # Deep-copy the default fields so mutating the nested vector attrs never
fields = DEFAULT_FIELDS.copy() # leaks into the module global or other instances.
fields = copy.deepcopy(DEFAULT_FIELDS)
fields[-1]["attrs"]["dims"] = embedding_dims fields[-1]["attrs"]["dims"] = embedding_dims
fields[-1]["attrs"]["distance_metric"] = distance_metric fields[-1]["attrs"]["distance_metric"] = distance_metric
+53 -1
View File
@@ -7,7 +7,7 @@ when vector is None.
""" """
from datetime import datetime from datetime import datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock, patch
import numpy as np import numpy as np
import pytz import pytz
@@ -115,6 +115,58 @@ def test_list_with_filter_builds_query():
assert query.query_string() == "@user_id:{alice}" assert query.query_string() == "@user_id:{alice}"
def test_create_col_keeps_distinct_dims_across_instances():
"""Building the schema for two collections with different embedding
dimensions must keep them distinct. Regression: DEFAULT_FIELDS.copy() is a
shallow copy, so the shared "embedding" field dict was mutated in place and
the second create_col() clobbered the first one's dims (and the module
global)."""
import mem0.vector_stores.redis as redis_module
db, _ = _make_redis_db()
db.client = MagicMock()
captured = []
def capture_schema(schema):
captured.append(schema)
return MagicMock()
with patch.object(redis_module.SearchIndex, "from_dict", side_effect=capture_schema):
db.create_col(name="col_384", vector_size=384)
db.create_col(name="col_1536", vector_size=1536)
assert captured[0]["fields"][-1]["attrs"]["dims"] == 384
assert captured[1]["fields"][-1]["attrs"]["dims"] == 1536
# the module-level default must never be mutated by building a schema
assert "dims" not in redis_module.DEFAULT_FIELDS[-1]["attrs"]
def test_init_keeps_distinct_dims_from_module_global():
"""__init__ must stamp the requested dims into the index schema without
mutating the shared module-level DEFAULT_FIELDS. The create_col test above
covers the other deepcopy site; all other tests build RedisDB via __new__
and so skip __init__, leaving this call site otherwise uncovered."""
import mem0.vector_stores.redis as redis_module
from mem0.vector_stores.redis import RedisDB
captured = []
def capture_schema(schema):
captured.append(schema)
return MagicMock()
with (
patch("mem0.vector_stores.redis.redis.Redis.from_url", return_value=MagicMock()),
patch.object(redis_module.SearchIndex, "from_dict", side_effect=capture_schema),
):
RedisDB("redis://localhost:6379", "col_384", 384)
assert captured[0]["fields"][-1]["attrs"]["dims"] == 384
# the module-level default must never be mutated by constructing an instance
assert "dims" not in redis_module.DEFAULT_FIELDS[-1]["attrs"]
def test_get_returns_none_for_missing_id(): def test_get_returns_none_for_missing_id():
"""get() must return None for a missing id. """get() must return None for a missing id.