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:

Solution patch:
diff --git a/haystack/components/classifiers/zero_shot_document_classifier.py b/haystack/components/classifiers/zero_shot_document_classifier.py
index cff245b35b..5aa52fde80 100644
--- a/haystack/components/classifiers/zero_shot_document_classifier.py
+++ b/haystack/components/classifiers/zero_shot_document_classifier.py
@@ -121,7 +121,6 @@ def __init__(
         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 @@ def run(self, documents: List[Document], batch_size: int = 1):
             )
 
         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 b11ebd0c71..a9d128ae15 100644
--- a/haystack/components/preprocessors/nltk_document_splitter.py
+++ b/haystack/components/preprocessors/nltk_document_splitter.py
@@ -16,7 +16,7 @@
 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 @@ def __init__(
         )
         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 e34efdb28e..72c444fdd6 100644
--- a/haystack/core/component/component.py
+++ b/haystack/core/component/component.py
@@ -449,6 +449,13 @@ def run(self, value: int):
                 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 0000000000..2a06faddeb
--- /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 320e2d65ca..8b4266dbb3 100644
--- a/test/core/component/test_component.py
+++ b/test/core/component/test_component.py
@@ -315,6 +315,20 @@ def from_dict(cls, data):
                 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: