prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Implement function to calculate Mean Reciprocal Rank metric
  As specified in proposal #5794 we need to implement a function to calculate the Mean Reciprocal Rank metric.
  
  Ideally the function should be part of the private interface and called only through the `calculate_metrics` function (see #6063). `_calculate_mrr()` could be a nice name.
  
  For more detailed information check out the original proposal.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `haystack/components/evaluators/statistical_evaluator.py`
  Content:
  ```
  1 import collections
  2 import itertools
  3 from enum import Enum
  4 from typing import Any, Dict, List, Union
  5 
  6 from numpy import array as np_array
  7 from numpy import mean as np_mean
  8 
  9 from haystack import default_from_dict, default_to_dict
  10 from haystack.core.component import component
  11 
  12 
  13 class StatisticalMetric(Enum):
  14     """
  15     Metrics supported by the StatisticalEvaluator.
  16     """
  17 
  18     F1 = "f1"
  19     EM = "exact_match"
  20     RECALL_SINGLE_HIT = "recall_single_hit"
  21     RECALL_MULTI_HIT = "recall_multi_hit"
  22 
  23     @classmethod
  24     def from_str(cls, metric: str) -> "StatisticalMetric":
  25         map = {e.value: e for e in StatisticalMetric}
  26         metric_ = map.get(metric)
  27         if metric_ is None:
  28             raise ValueError(f"Unknown statistical metric '{metric}'")
  29         return metric_
  30 
  31 
  32 @component
  33 class StatisticalEvaluator:
  34     """
  35     StatisticalEvaluator is a component that evaluates the performance of a model based on statistical metrics.
  36     It's usually used in QA and Retrieval Augmented Generation (RAG) pipelines to evaluate the quality of the generated answers.
  37 
  38     The supported metrics are:
  39     - F1: Measures word overlap between predictions and labels.
  40     - Exact Match: Measures the proportion of cases where prediction is identical to the expected label.
  41     """
  42 
  43     def __init__(self, metric: Union[str, StatisticalMetric]):
  44         """
  45         Creates a new instance of StatisticalEvaluator.
  46 
  47         :param metric: Metric to use for evaluation in this component. Supported metrics are F1 and Exact Match.
  48         """
  49         if isinstance(metric, str):
  50             metric = StatisticalMetric.from_str(metric)
  51         self._metric = metric
  52 
  53         self._metric_function = {
  54             StatisticalMetric.F1: self._f1,
  55             StatisticalMetric.EM: self._exact_match,
  56             StatisticalMetric.RECALL_SINGLE_HIT: self._recall_single_hit,
  57             StatisticalMetric.RECALL_MULTI_HIT: self._recall_multi_hit,
  58         }[self._metric]
  59 
  60     def to_dict(self) -> Dict[str, Any]:
  61         return default_to_dict(self, metric=self._metric.value)
  62 
  63     @classmethod
  64     def from_dict(cls, data: Dict[str, Any]) -> "StatisticalEvaluator":
  65         data["init_parameters"]["metric"] = StatisticalMetric(data["init_parameters"]["metric"])
  66         return default_from_dict(cls, data)
  67 
  68     @component.output_types(result=float)
  69     def run(self, labels: List[str], predictions: List[str]) -> Dict[str, Any]:
  70         """
  71         Run the StatisticalEvaluator to compute the metric between a list of predictions and a list of labels.
  72         Both must be list of strings of same length.
  73 
  74         :param predictions: List of predictions.
  75         :param labels: List of labels against which the predictions are compared.
  76         :returns: A dictionary with the following outputs:
  77                     * `result` - Calculated result of the chosen metric.
  78         """
  79         return {"result": self._metric_function(labels, predictions)}
  80 
  81     @staticmethod
  82     def _f1(labels: List[str], predictions: List[str]):
  83         """
  84         Measure word overlap between predictions and labels.
  85         """
  86         if len(labels) != len(predictions):
  87             raise ValueError("The number of predictions and labels must be the same.")
  88 
  89         if len(predictions) == 0:
  90             # We expect callers of this function already checked if predictions and labels are equal length
  91             return 0.0
  92 
  93         scores: List[float] = []
  94         tokenized_predictions = [pred.split() for pred in predictions]
  95         tokenized_labels = [label.split() for label in labels]
  96         for label_tokens, prediction_tokens in zip(tokenized_labels, tokenized_predictions):
  97             common = collections.Counter(label_tokens) & collections.Counter(prediction_tokens)
  98             num_same = sum(common.values())
  99             if len(label_tokens) == 0 or len(prediction_tokens) == 0:
  100                 # If either is no-answer, then F1 is 1 if they agree, 0 otherwise
  101                 return int(label_tokens == prediction_tokens)
  102             if num_same == 0:
  103                 return 0
  104             precision = 1.0 * num_same / len(prediction_tokens)
  105             recall = 1.0 * num_same / len(label_tokens)
  106             f1 = (2 * precision * recall) / (precision + recall)
  107             scores.append(f1)
  108 
  109         return np_mean(scores)
  110 
  111     @staticmethod
  112     def _exact_match(labels: List[str], predictions: List[str]) -> float:
  113         """
  114         Measure the proportion of cases where predictiond is identical to the the expected label.
  115         """
  116         if len(labels) != len(predictions):
  117             raise ValueError("The number of predictions and labels must be the same.")
  118 
  119         if len(predictions) == 0:
  120             # We expect callers of this function already checked if predictions and labels are equal length
  121             return 0.0
  122         score_list = np_array(predictions) == np_array(labels)
  123         return np_mean(score_list)
  124 
  125     @staticmethod
  126     def _recall_single_hit(labels: List[str], predictions: List[str]) -> float:
  127         """
  128         Measures how many times a label is present in at least one prediction.
  129         If the same label is found in multiple predictions it is only counted once.
  130         """
  131         if len(labels) == 0:
  132             return 0.0
  133 
  134         # In Recall Single Hit we only consider if a label is present in at least one prediction.
  135         # No need to count multiple occurrences of the same label in different predictions
  136         retrieved_labels = {l for l, p in itertools.product(labels, predictions) if l in p}
  137         return len(retrieved_labels) / len(labels)
  138 
  139     @staticmethod
  140     def _recall_multi_hit(labels: List[str], predictions: List[str]) -> float:
  141         """
  142         Measures how many times a label is present in at least one or more predictions.
  143         """
  144         if len(labels) == 0:
  145             return 0.0
  146 
  147         correct_retrievals = 0
  148         for label, prediction in itertools.product(labels, predictions):
  149             if label in prediction:
  150                 correct_retrievals += 1
  151 
  152         return correct_retrievals / len(labels)
  153
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/haystack/components/evaluators/statistical_evaluator.py b/haystack/components/evaluators/statistical_evaluator.py
  --- a/haystack/components/evaluators/statistical_evaluator.py
  +++ b/haystack/components/evaluators/statistical_evaluator.py
  @@ -19,6 +19,7 @@
       EM = "exact_match"
       RECALL_SINGLE_HIT = "recall_single_hit"
       RECALL_MULTI_HIT = "recall_multi_hit"
  +    MRR = "mean_reciprocal_rank"
   
       @classmethod
       def from_str(cls, metric: str) -> "StatisticalMetric":
  @@ -55,6 +56,7 @@
               StatisticalMetric.EM: self._exact_match,
               StatisticalMetric.RECALL_SINGLE_HIT: self._recall_single_hit,
               StatisticalMetric.RECALL_MULTI_HIT: self._recall_multi_hit,
  +            StatisticalMetric.MRR: self._mrr,
           }[self._metric]
   
       def to_dict(self) -> Dict[str, Any]:
  @@ -111,7 +113,7 @@
       @staticmethod
       def _exact_match(labels: List[str], predictions: List[str]) -> float:
           """
  -        Measure the proportion of cases where predictiond is identical to the the expected label.
  +        Measure the proportion of cases where prediction is identical to the the expected label.
           """
           if len(labels) != len(predictions):
               raise ValueError("The number of predictions and labels must be the same.")
  @@ -150,3 +152,20 @@
                   correct_retrievals += 1
   
           return correct_retrievals / len(labels)
  +
  +    @staticmethod
  +    def _mrr(labels: List[str], predictions: List[str]) -> float:
  +        """
  +        Measures the mean reciprocal rank of times a label is present in at least one or more predictions.
  +        """
  +        if len(labels) == 0:
  +            return 0.0
  +
  +        mrr_sum = 0.0
  +        for label in labels:
  +            for rank, prediction in enumerate(predictions):
  +                if label in prediction:
  +                    mrr_sum += 1 / (rank + 1)
  +                    break
  +
  +        return mrr_sum / len(labels)
  
verification_info: {"golden_diff": "diff --git a/haystack/components/evaluators/statistical_evaluator.py b/haystack/components/evaluators/statistical_evaluator.py\n--- a/haystack/components/evaluators/statistical_evaluator.py\n+++ b/haystack/components/evaluators/statistical_evaluator.py\n@@ -19,6 +19,7 @@\n     EM = \"exact_match\"\n     RECALL_SINGLE_HIT = \"recall_single_hit\"\n     RECALL_MULTI_HIT = \"recall_multi_hit\"\n+    MRR = \"mean_reciprocal_rank\"\n \n     @classmethod\n     def from_str(cls, metric: str) -> \"StatisticalMetric\":\n@@ -55,6 +56,7 @@\n             StatisticalMetric.EM: self._exact_match,\n             StatisticalMetric.RECALL_SINGLE_HIT: self._recall_single_hit,\n             StatisticalMetric.RECALL_MULTI_HIT: self._recall_multi_hit,\n+            StatisticalMetric.MRR: self._mrr,\n         }[self._metric]\n \n     def to_dict(self) -> Dict[str, Any]:\n@@ -111,7 +113,7 @@\n     @staticmethod\n     def _exact_match(labels: List[str], predictions: List[str]) -> float:\n         \"\"\"\n-        Measure the proportion of cases where predictiond is identical to the the expected label.\n+        Measure the proportion of cases where prediction is identical to the the expected label.\n         \"\"\"\n         if len(labels) != len(predictions):\n             raise ValueError(\"The number of predictions and labels must be the same.\")\n@@ -150,3 +152,20 @@\n                 correct_retrievals += 1\n \n         return correct_retrievals / len(labels)\n+\n+    @staticmethod\n+    def _mrr(labels: List[str], predictions: List[str]) -> float:\n+        \"\"\"\n+        Measures the mean reciprocal rank of times a label is present in at least one or more predictions.\n+        \"\"\"\n+        if len(labels) == 0:\n+            return 0.0\n+\n+        mrr_sum = 0.0\n+        for label in labels:\n+            for rank, prediction in enumerate(predictions):\n+                if label in prediction:\n+                    mrr_sum += 1 / (rank + 1)\n+                    break\n+\n+        return mrr_sum / len(labels)\n", "issue": "Implement function to calculate Mean Reciprocal Rank metric\nAs specified in proposal #5794 we need to implement a function to calculate the Mean Reciprocal Rank metric.\r\n\r\nIdeally the function should be part of the private interface and called only through the `calculate_metrics` function (see #6063). `_calculate_mrr()` could be a nice name.\r\n\r\nFor more detailed information check out the original proposal.\n", "before_files": [{"content": "import collections\nimport itertools\nfrom enum import Enum\nfrom typing import Any, Dict, List, Union\n\nfrom numpy import array as np_array\nfrom numpy import mean as np_mean\n\nfrom haystack import default_from_dict, default_to_dict\nfrom haystack.core.component import component\n\n\nclass StatisticalMetric(Enum):\n    \"\"\"\n    Metrics supported by the StatisticalEvaluator.\n    \"\"\"\n\n    F1 = \"f1\"\n    EM = \"exact_match\"\n    RECALL_SINGLE_HIT = \"recall_single_hit\"\n    RECALL_MULTI_HIT = \"recall_multi_hit\"\n\n    @classmethod\n    def from_str(cls, metric: str) -> \"StatisticalMetric\":\n        map = {e.value: e for e in StatisticalMetric}\n        metric_ = map.get(metric)\n        if metric_ is None:\n            raise ValueError(f\"Unknown statistical metric '{metric}'\")\n        return metric_\n\n\n@component\nclass StatisticalEvaluator:\n    \"\"\"\n    StatisticalEvaluator is a component that evaluates the performance of a model based on statistical metrics.\n    It's usually used in QA and Retrieval Augmented Generation (RAG) pipelines to evaluate the quality of the generated answers.\n\n    The supported metrics are:\n    - F1: Measures word overlap between predictions and labels.\n    - Exact Match: Measures the proportion of cases where prediction is identical to the expected label.\n    \"\"\"\n\n    def __init__(self, metric: Union[str, StatisticalMetric]):\n        \"\"\"\n        Creates a new instance of StatisticalEvaluator.\n\n        :param metric: Metric to use for evaluation in this component. Supported metrics are F1 and Exact Match.\n        \"\"\"\n        if isinstance(metric, str):\n            metric = StatisticalMetric.from_str(metric)\n        self._metric = metric\n\n        self._metric_function = {\n            StatisticalMetric.F1: self._f1,\n            StatisticalMetric.EM: self._exact_match,\n            StatisticalMetric.RECALL_SINGLE_HIT: self._recall_single_hit,\n            StatisticalMetric.RECALL_MULTI_HIT: self._recall_multi_hit,\n        }[self._metric]\n\n    def to_dict(self) -> Dict[str, Any]:\n        return default_to_dict(self, metric=self._metric.value)\n\n    @classmethod\n    def from_dict(cls, data: Dict[str, Any]) -> \"StatisticalEvaluator\":\n        data[\"init_parameters\"][\"metric\"] = StatisticalMetric(data[\"init_parameters\"][\"metric\"])\n        return default_from_dict(cls, data)\n\n    @component.output_types(result=float)\n    def run(self, labels: List[str], predictions: List[str]) -> Dict[str, Any]:\n        \"\"\"\n        Run the StatisticalEvaluator to compute the metric between a list of predictions and a list of labels.\n        Both must be list of strings of same length.\n\n        :param predictions: List of predictions.\n        :param labels: List of labels against which the predictions are compared.\n        :returns: A dictionary with the following outputs:\n                    * `result` - Calculated result of the chosen metric.\n        \"\"\"\n        return {\"result\": self._metric_function(labels, predictions)}\n\n    @staticmethod\n    def _f1(labels: List[str], predictions: List[str]):\n        \"\"\"\n        Measure word overlap between predictions and labels.\n        \"\"\"\n        if len(labels) != len(predictions):\n            raise ValueError(\"The number of predictions and labels must be the same.\")\n\n        if len(predictions) == 0:\n            # We expect callers of this function already checked if predictions and labels are equal length\n            return 0.0\n\n        scores: List[float] = []\n        tokenized_predictions = [pred.split() for pred in predictions]\n        tokenized_labels = [label.split() for label in labels]\n        for label_tokens, prediction_tokens in zip(tokenized_labels, tokenized_predictions):\n            common = collections.Counter(label_tokens) & collections.Counter(prediction_tokens)\n            num_same = sum(common.values())\n            if len(label_tokens) == 0 or len(prediction_tokens) == 0:\n                # If either is no-answer, then F1 is 1 if they agree, 0 otherwise\n                return int(label_tokens == prediction_tokens)\n            if num_same == 0:\n                return 0\n            precision = 1.0 * num_same / len(prediction_tokens)\n            recall = 1.0 * num_same / len(label_tokens)\n            f1 = (2 * precision * recall) / (precision + recall)\n            scores.append(f1)\n\n        return np_mean(scores)\n\n    @staticmethod\n    def _exact_match(labels: List[str], predictions: List[str]) -> float:\n        \"\"\"\n        Measure the proportion of cases where predictiond is identical to the the expected label.\n        \"\"\"\n        if len(labels) != len(predictions):\n            raise ValueError(\"The number of predictions and labels must be the same.\")\n\n        if len(predictions) == 0:\n            # We expect callers of this function already checked if predictions and labels are equal length\n            return 0.0\n        score_list = np_array(predictions) == np_array(labels)\n        return np_mean(score_list)\n\n    @staticmethod\n    def _recall_single_hit(labels: List[str], predictions: List[str]) -> float:\n        \"\"\"\n        Measures how many times a label is present in at least one prediction.\n        If the same label is found in multiple predictions it is only counted once.\n        \"\"\"\n        if len(labels) == 0:\n            return 0.0\n\n        # In Recall Single Hit we only consider if a label is present in at least one prediction.\n        # No need to count multiple occurrences of the same label in different predictions\n        retrieved_labels = {l for l, p in itertools.product(labels, predictions) if l in p}\n        return len(retrieved_labels) / len(labels)\n\n    @staticmethod\n    def _recall_multi_hit(labels: List[str], predictions: List[str]) -> float:\n        \"\"\"\n        Measures how many times a label is present in at least one or more predictions.\n        \"\"\"\n        if len(labels) == 0:\n            return 0.0\n\n        correct_retrievals = 0\n        for label, prediction in itertools.product(labels, predictions):\n            if label in prediction:\n                correct_retrievals += 1\n\n        return correct_retrievals / len(labels)\n", "path": "haystack/components/evaluators/statistical_evaluator.py"}], "after_files": [{"content": "import collections\nimport itertools\nfrom enum import Enum\nfrom typing import Any, Dict, List, Union\n\nfrom numpy import array as np_array\nfrom numpy import mean as np_mean\n\nfrom haystack import default_from_dict, default_to_dict\nfrom haystack.core.component import component\n\n\nclass StatisticalMetric(Enum):\n    \"\"\"\n    Metrics supported by the StatisticalEvaluator.\n    \"\"\"\n\n    F1 = \"f1\"\n    EM = \"exact_match\"\n    RECALL_SINGLE_HIT = \"recall_single_hit\"\n    RECALL_MULTI_HIT = \"recall_multi_hit\"\n    MRR = \"mean_reciprocal_rank\"\n\n    @classmethod\n    def from_str(cls, metric: str) -> \"StatisticalMetric\":\n        map = {e.value: e for e in StatisticalMetric}\n        metric_ = map.get(metric)\n        if metric_ is None:\n            raise ValueError(f\"Unknown statistical metric '{metric}'\")\n        return metric_\n\n\n@component\nclass StatisticalEvaluator:\n    \"\"\"\n    StatisticalEvaluator is a component that evaluates the performance of a model based on statistical metrics.\n    It's usually used in QA and Retrieval Augmented Generation (RAG) pipelines to evaluate the quality of the generated answers.\n\n    The supported metrics are:\n    - F1: Measures word overlap between predictions and labels.\n    - Exact Match: Measures the proportion of cases where prediction is identical to the expected label.\n    \"\"\"\n\n    def __init__(self, metric: Union[str, StatisticalMetric]):\n        \"\"\"\n        Creates a new instance of StatisticalEvaluator.\n\n        :param metric: Metric to use for evaluation in this component. Supported metrics are F1 and Exact Match.\n        \"\"\"\n        if isinstance(metric, str):\n            metric = StatisticalMetric.from_str(metric)\n        self._metric = metric\n\n        self._metric_function = {\n            StatisticalMetric.F1: self._f1,\n            StatisticalMetric.EM: self._exact_match,\n            StatisticalMetric.RECALL_SINGLE_HIT: self._recall_single_hit,\n            StatisticalMetric.RECALL_MULTI_HIT: self._recall_multi_hit,\n            StatisticalMetric.MRR: self._mrr,\n        }[self._metric]\n\n    def to_dict(self) -> Dict[str, Any]:\n        return default_to_dict(self, metric=self._metric.value)\n\n    @classmethod\n    def from_dict(cls, data: Dict[str, Any]) -> \"StatisticalEvaluator\":\n        data[\"init_parameters\"][\"metric\"] = StatisticalMetric(data[\"init_parameters\"][\"metric\"])\n        return default_from_dict(cls, data)\n\n    @component.output_types(result=float)\n    def run(self, labels: List[str], predictions: List[str]) -> Dict[str, Any]:\n        \"\"\"\n        Run the StatisticalEvaluator to compute the metric between a list of predictions and a list of labels.\n        Both must be list of strings of same length.\n\n        :param predictions: List of predictions.\n        :param labels: List of labels against which the predictions are compared.\n        :returns: A dictionary with the following outputs:\n                    * `result` - Calculated result of the chosen metric.\n        \"\"\"\n        return {\"result\": self._metric_function(labels, predictions)}\n\n    @staticmethod\n    def _f1(labels: List[str], predictions: List[str]):\n        \"\"\"\n        Measure word overlap between predictions and labels.\n        \"\"\"\n        if len(labels) != len(predictions):\n            raise ValueError(\"The number of predictions and labels must be the same.\")\n\n        if len(predictions) == 0:\n            # We expect callers of this function already checked if predictions and labels are equal length\n            return 0.0\n\n        scores: List[float] = []\n        tokenized_predictions = [pred.split() for pred in predictions]\n        tokenized_labels = [label.split() for label in labels]\n        for label_tokens, prediction_tokens in zip(tokenized_labels, tokenized_predictions):\n            common = collections.Counter(label_tokens) & collections.Counter(prediction_tokens)\n            num_same = sum(common.values())\n            if len(label_tokens) == 0 or len(prediction_tokens) == 0:\n                # If either is no-answer, then F1 is 1 if they agree, 0 otherwise\n                return int(label_tokens == prediction_tokens)\n            if num_same == 0:\n                return 0\n            precision = 1.0 * num_same / len(prediction_tokens)\n            recall = 1.0 * num_same / len(label_tokens)\n            f1 = (2 * precision * recall) / (precision + recall)\n            scores.append(f1)\n\n        return np_mean(scores)\n\n    @staticmethod\n    def _exact_match(labels: List[str], predictions: List[str]) -> float:\n        \"\"\"\n        Measure the proportion of cases where prediction is identical to the the expected label.\n        \"\"\"\n        if len(labels) != len(predictions):\n            raise ValueError(\"The number of predictions and labels must be the same.\")\n\n        if len(predictions) == 0:\n            # We expect callers of this function already checked if predictions and labels are equal length\n            return 0.0\n        score_list = np_array(predictions) == np_array(labels)\n        return np_mean(score_list)\n\n    @staticmethod\n    def _recall_single_hit(labels: List[str], predictions: List[str]) -> float:\n        \"\"\"\n        Measures how many times a label is present in at least one prediction.\n        If the same label is found in multiple predictions it is only counted once.\n        \"\"\"\n        if len(labels) == 0:\n            return 0.0\n\n        # In Recall Single Hit we only consider if a label is present in at least one prediction.\n        # No need to count multiple occurrences of the same label in different predictions\n        retrieved_labels = {l for l, p in itertools.product(labels, predictions) if l in p}\n        return len(retrieved_labels) / len(labels)\n\n    @staticmethod\n    def _recall_multi_hit(labels: List[str], predictions: List[str]) -> float:\n        \"\"\"\n        Measures how many times a label is present in at least one or more predictions.\n        \"\"\"\n        if len(labels) == 0:\n            return 0.0\n\n        correct_retrievals = 0\n        for label, prediction in itertools.product(labels, predictions):\n            if label in prediction:\n                correct_retrievals += 1\n\n        return correct_retrievals / len(labels)\n\n    @staticmethod\n    def _mrr(labels: List[str], predictions: List[str]) -> float:\n        \"\"\"\n        Measures the mean reciprocal rank of times a label is present in at least one or more predictions.\n        \"\"\"\n        if len(labels) == 0:\n            return 0.0\n\n        mrr_sum = 0.0\n        for label in labels:\n            for rank, prediction in enumerate(predictions):\n                if label in prediction:\n                    mrr_sum += 1 / (rank + 1)\n                    break\n\n        return mrr_sum / len(labels)\n", "path": "haystack/components/evaluators/statistical_evaluator.py"}]}