Repository: deepset-ai/haystack

Issue:
`component.set_output_types()` overrides `@component.output_types()` if both are used in the same Component
**Describe the bug**

Currently it's possible to both decorate a Component's run method using `@component.output_types()` and call `component.set_output_types()` when initialising it. 

`component.set_output_types()` overrides whatever has been set by `@component.output_types()`.

This behaviour is confusing and should be prevented.

**Expected behavior**

An exception is raised.

**Additional context**

This is related to #8280

**To Reproduce**

This should raise an exception.

```
from typing import List

from haystack import component


class MockComponent:
    def __init__(self):
        component.set_output_types(self, value=List[int])

    @component.output_types(value=int)
    def run(self):
        return {"value": 1}
```

Discussion:
coveralls: ## Pull Request Test Coverage Report for [Build 10903048626](https://coveralls.io/builds/69840735)


### Details

* **0** of **0**   changed or added relevant lines in **0** files are covered.
* **3** unchanged lines in **1** file lost coverage.
* Overall coverage increased (+**0.003%**) to **90.391%**

---


|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [components/classifiers/zero_shot_document_classifier.py](https://coveralls.io/builds/69840735/source?filename=components%2Fclassifiers%2Fzero_shot_document_classifier.py#L141) | 3 | 91.07% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/69840735/badge)](https://coveralls.io/builds/69840735) |
| :-- | --: |
| Change from base [Build 10902548715](https://coveralls.io/builds/69839930): |  0.003% |
| Covered Lines: | 7337 |
| Relevant Lines: | 8117 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/haystack/components/classifiers/zero_shot_document_classifier.py b/haystack/components/classifiers/zero_shot_document_classifier.py
index cff245b3..5aa52fde 100644
--- a/haystack/components/classifiers/zero_shot_document_classifier.py
+++ b/haystack/components/classifiers/zero_shot_document_classifier.py
@@ -121,7 +121,6 @@ class TransformersZeroShotDocumentClassifier:
         self.token = token
         self.labels = labels
         self.multi_label = multi_label
-        component.set_output_types(self, **{label: str for label in labels})
 
         huggingface_pipeline_kwargs = resolve_hf_pipeline_kwargs(
             huggingface_pipeline_kwargs=huggingface_pipeline_kwargs or {},
@@ -229,7 +228,7 @@ class TransformersZeroShotDocumentClassifier:
             )
 
         texts = [
-            doc.content if self.classification_field is None else doc.meta[self.classification_field]
+            (doc.content if self.classification_field is None else doc.meta[self.classification_field])
             for doc in documents
         ]
 
diff --git a/haystack/components/preprocessors/nltk_document_splitter.py b/haystack/components/preprocessors/nltk_document_splitter.py
index b11ebd0c..a9d128ae 100644
--- a/haystack/components/preprocessors/nltk_document_splitter.py
+++ b/haystack/components/preprocessors/nltk_document_splitter.py
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
 class NLTKDocumentSplitter(DocumentSplitter):
     def __init__(
         self,
-        split_by: Literal["word", "sentence", "page", "passage"] = "word",
+        split_by: Literal["word", "sentence", "page", "passage", "function"] = "word",
         split_length: int = 200,
         split_overlap: int = 0,
         split_threshold: int = 0,
@@ -64,7 +64,9 @@ class NLTKDocumentSplitter(DocumentSplitter):
         )
         self.language = language
 
-    def _split_into_units(self, text: str, split_by: Literal["word", "sentence", "passage", "page"]) -> List[str]:
+    def _split_into_units(
+        self, text: str, split_by: Literal["word", "sentence", "passage", "page", "function"]
+    ) -> List[str]:
         """
         Splits the text into units based on the specified split_by parameter.
 
diff --git a/haystack/core/component/component.py b/haystack/core/component/component.py
index e34efdb2..72c444fd 100644
--- a/haystack/core/component/component.py
+++ b/haystack/core/component/component.py
@@ -449,6 +449,13 @@ class _Component:
                 return {"output_1": 1, "output_2": "2"}
         ```
         """
+        has_decorator = hasattr(instance.run, "_output_types_cache")
+        if has_decorator:
+            raise ComponentError(
+                "Cannot call `set_output_types` on a component that already has "
+                "the 'output_types' decorator on its `run` method"
+            )
+
         instance.__haystack_output__ = Sockets(
             instance, {name: OutputSocket(name=name, type=type_) for name, type_ in types.items()}, OutputSocket
         )
diff --git a/releasenotes/notes/component-set-output-type-override-852a19b3f0621fb0.yaml b/releasenotes/notes/component-set-output-type-override-852a19b3f0621fb0.yaml
new file mode 100644
index 00000000..2a06fadd
--- /dev/null
+++ b/releasenotes/notes/component-set-output-type-override-852a19b3f0621fb0.yaml
@@ -0,0 +1,4 @@
+---
+fixes:
+  - |
+    Prevent `set_output_types`` from being called when the `output_types`` decorator is used.


Test patch:
diff --git a/test/core/component/test_component.py b/test/core/component/test_component.py
index 320e2d65..8b4266db 100644
--- a/test/core/component/test_component.py
+++ b/test/core/component/test_component.py
@@ -315,6 +315,20 @@ def test_output_types_decorator_wrong_method():
                 return cls()
 
 
+def test_output_types_decorator_and_set_output_types():
+    @component
+    class MockComponent:
+        def __init__(self) -> None:
+            component.set_output_types(self, value=int)
+
+        @component.output_types(value=int)
+        def run(self, value: int):
+            return {"value": 1}
+
+    with pytest.raises(ComponentError, match="Cannot call `set_output_types`"):
+        comp = MockComponent()
+
+
 def test_output_types_decorator_mismatch_run_async_run():
     @component
     class MockComponent: