fix: merge same-key operator dicts in AND metadata filters#4853
Conversation
…R/NOT blocks When multiple conditions in an AND block target the same field with different operators (e.g., price > 10 AND price < 20), dict.update() silently dropped all but the last condition. Replace with a merge helper that deep-merges nested operator dicts, preserving all constraints. Applied the same fix to OR and NOT blocks which had the identical pattern, and to top-level filter accumulation. Closes mem0ai#4850
xkonjin
left a comment
There was a problem hiding this comment.
Code review — merge same-key operator dicts in AND metadata filters
Fixes the bug where AND conditions with the same key would overwrite each other because of a bare .update() on the processed filters dict.
The merge_filters helper correctly deep-merges operator dicts so {"AND": [{"price": {"gt": 10}}, {"price": {"lt": 20}}]} now yields {"price": {"gt": 10, "lt": 20}} instead of the last one winning.
Bugs / edge cases spotted:
-
Duplicate logic — the same
merge_filters+_process_metadata_filtersimplementation is duplicated verbatim inmem0/memory/main.py(around line 1161 and again at line 2482). If only one was updated, the other would still have the overwrite bug. The diff shows both were updated, which is good, but consider extracting this to a single helper to prevent drift. -
Equality collision still loses — the new test
test_and_simple_equality_no_mergedocuments that"AND": [{"status": "active"}, {"status": "pending"}]results in{"status": "pending"}. That's acceptable for now because simple equality isn't a dict of operators, but it may surprise callers who expect AND to mean both must match. Consider whether scalar equality inside AND should be combined into an$inlist instead of last-write-wins. -
Deep merge is shallow for nested dicts —
target[key].update(value)only goes one level deep. If someone somehow nests operators (unlikely today), the merge would still be shallow. Not a blocker for the current API.
Tests cover the exact bug and adjacent cases. LGTM with the caveat about deduplicating the duplicated method.
Linked Issue
Closes #4850
Description
_process_metadata_filterssilently drops filter conditions when multiple AND conditions target the same field with different operators. For example:Root cause:
dict.update()replaces existing keys entirely. When two conditions share the same field name, the second condition's operator dict overwrites the first instead of merging.Fix: Added a
merge_filters()helper that deep-merges nested operator dicts when the same key already exists in the target. Applied consistently to:Both
Memory(sync) andAsyncMemoryclasses are fixed.Type of Change
Breaking Changes
N/A
Test Coverage
Manual testing
Reproduced the bug before the fix — confirmed
{"price": {"lt": 20}}withgt:10silently dropped. After the fix, output is{"price": {"gt": 10, "lt": 20}}.New regression tests (4 tests)
test_and_same_key_different_operators_mergedtest_and_same_key_three_operators_mergedtest_and_mixed_keys_with_same_key_overlaptest_and_simple_equality_no_mergeChecklist