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
- Remove the $and/$or wrapper and pass the single condition directly: where_document={"$contains": "hello"}
- If composing dynamically, only wrap in $and/$or when len(conditions) >= 2, otherwise use conditions[0]
- 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
- Never hardcode $and/$or around a possibly-single condition; use a builder that unwraps len==1
- Assert len(operand) >= 2 in filter-construction unit tests
- Log the composed where_document before querying during development
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
- Expected where document operand value for operator {operator
- Expected where document operand value for operator {operator
- $and cannot be combined with other keys
- $and must be a non-empty array
- Invalid where clause at index ${index}
AI-assisted analysis of chroma-core/chroma@aecdd12c8a (2026-08-16).
Data as JSON: /api/errors/c8f63d2e7a880aa5.
Report an issue: GitHub.