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 validateView on GitHub (pinned to aecdd12c8a)
Solutions
- Wrap the vector: {'$knn': {'query': [0.1, 0.2, 0.3]}}.
- Or use the class: Search(rank=Knn(query=[0.1, 0.2, 0.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
- Always build $knn as {'$knn': {'query': ...}} - the vector never sits directly under $knn.
- Prefer Knn(query=...) so the constructor shapes the payload.
- Review nested trees: each $-operator has a different payload shape (list vs dict).
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
- $knn query must be a list, numpy array, or SparseVector dict
- Expected dict for Rank, got {type(data).__name__}
- $val requires a number, got {type(value).__name__}
- $knn requires 'query' field
- $knn requires exactly one query embedding
AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16).
Data as JSON: /api/errors/4592ca95fa958276.
Report an issue: GitHub.