class_docstr: ConditionalRouter in Haystack 2.x pipelines is designed to manage data routing based on specific conditions.
  This is achieved by defining a list named 'routes'. Each element in this list is a dictionary representing a
  single route.
  
  A route dictionary comprises four key elements:
  - 'condition': A Jinja2 string expression that determines if the route is selected.
  - 'output': A Jinja2 expression defining the route's output value.
  - 'output_type': The type of the output data (e.g., str, List[int]).
  - 'output_name': The name under which the `output` value of the route is published. This name is used to connect
  the router to other components in the pipeline.
  
  Here's an example:
  
  ```python
  from haystack.components.routers import ConditionalRouter
  
  routes = [
      {
          "condition": "{{streams|length > 2}}",
          "output": "{{streams}}",
          "output_name": "enough_streams",
          "output_type": List[int],
      },
      {
          "condition": "{{streams|length <= 2}}",
          "output": "{{streams}}",
          "output_name": "insufficient_streams",
          "output_type": List[int],
      },
  ]
  router = ConditionalRouter(routes)
  # When 'streams' has more than 2 items, 'enough_streams' output will activate, emitting the list [1, 2, 3]
  kwargs = {"streams": [1, 2, 3], "query": "Haystack"}
  result = router.run(**kwargs)
  assert result == {"enough_streams": [1, 2, 3]}
  ```
  
  In this example, we configure two routes. The first route sends the 'streams' value to 'enough_streams' if the
  stream count exceeds two. Conversely, the second route directs 'streams' to 'insufficient_streams' when there
  are two or fewer streams.
  
  In the pipeline setup, the router is connected to other components using the output names. For example, the
  'enough_streams' output might be connected to another component that processes the streams, while the
  'insufficient_streams' output might be connected to a component that fetches more streams, and so on.
  
  Here is a pseudocode example of a pipeline that uses the ConditionalRouter and routes fetched ByteStreams to
  different components depending on the number of streams fetched:
  
  ```
  from typing import List
  from haystack import Pipeline
  from haystack.dataclasses import ByteStream
  from haystack.components.routers import ConditionalRouter
  
  routes = [
      {
          "condition": "{{streams|length > 2}}",
          "output": "{{streams}}",
          "output_name": "enough_streams",
          "output_type": List[ByteStream],
      },
      {
          "condition": "{{streams|length <= 2}}",
          "output": "{{streams}}",
          "output_name": "insufficient_streams",
          "output_type": List[ByteStream],
      },
  ]
  
  pipe = Pipeline()
  pipe.add_component("router", router)
  ...
  pipe.connect("router.enough_streams", "some_component_a.streams")
  pipe.connect("router.insufficient_streams", "some_component_b.streams_or_some_other_input")
  ...
  ```
class_docstr_tok: ["ConditionalRouter","in","Haystack","2.x","pipelines","is","designed","to","manage","data","routing","based","on","specific","conditions",".","This","is","achieved","by","defining","a","list","named","'routes","'",".","Each","element","in","this","list","is","a","dictionary","representing","a","single","route",".","A","route","dictionary","comprises","four","key","elements",":","-","'condition","'",":","A","Jinja2","string","expression","that","determines","if","the","route","is","selected",".","-","'output","'",":","A","Jinja2","expression","defining","the","route","'s","output","value",".","-","'output_type","'",":","The","type","of","the","output","data","(","e.g.",",","str",",","List","[","int","]",")",".","-","'output_name","'",":","The","name","under","which","the","`","output","`","value","of","the","route","is","published",".","This","name","is","used","to","connect","the","router","to","other","components","in","the","pipeline",".","Here","'s","an","example",":","``","`","python","from","haystack.components.routers","import","ConditionalRouter","routes","=","[","{","``","condition","''",":","``","{","{","streams|length",">","2","}","}","''",",","``","output","''",":","``","{","{","streams","}","}","''",",","``","output_name","''",":","``","enough_streams","''",",","``","output_type","''",":","List","[","int","]",",","}",",","{","``","condition","''",":","``","{","{","streams|length","<","=","2","}","}","''",",","``","output","''",":","``","{","{","streams","}","}","''",",","``","output_name","''",":","``","insufficient_streams","''",",","``","output_type","''",":","List","[","int","]",",","}",",","]","router","=","ConditionalRouter","(","routes",")","#","When","'streams","'","has","more","than","2","items",",","'enough_streams","'","output","will","activate",",","emitting","the","list","[","1",",","2",",","3","]","kwargs","=","{","``","streams","''",":","[","1",",","2",",","3","]",",","``","query","''",":","``","Haystack","''","}","result","=","router.run","(","*","*","kwargs",")","assert","result","==","{","``","enough_streams","''",":","[","1",",","2",",","3","]","}","``","`","In","this","example",",","we","configure","two","routes",".","The","first","route","sends","the","'streams","'","value","to","'enough_streams","'","if","the","stream","count","exceeds","two",".","Conversely",",","the","second","route","directs","'streams","'","to","'insufficient_streams","'","when","there","are","two","or","fewer","streams",".","In","the","pipeline","setup",",","the","router","is","connected","to","other","components","using","the","output","names",".","For","example",",","the","'enough_streams","'","output","might","be","connected","to","another","component","that","processes","the","streams",",","while","the","'insufficient_streams","'","output","might","be","connected","to","a","component","that","fetches","more","streams",",","and","so","on",".","Here","is","a","pseudocode","example","of","a","pipeline","that","uses","the","ConditionalRouter","and","routes","fetched","ByteStreams","to","different","components","depending","on","the","number","of","streams","fetched",":","``","`","from","typing","import","List","from","haystack","import","Pipeline","from","haystack.dataclasses","import","ByteStream","from","haystack.components.routers","import","ConditionalRouter","routes","=","[","{","``","condition","''",":","``","{","{","streams|length",">","2","}","}","''",",","``","output","''",":","``","{","{","streams","}","}","''",",","``","output_name","''",":","``","enough_streams","''",",","``","output_type","''",":","List","[","ByteStream","]",",","}",",","{","``","condition","''",":","``","{","{","streams|length","<","=","2","}","}","''",",","``","output","''",":","``","{","{","streams","}","}","''",",","``","output_name","''",":","``","insufficient_streams","''",",","``","output_type","''",":","List","[","ByteStream","]",",","}",",","]","pipe","=","Pipeline","(",")","pipe.add_component","(","``","router","''",",","router",")","...","pipe.connect","(","``","router.enough_streams","''",",","``","some_component_a.streams","''",")","pipe.connect","(","``","router.insufficient_streams","''",",","``","some_component_b.streams_or_some_other_input","''",")","...","``","`"]
docstr: Validates a list of routes.
  
  :param routes: A list of routes.
  :type routes: List[Dict]
docstr_tok: ["Validates","a","list","of","routes",".",":","param","routes",":","A","list","of","routes",".",":","type","routes",":","List","[","Dict","]"]
code: def _validate_routes(self, routes: List[Dict]):
      """
      Validates a list of routes.
  
      :param routes: A list of routes.
      :type routes: List[Dict]
      """
      env = NativeEnvironment()
      for route in routes:
          try:
              keys = set(route.keys())
          except AttributeError:
              raise ValueError(
                  f"Route must be a dictionary, got: {route}"
              )
  
          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}"
              )
          for field in ["condition", "output"]:
              if not self._validate_template(env, route[field]):
                  raise ValueError(
                      f"Invalid template for field '{field}': {route[field]}"
                  )
  
file_path: haystack/haystack/components/routers/conditional_router.py
imports: import logging
  from typing import List, Dict, Any, Set
  from jinja2 import meta, Environment, TemplateSyntaxError
  from jinja2.nativetypes import NativeEnvironment
  from haystack import component, default_from_dict, default_to_dict
  from haystack.utils.type_serialization import serialize_type, deserialize_type
label_desc: Use image node_id 5 for calling the ConditionalRouter obj's underlying member method code with example usage: obj._validate_routes(routes) without return types