chroma-core/chroma · error · ValueError

Expected document value for $and or $or to be a list with at

Error message

Expected document value for $and or $or to be a list with at least two where document expressions, got {operand}

What it means

Chroma validates where_document filters recursively in validate_where_document (chromadb/api/types.py). The logical operators $and and $or must map to a LIST of at least two where_document expressions; a list with zero or one element is rejected because a single condition should be expressed directly without the logical wrapper. This error is raised client-side before any query runs.

Source

Thrown at chromadb/api/types.py:1319

    for operator, operand in where_document.items():
        if operator not in [
            "$contains",
            "$not_contains",
            "$regex",
            "$not_regex",
            "$and",
            "$or",
        ]:
            raise ValueError(
                f"Expected where document operator to be one of $contains, $not_contains, $regex, $not_regex, $and, $or, got {operator}"
            )
        if operator == "$and" or operator == "$or":
            if not isinstance(operand, list):
                raise ValueError(
                    f"Expected document value for $and or $or to be a list of where document expressions, got {operand}"
                )
            if len(operand) <= 1:
                raise ValueError(
                    f"Expected document value for $and or $or to be a list with at least two where document expressions, got {operand}"
                )
            for where_document_expression in operand:
                validate_where_document(where_document_expression)
        # Value is $contains/$not_contains/$regex/$not_regex operator
        elif not isinstance(operand, str):
            raise ValueError(
                f"Expected where document operand value for operator {operator} to be a str, got {operand}"
            )
        elif len(operand) == 0:
            raise ValueError(
                f"Expected where document operand value for operator {operator} to be a non-empty str"
            )


def validate_include(include: Include, dissalowed: Optional[Include] = None) -> None:
    """Validates include to ensure it is a list of strings. Since get does not allow distances, allow_distances is used
    to control if distances is allowed"""

View on GitHub (pinned to aecdd12c8a)

Solutions

  1. Remove the $and/$or wrapper and pass the single condition directly: where_document={"$contains": "hello"}
  2. If composing dynamically, only wrap in $and/$or when len(conditions) >= 2, otherwise use conditions[0]
  3. Ensure each element of the list is itself a valid where_document dict (e.g. {"$contains": "..."}), not a bare string

Example fix

# before
where_document = {"$and": [{"$contains": "hello"}]}

# after
where_document = {"$contains": "hello"}

# dynamic composition
def build_where_document(conds):
    if not conds:
        return None
    if len(conds) == 1:
        return conds[0]
    return {"$and": conds}
Defensive patterns

Strategy: validation

Validate before calling

def build_where_document(conditions: list[dict]) -> dict | None:
    """Only wrap in $and/$or when there are >= 2 conditions."""
    if not conditions:
        return None
    if len(conditions) == 1:
        return conditions[0]
    return {"$and": conditions}

# usage
wd = build_where_document([{"$contains": term} for term in terms if term])
if wd is not None:
    res = collection.query(query_embeddings=[q], where_document=wd, n_results=5)

Type guard

def is_valid_logical_where_document(wd: object) -> bool:
    if not isinstance(wd, dict) or len(wd) != 1:
        return False
    op, operand = next(iter(wd.items()))
    if op not in ("$and", "$or"):
        return False
    return isinstance(operand, list) and len(operand) >= 2 and all(
        isinstance(e, dict) for e in operand
    )

Try / catch

try:
    res = collection.query(query_embeddings=[q], where_document=wd, n_results=5)
except ValueError as e:
    if "list with at least two where document expressions" in str(e):
        # degrade gracefully: unwrap a single condition and retry once
        op = next(iter(wd))
        if op in ("$and", "$or") and len(wd[op]) == 1:
            res = collection.query(query_embeddings=[q], where_document=wd[op][0], n_results=5)
        else:
            raise

Prevention

When it happens

Trigger: Calling collection.query(..., where_document={"$and": [{"$contains": "hello"}]}) or collection.get(..., where_document={"$or": [{"$contains": "a"}, ...]}) where the operand list has 0 or 1 items. Most often happens when filters are composed dynamically and a loop/condition set collapses to a single clause that still gets wrapped in $and/$or.

Common situations: Programmatically building filters from user-selected facets where only one facet is selected; refactoring an SQL-style OR down to one remaining branch; copy-pasting a $and template around a now-simplified condition.

Related errors


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