Repository: deepset-ai/haystack

Issue:
Add `max_retries` and `timeout` params to all `AzureOpenAI` classes
**Is your feature request related to a problem? Please describe.**

Currently all `OpenAI` related classes (e.g. `OpenAIDocumentEmbedder`, `OpenAIChatGenerator`) can be initialised by setting `max_retries` and `timeout` params.

The corresponding `AzureOpenAI` don't always have the same params.

**Describe the solution you'd like**

It would be nice to have these params in the `AzureOpenAI` classes

**Describe alternatives you've considered**

Subclass `AzureOpenAI` and create custom components.

**Additional context**

cc @anakin87 :)

Discussion:
CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/deepset-ai/haystack?pullRequest=7983) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/deepset-ai/haystack?pullRequest=7983) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/deepset-ai/haystack?pullRequest=7983) it.</sub>
nvzard: I've signed the CLA.
vblagoje: We need to add reno note @nvzard See https://github.com/deepset-ai/haystack/blob/main/CONTRIBUTING.md for more details
nvzard: My bad @vblagoje, I totally forgot about it.
vblagoje: There are still some failures @nvzard 
anakin87: Please let's also read the timeout from an env var if not set at init, as we do in `OpenAIGenerator`.
nvzard: Good catch @anakin87. I've added it as well. 

All tests are passing locally now 🤞🏻 
coveralls: ## Pull Request Test Coverage Report for [Build 9817242905](https://coveralls.io/builds/68493436)


### 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.001%**) to **90.004%**

---


|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [components/generators/azure.py](https://coveralls.io/builds/68493436/source?filename=components%2Fgenerators%2Fazure.py#L115) | 3 | 92.5% |
<!-- | **Total:** | **3** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/68493436/badge)](https://coveralls.io/builds/68493436) |
| :-- | --: |
| Change from base [Build 9804009410](https://coveralls.io/builds/68478044): |  0.001% |
| Covered Lines: | 6771 |
| Relevant Lines: | 7523 |

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


Patch:
diff --git a/haystack/components/generators/azure.py b/haystack/components/generators/azure.py
index caa3e0aa..ffe6f8a9 100644
--- a/haystack/components/generators/azure.py
+++ b/haystack/components/generators/azure.py
@@ -63,6 +63,7 @@ class AzureOpenAIGenerator(OpenAIGenerator):
         streaming_callback: Optional[Callable[[StreamingChunk], None]] = None,
         system_prompt: Optional[str] = None,
         timeout: Optional[float] = None,
+        max_retries: Optional[int] = None,
         generation_kwargs: Optional[Dict[str, Any]] = None,
     ):
         """
@@ -78,7 +79,10 @@ class AzureOpenAIGenerator(OpenAIGenerator):
         :param streaming_callback: A callback function that is called when a new token is received from the stream.
             The callback function accepts StreamingChunk as an argument.
         :param system_prompt: The prompt to use for the system. If not provided, the system prompt will be
-        :param timeout: The timeout to be passed to the underlying `AzureOpenAI` client.
+        :param timeout: The timeout to be passed to the underlying `AzureOpenAI` client, if not set it is
+            inferred from the `OPENAI_TIMEOUT` environment variable or set to 30.
+        :param max_retries: Maximum retries to establish contact with AzureOpenAI if it returns an internal error,
+            if not set it is inferred from the `OPENAI_MAX_RETRIES` environment variable or set to 5.
         :param generation_kwargs: Other parameters to use for the model. These parameters are all sent directly to
             the OpenAI endpoint. See OpenAI [documentation](https://platform.openai.com/docs/api-reference/chat) for
             more details.
@@ -125,7 +129,8 @@ class AzureOpenAIGenerator(OpenAIGenerator):
         self.azure_deployment = azure_deployment
         self.organization = organization
         self.model: str = azure_deployment or "gpt-35-turbo"
-        self.timeout = timeout
+        self.timeout = timeout or float(os.environ.get("OPENAI_TIMEOUT", 30.0))
+        self.max_retries = max_retries or int(os.environ.get("OPENAI_MAX_RETRIES", 5))
 
         self.client = AzureOpenAI(
             api_version=api_version,
@@ -134,7 +139,8 @@ class AzureOpenAIGenerator(OpenAIGenerator):
             api_key=api_key.resolve_value() if api_key is not None else None,
             azure_ad_token=azure_ad_token.resolve_value() if azure_ad_token is not None else None,
             organization=organization,
-            timeout=timeout,
+            timeout=self.timeout,
+            max_retries=self.max_retries,
         )
 
     def to_dict(self) -> Dict[str, Any]:
@@ -157,6 +163,7 @@ class AzureOpenAIGenerator(OpenAIGenerator):
             api_key=self.api_key.to_dict() if self.api_key is not None else None,
             azure_ad_token=self.azure_ad_token.to_dict() if self.azure_ad_token is not None else None,
             timeout=self.timeout,
+            max_retries=self.max_retries,
         )
 
     @classmethod
diff --git a/releasenotes/notes/max-retries-for-AzureOpenAIGenerator-0f1a1807dd2af041.yaml b/releasenotes/notes/max-retries-for-AzureOpenAIGenerator-0f1a1807dd2af041.yaml
new file mode 100644
index 00000000..7a00710c
--- /dev/null
+++ b/releasenotes/notes/max-retries-for-AzureOpenAIGenerator-0f1a1807dd2af041.yaml
@@ -0,0 +1,9 @@
+---
+enhancements:
+  - |
+    Add max_retries to AzureOpenAIGenerator.
+    AzureOpenAIGenerator can now be initialised by setting max_retries.
+    If not set, it is inferred from the `OPENAI_MAX_RETRIES`
+    environment variable or set to 5.
+    The timeout for AzureOpenAIGenerator, if not set, it is inferred
+    from the `OPENAI_TIMEOUT` environment variable or set to 30.


Test patch:
diff --git a/test/components/generators/test_azure.py b/test/components/generators/test_azure.py
index 39bef98a..b4638a16 100644
--- a/test/components/generators/test_azure.py
+++ b/test/components/generators/test_azure.py
@@ -39,7 +39,7 @@ class TestAzureOpenAIGenerator:
         assert component.client.api_key == "fake-api-key"
         assert component.azure_deployment == "gpt-35-turbo"
         assert component.streaming_callback is print_streaming_chunk
-        assert component.timeout is None
+        assert component.timeout == 30.0
         assert component.generation_kwargs == {"max_tokens": 10, "some_test_param": "test-params"}
 
     def test_to_dict_default(self, monkeypatch):
@@ -57,7 +57,8 @@ class TestAzureOpenAIGenerator:
                 "azure_endpoint": "some-non-existing-endpoint",
                 "organization": None,
                 "system_prompt": None,
-                "timeout": None,
+                "timeout": 30.0,
+                "max_retries": 5,
                 "generation_kwargs": {},
             },
         }
@@ -69,6 +70,7 @@ class TestAzureOpenAIGenerator:
             azure_ad_token=Secret.from_env_var("ENV_VAR1", strict=False),
             azure_endpoint="some-non-existing-endpoint",
             timeout=3.5,
+            max_retries=10,
             generation_kwargs={"max_tokens": 10, "some_test_param": "test-params"},
         )
 
@@ -85,6 +87,7 @@ class TestAzureOpenAIGenerator:
                 "organization": None,
                 "system_prompt": None,
                 "timeout": 3.5,
+                "max_retries": 10,
                 "generation_kwargs": {"max_tokens": 10, "some_test_param": "test-params"},
             },
         }