chroma-core/chroma · error · ValueError

Expected where to have exactly one operator, got {where}

Error message

Expected where to have exactly one operator, got {where}

What it means

A ChromaDB `where` clause must contain exactly one operator at each level: either one field expression ("field": value/operatorexpr) or one logical operator ($and/$or). This ValueError fires when the dict has zero or two-plus keys, e.g. {"a": 1, "b": 2} or {}. The single-key grammar is what allows the validator to distinguish a field filter from a logical combinator, and it is enforced recursively.

Source

Thrown at chromadb/api/types.py:1194

def validate_metadatas(metadatas: Metadatas) -> Metadatas:
    """Validates metadatas to ensure it is a list of dictionaries of strings to strings, ints, floats or bools"""
    if not isinstance(metadatas, list):
        raise ValueError(f"Expected metadatas to be a list, got {metadatas}")
    for metadata in metadatas:
        validate_metadata(metadata)
    return metadatas


def validate_where(where: Where) -> None:
    """
    Validates where to ensure it is a dictionary of strings to strings, ints, floats or operator expressions,
    or in the case of $and and $or, a list of where expressions
    """
    if not isinstance(where, dict):
        raise ValueError(f"Expected where to be a dict, got {where}")
    if len(where) != 1:
        raise ValueError(f"Expected where to have exactly one operator, got {where}")
    for key, value in where.items():
        if not isinstance(key, str):
            raise ValueError(f"Expected where key to be a str, got {key}")
        # $contains and $not_contains are only valid as operators within a
        # field expression (e.g. {"field": {"$contains": val}}), not as
        # top-level where keys.
        if key in ("$contains", "$not_contains"):
            raise ValueError(
                f"Expected where key to be a metadata field name or a logical "
                f"operator ($and, $or), got {key}"
            )
        if (
            key != "$and"
            and key != "$or"
            and key != "$in"
            and key != "$nin"
            and not isinstance(value, (str, int, float, dict))
        ):

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Rewrite multi-field filters with $and: {"$and": [{"source": "wiki"}, {"year": 2024}]} (list needs >= 2 entries).
  2. Build filters incrementally by appending single-key dicts to a list and wrapping in $and only if len > 1; pass where=None when the list is empty.
  3. Check for typos that accidentally merge keys, e.g. {**base, **extra} on where dicts.

Example fix

# before
where = {"source": "wiki", "year": 2024}
# after
where = {"$and": [{"source": "wiki"}, {"year": 2024}]}
Defensive patterns

Strategy: validation

Validate before calling

def build_where(conditions: list[dict]) -> dict | None:
    """Combine single-key condition dicts into a valid Chroma where."""
    if not conditions:
        return None
    if len(conditions) == 1:
        return conditions[0]
    return {"$and": conditions}

# usage: every element must itself have exactly one key
assert all(len(c) == 1 for c in conditions)

Type guard

def is_single_key(where: dict) -> bool:
    return len(where) == 1

Try / catch

try:
    result = collection.get(where=where)
except ValueError as e:
    if "exactly one operator" in str(e):
        # flatten multi-key dict into $and and retry once
        conds = [{k: v} for k, v in where.items()]
        where = conds[0] if len(conds) == 1 else {"$and": conds}
        result = collection.get(where=where)
    else:
        raise

Prevention

When it happens

Trigger: Passing where={"source": "wiki", "year": 2024} (two fields, no $and), where={} (empty dict, often from a filter builder that added nothing), or nesting a multi-key dict inside $and, e.g. {"$and": [{"a": 1, "b": 2}]}.

Common situations: Developers assuming Mongo-style multi-key filtering where {"a":1,"b":2} means implicit AND; auto-generated filters where each optional predicate is dict-merged (d1 | d2) instead of appended to an $and list; empty filters produced when no user filters are selected but the code still passes `where`.

Related errors


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