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: Executes the routing logic by evaluating the specified boolean condition expressions
  for each route in the order they are listed. The method directs the flow
  of data to the output specified in the first route, whose expression
  evaluates to True. If no route's expression evaluates to True, an exception
  is raised.
  
  :param kwargs: A dictionary containing the pipeline variables, which should
             include all variables used in the "condition" templates.
  
  :return: A dictionary containing the output and the corresponding result,
       based on the first route whose expression evaluates to True.
  
  :raises NoRouteSelectedException: If no route's expression evaluates to True.
docstr_tok: ["Executes","the","routing","logic","by","evaluating","the","specified","boolean","condition","expressions","for","each","route","in","the","order","they","are","listed",".","The","method","directs","the","flow","of","data","to","the","output","specified","in","the","first","route",",","whose","expression","evaluates","to","True",".","If","no","route","'s","expression","evaluates","to","True",",","an","exception","is","raised",".",":","param","kwargs",":","A","dictionary","containing","the","pipeline","variables",",","which","should","include","all","variables","used","in","the","``","condition","''","templates",".",":","return",":","A","dictionary","containing","the","output","and","the","corresponding","result",",","based","on","the","first","route","whose","expression","evaluates","to","True",".",":","raises","NoRouteSelectedException",":","If","no","route","'s","expression","evaluates","to","True","."]
code: def run(self, **kwargs):
      """
      Executes the routing logic by evaluating the specified boolean condition expressions
      for each route in the order they are listed. The method directs the flow
      of data to the output specified in the first route, whose expression
      evaluates to True. If no route's expression evaluates to True, an exception
      is raised.
  
      :param kwargs: A dictionary containing the pipeline variables, which should
                 include all variables used in the "condition" templates.
  
      :return: A dictionary containing the output and the corresponding result,
           based on the first route whose expression evaluates to True.
  
      :raises NoRouteSelectedException: If no route's expression evaluates to True.
      """
      # Create a Jinja native environment to evaluate the condition templates as Python expressions
      env = NativeEnvironment()
  
      for route in self.routes:
          try:
              t = env.from_string(route["condition"])
              if t.render(**kwargs):
                  # We now evaluate the `output` expression to determine the route output
                  t_output = env.from_string(route["output"])
                  output = t_output.render(**kwargs)
                  # and return the output as a dictionary under the output_name key
                  return {route["output_name"]: output}
          except Exception as e:
              raise RouteConditionException(
                  f"Error evaluating condition for route '{route}': {e}"
              ) from e
  
      raise NoRouteSelectedException(
          f"No route fired. Routes: {self.routes}"
      )
  
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 4 for calling the ConditionalRouter obj's underlying member method code with example usage: obj.run() and returns: dict