From 2692e49d50a7c4a7976829a7281b939ddbdd8e35 Mon Sep 17 00:00:00 2001 From: Frederik Berg <83548283+frederikb96@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:38:04 +0200 Subject: [PATCH] Fix: Add missing filter methods to AsyncMemory (#3624) Co-authored-by: Claude --- mem0/memory/main.py | 96 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/mem0/memory/main.py b/mem0/memory/main.py index 4a7b7ec93..db19a48be 100644 --- a/mem0/memory/main.py +++ b/mem0/memory/main.py @@ -1895,6 +1895,102 @@ class AsyncMemory(MemoryBase): return {"results": original_memories} + def _process_metadata_filters(self, metadata_filters: Dict[str, Any]) -> Dict[str, Any]: + """ + Process enhanced metadata filters and convert them to vector store compatible format. + + Args: + metadata_filters: Enhanced metadata filters with operators + + Returns: + Dict of processed filters compatible with vector store + """ + processed_filters = {} + + def process_condition(key: str, condition: Any) -> Dict[str, Any]: + if not isinstance(condition, dict): + # Simple equality: {"key": "value"} + if condition == "*": + # Wildcard: match everything for this field (implementation depends on vector store) + return {key: "*"} + return {key: condition} + + result = {} + for operator, value in condition.items(): + # Map platform operators to universal format that can be translated by each vector store + operator_map = { + "eq": "eq", "ne": "ne", "gt": "gt", "gte": "gte", + "lt": "lt", "lte": "lte", "in": "in", "nin": "nin", + "contains": "contains", "icontains": "icontains" + } + + if operator in operator_map: + result[key] = {operator_map[operator]: value} + else: + raise ValueError(f"Unsupported metadata filter operator: {operator}") + return result + + for key, value in metadata_filters.items(): + if key == "AND": + # Logical AND: combine multiple conditions + if not isinstance(value, list): + raise ValueError("AND operator requires a list of conditions") + for condition in value: + for sub_key, sub_value in condition.items(): + processed_filters.update(process_condition(sub_key, sub_value)) + elif key == "OR": + # Logical OR: Pass through to vector store for implementation-specific handling + if not isinstance(value, list) or not value: + raise ValueError("OR operator requires a non-empty list of conditions") + # Store OR conditions in a way that vector stores can interpret + processed_filters["$or"] = [] + for condition in value: + or_condition = {} + for sub_key, sub_value in condition.items(): + or_condition.update(process_condition(sub_key, sub_value)) + processed_filters["$or"].append(or_condition) + elif key == "NOT": + # Logical NOT: Pass through to vector store for implementation-specific handling + if not isinstance(value, list) or not value: + raise ValueError("NOT operator requires a non-empty list of conditions") + processed_filters["$not"] = [] + for condition in value: + not_condition = {} + for sub_key, sub_value in condition.items(): + not_condition.update(process_condition(sub_key, sub_value)) + processed_filters["$not"].append(not_condition) + else: + processed_filters.update(process_condition(key, value)) + + return processed_filters + + def _has_advanced_operators(self, filters: Dict[str, Any]) -> bool: + """ + Check if filters contain advanced operators that need special processing. + + Args: + filters: Dictionary of filters to check + + Returns: + bool: True if advanced operators are detected + """ + if not isinstance(filters, dict): + return False + + for key, value in filters.items(): + # Check for platform-style logical operators + if key in ["AND", "OR", "NOT"]: + return True + # Check for comparison operators (without $ prefix for universal compatibility) + if isinstance(value, dict): + for op in value.keys(): + if op in ["eq", "ne", "gt", "gte", "lt", "lte", "in", "nin", "contains", "icontains"]: + return True + # Check for wildcard values + if value == "*": + return True + return False + async def _search_vector_store(self, query, filters, limit, threshold: Optional[float] = None): embeddings = await asyncio.to_thread(self.embedding_model.embed, query, "search") memories = await asyncio.to_thread(