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

  1. Wrap the sub-filters in a list: `{"AND": [{"user_id": "u1"}, {"run_id": "r2"}]}`.
  2. For a single condition, drop the logical wrapper entirely — `{"user_id": "u1"}` is already an implicit AND at the top level.
  3. 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

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


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