Repository: deepset-ai/haystack

Issue:
Serialisation of the PromptModel is broken if the invocation_layer_class param is set, both from and to yaml
**Describe the bug**
A clear and concise description of what the bug is.

I am using a pipeline that uses a `PromptModel` with an `invocation_layer_class` specified, to deploy  Haystack API via docker.
On executing docker-compose up this will result in the error described below.

yml file:

name: PromptModel
params:
  api_key: EMPTY
  invocation_layer_class: !!python/name:haystack.nodes.prompt.invocation_layer.open_ai.OpenAIInvocationLayer ''
  max_length: 1024
  model_kwargs:
    api_base: http://localhost:8100/v1
    maximum_context_length: 2048
type: PromptModel

**Error message**
Error that was thrown (if available)

`yaml.constructor.ConstructorError: could not determine a constructor for the tag 'tag:yaml.org,2002:python/name:haystack.nodes.prompt.invocation_layer.open_ai.OpenAIInvocationLayer'`

**Expected behavior**
A clear and concise description of what you expected to happen.

I expected the pipleline to load correctly.

**Additional context**
Add any other context about the problem here, like document types / preprocessing steps / settings of reader etc.

The issue has been replicated by @anakin87

**To Reproduce**
Steps to reproduce the behavior
1. Serialize a pipeline with a `PromptModel `  with `invocation_layer_class` param, to yaml
2. Load the pipeline in the haystack api docker container


**FAQ Check**
- [x] Have you had a look at [our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)?

**System:**
 - OS: Windows11 + WSL2
 - GPU/CPU: GPU
 - Haystack version (commit or version number): 1.22.1
 - DocumentStore: WeaviateDocumentStore
 - Reader: -
 - Retriever: EmbeddingRetriever



Discussion:

Resolving patch:
diff --git a/haystack/nodes/prompt/prompt_model.py b/haystack/nodes/prompt/prompt_model.py
--- a/haystack/nodes/prompt/prompt_model.py
+++ b/haystack/nodes/prompt/prompt_model.py
@@ -1,5 +1,6 @@
 import inspect
 import logging
+import importlib
 from typing import Any, Dict, List, Optional, Tuple, Type, Union, overload
 
 from haystack.nodes.base import BaseComponent
@@ -40,7 +41,7 @@ def __init__(
         use_auth_token: Optional[Union[str, bool]] = None,
         use_gpu: Optional[bool] = None,
         devices: Optional[List[Union[str, "torch.device"]]] = None,
-        invocation_layer_class: Optional[Type[PromptModelInvocationLayer]] = None,
+        invocation_layer_class: Optional[Union[Type[PromptModelInvocationLayer], str]] = None,
         model_kwargs: Optional[Dict] = None,
     ):
         """
@@ -73,7 +74,7 @@ def __init__(
         self.model_invocation_layer = self.create_invocation_layer(invocation_layer_class=invocation_layer_class)
 
     def create_invocation_layer(
-        self, invocation_layer_class: Optional[Type[PromptModelInvocationLayer]]
+        self, invocation_layer_class: Optional[Union[Type[PromptModelInvocationLayer], str]]
     ) -> PromptModelInvocationLayer:
         kwargs = {
             "api_key": self.api_key,
@@ -84,6 +85,18 @@ def create_invocation_layer(
         }
         all_kwargs = {**self.model_kwargs, **kwargs}
 
+        if isinstance(invocation_layer_class, str):
+            module_name, class_name = invocation_layer_class.rsplit(".", maxsplit=1)
+            try:
+                module = importlib.import_module(module_name)
+            except ImportError as e:
+                msg = f"Can't find module {module_name}"
+                raise ValueError(msg) from e
+            invocation_layer_class = getattr(module, class_name)
+            if invocation_layer_class is None:
+                msg = f"Can'f find class {class_name} in module {module_name}"
+                ValueError(msg)
+
         if invocation_layer_class:
             return invocation_layer_class(
                 model_name_or_path=self.model_name_or_path, max_length=self.max_length, **all_kwargs


Test patch:
diff --git a/test/prompt/test_prompt_model.py b/test/prompt/test_prompt_model.py
--- a/test/prompt/test_prompt_model.py
+++ b/test/prompt/test_prompt_model.py
@@ -39,6 +39,16 @@ def test_constructor_with_no_supported_model():
         PromptModel("some-random-model")
 
 
+@pytest.mark.unit
+def test_constructor_with_invocation_layer_class_string():
+    model = PromptModel(
+        invocation_layer_class="haystack.nodes.prompt.invocation_layer.CohereInvocationLayer", api_key="fake_api_key"
+    )
+    from haystack.nodes.prompt.invocation_layer import CohereInvocationLayer
+
+    assert isinstance(model.model_invocation_layer, CohereInvocationLayer)
+
+
 @pytest.mark.asyncio
 async def test_ainvoke():
     def async_return(result):