{"record":{"id":"c8f63d2e7a880aa5","repo":"chroma-core/chroma","slug":"expected-document-value-for-and-or-or-to-be-a-li-c8f63d","errorCode":null,"errorMessage":"Expected document value for $and or $or to be a list with at least two where document expressions, got {operand}","messagePattern":"Expected document value for \\$and or \\$or to be a list with at least two where document expressions, got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"chromadb/api/types.py","lineNumber":1319,"sourceCode":"    for operator, operand in where_document.items():\n        if operator not in [\n            \"$contains\",\n            \"$not_contains\",\n            \"$regex\",\n            \"$not_regex\",\n            \"$and\",\n            \"$or\",\n        ]:\n            raise ValueError(\n                f\"Expected where document operator to be one of $contains, $not_contains, $regex, $not_regex, $and, $or, got {operator}\"\n            )\n        if operator == \"$and\" or operator == \"$or\":\n            if not isinstance(operand, list):\n                raise ValueError(\n                    f\"Expected document value for $and or $or to be a list of where document expressions, got {operand}\"\n                )\n            if len(operand) <= 1:\n                raise ValueError(\n                    f\"Expected document value for $and or $or to be a list with at least two where document expressions, got {operand}\"\n                )\n            for where_document_expression in operand:\n                validate_where_document(where_document_expression)\n        # Value is $contains/$not_contains/$regex/$not_regex operator\n        elif not isinstance(operand, str):\n            raise ValueError(\n                f\"Expected where document operand value for operator {operator} to be a str, got {operand}\"\n            )\n        elif len(operand) == 0:\n            raise ValueError(\n                f\"Expected where document operand value for operator {operator} to be a non-empty str\"\n            )\n\n\ndef validate_include(include: Include, dissalowed: Optional[Include] = None) -> None:\n    \"\"\"Validates include to ensure it is a list of strings. Since get does not allow distances, allow_distances is used\n    to control if distances is allowed\"\"\"","sourceCodeStart":1301,"sourceCodeEnd":1337,"githubUrl":"https://github.com/chroma-core/chroma/blob/aecdd12c8a891610db8653630b066b32ceb678b5/chromadb/api/types.py#L1301-L1337","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before\nwhere_document = {\"$and\": [{\"$contains\": \"hello\"}]}\n\n# after\nwhere_document = {\"$contains\": \"hello\"}\n\n# dynamic composition\ndef build_where_document(conds):\n    if not conds:\n        return None\n    if len(conds) == 1:\n        return conds[0]\n    return {\"$and\": conds}","handlingStrategy":"validation","validationCode":"def build_where_document(conditions: list[dict]) -> dict | None:\n    \"\"\"Only wrap in $and/$or when there are >= 2 conditions.\"\"\"\n    if not conditions:\n        return None\n    if len(conditions) == 1:\n        return conditions[0]\n    return {\"$and\": conditions}\n\n# usage\nwd = build_where_document([{\"$contains\": term} for term in terms if term])\nif wd is not None:\n    res = collection.query(query_embeddings=[q], where_document=wd, n_results=5)","typeGuard":"def is_valid_logical_where_document(wd: object) -> bool:\n    if not isinstance(wd, dict) or len(wd) != 1:\n        return False\n    op, operand = next(iter(wd.items()))\n    if op not in (\"$and\", \"$or\"):\n        return False\n    return isinstance(operand, list) and len(operand) >= 2 and all(\n        isinstance(e, dict) for e in operand\n    )","tryCatchPattern":"try:\n    res = collection.query(query_embeddings=[q], where_document=wd, n_results=5)\nexcept ValueError as e:\n    if \"list with at least two where document expressions\" in str(e):\n        # degrade gracefully: unwrap a single condition and retry once\n        op = next(iter(wd))\n        if op in (\"$and\", \"$or\") and len(wd[op]) == 1:\n            res = collection.query(query_embeddings=[q], where_document=wd[op][0], n_results=5)\n        else:\n            raise","preventionTips":["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"],"tags":["chromadb","where-document","query-filter","input-validation"],"backgroundTag":"query-filter-validation","analyzedSha":"aecdd12c8a891610db8653630b066b32ceb678b5","analyzedAt":"2026-08-16T21:53:27.228Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}