Repository: deepset-ai/haystack

Issue:
Azure OpenAI Service Gives OpenAI API key error with gpt-4
**Describe the bug**
Currently, if you want to use gpt-4 model with Azure OpenAI Service, a OpenAIUnauthorizedError is raised (same as in issue [#4626](https://github.com/deepset-ai/haystack/issues/4626)).

After investigation, I noticed it was because of the order on the invocation layer. There is a for loop that goes through the invocation layer and has OpenAI ChatGPT layer before the Azure OpenAI ChatGPT. When model gpt-4 is chosen, it select OpenAI ChatGPT and its API endpoint that leads to authentication error because Azure URL is not used. Using gpt-3.5-turbo is working because their names differ between Azure (gpt-35-turbo) and OpenAI (gpt-3.5-turbo) maybe to fix that ordering bug.

**Error message**
OpenAIUnauthorizedError
                
**Expected behavior**
A call to the right API returning the answer requested.

**Additional context**
N/A

**To Reproduce**
```python
from haystack.nodes import PromptNode, PromptTemplate

my_prompt = PromptTemplate(
    prompt="""Synthesize a comprehensive answer from the following text for the given question. 
                             Provide a clear and concise response that summarizes the key points and information presented in the text. 
                             Your answer should be in your own words and be no longer than 50 words. 
                             \n\n Related text: {join(documents)} \n\n Question: {query} \n\n Answer:""",
)

prompt_node = PromptNode(model_name_or_path='gpt-4',
api_key=AZURE_API_KEY,
max_length=800,
default_prompt_template=my_prompt,
model_kwargs={"azure_base_url": AZURE_BASE_URL, "azure_deployment_name": AZURE_DEPLOYMENT_NAME},
)

from haystack.pipelines import Pipeline
pipe = Pipeline()
pipe.add_node(component=retriever, name="retriever", inputs=["Query"])
pipe.add_node(component=prompt_node, name="prompt_node", inputs=["retriever"])
output = pipe.run(query="How are you?")
```

Current workaround is to specify the InvocationLayer:
```python
from haystack.nodes import PromptNode, PromptTemplate, PromptModel
from haystack.nodes.prompt.invocation_layer.azure_chatgpt import AzureChatGPTInvocationLayer

my_prompt = PromptTemplate(
    prompt="""Synthesize a comprehensive answer from the following text for the given question. 
                             Provide a clear and concise response that summarizes the key points and information presented in the text. 
                             Your answer should be in your own words and be no longer than 50 words. 
                             \n\n Related text: {join(documents)} \n\n Question: {query} \n\n Answer:""",
)

prompt_model_azure_open_ai = PromptModel(
    model_name_or_path="gpt-4", 
    api_key=AZURE_API_KEY,
    invocation_layer_class=AzureChatGPTInvocationLayer,
    model_kwargs={
        "azure_base_url": AZURE_BASE_URL, 
        "azure_deployment_name": AZURE_DEPLOYMENT_NAME,
    }
)
prompt_node =
 PromptNode(model_name_or_path=prompt_model_azure_open_ai, max_length=800, default_prompt_template=my_prompt)
```

I'll try to propose a PR to fix this but I am unsure on how to handle this between adding a fix similar to gpt-35-turbo vs gpt3.5-turbo or something else as this solution might not be very sustainable if new gpt models become available. Otherwise, we could check if Azure information are sent to only select the appropriate layer to be checked.
Don't hesitate to propose another approach.

**FAQ Check**
- [x] Have you had a look at [our new FAQ page](https://docs.haystack.deepset.ai/docs/faq)?

**System:**
 - OS: Windows 10
 - GPU/CPU: N/A
 - Haystack version (commit or version number): 1.18.0-rc0
 - DocumentStore: N/A
 - Reader: N/A
 - Retriever: N/A



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 @@
-from typing import Dict, List, Optional, Tuple, Union, Any, Type, overload
 import logging
+import re
+from typing import Any, Dict, List, Optional, Tuple, Type, Union, overload
 
 import torch
 
@@ -81,9 +82,16 @@ def create_invocation_layer(
             return invocation_layer_class(
                 model_name_or_path=self.model_name_or_path, max_length=self.max_length, **all_kwargs
             )
-        # search all invocation layer classes and find the first one that supports the model,
+
+        potential_invocation_layer = PromptModelInvocationLayer.invocation_layer_providers
+        # if azure_base_url exist as an argument, invocation layer classes are filtered to only keep the ones relatives to azure
+        if "azure_base_url" in self.model_kwargs:
+            potential_invocation_layer = [
+                layer for layer in potential_invocation_layer if re.search(r"azure", layer.__name__, re.IGNORECASE)
+            ]
+        # search all invocation layer classes candidates and find the first one that supports the model,
         # then create an instance of that invocation layer
-        for invocation_layer in PromptModelInvocationLayer.invocation_layer_providers:
+        for invocation_layer in potential_invocation_layer:
             if invocation_layer.supports(self.model_name_or_path, **all_kwargs):
                 return invocation_layer(
                     model_name_or_path=self.model_name_or_path, max_length=self.max_length, **all_kwargs


Test patch: