{"record":{"id":"a4be82dd683b4d7b","repo":"chroma-core/chroma","slug":"sub-requires-a-dict-with-left-and-right-got","errorCode":null,"errorMessage":"$sub requires a dict with 'left' and 'right', got {type(sub_data).__name__}","messagePattern":"\\$sub requires a dict with 'left' and 'right', got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"chromadb/execution/expression/operator.py","lineNumber":771,"sourceCode":"            if not isinstance(ranks_data, (list, tuple)):\n                raise TypeError(\n                    f\"$sum requires a list, got {type(ranks_data).__name__}\"\n                )\n            if len(ranks_data) < 2:\n                raise ValueError(\n                    f\"$sum requires at least 2 ranks, got {len(ranks_data)}\"\n                )\n\n            ranks = [Rank.from_dict(r) for r in ranks_data]\n            result = ranks[0]\n            for r in ranks[1:]:\n                result = result + r\n            return result\n\n        elif op == \"$sub\":\n            sub_data = data[\"$sub\"]\n            if not isinstance(sub_data, dict):\n                raise TypeError(\n                    f\"$sub requires a dict with 'left' and 'right', got {type(sub_data).__name__}\"\n                )\n            if \"left\" not in sub_data or \"right\" not in sub_data:\n                raise ValueError(\"$sub requires 'left' and 'right' fields\")\n\n            left = Rank.from_dict(sub_data[\"left\"])\n            right = Rank.from_dict(sub_data[\"right\"])\n            return left - right\n\n        elif op == \"$mul\":\n            ranks_data = data[\"$mul\"]\n            if not isinstance(ranks_data, (list, tuple)):\n                raise TypeError(\n                    f\"$mul requires a list, got {type(ranks_data).__name__}\"\n                )\n            if len(ranks_data) < 2:\n                raise ValueError(\n                    f\"$mul requires at least 2 ranks, got {len(ranks_data)}\"","sourceCodeStart":753,"sourceCodeEnd":789,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/execution/expression/operator.py#L753-L789","documentation":"When deserializing a rank expression, Chroma's $sub operator models binary subtraction and expects a dict with 'left' and 'right' keys, unlike $sum/$mul which take operand lists. Rank.from_dict raises this TypeError when the $sub payload is not a dict (e.g. a two-element list, mirroring $sum's shape, or a scalar).","triggerScenarios":"Rank.from_dict({'$sub': [left, right]}) or {'$sub': 5}, or the equivalent nested inside a larger expression tree that a client or service deserializes.","commonSituations":"Developers assume all arithmetic operators share the list shape of $sum/$mul and encode subtraction as a pair array; code that generates operator dicts from a generic 'binary op -> [a, b]' template; JSON written by hand or by another tool that uses the wrong payload shape.","solutions":["Encode subtraction as {\"$sub\": {\"left\": <rank>, \"right\": <rank>}} with both keys present","If you prefer list-shaped input, convert before serialization: {'$sub': {'left': pair[0], 'right': pair[1]}}","Build the expression in Python as left_rank - right_rank and call .to_dict() to get a guaranteed-valid shape","Guard with isinstance(payload, dict) before calling Rank.from_dict"],"exampleFix":"# before\nexpr = {\"$sub\": [dense_rank, sparse_rank]}\n\n# after\nexpr = {\"$sub\": {\"left\": dense_rank, \"right\": sparse_rank}}","handlingStrategy":"validation","validationCode":"def valid_sub_expr(expr):\n    return (\n        isinstance(expr, dict) and set(expr) == {\"$sub\"}\n        and isinstance(expr[\"$sub\"], dict)\n    )\n\nif not valid_sub_expr(rank_expr):\n    raise ValueError(f\"$sub payload must be a dict with left/right, got {rank_expr!r}\")","typeGuard":"def is_sub_expr(d) -> bool:\n    return (isinstance(d, dict) and set(d) == {\"$sub\"}\n            and isinstance(d[\"$sub\"], dict))","tryCatchPattern":"try:\n    rank = Rank.from_dict(rank_expr)\nexcept (TypeError, ValueError) as e:\n    raise ValueError(f\"invalid rank expression {rank_expr!r}: {e}\") from e","preventionTips":["Remember the two payload shapes: $sum/$mul/$max/$min take lists, $sub/$div take {'left','right'} dicts","Emit expressions via Python subtraction (a - b).to_dict() rather than hand-building dicts","Write a tiny schema test enumerating every operator shape your service accepts"],"tags":["chromadb","rank-expression","validation","deserialization","type-error"],"backgroundTag":"query-expression-validation","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}