{"record":{"id":"55a78a905418084b","repo":"mem0ai/mem0","slug":"key-filter-value-must-be-a-list-of-filter-dicts-55a78a","errorCode":null,"errorMessage":"{key} filter value must be a list of filter dicts, got {type(value).__name__}","messagePattern":"(.+?) filter value must be a list of filter dicts, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mem0/vector_stores/qdrant.py","lineNumber":375,"sourceCode":"        # Memory._process_metadata_filters() renames OR→$or and NOT→$not,\n        # but effective_filters retains the original OR/NOT keys from\n        # deepcopy(input_filters).  Without dedup the same sub-conditions\n        # would be evaluated twice.\n        key_map = {\"$or\": \"OR\", \"$not\": \"NOT\", \"$and\": \"AND\"}\n        normalized = {}\n        for key, value in filters.items():\n            norm_key = key_map.get(key, key)\n            if norm_key not in normalized:\n                normalized[norm_key] = value\n\n        must = []\n        should = []\n        must_not = []\n\n        for key, value in normalized.items():\n            if key in (\"AND\", \"OR\", \"NOT\"):\n                if not isinstance(value, list):\n                    raise ValueError(\n                        f\"{key} filter value must be a list of filter dicts, \"\n                        f\"got {type(value).__name__}\"\n                    )\n                for i, item in enumerate(value):\n                    if not isinstance(item, dict):\n                        raise ValueError(\n                            f\"{key} filter list item at index {i} must be a dict, \"\n                            f\"got {type(item).__name__}: {item!r}\"\n                        )\n\n            if key == \"AND\":\n                for sub in value:\n                    built = self._create_filter(sub)\n                    if built:\n                        must.append(built)\n            elif key == \"OR\":\n                for sub in value:\n                    built = self._create_filter(sub)","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/mem0/vector_stores/qdrant.py#L357-L393","documentation":"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.","triggerScenarios":"`{\"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'.","commonSituations":"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.","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."],"exampleFix":"# before\nfilters = {\"AND\": {\"user_id\": \"u1\", \"run_id\": \"r2\"}}\n\n# after\nfilters = {\"AND\": [{\"user_id\": \"u1\"}, {\"run_id\": \"r2\"}]}\n\n# single condition: no wrapper needed\nfilters = {\"user_id\": \"u1\"}","handlingStrategy":"type-guard","validationCode":"def normalize_logic_filters(filters: dict) -> dict:\n    out = {}\n    for key, value in filters.items():\n        if key.upper() in (\"AND\", \"OR\", \"NOT\"):\n            if isinstance(value, dict):\n                value = [{k: v} for k, v in value.items()]  # shorthand -> list form\n            if not isinstance(value, list):\n                raise ValueError(f\"{key} must be a list of filter dicts\")\n        out[key.upper()] = value\n    return out","typeGuard":"def is_valid_logic_value(value) -> bool:\n    return isinstance(value, list)","tryCatchPattern":"try:\n    results = memory.search(\"q\", filters=filters)\nexcept ValueError as e:\n    if \"must be a list of filter dicts\" in str(e):\n        raise BadRequest(f\"Malformed logical filter: {filters}\") from e\n    raise","preventionTips":["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."],"tags":["filters","qdrant","validation","query-syntax","vector-store"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}