Skip to content

fix(qdrant): implement enhanced metadata filtering operators#4127

Merged
whysosaket merged 10 commits intomem0ai:mainfrom
longway-code:fix/qdrant-enhanced-metadata-filtering
Mar 21, 2026
Merged

fix(qdrant): implement enhanced metadata filtering operators#4127
whysosaket merged 10 commits intomem0ai:mainfrom
longway-code:fix/qdrant-enhanced-metadata-filtering

Conversation

@longway-code
Copy link
Copy Markdown
Contributor

Closes #3975

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).

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 mem0ai#3975
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Feb 24, 2026

CLA assistant check
All committers have signed the CLA.

kartik-mem0 and others added 9 commits March 21, 2026 18:02
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>
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>
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>
Co-Authored-By: Claude Sonnet 4.6 <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>
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>
…ator items

List values passed directly (e.g. {"tags": ["a", "b"]}) now map to
MatchAny instead of crashing with a Pydantic ValidationError.  Non-dict
items inside AND/OR/NOT lists now raise a clear ValueError with the
index and value, instead of an AttributeError on .items().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@whysosaket whysosaket merged commit 7a09663 into mem0ai:main Mar 21, 2026
6 of 7 checks passed
lukaj99 added a commit to lukaj99/mem0 that referenced this pull request Mar 21, 2026
jamebobob pushed a commit to jamebobob/mem0-vigil-recall that referenced this pull request Mar 29, 2026
…4127)

Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhanced Metadata Filtering Not Working with Qdrant Backend

4 participants