mem0ai/mem0 · error · ValueError
{key} filter value must be a list of filter dicts, got {type
Error message
{key} filter value must be a list of filter dicts, got {type(value).__name__} What it means
Raised during filter normalization when a logical operator key (AND, OR, NOT — including lowercase aliases mapped to them) has a non-list value. Logical operators are defined to take a list of sub-filter dicts so each element can be recursively translated into its own Filter; a single dict or scalar cannot be iterated uniformly.
Source
Thrown at mem0/vector_stores/qdrant.py:375
# Memory._process_metadata_filters() renames OR→$or and NOT→$not,
# but effective_filters retains the original OR/NOT keys from
# deepcopy(input_filters). Without dedup the same sub-conditions
# would be evaluated twice.
key_map = {"$or": "OR", "$not": "NOT", "$and": "AND"}
normalized = {}
for key, value in filters.items():
norm_key = key_map.get(key, key)
if norm_key not in normalized:
normalized[norm_key] = value
must = []
should = []
must_not = []
for key, value in normalized.items():
if key in ("AND", "OR", "NOT"):
if not isinstance(value, list):
raise ValueError(
f"{key} filter value must be a list of filter dicts, "
f"got {type(value).__name__}"
)
for i, item in enumerate(value):
if not isinstance(item, dict):
raise ValueError(
f"{key} filter list item at index {i} must be a dict, "
f"got {type(item).__name__}: {item!r}"
)
if key == "AND":
for sub in value:
built = self._create_filter(sub)
if built:
must.append(built)
elif key == "OR":
for sub in value:
built = self._create_filter(sub)View on GitHub (pinned to 001c235229)
Solutions
- Wrap the sub-filters in a list: `{"AND": [{"user_id": "u1"}, {"run_id": "r2"}]}`.
- For a single condition, drop the logical wrapper entirely — `{"user_id": "u1"}` is already an implicit AND at the top level.
- Validate generated/external filter JSON with a schema that requires AND/OR/NOT to be arrays of objects.
Example fix
# before
filters = {"AND": {"user_id": "u1", "run_id": "r2"}}
# after
filters = {"AND": [{"user_id": "u1"}, {"run_id": "r2"}]}
# single condition: no wrapper needed
filters = {"user_id": "u1"} Defensive patterns
Strategy: type-guard
Validate before calling
def normalize_logic_filters(filters: dict) -> dict:
out = {}
for key, value in filters.items():
if key.upper() in ("AND", "OR", "NOT"):
if isinstance(value, dict):
value = [{k: v} for k, v in value.items()] # shorthand -> list form
if not isinstance(value, list):
raise ValueError(f"{key} must be a list of filter dicts")
out[key.upper()] = value
return out Type guard
def is_valid_logic_value(value) -> bool:
return isinstance(value, list) Try / catch
try:
results = memory.search("q", filters=filters)
except ValueError as e:
if "must be a list of filter dicts" in str(e):
raise BadRequest(f"Malformed logical filter: {filters}") from e
raise Prevention
- Adopt the convention AND/OR/NOT always take arrays, even for a single element: `{"AND": [{...}]}`.
- Single-condition filters need no logical wrapper — drop it.
- Schema-validate external filter JSON: logical keys must be arrays of objects.
When it happens
Trigger: `{"AND": {"user_id": "u1", "run_id": "r2"}}` (single dict instead of list), `{"OR": "user_id"}` (string), or `{"not": {"x": 1}}` after lowercase key normalization maps 'not'→'NOT'.
Common situations: Writing intuitive shorthand `"AND": {...}` like SQL/JSON-API semantics; refactoring from nested single-condition filters into combined ones; LLM emitting an object where an array was expected in tool-call output.
Related errors
- Cannot mix range operators ({ops & range_ops}) with non-rang
- Unsupported filter operator(s) for field '{key}': {ops}. Sup
- {key} filter list item at index {i} must be a dict, got {typ
- Invalid datetime value in range filter for field '{key}': {e
- AND filter value must be a list of filter dicts, got ${typeo
AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15).
Data as JSON: /api/errors/55a78a905418084b.
Report an issue: GitHub.