From 72c2a7f3227a33651fd709225f8e9cb19e70e091 Mon Sep 17 00:00:00 2001 From: kartik-mem0 Date: Sat, 21 Mar 2026 18:07:07 +0530 Subject: [PATCH] fix(qdrant): fix MatchText comment and validate logical operator inputs Corrects the MatchText comment to accurately describe Qdrant behavior (tokenized match with a full-text index; exact substring without one), and adds ValueError validation when AND/OR/NOT filter values are not lists, replacing unhelpful AttributeError crashes. Co-Authored-By: Claude Sonnet 4.6 --- mem0/vector_stores/qdrant.py | 13 +++++++++++-- tests/vector_stores/test_qdrant.py | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/mem0/vector_stores/qdrant.py b/mem0/vector_stores/qdrant.py index 5f9051b22..5bcab11f2 100644 --- a/mem0/vector_stores/qdrant.py +++ b/mem0/vector_stores/qdrant.py @@ -187,8 +187,10 @@ class Qdrant(VectorStoreBase): elif "nin" in value: return FieldCondition(key=key, match=MatchExcept(**{"except": value["nin"]})) elif "contains" in value or "icontains" in value: - # MatchText performs case-insensitive substring search on full-text indexed fields. - # Note: icontains behaves identically to contains for Qdrant keyword payloads. + # MatchText: with a full-text index, tokenized matching (all words must appear). + # Without a full-text index, exact substring match. + # Note: icontains maps to the same MatchText — case sensitivity depends on + # the full-text index tokenizer configuration, not on this operator name. text = value.get("contains") or value.get("icontains") return FieldCondition(key=key, match=MatchText(text=text)) else: @@ -220,6 +222,13 @@ class Qdrant(VectorStoreBase): must_not = [] for key, value in filters.items(): + if key in ("AND", "OR", "NOT"): + if not isinstance(value, list): + raise ValueError( + f"{key} filter value must be a list of filter dicts, " + f"got {type(value).__name__}" + ) + if key == "AND": for sub in value: built = self._create_filter(sub) diff --git a/tests/vector_stores/test_qdrant.py b/tests/vector_stores/test_qdrant.py index b85134bf2..38aa39f64 100644 --- a/tests/vector_stores/test_qdrant.py +++ b/tests/vector_stores/test_qdrant.py @@ -568,5 +568,20 @@ class TestQdrantEnhancedFilters(unittest.TestCase): result = self.qdrant._create_filter({"category": "*"}) self.assertIsNone(result) + def test_and_with_non_list_raises_error(self): + """AND with non-list value should raise ValueError.""" + with self.assertRaises(ValueError): + self.qdrant._create_filter({"AND": "not_a_list"}) + + def test_or_with_non_list_raises_error(self): + """OR with non-list value should raise ValueError.""" + with self.assertRaises(ValueError): + self.qdrant._create_filter({"OR": {"category": "work"}}) + + def test_not_with_non_list_raises_error(self): + """NOT with non-list value should raise ValueError.""" + with self.assertRaises(ValueError): + self.qdrant._create_filter({"NOT": "invalid"}) + def tearDown(self): del self.qdrant