deepset-ai/haystack · error · FilterError

'operator' key missing in {condition}

Error message

'operator' key missing in {condition}

What it means

Logical filter conditions (AND/OR groups) must contain an 'operator' key. _logic_condition raises FilterError when the condition dict lacks it, indicating a malformed filter expression.

Source

Thrown at haystack/utils/filters.py:272

COMPARISON_OPERATORS = {
    "==": _equal,
    "!=": _not_equal,
    ">": _greater_than,
    ">=": _greater_than_equal,
    "<": _less_than,
    "<=": _less_than_equal,
    "in": _in,
    "not in": _not_in,
}


def _logic_condition(
    condition: dict[str, Any], document: Document | ByteStream, strict_datetime_comparison: bool
) -> bool:
    if "operator" not in condition:
        msg = f"'operator' key missing in {condition}"
        raise FilterError(msg)
    if "conditions" not in condition:
        msg = f"'conditions' key missing in {condition}"
        raise FilterError(msg)
    operator: str = condition["operator"]
    if operator not in LOGICAL_OPERATORS:
        msg = f"Unknown logical operator '{operator}'. Valid operators are: {sorted(LOGICAL_OPERATORS)}"
        raise FilterError(msg)
    conditions: list[dict[str, Any]] = condition["conditions"]
    return LOGICAL_OPERATORS[operator](
        document=document, conditions=conditions, strict_datetime_comparison=strict_datetime_comparison
    )


def _comparison_condition(
    condition: dict[str, Any], document: Document | ByteStream, strict_datetime_comparison: bool
) -> bool:
    if "field" not in condition:
        # 'field' key is only found in comparison dictionaries.

View on GitHub (pinned to e318778c9b)

Solutions

  1. Add "operator": "AND" (or "OR") at the top of the logical condition.
  2. Validate filter structure against the documented schema before use.
  3. Check for code paths that strip or rename keys when building filters dynamically.

Example fix

// before
filters = {"conditions": [{"field": "meta.x", "operator": "==", "value": 1}]}
// after
filters = {"operator": "AND", "conditions": [{"field": "meta.x", "operator": "==", "value": 1}]}
Defensive patterns

Strategy: validation

Validate before calling

assert "operator" in condition, f"logical condition needs 'operator': {condition}"

Type guard

def is_logical_condition(c: dict) -> bool:
    return isinstance(c, dict) and "operator" in c and "conditions" in c

Try / catch

try:
    ok = document_matches_filter(doc, filters)
except FilterError as e:
    raise ValueError(f"malformed filter: {e}") from e

Prevention

When it happens

Trigger: Passing {"conditions": [...]} without the top-level 'operator' key to document_matches_filter or a store's filter application.

Common situations: Hand-written filter dicts in configs; merging/transforming filters programmatically and dropping keys; using the legacy single-dict filter syntax in a context expecting the new nested syntax.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/65f0ede49054b3bd. Report an issue: GitHub.