From 56ec7d20f19b698d106f519c174089caceebb5dc Mon Sep 17 00:00:00 2001 From: microbluey Date: Thu, 23 Jul 2026 22:11:09 +0800 Subject: [PATCH] fix(vector_stores/opensearch): translate the '*' wildcard to an exists query for every key (#6522) --- mem0/vector_stores/opensearch.py | 12 ++++++-- tests/vector_stores/test_opensearch.py | 40 ++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/mem0/vector_stores/opensearch.py b/mem0/vector_stores/opensearch.py index ad06a2419..d041e9a65 100644 --- a/mem0/vector_stores/opensearch.py +++ b/mem0/vector_stores/opensearch.py @@ -35,8 +35,16 @@ def _build_filter_clauses(filters): for key, value in (filters or {}).items(): if value is None: continue - if key not in _IDENTITY_FILTER_KEYS and (not isinstance(value, (str, int, float, bool)) or value == "*"): - logger.debug(f"Ignoring non-scalar or wildcard filter value for key {key!r}") + if value == "*": + # "Any value" wildcard (a documented Platform pattern): match + # documents where the field exists — as opensearch.ts already + # does for every key — instead of a literal, near-always-empty + # term match on the string "*". + _validate_filter(key, value) + filter_clauses.append({"exists": {"field": f"payload.{key}"}}) + continue + if key not in _IDENTITY_FILTER_KEYS and not isinstance(value, (str, int, float, bool)): + logger.debug(f"Ignoring non-scalar filter value for key {key!r}") continue _validate_filter(key, value) field = f"payload.{key}.keyword" if isinstance(value, str) else f"payload.{key}" diff --git a/tests/vector_stores/test_opensearch.py b/tests/vector_stores/test_opensearch.py index 3b93aadbd..ae413cf44 100644 --- a/tests/vector_stores/test_opensearch.py +++ b/tests/vector_stores/test_opensearch.py @@ -643,13 +643,49 @@ class TestOpenSearchCustomFilters(TestOpenSearchFilterValidation): clauses = self._search_body()["query"]["bool"]["filter"] self.assertEqual(clauses, [{"term": {"payload.user_id.keyword": "alice"}}]) - def test_search_ignores_wildcard_filter_value(self): + def test_search_translates_wildcard_filter_value_to_exists(self): self.os_db.search(query="", vectors=[[0.1] * 1536], filters={"user_id": "alice", "category": "*"}) clauses = self._search_body()["query"]["bool"]["filter"] - self.assertEqual(clauses, [{"term": {"payload.user_id.keyword": "alice"}}]) + self.assertEqual( + clauses, + [ + {"term": {"payload.user_id.keyword": "alice"}}, + {"exists": {"field": "payload.category"}}, + ], + ) def test_list_ignores_or_operator_filter(self): self.os_db.list(filters={"user_id": "alice", "$or": [{"a": 1}]}) self.client_mock.search.assert_called_once() clauses = self._search_body()["query"]["bool"]["filter"] self.assertEqual(clauses, [{"term": {"payload.user_id.keyword": "alice"}}]) + + +class TestOpenSearchWildcardFilters(TestOpenSearchFilterValidation): + """The "*" wildcard means "any value" (a documented Platform pattern) and + must build an exists query — as opensearch.ts does — for every key, + including the identity keys, instead of a literal term match on "*".""" + + def setUp(self): + super().setUp() + self.client_mock.search.return_value = {"hits": {"hits": []}} + + def _search_body(self): + return self.client_mock.search.call_args[1]["body"] + + def test_identity_key_wildcard_builds_exists_query(self): + self.os_db.search(query="", vectors=[[0.1] * 1536], filters={"agent_id": "*"}) + clauses = self._search_body()["query"]["bool"]["filter"] + self.assertIn({"exists": {"field": "payload.agent_id"}}, clauses) + self.assertNotIn({"term": {"payload.agent_id.keyword": "*"}}, clauses) + + def test_custom_key_wildcard_builds_exists_query(self): + self.os_db.list(filters={"category": "*"}) + clauses = self._search_body()["query"]["bool"]["filter"] + self.assertIn({"exists": {"field": "payload.category"}}, clauses) + + def test_wildcard_combines_with_term_filters(self): + self.os_db.keyword_search(query="q", filters={"user_id": "u1", "topic": "*"}) + clauses = self._search_body()["query"]["bool"]["filter"] + self.assertIn({"term": {"payload.user_id.keyword": "u1"}}, clauses) + self.assertIn({"exists": {"field": "payload.topic"}}, clauses)