Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 43 additions & 7 deletions mem0/vector_stores/qdrant.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,22 +142,58 @@ def _create_filter(self, filters: dict) -> Filter:
"""
Create a Filter object from the provided filters.

Supports simple key-value filters as well as composite filters with
$and and $or operators.

Args:
filters (dict): Filters to apply.
filters (dict): Filters to apply. Can contain:
- Simple key-value pairs: {"user_id": "123"}
- Range filters: {"score": {"gte": 0.5, "lte": 1.0}}
- AND operator: {"$and": [{"user_id": "123"}, {"agent_id": "456"}]}
- OR operator: {"$or": [{"user_id": "123"}, {"user_id": "456"}]}

Returns:
Filter: The created Filter object.
"""
if not filters:
return None

conditions = []

must_conditions = []
should_conditions = []

for key, value in filters.items():
if isinstance(value, dict) and "gte" in value and "lte" in value:
conditions.append(FieldCondition(key=key, range=Range(gte=value["gte"], lte=value["lte"])))
if key == "$and":
# Handle $and operator: all conditions must match
if isinstance(value, list):
for sub_filter in value:
sub_result = self._create_filter(sub_filter)
if sub_result:
must_conditions.append(sub_result)
elif key == "$or":
# Handle $or operator: at least one condition must match
if isinstance(value, list):
for sub_filter in value:
sub_result = self._create_filter(sub_filter)
if sub_result:
should_conditions.append(sub_result)
elif isinstance(value, dict) and "gte" in value and "lte" in value:
# Handle range filters
must_conditions.append(FieldCondition(key=key, range=Range(gte=value["gte"], lte=value["lte"])))
elif isinstance(value, dict) and ("$and" in value or "$or" in value):
# Handle nested operators on a field
sub_result = self._create_filter(value)
if sub_result:
must_conditions.append(sub_result)
else:
conditions.append(FieldCondition(key=key, match=MatchValue(value=value)))
return Filter(must=conditions) if conditions else None
# Handle simple key-value pairs
must_conditions.append(FieldCondition(key=key, match=MatchValue(value=value)))

if must_conditions or should_conditions:
return Filter(
must=must_conditions if must_conditions else None,
should=should_conditions if should_conditions else None
)
return None

def search(self, query: str, vectors: list, limit: int = 5, filters: dict = None) -> list:
"""
Expand Down
61 changes: 61 additions & 0 deletions tests/vector_stores/test_qdrant.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,67 @@ def test_list_with_no_filters(self):
# The list method returns the result directly
self.assertEqual(len(results), 1)

def test_create_filter_with_and_operator(self):
"""Test _create_filter with $and operator."""
filters = {
"$and": [
{"user_id": "alice"},
{"agent_id": "agent1"}
]
}
result = self.qdrant._create_filter(filters)

self.assertIsInstance(result, Filter)
self.assertEqual(len(result.must), 2)

def test_create_filter_with_or_operator(self):
"""Test _create_filter with $or operator."""
filters = {
"$or": [
{"user_id": "alice"},
{"user_id": "bob"}
]
}
result = self.qdrant._create_filter(filters)

self.assertIsInstance(result, Filter)
self.assertEqual(len(result.should), 2)
self.assertIsNone(result.must)

def test_create_filter_with_mixed_operators(self):
"""Test _create_filter with mixed $and and $or operators."""
filters = {
"$and": [
{"agent_id": "agent1"}
],
"$or": [
{"user_id": "alice"},
{"user_id": "bob"}
]
}
result = self.qdrant._create_filter(filters)

self.assertIsInstance(result, Filter)
self.assertEqual(len(result.must), 1)
self.assertEqual(len(result.should), 2)

def test_create_filter_with_simple_and_composite(self):
"""Test _create_filter with both simple filters and composite operators."""
filters = {
"run_id": "run1",
"$or": [
{"user_id": "alice"},
{"user_id": "bob"}
]
}
result = self.qdrant._create_filter(filters)

self.assertIsInstance(result, Filter)
# Simple filter goes to must
self.assertEqual(len(result.must), 1)
# $or conditions go to should
self.assertEqual(len(result.should), 2)

def test_delete_col(self):
self.qdrant.delete_col()
self.client_mock.delete_collection.assert_called_once_with(collection_name="test_collection")
Expand Down