Repository: deepset-ai/haystack

Issue:
DocumentJoiner component's run method doesn't accept a top_k value
**Describe the bug**
The `DocumentJoiner` component's run method doesn't allow you to pass a value for the `top_k` parameter, see [here](https://github.com/deepset-ai/haystack/blob/c8d53b3ebff901069a47acc45e1b0efe546e5303/haystack/components/joiners/document_joiner.py#L77). If you try to do so, you'll get the following: `ValueError: Input top_k not found in component joiner.`

This means you can only pass a value for `top_k` when you initialize the component, and so you can't give it a value e.g. at query time using `pipe.run("DocumentJoiner": {"top_k": top_k})` if you use it in a query pipeline.

**Expected behavior**
Providing this component with a `top_k` value in a pipeline using e.g. `pipe.run("DocumentJoiner": {"top_k": top_k})` should not raise a ValueError.

A simple change to the run method, adding `top_k` as an argument, and replacing [these lines](https://github.com/deepset-ai/haystack/blob/c8d53b3ebff901069a47acc45e1b0efe546e5303/haystack/components/joiners/document_joiner.py#L106-L107) inside the function with the following should fix it: 
``` python 
if top_k:
    output_documents = output_documents[: top_k]
elif self.top_k:
    output_documents = output_documents[: self.top_k]
```


**To Reproduce**
Create a query pipeline containing a `DocumentJoiner` and run it using `pipe.run("DocumentJoiner": {"top_k": top_k})`.

**FAQ Check**
- [x] Have you had a look at [our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)?




Discussion:
Hey, @ms130...

This sounds like a simple and useful improvement.

Feel free to open a PR!

Resolving patch:
diff --git a/haystack/components/joiners/document_joiner.py b/haystack/components/joiners/document_joiner.py
--- a/haystack/components/joiners/document_joiner.py
+++ b/haystack/components/joiners/document_joiner.py
@@ -74,12 +74,14 @@ def __init__(
         self.sort_by_score = sort_by_score
 
     @component.output_types(documents=List[Document])
-    def run(self, documents: Variadic[List[Document]]):
+    def run(self, documents: Variadic[List[Document]], top_k: Optional[int] = None):
         """
         Joins multiple lists of Documents into a single list depending on the `join_mode` parameter.
 
         :param documents:
             List of list of Documents to be merged.
+        :param top_k:
+            The maximum number of Documents to return. Overrides the instance's `top_k` if provided.
 
         :returns:
             A dictionary with the following keys:
@@ -103,8 +105,11 @@ def run(self, documents: Variadic[List[Document]]):
                     "score, so those with score=None were sorted as if they had a score of -infinity."
                 )
 
-        if self.top_k:
+        if top_k:
+            output_documents = output_documents[:top_k]
+        elif self.top_k:
             output_documents = output_documents[: self.top_k]
+
         return {"documents": output_documents}
 
     def _concatenate(self, document_lists):


Test patch:
diff --git a/test/components/joiners/test_document_joiner.py b/test/components/joiners/test_document_joiner.py
--- a/test/components/joiners/test_document_joiner.py
+++ b/test/components/joiners/test_document_joiner.py
@@ -115,6 +115,14 @@ def test_run_with_reciprocal_rank_fusion_join_mode(self):
         ]
         assert all(doc.id in expected_document_ids for doc in output["documents"])
 
+    def test_run_with_top_k_in_run_method(self):
+        joiner = DocumentJoiner()
+        documents_1 = [Document(content="a"), Document(content="b"), Document(content="c")]
+        documents_2 = [Document(content="d"), Document(content="e"), Document(content="f")]
+        top_k = 4
+        output = joiner.run([documents_1, documents_2], top_k=top_k)
+        assert len(output["documents"]) == top_k
+
     def test_sort_by_score_without_scores(self, caplog):
         joiner = DocumentJoiner()
         with caplog.at_level(logging.INFO):