diff --git a/haystack/core/pipeline/base.py b/haystack/core/pipeline/base.py index 27c3c0c1cb..c1424d9304 100644 --- a/haystack/core/pipeline/base.py +++ b/haystack/core/pipeline/base.py @@ -417,7 +417,7 @@ def remove_component(self, name: str) -> Component: return instance - def connect(self, sender: str, receiver: str) -> "PipelineBase": # noqa: PLR0915 PLR0912 + def connect(self, sender: str, receiver: str) -> "PipelineBase": # noqa: PLR0915 PLR0912 C901 pylint: disable=too-many-branches """ Connects two components together. @@ -456,13 +456,20 @@ def connect(self, sender: str, receiver: str) -> "PipelineBase": # noqa: PLR091 except KeyError as exc: raise ValueError(f"Component named {receiver_component_name} not found in the pipeline.") from exc + if not sender_sockets: + raise PipelineConnectError( + f"'{sender_component_name}' does not have any output connections. " + f"Please check that the output types of '{sender_component_name}.run' are set, " + f"for example by using the '@component.output_types' decorator." + ) + # If the name of either socket is given, get the socket sender_socket: Optional[OutputSocket] = None if sender_socket_name: sender_socket = sender_sockets.get(sender_socket_name) if not sender_socket: raise PipelineConnectError( - f"'{sender} does not exist. " + f"'{sender}' does not exist. " f"Output connections of {sender_component_name} are: " + ", ".join([f"{name} (type {_type_name(socket.type)})" for name, socket in sender_sockets.items()]) ) diff --git a/releasenotes/notes/informative-error-message-on-two-components-connection-0cc535409855b06e.yaml b/releasenotes/notes/informative-error-message-on-two-components-connection-0cc535409855b06e.yaml new file mode 100644 index 0000000000..22add5f067 --- /dev/null +++ b/releasenotes/notes/informative-error-message-on-two-components-connection-0cc535409855b06e.yaml @@ -0,0 +1,3 @@ +fixes: + - | + Return a more informative error message when attempting to connect two components and the sender component does not have any OutputSockets defined. diff --git a/test/core/pipeline/test_pipeline_base.py b/test/core/pipeline/test_pipeline_base.py index 2c48729c59..c8fbd445ff 100644 --- a/test/core/pipeline/test_pipeline_base.py +++ b/test/core/pipeline/test_pipeline_base.py @@ -259,6 +259,40 @@ def test_remove_component_allows_you_to_reuse_the_component(self): # instance = pipe2.get_component("some") # assert instance == component + def test_connect_with_nonexistent_output_socket_name(self): + """Test connecting using a non-existent output socket name.""" + comp1 = component_class("Comp1", output_types={"output": int})() + comp2 = component_class("Comp2", input_types={"value": int})() + pipe = PipelineBase() + pipe.add_component("comp1", comp1) + pipe.add_component("comp2", comp2) + + with pytest.raises(PipelineConnectError) as excinfo: + pipe.connect("comp1.value", "comp2.value") + + assert "'comp1.value' does not exist" in str(excinfo.value) + assert "Output connections of comp1 are: output (type int)" in str(excinfo.value) + + def test_connect_with_no_output_sockets(self): + """Test connecting from a component that has no output sockets at all.""" + + @component + class NoOutputComponent: + def run(self): + pass + + comp1 = NoOutputComponent() + comp2 = component_class("Comp2", input_types={"value": int})() + pipe = PipelineBase() + pipe.add_component("comp1", comp1) + pipe.add_component("comp2", comp2) + + with pytest.raises(PipelineConnectError) as excinfo: + pipe.connect("comp1.value", "comp2.value") + + assert "'comp1' does not have any output connections" in str(excinfo.value) + assert "@component.output_types" in str(excinfo.value) + # UNIT def test_get_component_name(self): pipe = PipelineBase()