chroma-core/chroma · error · ValueError

$min requires at least 2 ranks, got {len(ranks_data)}

Error message

$min requires at least 2 ranks, got {len(ranks_data)}

What it means

$min combines at least two rank expressions via successive pairwise minimums, so Chroma's parser rejects payloads with fewer than two operands. This ValueError from Rank.from_dict fires when the $min list has 0 or 1 elements.

Source

Thrown at chromadb/execution/expression/operator.py:859

            if len(ranks_data) < 2:
                raise ValueError(
                    f"$max 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.max(r)
            return result

        elif op == "$min":
            ranks_data = data["$min"]
            if not isinstance(ranks_data, (list, tuple)):
                raise TypeError(
                    f"$min requires a list, got {type(ranks_data).__name__}"
                )
            if len(ranks_data) < 2:
                raise ValueError(
                    f"$min 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.min(r)
            return result

        else:
            raise ValueError(f"Unknown rank operator: {op}")

    # Arithmetic operators
    def __add__(self, other: Union["Rank", float, int]) -> "Sum":
        """Addition: rank1 + rank2 or rank + value"""
        other_rank = Val(other) if isinstance(other, (int, float)) else other
        # Flatten if already Sum
        if isinstance(self, Sum):

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Pass at least two operands, or drop the wrapper and pass the single rank dict directly
  2. When capping against a bound, always include both: {"$min": [rank, {"$val": bound}]}
  3. Branch when building dynamically: expr = ranks[0] if len(ranks) == 1 else {'$min': ranks}
  4. Validate operand count before deserialization

Example fix

# before
expr = {"$min": [knn_rank]}

# after
expr = {"$min": [knn_rank, {"$val": 1.0}]}
Defensive patterns

Strategy: validation

Validate before calling

def valid_min_expr(expr):
    return (
        isinstance(expr, dict) and set(expr) == {"$min"}
        and isinstance(expr["$min"], (list, tuple))
        and len(expr["$min"]) >= 2
    )

if not valid_min_expr(rank_expr):
    raise ValueError(f"$min needs >= 2 operands: {rank_expr!r}")

Type guard

def is_min_expr(d) -> bool:
    return (isinstance(d, dict) and set(d) == {"$min"}
            and isinstance(d["$min"], (list, tuple))
            and len(d["$min"]) >= 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

When it happens

Trigger: Rank.from_dict({'$min': []}) or {'$min': [single_rank]} — e.g. a cap built over a dynamic list of bounds where only one remains, or a lone operand wrapped needlessly.

Common situations: Configurable clamping where the bounds list collapses to one element; 'consistency' wrapping of a single term; trimmed test fixtures.

Related errors


AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16). Data as JSON: /api/errors/20bf692ae8187792. Report an issue: GitHub.