Memory._process_metadata_filters() renames OR→$or and NOT→$not, but
effective_filters also retains the original OR/NOT keys from the
deepcopy of input_filters. Without dedup, the same sub-conditions were
evaluated twice in the Qdrant filter.
Normalize the filter dict upfront so $or/$not/$and map to OR/NOT/AND
and only the first occurrence is kept. Also replace the fragile
value.get("contains") or value.get("icontains") pattern with explicit
key lookup, and add a logger.debug for icontains explaining that Qdrant
MatchText case sensitivity depends on full-text index configuration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Memory._process_metadata_filters() renames OR→$or and NOT→$not before
passing filters to the vector store. Without this fix, $or/$not keys
with list values hit _build_field_condition and crash with
ValidationError (MatchValue rejects lists).
Normalize $or→OR, $not→NOT, $and→AND at the start of _create_filter
so both naming conventions work.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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 <noreply@anthropic.com>
Wildcard '*' values now return None from _build_field_condition (match any /
field exists semantics), and _create_filter skips None conditions so a
filter containing only wildcards returns None. Updated return type annotations
to Optional[FieldCondition] and Optional[Filter] accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Raise ValueError when a filter dict for a single field combines range
operators (gt/gte/lt/lte) with non-range operators (eq/ne/in/nin/etc),
preventing silent data loss where the non-range conditions were dropped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Qdrant vector store's _create_filter only handled simple equality
and a single gte+lte range case, causing ValidationError for all other
operators documented at docs.mem0.ai/open-source/features/metadata-filtering.
Add _build_field_condition to translate the universal filter syntax into
Qdrant-native models:
- Comparison: eq, ne, gt, gte, lt, lte → MatchValue / MatchExcept / Range
- List: in, nin → MatchAny / MatchExcept
- String: contains, icontains → MatchText
- Logical: AND, OR, NOT → Filter(must/should/must_not), recursive
Existing simple equality and gte+lte range filters continue to work
unchanged (backward compatible).
Closes#3975