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 itView on GitHub (pinned to e318778c9b)
Solutions
- Ensure routes is a list of dictionaries (or Route dataclass instances, which are dict-convertible as intended).
- If passing a single route, wrap it: routes=[route].
- 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
- Always pass routes as a list of dicts; wrap a single route in a list.
- Build routes via a helper function that constructs dicts with all required keys.
- Construct ConditionalRouter early (at startup) so validation errors surface before runtime.
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
- Hook of type '{type(h).__name__}' is registered under hook p
- 'dimension' must be a positive integer.
- 'dimension' must be a positive integer.
- 'chat_generators' must be a non-empty list
- required_variables must not be empty. Set it to '*' to requi
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/cacac0b04c6a91ab.
Report an issue: GitHub.