mem0ai/mem0 · error · ValueError
{key} filter list item at index {i} must be a dict, got {typ
Error message
{key} filter list item at index {i} must be a dict, got {type(item).__name__}: {item!r} What it means
Raised when an AND/OR/NOT list contains an element that is not a dict. Each list element is expected to be a sub-filter object that `_create_filter` can recurse into; strings, numbers, or lists break that contract. The message includes the offending index, type, and repr for fast localization.
Source
Thrown at mem0/vector_stores/qdrant.py:381
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)
if built:
should.append(built)
elif key == "NOT":
for sub in value:
built = self._create_filter(sub)
if built:View on GitHub (pinned to 001c235229)
Solutions
- Make every element a one-field dict: `{"OR": [{"user_id": "u1"}, {"agent_id": "a9"}]}`.
- If a bare field name appears, it is likely a leftover of intent like equality — rewrite as `{"field": value}`.
- Filter out None/empty entries when building the list dynamically: `[c for c in conditions if isinstance(c, dict) and c]`.
Example fix
# before
filters = {"OR": ["user_id", {"agent_id": "a9"}]}
# after
filters = {"OR": [{"user_id": "u1"}, {"agent_id": "a9"}]}
# safe dynamic assembly
conditions = [c if isinstance(c, dict) else {"user_id": c} for c in raw_conditions]
filters = {"OR": [c for c in conditions if c]} Defensive patterns
Strategy: type-guard
Validate before calling
def clean_logic_list(key: str, items):
cleaned = [i for i in items if isinstance(i, dict) and i]
if len(cleaned) != len(items):
raise ValueError(f"{key} list contains non-dict/empty items: {items!r}")
return cleaned Type guard
def is_valid_logic_list(items) -> bool:
return isinstance(items, list) and bool(items) and all(
isinstance(i, dict) and i for i in items
) Try / catch
try:
results = memory.search("q", filters=filters)
except ValueError as e:
if "must be a dict" in str(e):
raise BadRequest(f"Malformed logical filter item: {filters}") from e
raise Prevention
- When building condition lists programmatically, append only `{field: condition}` dicts.
- Filter out None/empty entries as you assemble, not at query time.
- For LLM-generated filters, validate the shape (arrays of objects under logical keys) before use.
When it happens
Trigger: `{"OR": ["user_id"]}` (bare field name), `{"AND": [{"a": 1}, ["b", 2]]}` (nested list), `{"NOT": [None]}` from optional fields never populated — any non-dict element inside a logical operator list.
Common situations: Programmatic filter assembly appending raw values instead of `{field: {...}}` dicts; JSON deserialization where a caller sent `"OR": ["cond1"]`; LLM tool output mixing scalar shorthand into condition arrays.
Related errors
- Cannot mix range operators ({ops & range_ops}) with non-rang
- Unsupported filter operator(s) for field '{key}': {ops}. Sup
- {key} filter value must be a list of filter dicts, got {type
- 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/5c642f603ad72d0e.
Report an issue: GitHub.