{"record":{"id":"22d7e19599648f83","repo":"mem0ai/mem0","slug":"unsupported-filter-operator-s-for-field-key-22d7e1","errorCode":null,"errorMessage":"Unsupported filter operator(s) for field '{key}': {ops}. Supported operators: {supported}","messagePattern":"Unsupported filter operator\\(s\\) for field '(.+?)': (.+?)\\. Supported operators: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"mem0/vector_stores/qdrant.py","lineNumber":334,"sourceCode":"            return FieldCondition(key=key, match=MatchAny(any=value[\"in\"]))\n        elif \"nin\" in value:\n            return FieldCondition(key=key, match=MatchExcept(**{\"except\": value[\"nin\"]}))\n        elif \"contains\" in value or \"icontains\" in value:\n            # MatchText: with a full-text index, tokenized matching (all words must appear).\n            # Without a full-text index, exact substring match.\n            op = \"icontains\" if \"icontains\" in value else \"contains\"\n            text = value[op]\n            if op == \"icontains\":\n                logger.debug(\n                    \"icontains on field '%s': Qdrant MatchText case sensitivity depends on \"\n                    \"full-text index configuration. Without a full-text index this behaves \"\n                    \"as a case-sensitive substring match (same as 'contains').\",\n                    key,\n                )\n            return FieldCondition(key=key, match=MatchText(text=text))\n        else:\n            supported = {\"eq\", \"ne\", \"gt\", \"gte\", \"lt\", \"lte\", \"in\", \"nin\", \"contains\", \"icontains\"}\n            raise ValueError(\n                f\"Unsupported filter operator(s) for field '{key}': {ops}. \"\n                f\"Supported operators: {supported}\"\n            )\n\n    def _create_filter(self, filters: dict) -> Optional[Filter]:\n        \"\"\"\n        Create a Filter object from the provided filters.\n\n        Supports the enhanced filter syntax with comparison operators (eq, ne,\n        gt, gte, lt, lte), list operators (in, nin), string operators (contains,\n        icontains), and logical operators (AND, OR, NOT).\n\n        Args:\n            filters (dict): Filters to apply.\n\n        Returns:\n            Filter: The created Filter object, or None if filters is empty.\n        \"\"\"","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/mem0/vector_stores/qdrant.py#L316-L352","documentation":"Raised by Qdrant's `_build_field_condition` when a field dict's keys are none of the ten supported operators (eq, ne, gt, gte, lt, lte, in, nin, contains, icontains). It is the catch-all for misspelled or unrecognized operators, and the message enumerates the full supported set so the fix is mechanical.","triggerScenarios":"Filters containing `{\"field\": {\"between\": [1, 5]}}`, `{\"field\": {\"equals\": \"x\"}}`, `{\"field\": {\"$eq\": \"x\"}}` (Mongo-style $ prefixes), `{\"field\": {\"starts_with\": \"abc\"}}`, or an empty dict `{\"field\": {}}` which falls through every elif.","commonSituations":"Translating MongoDB/SQL WHERE clauses to mem0 filter syntax; LLM-generated filters inventing plausible operator names; typos like 'gte ' with whitespace or 'GTE' capitalized; assuming regex/prefix operators exist because the docs mention full-text search.","solutions":["Rewrite the operator using the supported set: `equals`→`eq`, `$gt`→`gt`, `between`→ two AND clauses with gte/lte.","For prefix/substring needs, use `contains`/`icontains` (MatchText) — anything beyond that must be applied client-side after retrieval.","If an empty operator dict slipped in from optional user input, strip falsy entries before passing filters."],"exampleFix":"# before\nfilters = {\"status\": {\"equals\": \"active\"}}\n\n# after\nfilters = {\"status\": {\"eq\": \"active\"}}\n\n# between -> two range conditions\n# before: {\"score\": {\"between\": [1, 5]}}\nfilters = {\"AND\": [{\"score\": {\"gte\": 1}}, {\"score\": {\"lte\": 5}}]}","handlingStrategy":"type-guard","validationCode":"SUPPORTED_OPS = {\"eq\", \"ne\", \"gt\", \"gte\", \"lt\", \"lte\", \"in\", \"nin\", \"contains\", \"icontains\"}\n\ndef check_filters(filters: dict) -> None:\n    for key, value in filters.items():\n        if isinstance(value, dict):\n            bad = set(value) - SUPPORTED_OPS\n            if bad or not value:\n                raise ValueError(f\"Bad filter for {key}: unsupported/empty ops {bad or '{}'}\")","typeGuard":"def is_supported_condition(cond) -> bool:\n    if not isinstance(cond, dict) or not cond:\n        return False\n    return set(cond) <= {\"eq\", \"ne\", \"gt\", \"gte\", \"lt\", \"lte\", \"in\", \"nin\", \"contains\", \"icontains\"}","tryCatchPattern":"try:\n    results = memory.search(\"q\", filters=filters)\nexcept ValueError as e:\n    if \"Unsupported filter operator\" in str(e):\n        raise BadRequest(f\"Rejected filter syntax: {filters}\") from e\n    raise","preventionTips":["Keep a mapping table from your query DSL / Mongo operators to the ten supported ops and translate in one place.","Treat filters as an API contract: validate them with Pydantic before they reach mem0.","Cover filter translation with unit tests, including negative cases ($-prefixed ops, empty dicts)."],"tags":["filters","qdrant","validation","query-syntax","vector-store"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}