prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  feat: Add embed_meta_fields support to rankers like in Haystack v1
  **Is your feature request related to a problem? Please describe.**
  We have found that `embed_meta_fields` greatly improves ranking when metadata is relevant while searching. This is a feature that we use a lot from v1 that would be great to also have in v2.
  
  **Describe the solution you'd like**
  Add support for `embed_meta_fields` as an init parameter to rankers like `TransformersSimilarityRanker`.
  
  
  --- 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/rankers/transformers_similarity.py`
  Content:
  ```
  1 import logging
  2 from pathlib import Path
  3 from typing import List, Union, Dict, Any, Optional
  4 
  5 from haystack import ComponentError, Document, component, default_to_dict
  6 from haystack.lazy_imports import LazyImport
  7 
  8 logger = logging.getLogger(__name__)
  9 
  10 
  11 with LazyImport(message="Run 'pip install transformers[torch,sentencepiece]'") as torch_and_transformers_import:
  12     import torch
  13     from transformers import AutoModelForSequenceClassification, AutoTokenizer
  14 
  15 
  16 @component
  17 class TransformersSimilarityRanker:
  18     """
  19     Ranks Documents based on their similarity to the query.
  20     It uses a pre-trained cross-encoder model (from the Hugging Face Hub) to embed the query and the Documents.
  21 
  22     Usage example:
  23     ```
  24     from haystack import Document
  25     from haystack.components.rankers import TransformersSimilarityRanker
  26 
  27     ranker = TransformersSimilarityRanker()
  28     docs = [Document(content="Paris"), Document(content="Berlin")]
  29     query = "City in Germany"
  30     output = ranker.run(query=query, documents=docs)
  31     docs = output["documents"]
  32     assert len(docs) == 2
  33     assert docs[0].content == "Berlin"
  34     ```
  35     """
  36 
  37     def __init__(
  38         self,
  39         model_name_or_path: Union[str, Path] = "cross-encoder/ms-marco-MiniLM-L-6-v2",
  40         device: str = "cpu",
  41         token: Union[bool, str, None] = None,
  42         top_k: int = 10,
  43     ):
  44         """
  45         Creates an instance of TransformersSimilarityRanker.
  46 
  47         :param model_name_or_path: The name or path of a pre-trained cross-encoder model
  48             from the Hugging Face Hub.
  49         :param device: The torch device (for example, cuda:0, cpu, mps) to which you want to limit model inference.
  50         :param token: The API token used to download private models from Hugging Face.
  51             If this parameter is set to `True`, the token generated when running
  52             `transformers-cli login` (stored in ~/.huggingface) is used.
  53         :param top_k: The maximum number of Documents to return per query.
  54         """
  55         torch_and_transformers_import.check()
  56 
  57         self.model_name_or_path = model_name_or_path
  58         if top_k <= 0:
  59             raise ValueError(f"top_k must be > 0, but got {top_k}")
  60         self.top_k = top_k
  61         self.device = device
  62         self.token = token
  63         self.model = None
  64         self.tokenizer = None
  65 
  66     def _get_telemetry_data(self) -> Dict[str, Any]:
  67         """
  68         Data that is sent to Posthog for usage analytics.
  69         """
  70         return {"model": str(self.model_name_or_path)}
  71 
  72     def warm_up(self):
  73         """
  74         Warm up the model and tokenizer used for scoring the Documents.
  75         """
  76         if self.model_name_or_path and not self.model:
  77             self.model = AutoModelForSequenceClassification.from_pretrained(self.model_name_or_path, token=self.token)
  78             self.model = self.model.to(self.device)
  79             self.model.eval()
  80             self.tokenizer = AutoTokenizer.from_pretrained(self.model_name_or_path, token=self.token)
  81 
  82     def to_dict(self) -> Dict[str, Any]:
  83         """
  84         Serialize this component to a dictionary.
  85         """
  86         return default_to_dict(
  87             self,
  88             device=self.device,
  89             model_name_or_path=self.model_name_or_path,
  90             token=self.token if not isinstance(self.token, str) else None,  # don't serialize valid tokens
  91             top_k=self.top_k,
  92         )
  93 
  94     @component.output_types(documents=List[Document])
  95     def run(self, query: str, documents: List[Document], top_k: Optional[int] = None):
  96         """
  97         Returns a list of Documents ranked by their similarity to the given query.
  98 
  99         :param query: Query string.
  100         :param documents: List of Documents.
  101         :param top_k: The maximum number of Documents you want the Ranker to return.
  102         :return: List of Documents sorted by their similarity to the query with the most similar Documents appearing first.
  103         """
  104         if not documents:
  105             return {"documents": []}
  106 
  107         if top_k is None:
  108             top_k = self.top_k
  109 
  110         elif top_k <= 0:
  111             raise ValueError(f"top_k must be > 0, but got {top_k}")
  112 
  113         # If a model path is provided but the model isn't loaded
  114         if self.model_name_or_path and not self.model:
  115             raise ComponentError(
  116                 f"The component {self.__class__.__name__} wasn't warmed up. Run 'warm_up()' before calling 'run()'."
  117             )
  118 
  119         query_doc_pairs = [[query, doc.content] for doc in documents]
  120         features = self.tokenizer(
  121             query_doc_pairs, padding=True, truncation=True, return_tensors="pt"
  122         ).to(  # type: ignore
  123             self.device
  124         )
  125         with torch.inference_mode():
  126             similarity_scores = self.model(**features).logits.squeeze(dim=1)  # type: ignore
  127 
  128         _, sorted_indices = torch.sort(similarity_scores, descending=True)
  129         ranked_docs = []
  130         for sorted_index_tensor in sorted_indices:
  131             i = sorted_index_tensor.item()
  132             documents[i].score = similarity_scores[i].item()
  133             ranked_docs.append(documents[i])
  134         return {"documents": ranked_docs[:top_k]}
  135
  ```
  --- 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/rankers/transformers_similarity.py b/haystack/components/rankers/transformers_similarity.py
  --- a/haystack/components/rankers/transformers_similarity.py
  +++ b/haystack/components/rankers/transformers_similarity.py
  @@ -40,6 +40,8 @@
           device: str = "cpu",
           token: Union[bool, str, None] = None,
           top_k: int = 10,
  +        meta_fields_to_embed: Optional[List[str]] = None,
  +        embedding_separator: str = "\n",
       ):
           """
           Creates an instance of TransformersSimilarityRanker.
  @@ -51,6 +53,8 @@
               If this parameter is set to `True`, the token generated when running
               `transformers-cli login` (stored in ~/.huggingface) is used.
           :param top_k: The maximum number of Documents to return per query.
  +        :param meta_fields_to_embed: List of meta fields that should be embedded along with the Document content.
  +        :param embedding_separator: Separator used to concatenate the meta fields to the Document content.
           """
           torch_and_transformers_import.check()
   
  @@ -62,6 +66,8 @@
           self.token = token
           self.model = None
           self.tokenizer = None
  +        self.meta_fields_to_embed = meta_fields_to_embed or []
  +        self.embedding_separator = embedding_separator
   
       def _get_telemetry_data(self) -> Dict[str, Any]:
           """
  @@ -89,6 +95,8 @@
               model_name_or_path=self.model_name_or_path,
               token=self.token if not isinstance(self.token, str) else None,  # don't serialize valid tokens
               top_k=self.top_k,
  +            meta_fields_to_embed=self.meta_fields_to_embed,
  +            embedding_separator=self.embedding_separator,
           )
   
       @component.output_types(documents=List[Document])
  @@ -116,7 +124,14 @@
                   f"The component {self.__class__.__name__} wasn't warmed up. Run 'warm_up()' before calling 'run()'."
               )
   
  -        query_doc_pairs = [[query, doc.content] for doc in documents]
  +        query_doc_pairs = []
  +        for doc in documents:
  +            meta_values_to_embed = [
  +                str(doc.meta[key]) for key in self.meta_fields_to_embed if key in doc.meta and doc.meta[key]
  +            ]
  +            text_to_embed = self.embedding_separator.join(meta_values_to_embed + [doc.content or ""])
  +            query_doc_pairs.append([query, text_to_embed])
  +
           features = self.tokenizer(
               query_doc_pairs, padding=True, truncation=True, return_tensors="pt"
           ).to(  # type: ignore
  
verification_info: {"golden_diff": "diff --git a/haystack/components/rankers/transformers_similarity.py b/haystack/components/rankers/transformers_similarity.py\n--- a/haystack/components/rankers/transformers_similarity.py\n+++ b/haystack/components/rankers/transformers_similarity.py\n@@ -40,6 +40,8 @@\n         device: str = \"cpu\",\n         token: Union[bool, str, None] = None,\n         top_k: int = 10,\n+        meta_fields_to_embed: Optional[List[str]] = None,\n+        embedding_separator: str = \"\\n\",\n     ):\n         \"\"\"\n         Creates an instance of TransformersSimilarityRanker.\n@@ -51,6 +53,8 @@\n             If this parameter is set to `True`, the token generated when running\n             `transformers-cli login` (stored in ~/.huggingface) is used.\n         :param top_k: The maximum number of Documents to return per query.\n+        :param meta_fields_to_embed: List of meta fields that should be embedded along with the Document content.\n+        :param embedding_separator: Separator used to concatenate the meta fields to the Document content.\n         \"\"\"\n         torch_and_transformers_import.check()\n \n@@ -62,6 +66,8 @@\n         self.token = token\n         self.model = None\n         self.tokenizer = None\n+        self.meta_fields_to_embed = meta_fields_to_embed or []\n+        self.embedding_separator = embedding_separator\n \n     def _get_telemetry_data(self) -> Dict[str, Any]:\n         \"\"\"\n@@ -89,6 +95,8 @@\n             model_name_or_path=self.model_name_or_path,\n             token=self.token if not isinstance(self.token, str) else None,  # don't serialize valid tokens\n             top_k=self.top_k,\n+            meta_fields_to_embed=self.meta_fields_to_embed,\n+            embedding_separator=self.embedding_separator,\n         )\n \n     @component.output_types(documents=List[Document])\n@@ -116,7 +124,14 @@\n                 f\"The component {self.__class__.__name__} wasn't warmed up. Run 'warm_up()' before calling 'run()'.\"\n             )\n \n-        query_doc_pairs = [[query, doc.content] for doc in documents]\n+        query_doc_pairs = []\n+        for doc in documents:\n+            meta_values_to_embed = [\n+                str(doc.meta[key]) for key in self.meta_fields_to_embed if key in doc.meta and doc.meta[key]\n+            ]\n+            text_to_embed = self.embedding_separator.join(meta_values_to_embed + [doc.content or \"\"])\n+            query_doc_pairs.append([query, text_to_embed])\n+\n         features = self.tokenizer(\n             query_doc_pairs, padding=True, truncation=True, return_tensors=\"pt\"\n         ).to(  # type: ignore\n", "issue": "feat: Add embed_meta_fields support to rankers like in Haystack v1\n**Is your feature request related to a problem? Please describe.**\r\nWe have found that `embed_meta_fields` greatly improves ranking when metadata is relevant while searching. This is a feature that we use a lot from v1 that would be great to also have in v2.\r\n\r\n**Describe the solution you'd like**\r\nAdd support for `embed_meta_fields` as an init parameter to rankers like `TransformersSimilarityRanker`.\r\n\n", "before_files": [{"content": "import logging\nfrom pathlib import Path\nfrom typing import List, Union, Dict, Any, Optional\n\nfrom haystack import ComponentError, Document, component, default_to_dict\nfrom haystack.lazy_imports import LazyImport\n\nlogger = logging.getLogger(__name__)\n\n\nwith LazyImport(message=\"Run 'pip install transformers[torch,sentencepiece]'\") as torch_and_transformers_import:\n    import torch\n    from transformers import AutoModelForSequenceClassification, AutoTokenizer\n\n\n@component\nclass TransformersSimilarityRanker:\n    \"\"\"\n    Ranks Documents based on their similarity to the query.\n    It uses a pre-trained cross-encoder model (from the Hugging Face Hub) to embed the query and the Documents.\n\n    Usage example:\n    ```\n    from haystack import Document\n    from haystack.components.rankers import TransformersSimilarityRanker\n\n    ranker = TransformersSimilarityRanker()\n    docs = [Document(content=\"Paris\"), Document(content=\"Berlin\")]\n    query = \"City in Germany\"\n    output = ranker.run(query=query, documents=docs)\n    docs = output[\"documents\"]\n    assert len(docs) == 2\n    assert docs[0].content == \"Berlin\"\n    ```\n    \"\"\"\n\n    def __init__(\n        self,\n        model_name_or_path: Union[str, Path] = \"cross-encoder/ms-marco-MiniLM-L-6-v2\",\n        device: str = \"cpu\",\n        token: Union[bool, str, None] = None,\n        top_k: int = 10,\n    ):\n        \"\"\"\n        Creates an instance of TransformersSimilarityRanker.\n\n        :param model_name_or_path: The name or path of a pre-trained cross-encoder model\n            from the Hugging Face Hub.\n        :param device: The torch device (for example, cuda:0, cpu, mps) to which you want to limit model inference.\n        :param token: The API token used to download private models from Hugging Face.\n            If this parameter is set to `True`, the token generated when running\n            `transformers-cli login` (stored in ~/.huggingface) is used.\n        :param top_k: The maximum number of Documents to return per query.\n        \"\"\"\n        torch_and_transformers_import.check()\n\n        self.model_name_or_path = model_name_or_path\n        if top_k <= 0:\n            raise ValueError(f\"top_k must be > 0, but got {top_k}\")\n        self.top_k = top_k\n        self.device = device\n        self.token = token\n        self.model = None\n        self.tokenizer = None\n\n    def _get_telemetry_data(self) -> Dict[str, Any]:\n        \"\"\"\n        Data that is sent to Posthog for usage analytics.\n        \"\"\"\n        return {\"model\": str(self.model_name_or_path)}\n\n    def warm_up(self):\n        \"\"\"\n        Warm up the model and tokenizer used for scoring the Documents.\n        \"\"\"\n        if self.model_name_or_path and not self.model:\n            self.model = AutoModelForSequenceClassification.from_pretrained(self.model_name_or_path, token=self.token)\n            self.model = self.model.to(self.device)\n            self.model.eval()\n            self.tokenizer = AutoTokenizer.from_pretrained(self.model_name_or_path, token=self.token)\n\n    def to_dict(self) -> Dict[str, Any]:\n        \"\"\"\n        Serialize this component to a dictionary.\n        \"\"\"\n        return default_to_dict(\n            self,\n            device=self.device,\n            model_name_or_path=self.model_name_or_path,\n            token=self.token if not isinstance(self.token, str) else None,  # don't serialize valid tokens\n            top_k=self.top_k,\n        )\n\n    @component.output_types(documents=List[Document])\n    def run(self, query: str, documents: List[Document], top_k: Optional[int] = None):\n        \"\"\"\n        Returns a list of Documents ranked by their similarity to the given query.\n\n        :param query: Query string.\n        :param documents: List of Documents.\n        :param top_k: The maximum number of Documents you want the Ranker to return.\n        :return: List of Documents sorted by their similarity to the query with the most similar Documents appearing first.\n        \"\"\"\n        if not documents:\n            return {\"documents\": []}\n\n        if top_k is None:\n            top_k = self.top_k\n\n        elif top_k <= 0:\n            raise ValueError(f\"top_k must be > 0, but got {top_k}\")\n\n        # If a model path is provided but the model isn't loaded\n        if self.model_name_or_path and not self.model:\n            raise ComponentError(\n                f\"The component {self.__class__.__name__} wasn't warmed up. Run 'warm_up()' before calling 'run()'.\"\n            )\n\n        query_doc_pairs = [[query, doc.content] for doc in documents]\n        features = self.tokenizer(\n            query_doc_pairs, padding=True, truncation=True, return_tensors=\"pt\"\n        ).to(  # type: ignore\n            self.device\n        )\n        with torch.inference_mode():\n            similarity_scores = self.model(**features).logits.squeeze(dim=1)  # type: ignore\n\n        _, sorted_indices = torch.sort(similarity_scores, descending=True)\n        ranked_docs = []\n        for sorted_index_tensor in sorted_indices:\n            i = sorted_index_tensor.item()\n            documents[i].score = similarity_scores[i].item()\n            ranked_docs.append(documents[i])\n        return {\"documents\": ranked_docs[:top_k]}\n", "path": "haystack/components/rankers/transformers_similarity.py"}], "after_files": [{"content": "import logging\nfrom pathlib import Path\nfrom typing import List, Union, Dict, Any, Optional\n\nfrom haystack import ComponentError, Document, component, default_to_dict\nfrom haystack.lazy_imports import LazyImport\n\nlogger = logging.getLogger(__name__)\n\n\nwith LazyImport(message=\"Run 'pip install transformers[torch,sentencepiece]'\") as torch_and_transformers_import:\n    import torch\n    from transformers import AutoModelForSequenceClassification, AutoTokenizer\n\n\n@component\nclass TransformersSimilarityRanker:\n    \"\"\"\n    Ranks Documents based on their similarity to the query.\n    It uses a pre-trained cross-encoder model (from the Hugging Face Hub) to embed the query and the Documents.\n\n    Usage example:\n    ```\n    from haystack import Document\n    from haystack.components.rankers import TransformersSimilarityRanker\n\n    ranker = TransformersSimilarityRanker()\n    docs = [Document(content=\"Paris\"), Document(content=\"Berlin\")]\n    query = \"City in Germany\"\n    output = ranker.run(query=query, documents=docs)\n    docs = output[\"documents\"]\n    assert len(docs) == 2\n    assert docs[0].content == \"Berlin\"\n    ```\n    \"\"\"\n\n    def __init__(\n        self,\n        model_name_or_path: Union[str, Path] = \"cross-encoder/ms-marco-MiniLM-L-6-v2\",\n        device: str = \"cpu\",\n        token: Union[bool, str, None] = None,\n        top_k: int = 10,\n        meta_fields_to_embed: Optional[List[str]] = None,\n        embedding_separator: str = \"\\n\",\n    ):\n        \"\"\"\n        Creates an instance of TransformersSimilarityRanker.\n\n        :param model_name_or_path: The name or path of a pre-trained cross-encoder model\n            from the Hugging Face Hub.\n        :param device: The torch device (for example, cuda:0, cpu, mps) to which you want to limit model inference.\n        :param token: The API token used to download private models from Hugging Face.\n            If this parameter is set to `True`, the token generated when running\n            `transformers-cli login` (stored in ~/.huggingface) is used.\n        :param top_k: The maximum number of Documents to return per query.\n        :param meta_fields_to_embed: List of meta fields that should be embedded along with the Document content.\n        :param embedding_separator: Separator used to concatenate the meta fields to the Document content.\n        \"\"\"\n        torch_and_transformers_import.check()\n\n        self.model_name_or_path = model_name_or_path\n        if top_k <= 0:\n            raise ValueError(f\"top_k must be > 0, but got {top_k}\")\n        self.top_k = top_k\n        self.device = device\n        self.token = token\n        self.model = None\n        self.tokenizer = None\n        self.meta_fields_to_embed = meta_fields_to_embed or []\n        self.embedding_separator = embedding_separator\n\n    def _get_telemetry_data(self) -> Dict[str, Any]:\n        \"\"\"\n        Data that is sent to Posthog for usage analytics.\n        \"\"\"\n        return {\"model\": str(self.model_name_or_path)}\n\n    def warm_up(self):\n        \"\"\"\n        Warm up the model and tokenizer used for scoring the Documents.\n        \"\"\"\n        if self.model_name_or_path and not self.model:\n            self.model = AutoModelForSequenceClassification.from_pretrained(self.model_name_or_path, token=self.token)\n            self.model = self.model.to(self.device)\n            self.model.eval()\n            self.tokenizer = AutoTokenizer.from_pretrained(self.model_name_or_path, token=self.token)\n\n    def to_dict(self) -> Dict[str, Any]:\n        \"\"\"\n        Serialize this component to a dictionary.\n        \"\"\"\n        return default_to_dict(\n            self,\n            device=self.device,\n            model_name_or_path=self.model_name_or_path,\n            token=self.token if not isinstance(self.token, str) else None,  # don't serialize valid tokens\n            top_k=self.top_k,\n            meta_fields_to_embed=self.meta_fields_to_embed,\n            embedding_separator=self.embedding_separator,\n        )\n\n    @component.output_types(documents=List[Document])\n    def run(self, query: str, documents: List[Document], top_k: Optional[int] = None):\n        \"\"\"\n        Returns a list of Documents ranked by their similarity to the given query.\n\n        :param query: Query string.\n        :param documents: List of Documents.\n        :param top_k: The maximum number of Documents you want the Ranker to return.\n        :return: List of Documents sorted by their similarity to the query with the most similar Documents appearing first.\n        \"\"\"\n        if not documents:\n            return {\"documents\": []}\n\n        if top_k is None:\n            top_k = self.top_k\n\n        elif top_k <= 0:\n            raise ValueError(f\"top_k must be > 0, but got {top_k}\")\n\n        # If a model path is provided but the model isn't loaded\n        if self.model_name_or_path and not self.model:\n            raise ComponentError(\n                f\"The component {self.__class__.__name__} wasn't warmed up. Run 'warm_up()' before calling 'run()'.\"\n            )\n\n        query_doc_pairs = []\n        for doc in documents:\n            meta_values_to_embed = [\n                str(doc.meta[key]) for key in self.meta_fields_to_embed if key in doc.meta and doc.meta[key]\n            ]\n            text_to_embed = self.embedding_separator.join(meta_values_to_embed + [doc.content or \"\"])\n            query_doc_pairs.append([query, text_to_embed])\n\n        features = self.tokenizer(\n            query_doc_pairs, padding=True, truncation=True, return_tensors=\"pt\"\n        ).to(  # type: ignore\n            self.device\n        )\n        with torch.inference_mode():\n            similarity_scores = self.model(**features).logits.squeeze(dim=1)  # type: ignore\n\n        _, sorted_indices = torch.sort(similarity_scores, descending=True)\n        ranked_docs = []\n        for sorted_index_tensor in sorted_indices:\n            i = sorted_index_tensor.item()\n            documents[i].score = similarity_scores[i].item()\n            ranked_docs.append(documents[i])\n        return {\"documents\": ranked_docs[:top_k]}\n", "path": "haystack/components/rankers/transformers_similarity.py"}]}