chroma-core/chroma · error · TypeError
$sub requires a dict with 'left' and 'right', got {type(sub_
Error message
$sub requires a dict with 'left' and 'right', got {type(sub_data).__name__} What it means
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).
Source
Thrown at chromadb/execution/expression/operator.py:771
if not isinstance(ranks_data, (list, tuple)):
raise TypeError(
f"$sum requires a list, got {type(ranks_data).__name__}"
)
if len(ranks_data) < 2:
raise ValueError(
f"$sum 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 == "$sub":
sub_data = data["$sub"]
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)}"View on GitHub (pinned to aecdd12c8a)
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
Example fix
# before
expr = {"$sub": [dense_rank, sparse_rank]}
# after
expr = {"$sub": {"left": dense_rank, "right": sparse_rank}} Defensive patterns
Strategy: validation
Validate before calling
def valid_sub_expr(expr):
return (
isinstance(expr, dict) and set(expr) == {"$sub"}
and isinstance(expr["$sub"], dict)
)
if not valid_sub_expr(rank_expr):
raise ValueError(f"$sub payload must be a dict with left/right, got {rank_expr!r}") Type guard
def is_sub_expr(d) -> bool:
return (isinstance(d, dict) and set(d) == {"$sub"}
and isinstance(d["$sub"], dict)) 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
- 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
When it happens
Trigger: Rank.from_dict({'$sub': [left, right]}) or {'$sub': 5}, or the equivalent nested inside a larger expression tree that a client or service deserializes.
Common situations: 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.
Related errors
- $mul requires a list, got {type(ranks_data).__name__}
- $div requires a dict with 'left' and 'right', got {type(div_
- $abs requires a rank dict, got {type(child_data).__name__}
- $exp requires a rank dict, got {type(child_data).__name__}
- $log requires a rank dict, got {type(child_data).__name__}
AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16).
Data as JSON: /api/errors/a4be82dd683b4d7b.
Report an issue: GitHub.