chroma-core/chroma · error · TypeError

$log requires a rank dict, got {type(child_data).__name__}

Error message

$log requires a rank dict, got {type(child_data).__name__}

What it means

$log is a unary rank operator applying the logarithm to one nested rank expression (the counterpart of $exp), and its payload must be a single rank dict. Rank.from_dict raises this TypeError when the $log payload is not a dict — typically a bare number or a one-element list.

Source

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

            child_data = data["$abs"]
            if not isinstance(child_data, dict):
                raise TypeError(
                    f"$abs requires a rank dict, got {type(child_data).__name__}"
                )
            return abs(Rank.from_dict(child_data))

        elif op == "$exp":
            child_data = data["$exp"]
            if not isinstance(child_data, dict):
                raise TypeError(
                    f"$exp requires a rank dict, got {type(child_data).__name__}"
                )
            return Rank.from_dict(child_data).exp()

        elif op == "$log":
            child_data = data["$log"]
            if not isinstance(child_data, dict):
                raise TypeError(
                    f"$log requires a rank dict, got {type(child_data).__name__}"
                )
            return Rank.from_dict(child_data).log()

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

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Pass one unwrapped rank dict: {"$log": {"$val": 10}} or {"$log": knn_dict}
  2. Use $val to wrap literals before nesting
  3. Build in Python as rank_expr.log().to_dict()
  4. Guard: isinstance(expr['$log'], dict) before Rank.from_dict

Example fix

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

# after
expr = {"$log": knn_rank}
Defensive patterns

Strategy: validation

Validate before calling

def valid_log_expr(expr):
    return (
        isinstance(expr, dict) and set(expr) == {"$log"}
        and isinstance(expr["$log"], dict)
    )

if not valid_log_expr(rank_expr):
    raise ValueError(f"$log payload must be a single rank dict: {rank_expr!r}")

Type guard

def is_log_expr(d) -> bool:
    return (isinstance(d, dict) and set(d) == {"$log"}
            and isinstance(d["$log"], 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

When it happens

Trigger: Rank.from_dict({'$log': 10}), {'$log': [rank_dict]}, or the same nested inside $sum/$mul trees being deserialized.

Common situations: Score-normalization pipelines that log-transform raw scores, with the operand accidentally list-wrapped or the literal passed without $val; JSON from generators that uniformly list-wrap all operands.

Related errors


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