chroma-core/chroma · error · TypeError

$val requires a number, got {type(value).__name__}

Error message

$val requires a number, got {type(value).__name__}

What it means

The $val operator holds a constant rank score and its value must be an int or float (bools technically pass since bool subclasses int). Strings like '0.5', None, lists, or nested dicts raise TypeError because there is no coercion in the parser.

Source

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

        - {"$min": [ranks]} -> rank1.min(rank2).min(rank3)...
        """
        if not isinstance(data, dict):
            raise TypeError(f"Expected dict for Rank, got {type(data).__name__}")

        if not data:
            raise ValueError("Rank dict cannot be empty")

        if len(data) != 1:
            raise ValueError(
                f"Rank dict must contain exactly one operator, got {len(data)}"
            )

        op = next(iter(data.keys()))

        if op == "$val":
            value = data["$val"]
            if not isinstance(value, (int, float)):
                raise TypeError(f"$val requires a number, got {type(value).__name__}")
            return Val(value)

        elif op == "$knn":
            knn_data = data["$knn"]
            if not isinstance(knn_data, dict):
                raise TypeError(f"$knn requires a dict, got {type(knn_data).__name__}")

            if "query" not in knn_data:
                raise ValueError("$knn requires 'query' field")

            query = knn_data["query"]

            if isinstance(query, dict):
                # SparseVector case - deserialize from transport format
                if query.get(TYPE_KEY) == SPARSE_VECTOR_TYPE_VALUE:
                    query = SparseVector.from_dict(query)
                else:
                    # Old format or invalid - try to construct directly

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Use a bare number: {'$val': 0.5}.
  2. Cast known-clean strings at the boundary: {'$val': float(w)}.
  3. If the weight is optional, omit the $val term entirely rather than sending None.

Example fix

# before
Search(rank={'$val': boost_str})   # boost_str == '1.5' -> TypeError

# after
Search(rank={'$val': float(boost_str)})
Defensive patterns

Strategy: validation

Validate before calling

weight = request.get('weight')
rank = {'$val': float(weight)} if weight is not None else None
Search(rank=rank)

Type guard

def is_numeric_val(node) -> bool:
    return (
        isinstance(node, dict)
        and set(node) == {'$val'}
        and isinstance(node['$val'], (int, float))
        and not isinstance(node['$val'], bool)
    )

Prevention

When it happens

Trigger: Search(rank={'$val': '0.5'}); {'$val': None}; {'$val': [0.5]}; configs storing weights as strings ('weight': '1.5').

Common situations: YAML/env-var configs where numbers stay strings; JSON with quoted numerics; optional weights defaulting to None instead of being omitted from the expression.

Related errors


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