chroma-core/chroma · error · ValueError
$mul requires at least 2 ranks, got {len(ranks_data)}
Error message
$mul requires at least 2 ranks, got {len(ranks_data)} What it means
$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.
Source
Thrown at chromadb/execution/expression/operator.py:788
if not isinstance(sub_data, dict):
raise TypeError(
f"$sub requires a dict with 'left' and 'right', got {type(sub_data).__name__}"
)
if "left" not in sub_data or "right" not in sub_data:
raise ValueError("$sub requires 'left' and 'right' fields")
left = Rank.from_dict(sub_data["left"])
right = Rank.from_dict(sub_data["right"])
return left - right
elif op == "$mul":
ranks_data = data["$mul"]
if not isinstance(ranks_data, (list, tuple)):
raise TypeError(
f"$mul requires a list, got {type(ranks_data).__name__}"
)
if len(ranks_data) < 2:
raise ValueError(
f"$mul requires at least 2 ranks, got {len(ranks_data)}"
)
ranks = [Rank.from_dict(r) for r in ranks_data]
result = ranks[0]
for r in ranks[1:]:
result = result * r
return result
elif op == "$div":
div_data = data["$div"]
if not isinstance(div_data, dict):
raise TypeError(
f"$div requires a dict with 'left' and 'right', got {type(div_data).__name__}"
)
if "left" not in div_data or "right" not in div_data:
raise ValueError("$div requires 'left' and 'right' fields")
View on GitHub (pinned to aecdd12c8a)
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
Example fix
# before
expr = {"$mul": [{"$knn": {"query": q, "return_rank": True}}]}
# after
expr = {"$knn": {"query": q, "return_rank": True}} Defensive patterns
Strategy: validation
Validate before calling
def valid_mul_expr(expr):
return (
isinstance(expr, dict) and set(expr) == {"$mul"}
and isinstance(expr["$mul"], (list, tuple))
and len(expr["$mul"]) >= 2
)
if not valid_mul_expr(rank_expr):
raise ValueError(f"$mul needs >= 2 operands: {rank_expr!r}") Type guard
def is_mul_expr(d) -> bool:
return (isinstance(d, dict) and set(d) == {"$mul"}
and isinstance(d["$mul"], (list, tuple))
and len(d["$mul"]) >= 2) Try / catch
try:
rank = Rank.from_dict(rank_expr)
except (TypeError, ValueError) as e:
raise ValueError(f"invalid rank expression {rank_expr!r}: {e}") from e Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- $sum requires at least 2 ranks, got {len(ranks_data)}
- $sub requires a dict with 'left' and 'right', got {type(sub_
- $sub requires 'left' and 'right' fields
- $mul requires a list, got {type(ranks_data).__name__}
- $div requires a dict with 'left' and 'right', got {type(div_
AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16).
Data as JSON: /api/errors/8204d54c7c9c03e8.
Report an issue: GitHub.