{"record":{"id":"8204d54c7c9c03e8","repo":"chroma-core/chroma","slug":"mul-requires-at-least-2-ranks-got-len-ranks-dat","errorCode":null,"errorMessage":"$mul requires at least 2 ranks, got {len(ranks_data)}","messagePattern":"\\$mul requires at least 2 ranks, got (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/execution/expression/operator.py","lineNumber":788,"sourceCode":"            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]\n            for r in ranks[1:]:\n                result = result * r\n            return result\n\n        elif op == \"$div\":\n            div_data = data[\"$div\"]\n            if not isinstance(div_data, dict):\n                raise TypeError(\n                    f\"$div requires a dict with 'left' and 'right', got {type(div_data).__name__}\"\n                )\n            if \"left\" not in div_data or \"right\" not in div_data:\n                raise ValueError(\"$div requires 'left' and 'right' fields\")\n","sourceCodeStart":770,"sourceCodeEnd":806,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/execution/expression/operator.py#L770-L806","documentation":"$mul folds at least two rank expressions via multiplication (e.g. Knn * Val(0.8) for weighting), so Chroma's parser rejects a $mul payload with fewer than two operands. This ValueError from Rank.from_dict fires when the operand list has 0 or 1 elements.","triggerScenarios":"Rank.from_dict({'$mul': []}) or {'$mul': [single_rank]} — typically a weighting expression built dynamically where the single weighted term ends up alone, or a scaling-by-one wrapped needlessly in $mul.","commonSituations":"Weighted fusion code that multiplies N scores and N weights but one side is filtered out; 'for consistency' wrapping of a lone term; test fixtures trimmed from a two-element example.","solutions":["Pass at least two operands in the $mul list, or pass the single rank dict directly without the $mul wrapper","Branch when building dynamically: expr = ranks[0] if len(ranks) == 1 else {'$mul': ranks}","Apply constant scaling with a bare two-element product, e.g. {\"$mul\": [knn, {\"$val\": 0.8}]}","Validate operand count before deserialization"],"exampleFix":"# before\nexpr = {\"$mul\": [{\"$knn\": {\"query\": q, \"return_rank\": True}}]}\n\n# after\nexpr = {\"$knn\": {\"query\": q, \"return_rank\": True}}","handlingStrategy":"validation","validationCode":"def valid_mul_expr(expr):\n    return (\n        isinstance(expr, dict) and set(expr) == {\"$mul\"}\n        and isinstance(expr[\"$mul\"], (list, tuple))\n        and len(expr[\"$mul\"]) >= 2\n    )\n\nif not valid_mul_expr(rank_expr):\n    raise ValueError(f\"$mul needs >= 2 operands: {rank_expr!r}\")","typeGuard":"def is_mul_expr(d) -> bool:\n    return (isinstance(d, dict) and set(d) == {\"$mul\"}\n            and isinstance(d[\"$mul\"], (list, tuple))\n            and len(d[\"$mul\"]) >= 2)","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":["Never wrap a lone operand in $mul — pass it directly","For constant scaling always include the scalar: {\"$mul\": [rank, {\"$val\": c}]}","Branch on operand count when building fusion expressions dynamically"],"tags":["chromadb","rank-expression","validation","deserialization","hybrid-search"],"backgroundTag":"query-expression-validation","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}