{"record":{"id":"d47c43c9ddfb5166","repo":"chroma-core/chroma","slug":"sub-requires-left-and-right-fields","errorCode":null,"errorMessage":"$sub requires 'left' and 'right' fields","messagePattern":"\\$sub requires 'left' and 'right' fields","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/execution/expression/operator.py","lineNumber":775,"sourceCode":"            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)}\"\n                )\n\n            ranks = [Rank.from_dict(r) for r in ranks_data]\n            result = ranks[0]","sourceCodeStart":757,"sourceCodeEnd":793,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/execution/expression/operator.py#L757-L793","documentation":"The $sub rank operator in Chroma requires exactly two fields, 'left' and 'right', naming the minuend and subtrahend rank expressions. Rank.from_dict raises this ValueError when the payload is a dict but one or both of those keys are missing. Extra keys are ignored; only presence of both names matters.","triggerScenarios":"Rank.from_dict({'$sub': {'left': {...}}}) with 'right' omitted, or keys renamed by a generator ('lhs'/'rhs', 'a'/'b', 'minuend'/'subtrahend').","commonSituations":"Hand-written JSON with typos or abbreviated key names; code that builds the dict dynamically and skips the second operand when it is None; partial copy-paste of an example expression.","solutions":["Include both keys: {\"$sub\": {\"left\": <rank>, \"right\": <rank>}}","If the right operand is optional in your logic, substitute {\"$val\": 0} instead of omitting the key","Check 'left' in d['$sub'] and 'right' in d['$sub'] before calling Rank.from_dict","Generate the dict via Python (left_rank - right_rank).to_dict() so keys are always correct"],"exampleFix":"# before\nexpr = {\"$sub\": {\"left\": knn_rank}}\n\n# after\nexpr = {\"$sub\": {\"left\": knn_rank, \"right\": {\"$val\": 0}}}","handlingStrategy":"validation","validationCode":"sub = rank_expr.get(\"$sub\")\nif not (isinstance(sub, dict) and \"left\" in sub and \"right\" in sub):\n    raise ValueError(f\"$sub requires 'left' and 'right': {rank_expr!r}\")","typeGuard":"def is_complete_sub_expr(d) -> bool:\n    return (isinstance(d, dict) and set(d) == {\"$sub\"}\n            and isinstance(d[\"$sub\"], dict)\n            and {\"left\", \"right\"} <= set(d[\"$sub\"]))","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":["Use exact key names 'left' and 'right' — no abbreviations","When an operand is optional in your logic, substitute {\"$val\": 0} instead of omitting it","Prefer (left - right).to_dict() so keys are generated by the library"],"tags":["chromadb","rank-expression","validation","deserialization","missing-field"],"backgroundTag":"query-expression-validation","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}