deepset-ai/haystack · error · ValueError

Route must be a dictionary, got: {route}

Error message

Route must be a dictionary, got: {route}

What it means

During ConditionalRouter construction, _validate_routes found an entry in the routes list that is not a dictionary (it has no .keys()). A ValueError is raised at init time so malformed route definitions never reach runtime.

Source

Thrown at haystack/components/routers/conditional_router.py:492

                # If this was a type-validation failure or missing passthrough variable, let it propagate
                if isinstance(e, ValueError):
                    raise
                msg = f"Error evaluating condition for route '{route}': {e}"
                raise RouteConditionException(msg) from e

        raise NoRouteSelectedException(f"No route fired. Routes: {self.routes}")

    def _validate_routes(self, routes: list[Route]) -> None:
        """
        Validates a list of routes.

        :param routes: A list of routes.
        """
        for route in routes:
            try:
                keys = set(route.keys())
            except AttributeError as e:
                raise ValueError(f"Route must be a dictionary, got: {route}") from e

            mandatory_fields = {"condition", "output", "output_type", "output_name"}
            has_all_mandatory_fields = mandatory_fields.issubset(keys)
            if not has_all_mandatory_fields:
                raise ValueError(
                    f"Route must contain 'condition', 'output', 'output_type' and 'output_name' fields: {route}"
                )

            # Validate outputs are consistent
            outputs = route["output"] if isinstance(route["output"], list) else [route["output"]]
            output_types = route["output_type"] if isinstance(route["output_type"], list) else [route["output_type"]]
            output_names = route["output_name"] if isinstance(route["output_name"], list) else [route["output_name"]]

            # Check lengths match
            if not len(outputs) == len(output_types) == len(output_names):
                raise ValueError(f"Route output, output_type and output_name must have same length: {route}")

            # Condition is always a Jinja2 template — validate it

View on GitHub (pinned to e318778c9b)

Solutions

  1. Ensure routes is a list of dictionaries (or Route dataclass instances, which are dict-convertible as intended).
  2. If passing a single route, wrap it: routes=[route].
  3. Fix config/YAML so each route is a mapping with keys condition/output/output_type/output_name.

Example fix

// before
router = ConditionalRouter(routes="{{ q }}")
// after
router = ConditionalRouter(routes=[{...route dict...}])
Defensive patterns

Strategy: type-guard

Validate before calling

assert all(isinstance(r, dict) for r in routes), "every route must be a dict"
router = ConditionalRouter(routes=routes)

Type guard

def is_valid_route_list(routes) -> bool:
    return isinstance(routes, list) and all(isinstance(r, dict) for r in routes)

Try / catch

try:
    router = ConditionalRouter(routes=routes)
except ValueError as e:
    if str(e).startswith("Route must be a dictionary"):
        routes = [dict(r) if hasattr(r, "keys") else normalize(r) for r in routes]
        router = ConditionalRouter(routes=routes)
    else:
        raise

Prevention

When it happens

Trigger: ConditionalRouter(routes=[...]) where a list element is a string, tuple, Route-like object, or other non-dict instead of a dict/Route mapping.

Common situations: Hand-editing route lists in YAML/config and dropping the dict structure; passing a single route (not wrapped in a list) so the string is iterated character-by-character; loading legacy configs with a different route format.

Related errors


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