chroma-core/chroma · error · TypeError

$knn requires a dict, got {type(knn_data).__name__}

Error message

$knn requires a dict, got {type(knn_data).__name__}

What it means

The $knn operator takes an options dict ({'query': ..., 'key': ..., 'limit': ..., 'default': ..., 'return_rank': ...}), not a bare vector. Passing the embedding list directly raises TypeError because the parser looks for the 'query' field inside a mapping before anything else.

Source

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

            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
                    raise ValueError(
                        f"Expected dict with {TYPE_KEY}='{SPARSE_VECTOR_TYPE_VALUE}', got {query}"
                    )

            elif isinstance(query, (list, tuple, np.ndarray)):
                # Dense vector case - normalize then validate

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Wrap the vector: {'$knn': {'query': [0.1, 0.2, 0.3]}}.
  2. Or use the class: Search(rank=Knn(query=[0.1, 0.2, 0.3])).
  3. Remember the other knobs (key, limit, default, return_rank) live beside 'query' in the same dict.

Example fix

# before
Search(rank={'$knn': embedding})           # -> TypeError: got list

# after
Search(rank={'$knn': {'query': embedding}})
Defensive patterns

Strategy: type-guard

Validate before calling

def knn_node(query, **opts):
    return {'$knn': {'query': list(query), **opts}}

Search(rank=knn_node(embedding, key='#embedding', limit=16))

Type guard

def is_knn_options(node) -> bool:
    return (
        isinstance(node, dict)
        and 'query' in node
        and set(node) <= {'query', 'key', 'limit', 'default', 'return_rank'}
    )

Prevention

When it happens

Trigger: Search(rank={'$knn': [0.1, 0.2, 0.3]}); {'$knn': [[0.1], [0.2]]}; nested mistakes like {'$sum': [{'$knn': query_vector}, {'$val': 1}]} where the vector sits directly under $knn.

Common situations: Shorthand thinking ('the vector is the only argument'); refactoring code that passed query_embeddings=[...] to collection.query; hand-writing expressions from docs and dropping the wrapper level.

Related errors


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