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

  1. Make every element a one-field dict: `{"OR": [{"user_id": "u1"}, {"agent_id": "a9"}]}`.
  2. If a bare field name appears, it is likely a leftover of intent like equality — rewrite as `{"field": value}`.
  3. 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 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


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/5c642f603ad72d0e. Report an issue: GitHub.