This is an automated email from the ASF dual-hosted git repository.

wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git


The following commit(s) were added to refs/heads/main by this push:
     new 4bc67540 [integration][openai] Align default parameters for openai 
chat model between java and python (#883)
4bc67540 is described below

commit 4bc67540fbf21b2b42bbe7d204d292053a175e39
Author: rob-9 <[email protected]>
AuthorDate: Wed Aug 12 04:35:35 2026 -0700

    [integration][openai] Align default parameters for openai chat model 
between java and python (#883)
---
 docs/content/docs/development/chat_models.md       |  16 +-
 .../openai/AzureOpenAIChatModelConnection.java     |  24 ++-
 .../openai/OpenAIChatCompletionsUtils.java         | 108 ++++++++++-
 .../openai/OpenAICompletionsConnection.java        |  24 ++-
 .../openai/OpenAIResponsesModelConnection.java     |  28 ++-
 .../openai/AzureOpenAIChatModelConnectionTest.java |  20 ++
 .../openai/OpenAIChatCompletionsUtilsTest.java     |  29 +++
 .../chatmodels/openai/OpenAIClientTestUtils.java   |  53 ++++++
 .../openai/OpenAICompletionsConnectionTest.java    |  27 +++
 .../openai/OpenAIResponsesModelConnectionTest.java | 204 +++++++++++++++++++++
 .../chat_models/azure/azure_openai_chat_model.py   |  10 +-
 .../azure/tests/test_azure_openai_chat_model.py    |  53 ++++++
 .../chat_models/openai/openai_chat_model.py        |  11 +-
 .../openai/tests/test_openai_chat_model.py         |  76 ++++++++
 14 files changed, 643 insertions(+), 40 deletions(-)

diff --git a/docs/content/docs/development/chat_models.md 
b/docs/content/docs/development/chat_models.md
index 2fb2d19b..63b1862e 100644
--- a/docs/content/docs/development/chat_models.md
+++ b/docs/content/docs/development/chat_models.md
@@ -508,7 +508,7 @@ Azure OpenAI provides access to OpenAI models (GPT-4, 
GPT-4o, etc.) through Azur
 | `api_key` | str | Required | Azure OpenAI API key for authentication |
 | `api_version` | str | Required | Azure OpenAI REST API version (e.g., 
"2024-10-21"). See [API 
versions](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning)
 |
 | `azure_endpoint` | str | Required | Azure OpenAI endpoint URL (e.g., 
`https://{resource-name}.openai.azure.com`) |
-| `timeout` | float | `60.0` | API request timeout in seconds |
+| `timeout` | float | `60.0` | API request timeout in seconds; `0` disables 
timeouts |
 | `max_retries` | int | `3` | Maximum number of API retry attempts |
 
 {{< /tab >}}
@@ -520,8 +520,8 @@ Azure OpenAI provides access to OpenAI models (GPT-4, 
GPT-4o, etc.) through Azur
 | `api_key` | String | Required | Azure OpenAI API key for authentication |
 | `api_version` | String | Required | Azure OpenAI REST API version (e.g., 
"2024-10-21"). See [API 
versions](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#rest-api-versioning)
 |
 | `azure_endpoint` | String | Required | Azure OpenAI endpoint URL (e.g., 
`https://{resource-name}.openai.azure.com`) — either a direct Azure resource or 
a proxy/gateway URL that fronts an Azure OpenAI service |
-| `timeout` | int | None | Timeout in seconds for API requests; must be 
greater than 0, otherwise ignored (SDK default applies) |
-| `max_retries` | int | None | Maximum number of API retry attempts; must be 
non-negative, otherwise ignored (SDK default applies) |
+| `timeout` | float | `60` | Timeout in seconds for API requests; `0` disables 
timeouts; must be 0–2,147,483.647 |
+| `max_retries` | int | `3` | Maximum number of API retry attempts; must be 
non-negative |
 | `azure_url_path_mode` | String | `"AUTO"` | Controls how the SDK constructs 
Azure OpenAI request URLs. One of `"AUTO"`, `"LEGACY"`, or `"UNIFIED"`. Custom 
gateways that proxy Azure OpenAI typically need `"LEGACY"` to force the 
`/openai/deployments/{model}` path |
 
 {{< /tab >}}
@@ -911,7 +911,7 @@ OpenAI provides cloud-based chat models with 
state-of-the-art performance for a
 | `api_key` | str | Required | OpenAI API key for authentication |
 | `api_base_url` | str | `"https://api.openai.com/v1"` | Base URL for OpenAI 
API |
 | `max_retries` | int | `3` | Maximum number of API retry attempts |
-| `timeout` | float | `60.0` | API request timeout in seconds |
+| `timeout` | float | `60.0` | API request timeout in seconds; `0` disables 
timeouts |
 | `default_headers` | dict | None | Default headers for API requests |
 | `reuse_client` | bool | `True` | Whether to reuse the OpenAI client between 
requests |
 
@@ -923,8 +923,8 @@ OpenAI provides cloud-based chat models with 
state-of-the-art performance for a
 |-----------|------|---------|-------------|
 | `api_key` | String | Required | OpenAI API key for authentication |
 | `api_base_url` | String | `"https://api.openai.com/v1"` | Base URL for 
OpenAI API |
-| `max_retries` | int | `2` | Maximum number of API retry attempts |
-| `timeout` | int | None | Timeout in seconds for API requests |
+| `max_retries` | int | `3` | Maximum number of API retry attempts; must be 
non-negative |
+| `timeout` | float | `60` | Timeout in seconds for API requests; `0` disables 
timeouts; must be 0–2,147,483.647 |
 | `default_headers` | Map<String, String> | None | Default headers for API 
requests |
 | `model` | String | None | Default model to use if not specified in setup |
 
@@ -1054,8 +1054,8 @@ Responses API is only supported in Java currently. To use 
OpenAI Responses API f
 |-----------|------|---------|-------------|
 | `api_key` | String | Required | OpenAI API key for authentication |
 | `api_base_url` | String | None | Base URL for OpenAI API (useful for 
proxies) |
-| `max_retries` | int | `2` | Maximum number of API retry attempts |
-| `timeout` | int | None | Timeout in seconds for API requests |
+| `max_retries` | int | `3` | Maximum number of API retry attempts; must be 
non-negative |
+| `timeout` | float | `60` | Timeout in seconds for API requests; `0` disables 
timeouts; must be 0–2,147,483.647 |
 | `default_headers` | Map<String, String> | None | Default headers for API 
requests |
 | `model` | String | None | Default model to use if not specified in setup |
 
diff --git 
a/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/AzureOpenAIChatModelConnection.java
 
b/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/AzureOpenAIChatModelConnection.java
index 31b2c5ab..9f9267f1 100644
--- 
a/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/AzureOpenAIChatModelConnection.java
+++ 
b/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/AzureOpenAIChatModelConnection.java
@@ -141,6 +141,8 @@ public class AzureOpenAIChatModelConnection extends 
BaseChatModelConnection {
     private static final Pattern API_VERSION_DATE_PREFIX = 
Pattern.compile("^\\d{4}-\\d{2}-\\d{2}");
 
     private final OpenAIClient client;
+    private final Duration timeout;
+    private final int maxRetries;
 
     private final String apiVersion;
 
@@ -170,15 +172,11 @@ public class AzureOpenAIChatModelConnection extends 
BaseChatModelConnection {
                         .credential(AzureApiKeyCredential.create(apiKey))
                         
.azureServiceVersion(AzureOpenAIServiceVersion.fromString(apiVersion));
 
-        Integer timeoutSeconds = descriptor.getArgument("timeout");
-        if (timeoutSeconds != null && timeoutSeconds > 0) {
-            clientBuilder.timeout(Duration.ofSeconds(timeoutSeconds));
-        }
+        this.timeout = OpenAIChatCompletionsUtils.parseTimeout(descriptor);
+        
clientBuilder.timeout(OpenAIChatCompletionsUtils.toSdkTimeout(this.timeout));
 
-        Integer maxRetries = descriptor.getArgument("max_retries");
-        if (maxRetries != null && maxRetries >= 0) {
-            clientBuilder.maxRetries(maxRetries);
-        }
+        this.maxRetries = 
OpenAIChatCompletionsUtils.parseMaxRetries(descriptor);
+        clientBuilder.maxRetries(this.maxRetries);
 
         String azureUrlPathMode = 
descriptor.getArgument("azure_url_path_mode");
         if (azureUrlPathMode != null && !azureUrlPathMode.isBlank()) {
@@ -196,6 +194,16 @@ public class AzureOpenAIChatModelConnection extends 
BaseChatModelConnection {
         this.client = clientBuilder.build();
     }
 
+    // visible for testing
+    Duration getTimeout() {
+        return timeout;
+    }
+
+    // visible for testing
+    int getMaxRetries() {
+        return maxRetries;
+    }
+
     /**
      * Whether Azure documents json_schema strict support for {@code 
effectiveModel}.
      *
diff --git 
a/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIChatCompletionsUtils.java
 
b/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIChatCompletionsUtils.java
index c9d8c8d9..35eddaf1 100644
--- 
a/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIChatCompletionsUtils.java
+++ 
b/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIChatCompletionsUtils.java
@@ -21,6 +21,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.openai.core.JsonValue;
+import com.openai.core.Timeout;
 import com.openai.models.chat.completions.ChatCompletionAssistantMessageParam;
 import com.openai.models.chat.completions.ChatCompletionMessage;
 import 
com.openai.models.chat.completions.ChatCompletionMessageFunctionToolCall;
@@ -31,7 +32,12 @@ import 
com.openai.models.chat.completions.ChatCompletionToolMessageParam;
 import com.openai.models.chat.completions.ChatCompletionUserMessageParam;
 import org.apache.flink.agents.api.chat.messages.ChatMessage;
 import org.apache.flink.agents.api.chat.messages.MessageRole;
+import org.apache.flink.agents.api.resource.ResourceDescriptor;
 
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -41,8 +47,9 @@ import java.util.stream.Collectors;
 
 /**
  * Static helpers for converting between Flink Agents {@link ChatMessage} and 
OpenAI Chat
- * Completions API message types. Restricted to message conversion (no 
tool-definition conversion —
- * that stays per-connection).
+ * Completions API message types, plus shared parsing/validation of common 
connection arguments
+ * ({@code timeout}, {@code max_retries}). No tool-definition conversion — 
that stays
+ * per-connection.
  *
  * <p>Used by both {@code OpenAICompletionsConnection} (OpenAI / 
OpenAI-compatible providers) and
  * {@code AzureOpenAIChatModelConnection} (Azure OpenAI). Both rely on the 
same openai-java SDK
@@ -50,8 +57,105 @@ import java.util.stream.Collectors;
  */
 final class OpenAIChatCompletionsUtils {
 
+    private static final BigDecimal MAX_TIMEOUT_SECONDS =
+            BigDecimal.valueOf(Integer.MAX_VALUE).movePointLeft(3);
+
+    /** Default timeout in seconds for OpenAI API requests (aligned with 
Python SDK). */
+    static final int DEFAULT_TIMEOUT_SECONDS = 60;
+
+    /** Default max retries for OpenAI API requests (aligned with Python SDK). 
*/
+    static final int DEFAULT_MAX_RETRIES = 3;
+
     private OpenAIChatCompletionsUtils() {}
 
+    /**
+     * Resolve and validate the {@code timeout} argument (in seconds). The raw 
value is validated
+     * before any numeric conversion so that e.g. {@code -0.5} cannot truncate 
to {@code 0} and
+     * bypass the non-negative check. Fractional values are rounded up to the 
SDK's millisecond
+     * precision so that a positive value can never become an unlimited 
timeout.
+     */
+    static Duration parseTimeout(ResourceDescriptor descriptor) {
+        Number raw = descriptor.getArgument("timeout");
+        if (raw == null) {
+            return Duration.ofSeconds(DEFAULT_TIMEOUT_SECONDS);
+        }
+        BigDecimal seconds = toBigDecimal(raw, "timeout");
+        if (seconds.signum() < 0) {
+            throw new IllegalArgumentException("timeout must be >= 0, got: " + 
raw);
+        }
+        if (seconds.compareTo(MAX_TIMEOUT_SECONDS) > 0) {
+            throw new IllegalArgumentException(
+                    "timeout exceeds the SDK maximum of "
+                            + MAX_TIMEOUT_SECONDS.toPlainString()
+                            + " seconds, got: "
+                            + raw);
+        }
+        try {
+            // The SDK's OkHttp transport accepts millisecond precision. Round 
positive values up
+            // so a valid nonzero timeout cannot become Duration.ZERO, which 
disables timeouts.
+            BigInteger milliseconds =
+                    seconds.multiply(BigDecimal.valueOf(1_000L))
+                            .setScale(0, RoundingMode.CEILING)
+                            .toBigIntegerExact();
+            return Duration.ofMillis(milliseconds.longValueExact());
+        } catch (ArithmeticException e) {
+            throw new IllegalArgumentException(
+                    "timeout is outside the supported range, got: " + raw, e);
+        }
+    }
+
+    /**
+     * Configure every SDK timeout component from the connection timeout. A 
zero duration means no
+     * timeout in openai-java, so all components must be set explicitly; 
setting only the request
+     * timeout leaves the SDK's default connection timeout in effect.
+     */
+    static Timeout toSdkTimeout(Duration timeout) {
+        return Timeout.builder()
+                .connect(timeout)
+                .read(timeout)
+                .write(timeout)
+                .request(timeout)
+                .build();
+    }
+
+    /**
+     * Resolve and validate the {@code max_retries} argument. Requires an 
exact non-negative integer
+     * within int range, matching Python-side validation (pydantic rejects 
fractional values for int
+     * fields).
+     */
+    static int parseMaxRetries(ResourceDescriptor descriptor) {
+        Number raw = descriptor.getArgument("max_retries");
+        if (raw == null) {
+            return DEFAULT_MAX_RETRIES;
+        }
+        BigDecimal value = toBigDecimal(raw, "max_retries");
+        try {
+            BigInteger retries = value.toBigIntegerExact();
+            if (retries.signum() < 0
+                    || 
retries.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {
+                throw new IllegalArgumentException(
+                        "max_retries must be a non-negative integer, got: " + 
raw);
+            }
+            return retries.intValueExact();
+        } catch (ArithmeticException e) {
+            throw new IllegalArgumentException(
+                    "max_retries must be a non-negative integer, got: " + raw, 
e);
+        }
+    }
+
+    private static BigDecimal toBigDecimal(Number raw, String argumentName) {
+        if ((raw instanceof Double || raw instanceof Float)
+                && !Double.isFinite(raw.doubleValue())) {
+            throw new IllegalArgumentException(argumentName + " must be 
finite, got: " + raw);
+        }
+        try {
+            return new BigDecimal(raw.toString());
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException(
+                    argumentName + " must be a finite number, got: " + raw, e);
+        }
+    }
+
     private static final ObjectMapper mapper = new ObjectMapper();
     private static final TypeReference<Map<String, Object>> MAP_TYPE = new 
TypeReference<>() {};
 
diff --git 
a/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnection.java
 
b/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnection.java
index 92683b9c..f39c6ed3 100644
--- 
a/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnection.java
+++ 
b/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnection.java
@@ -84,6 +84,8 @@ public class OpenAICompletionsConnection extends 
BaseChatModelConnection {
     private static final ObjectMapper mapper = new ObjectMapper();
     private final OpenAIClient client;
     private final String defaultModel;
+    private final Duration timeout;
+    private final int maxRetries;
 
     public OpenAICompletionsConnection(
             ResourceDescriptor descriptor, ResourceContext resourceContext) {
@@ -101,15 +103,11 @@ public class OpenAICompletionsConnection extends 
BaseChatModelConnection {
             builder.baseUrl(apiBaseUrl);
         }
 
-        Integer timeoutSeconds = descriptor.getArgument("timeout");
-        if (timeoutSeconds != null && timeoutSeconds > 0) {
-            builder.timeout(Duration.ofSeconds(timeoutSeconds));
-        }
+        this.timeout = OpenAIChatCompletionsUtils.parseTimeout(descriptor);
+        builder.timeout(OpenAIChatCompletionsUtils.toSdkTimeout(this.timeout));
 
-        Integer maxRetries = descriptor.getArgument("max_retries");
-        if (maxRetries != null && maxRetries >= 0) {
-            builder.maxRetries(maxRetries);
-        }
+        this.maxRetries = 
OpenAIChatCompletionsUtils.parseMaxRetries(descriptor);
+        builder.maxRetries(this.maxRetries);
 
         Map<String, String> defaultHeaders = 
descriptor.getArgument("default_headers");
         if (defaultHeaders != null && !defaultHeaders.isEmpty()) {
@@ -122,6 +120,16 @@ public class OpenAICompletionsConnection extends 
BaseChatModelConnection {
         this.client = builder.build();
     }
 
+    // visible for testing
+    Duration getTimeout() {
+        return timeout;
+    }
+
+    // visible for testing
+    int getMaxRetries() {
+        return maxRetries;
+    }
+
     // Models for which OpenAI documents json_schema strict Structured Outputs 
support.
     // Source of truth: 
https://platform.openai.com/docs/guides/structured-outputs
     //
diff --git 
a/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIResponsesModelConnection.java
 
b/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIResponsesModelConnection.java
index 0fcd484b..85d26003 100644
--- 
a/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIResponsesModelConnection.java
+++ 
b/integrations/chat-models/openai/src/main/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIResponsesModelConnection.java
@@ -58,8 +58,10 @@ import java.util.Optional;
  * <ul>
  *   <li><b>api_key</b> (required): OpenAI API key
  *   <li><b>api_base_url</b> (optional): Base URL for OpenAI API (useful for 
proxies)
- *   <li><b>timeout</b> (optional): Timeout in seconds for API requests
- *   <li><b>max_retries</b> (optional): Maximum number of retry attempts 
(default: 2)
+ *   <li><b>timeout</b> (optional): Timeout in seconds for API requests; must 
be non-negative
+ *       (default: 60)
+ *   <li><b>max_retries</b> (optional): Maximum number of retry attempts; must 
be non-negative
+ *       (default: 3)
  *   <li><b>default_headers</b> (optional): Map of default headers to include 
in all requests
  *   <li><b>model</b> (optional): Default model to use if not specified in 
setup
  * </ul>
@@ -86,6 +88,8 @@ public class OpenAIResponsesModelConnection extends 
BaseChatModelConnection {
 
     private final OpenAIClient client;
     private final String defaultModel;
+    private final Duration timeout;
+    private final int maxRetries;
 
     public OpenAIResponsesModelConnection(
             ResourceDescriptor descriptor, ResourceContext resourceContext) {
@@ -103,15 +107,11 @@ public class OpenAIResponsesModelConnection extends 
BaseChatModelConnection {
             builder.baseUrl(apiBaseUrl);
         }
 
-        Integer timeoutSeconds = descriptor.getArgument("timeout");
-        if (timeoutSeconds != null && timeoutSeconds > 0) {
-            builder.timeout(Duration.ofSeconds(timeoutSeconds));
-        }
+        this.timeout = OpenAIChatCompletionsUtils.parseTimeout(descriptor);
+        builder.timeout(OpenAIChatCompletionsUtils.toSdkTimeout(this.timeout));
 
-        Integer maxRetries = descriptor.getArgument("max_retries");
-        if (maxRetries != null && maxRetries >= 0) {
-            builder.maxRetries(maxRetries);
-        }
+        this.maxRetries = 
OpenAIChatCompletionsUtils.parseMaxRetries(descriptor);
+        builder.maxRetries(this.maxRetries);
 
         Map<String, String> defaultHeaders = 
descriptor.getArgument("default_headers");
         if (defaultHeaders != null && !defaultHeaders.isEmpty()) {
@@ -453,6 +453,14 @@ public class OpenAIResponsesModelConnection extends 
BaseChatModelConnection {
         return mapper.convertValue(value, MAP_TYPE);
     }
 
+    Duration getTimeout() {
+        return timeout;
+    }
+
+    int getMaxRetries() {
+        return maxRetries;
+    }
+
     @Override
     public void close() throws Exception {
         this.client.close();
diff --git 
a/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/AzureOpenAIChatModelConnectionTest.java
 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/AzureOpenAIChatModelConnectionTest.java
index 97b9a2cb..2bda0db2 100644
--- 
a/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/AzureOpenAIChatModelConnectionTest.java
+++ 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/AzureOpenAIChatModelConnectionTest.java
@@ -43,6 +43,7 @@ import org.junit.jupiter.params.provider.MethodSource;
 import org.junit.jupiter.params.provider.NullAndEmptySource;
 import org.junit.jupiter.params.provider.ValueSource;
 
+import java.time.Duration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -95,6 +96,25 @@ class AzureOpenAIChatModelConnectionTest {
         return connection(CAPABLE_API_VERSION);
     }
 
+    @Test
+    void testConnectionArgumentDefaultsAndZeroTimeout() {
+        AzureOpenAIChatModelConnection connection =
+                new AzureOpenAIChatModelConnection(
+                        connectionDescriptor()
+                                .addInitialArgument("api_key", "test-key")
+                                .addInitialArgument("api_version", 
"2024-02-01")
+                                .addInitialArgument(
+                                        "azure_endpoint", 
"https://example.openai.azure.com";)
+                                .addInitialArgument("timeout", 0)
+                                .addInitialArgument("max_retries", 0)
+                                .build(),
+                        NOOP);
+        assertThat(connection).isInstanceOf(BaseChatModelConnection.class);
+        assertThat(connection.getTimeout()).isEqualTo(Duration.ZERO);
+        assertThat(connection.getMaxRetries()).isZero();
+        OpenAIClientTestUtils.assertNoTimeoutConfigured(connection);
+    }
+
     /**
      * Model params addressing {@link #DEPLOYMENT}. A null {@code 
modelOfAzureDeployment} omits the
      * key entirely, which is how the setup emits an unset backing model.
diff --git 
a/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIChatCompletionsUtilsTest.java
 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIChatCompletionsUtilsTest.java
index 9f260366..da93951e 100644
--- 
a/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIChatCompletionsUtilsTest.java
+++ 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIChatCompletionsUtilsTest.java
@@ -20,12 +20,16 @@ package 
org.apache.flink.agents.integrations.chatmodels.openai;
 
 import com.openai.models.chat.completions.ChatCompletionMessage;
 import org.apache.flink.agents.api.chat.messages.ChatMessage;
+import org.apache.flink.agents.api.resource.ResourceDescriptor;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
+import java.math.BigDecimal;
+import java.time.Duration;
 import java.util.Optional;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /**
  * Unit tests for how {@link OpenAIChatCompletionsUtils} carries a provider 
refusal from a Chat
@@ -60,4 +64,29 @@ class OpenAIChatCompletionsUtilsTest {
 
         assertThat(result.getExtraArgs()).doesNotContainKey("refusal");
     }
+
+    @Test
+    void testMaxRetriesRejectsFractionalBigDecimal() {
+        assertThatThrownBy(
+                        () ->
+                                OpenAIChatCompletionsUtils.parseMaxRetries(
+                                        descriptor(
+                                                "max_retries",
+                                                new 
BigDecimal("2.0000000000000000000000001"))))
+                .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void testPositiveTimeoutBelowMillisecondRoundsUpToSdkPrecision() {
+        assertThat(
+                        OpenAIChatCompletionsUtils.parseTimeout(
+                                descriptor("timeout", new 
BigDecimal("0.0000000001"))))
+                .isEqualTo(Duration.ofMillis(1));
+    }
+
+    private static ResourceDescriptor descriptor(String argumentName, Number 
value) {
+        return 
ResourceDescriptor.Builder.newBuilder(OpenAICompletionsConnection.class.getName())
+                .addInitialArgument(argumentName, value)
+                .build();
+    }
 }
diff --git 
a/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIClientTestUtils.java
 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIClientTestUtils.java
new file mode 100644
index 00000000..6347d63f
--- /dev/null
+++ 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIClientTestUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.agents.integrations.chatmodels.openai;
+
+import com.openai.client.OpenAIClient;
+import com.openai.core.ClientOptions;
+
+import java.lang.reflect.Field;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Test helpers for inspecting the timeout configured in openai-java. */
+final class OpenAIClientTestUtils {
+
+    private OpenAIClientTestUtils() {}
+
+    static void assertNoTimeoutConfigured(Object connection) {
+        OpenAIClient client = readField(connection, "client", 
OpenAIClient.class);
+        ClientOptions options = readField(client, "clientOptions", 
ClientOptions.class);
+
+        // A zero duration maps to an unlimited timeout in each OkHttp timeout 
component.
+        assertThat(options.timeout().connect()).isZero();
+        assertThat(options.timeout().read()).isZero();
+        assertThat(options.timeout().write()).isZero();
+        assertThat(options.timeout().request()).isZero();
+    }
+
+    private static <T> T readField(Object target, String name, Class<T> type) {
+        try {
+            Field field = target.getClass().getDeclaredField(name);
+            field.setAccessible(true);
+            return type.cast(field.get(target));
+        } catch (ReflectiveOperationException e) {
+            throw new AssertionError("Unable to inspect openai-java client 
configuration", e);
+        }
+    }
+}
diff --git 
a/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnectionTest.java
 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnectionTest.java
index be7a966e..1c5a4d15 100644
--- 
a/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnectionTest.java
+++ 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnectionTest.java
@@ -22,6 +22,7 @@ import com.openai.models.ResponseFormatJsonSchema;
 import com.openai.models.chat.completions.ChatCompletionCreateParams;
 import org.apache.flink.agents.api.chat.messages.ChatMessage;
 import org.apache.flink.agents.api.chat.messages.MessageRole;
+import org.apache.flink.agents.api.chat.model.BaseChatModelConnection;
 import org.apache.flink.agents.api.resource.ResourceContext;
 import org.apache.flink.agents.api.resource.ResourceDescriptor;
 import org.apache.flink.agents.api.tools.Tool;
@@ -32,11 +33,13 @@ import org.apache.flink.agents.api.tools.ToolType;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
+import java.time.Duration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /**
  * Unit tests for {@link OpenAICompletionsConnection}'s native 
structured-output behavior. These
@@ -72,6 +75,30 @@ class OpenAICompletionsConnectionTest {
         return List.of(new ChatMessage(MessageRole.USER, "hi"));
     }
 
+    @Test
+    void testConnectionArgumentValidation() {
+        ResourceDescriptor missingKey =
+                
ResourceDescriptor.Builder.newBuilder(OpenAICompletionsConnection.class.getName())
+                        .build();
+        assertThatThrownBy(() -> new OpenAICompletionsConnection(missingKey, 
NOOP))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("api_key");
+
+        OpenAICompletionsConnection connection =
+                new OpenAICompletionsConnection(
+                        ResourceDescriptor.Builder.newBuilder(
+                                        
OpenAICompletionsConnection.class.getName())
+                                .addInitialArgument("api_key", "test-key")
+                                .addInitialArgument("timeout", 0)
+                                .addInitialArgument("max_retries", 0)
+                                .build(),
+                        NOOP);
+        assertThat(connection).isInstanceOf(BaseChatModelConnection.class);
+        assertThat(connection.getTimeout()).isEqualTo(Duration.ZERO);
+        assertThat(connection.getMaxRetries()).isZero();
+        OpenAIClientTestUtils.assertNoTimeoutConfigured(connection);
+    }
+
     @Test
     @DisplayName("Native response_format json_schema strict applied for a POJO 
on a capable model")
     void testNativeAppliedForPojoCapableModel() {
diff --git 
a/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIResponsesModelConnectionTest.java
 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIResponsesModelConnectionTest.java
new file mode 100644
index 00000000..d5f0641c
--- /dev/null
+++ 
b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIResponsesModelConnectionTest.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.flink.agents.integrations.chatmodels.openai;
+
+import org.apache.flink.agents.api.chat.model.BaseChatModelConnection;
+import org.apache.flink.agents.api.resource.ResourceContext;
+import org.apache.flink.agents.api.resource.ResourceDescriptor;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.time.Duration;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Unit tests for {@link OpenAIResponsesModelConnection} — constructor 
validation and default
+ * resolution only, no network access.
+ */
+class OpenAIResponsesModelConnectionTest {
+
+    private static final ResourceContext NOOP = 
ResourceContext.fromGetResource((a, b) -> null);
+
+    private static ResourceDescriptor.Builder connectionDescriptor() {
+        return ResourceDescriptor.Builder.newBuilder(
+                OpenAIResponsesModelConnection.class.getName());
+    }
+
+    @Test
+    @DisplayName("Constructor throws when api_key is missing")
+    void testConstructorMissingApiKey() {
+        ResourceDescriptor desc = connectionDescriptor().build();
+        assertThatThrownBy(() -> new OpenAIResponsesModelConnection(desc, 
NOOP))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("api_key");
+    }
+
+    @Test
+    @DisplayName("Constructor succeeds with api_key only (no network call)")
+    void testConstructorMinimal() {
+        ResourceDescriptor desc =
+                connectionDescriptor().addInitialArgument("api_key", 
"test-key").build();
+        OpenAIResponsesModelConnection conn = new 
OpenAIResponsesModelConnection(desc, NOOP);
+        assertThat(conn).isInstanceOf(BaseChatModelConnection.class);
+    }
+
+    @Test
+    @DisplayName("Defaults resolve to timeout=60 and max_retries=3 when not 
specified")
+    void testDefaultTimeoutAndMaxRetries() {
+        ResourceDescriptor desc =
+                connectionDescriptor().addInitialArgument("api_key", 
"test-key").build();
+        OpenAIResponsesModelConnection conn = new 
OpenAIResponsesModelConnection(desc, NOOP);
+
+        assertThat(conn.getTimeout())
+                
.isEqualTo(Duration.ofSeconds(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS));
+        
assertThat(conn.getMaxRetries()).isEqualTo(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES);
+    }
+
+    @Test
+    @DisplayName("Explicit timeout and max_retries override the defaults")
+    void testExplicitOverrides() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("timeout", 120)
+                        .addInitialArgument("max_retries", 5)
+                        .build();
+        OpenAIResponsesModelConnection conn = new 
OpenAIResponsesModelConnection(desc, NOOP);
+
+        assertThat(conn.getTimeout()).isEqualTo(Duration.ofSeconds(120));
+        assertThat(conn.getMaxRetries()).isEqualTo(5);
+    }
+
+    @Test
+    @DisplayName("Negative timeout throws IllegalArgumentException")
+    void testNegativeTimeoutThrows() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("timeout", -5)
+                        .build();
+        assertThatThrownBy(() -> new OpenAIResponsesModelConnection(desc, 
NOOP))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("timeout");
+    }
+
+    @Test
+    @DisplayName("Negative max_retries throws IllegalArgumentException")
+    void testNegativeMaxRetriesThrows() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("max_retries", -1)
+                        .build();
+        assertThatThrownBy(() -> new OpenAIResponsesModelConnection(desc, 
NOOP))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("max_retries");
+    }
+
+    @Test
+    @DisplayName("Negative fractional timeout throws instead of truncating to 
zero")
+    void testNegativeFractionalTimeoutThrows() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("timeout", -0.5)
+                        .build();
+        assertThatThrownBy(() -> new OpenAIResponsesModelConnection(desc, 
NOOP))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("timeout");
+    }
+
+    @Test
+    @DisplayName("Fractional max_retries throws instead of truncating")
+    void testFractionalMaxRetriesThrows() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("max_retries", 2.5)
+                        .build();
+        assertThatThrownBy(() -> new OpenAIResponsesModelConnection(desc, 
NOOP))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("max_retries");
+    }
+
+    @Test
+    @DisplayName("Negative fractional max_retries throws instead of truncating 
to zero")
+    void testNegativeFractionalMaxRetriesThrows() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("max_retries", -0.5)
+                        .build();
+        assertThatThrownBy(() -> new OpenAIResponsesModelConnection(desc, 
NOOP))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("max_retries");
+    }
+
+    @Test
+    @DisplayName("max_retries beyond int range throws instead of overflowing")
+    void testOverflowMaxRetriesThrows() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("max_retries", 4294967296L)
+                        .build();
+        assertThatThrownBy(() -> new OpenAIResponsesModelConnection(desc, 
NOOP))
+                .isInstanceOf(IllegalArgumentException.class)
+                .hasMessageContaining("max_retries");
+    }
+
+    @Test
+    @DisplayName("Zero timeout disables the effective SDK timeout")
+    void testZeroTimeoutDisablesSdkTimeout() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("timeout", 0)
+                        .build();
+        OpenAIResponsesModelConnection conn = new 
OpenAIResponsesModelConnection(desc, NOOP);
+        assertThat(conn.getTimeout()).isEqualTo(Duration.ZERO);
+        OpenAIClientTestUtils.assertNoTimeoutConfigured(conn);
+    }
+
+    @Test
+    @DisplayName("Sub-millisecond timeout rounds up to the SDK precision")
+    void testSubMillisecondTimeoutRoundsUpToSdkPrecision() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("timeout", 0.0001)
+                        .build();
+        OpenAIResponsesModelConnection conn = new 
OpenAIResponsesModelConnection(desc, NOOP);
+        assertThat(conn.getTimeout()).isEqualTo(Duration.ofMillis(1));
+    }
+
+    @Test
+    @DisplayName("Zero max_retries is accepted as valid")
+    void testZeroMaxRetriesAccepted() {
+        ResourceDescriptor desc =
+                connectionDescriptor()
+                        .addInitialArgument("api_key", "test-key")
+                        .addInitialArgument("max_retries", 0)
+                        .build();
+        OpenAIResponsesModelConnection conn = new 
OpenAIResponsesModelConnection(desc, NOOP);
+        assertThat(conn.getMaxRetries()).isEqualTo(0);
+    }
+}
diff --git 
a/python/flink_agents/integrations/chat_models/azure/azure_openai_chat_model.py 
b/python/flink_agents/integrations/chat_models/azure/azure_openai_chat_model.py
index 422d9d09..50884bf6 100644
--- 
a/python/flink_agents/integrations/chat_models/azure/azure_openai_chat_model.py
+++ 
b/python/flink_agents/integrations/chat_models/azure/azure_openai_chat_model.py
@@ -43,6 +43,8 @@ from 
flink_agents.integrations.chat_models.openai.openai_utils import (
 )
 
 logger = logging.getLogger(__name__)
+MAX_OPENAI_TIMEOUT_SECONDS = 2_147_483.647
+MAX_OPENAI_RETRIES = 2_147_483_647
 
 _RESERVED_KWARG_KEYS = frozenset(
     {"model", "model_of_azure_deployment", "temperature", "max_tokens", 
"logprobs"}
@@ -144,13 +146,16 @@ class 
AzureOpenAIChatModelConnection(BaseChatModelConnection):
     )
     timeout: float = Field(
         default=60.0,
-        description="The number of seconds to wait for an API call before it 
times out.",
+        description="The number of seconds to wait for an API call before it 
times out. Set to 0 to disable timeouts.",
         ge=0,
+        le=MAX_OPENAI_TIMEOUT_SECONDS,
+        allow_inf_nan=False,
     )
     max_retries: int = Field(
         default=3,
         description="The number of times to retry the API call upon failure.",
         ge=0,
+        le=MAX_OPENAI_RETRIES,
     )
 
     def __init__(
@@ -183,7 +188,8 @@ class 
AzureOpenAIChatModelConnection(BaseChatModelConnection):
                 azure_endpoint=self.azure_endpoint,
                 api_key=self.api_key,
                 api_version=self.api_version,
-                timeout=self.timeout,
+                # Match Java's Duration.ZERO: None avoids an immediate timeout 
in httpx.
+                timeout=None if self.timeout == 0 else self.timeout,
                 max_retries=self.max_retries,
             )
         return self._client
diff --git 
a/python/flink_agents/integrations/chat_models/azure/tests/test_azure_openai_chat_model.py
 
b/python/flink_agents/integrations/chat_models/azure/tests/test_azure_openai_chat_model.py
index 983bbb47..0f1c99fb 100644
--- 
a/python/flink_agents/integrations/chat_models/azure/tests/test_azure_openai_chat_model.py
+++ 
b/python/flink_agents/integrations/chat_models/azure/tests/test_azure_openai_chat_model.py
@@ -15,15 +15,19 @@
 #  See the License for the specific language governing permissions and
 # limitations under the License.
 
#################################################################################
+import math
 import os
 from unittest.mock import MagicMock
 
 import pytest
+from pydantic import ValidationError
 
 from flink_agents.api.chat_message import ChatMessage, MessageRole
 from flink_agents.api.resource import Resource, ResourceType
 from flink_agents.api.resource_context import ResourceContext
 from flink_agents.integrations.chat_models.azure.azure_openai_chat_model 
import (
+    MAX_OPENAI_RETRIES,
+    MAX_OPENAI_TIMEOUT_SECONDS,
     AzureOpenAIChatModelConnection,
     AzureOpenAIChatModelSetup,
 )
@@ -131,6 +135,55 @@ def test_model_field_roundtrip() -> None:
     assert restored.model == "test-deployment"
 
 
+def test_zero_timeout_disables_client_timeout() -> None:
+    """Keep zero-timeout semantics aligned with the Java OpenAI SDK."""
+    conn = AzureOpenAIChatModelConnection(
+        api_key="fake-key",
+        azure_endpoint="https://example.openai.azure.com";,
+        api_version="2024-02-01",
+        timeout=0,
+    )
+
+    assert conn.client.timeout is None
+    # openai>=3 wraps timeouts in its own Timeout class, so compare
+    # components instead of instances.
+    transport_timeout = conn.client._client.timeout
+    assert transport_timeout.connect is None
+    assert transport_timeout.read is None
+    assert transport_timeout.write is None
+    assert transport_timeout.pool is None
+
+
[email protected]("timeout", [math.nan, math.inf])
+def test_connection_rejects_non_finite_timeout(timeout: float) -> None:
+    with pytest.raises(ValidationError, match="finite"):
+        AzureOpenAIChatModelConnection(
+            api_key="fake-key",
+            azure_endpoint="https://example.openai.azure.com";,
+            api_version="2024-02-01",
+            timeout=timeout,
+        )
+
+
[email protected](
+    ("argument", "value"),
+    [
+        ("timeout", MAX_OPENAI_TIMEOUT_SECONDS + 0.001),
+        ("max_retries", MAX_OPENAI_RETRIES + 1),
+    ],
+)
+def test_connection_rejects_values_beyond_java_sdk_limits(
+    argument: str, value: float | int
+) -> None:
+    with pytest.raises(ValidationError, match="less than or equal"):
+        AzureOpenAIChatModelConnection(
+            api_key="fake-key",
+            azure_endpoint="https://example.openai.azure.com";,
+            api_version="2024-02-01",
+            **{argument: value},
+        )
+
+
 def test_model_kwargs_nests_additional_kwargs() -> None:
     """`additional_kwargs` is nested under its own key, not flattened.
 
diff --git 
a/python/flink_agents/integrations/chat_models/openai/openai_chat_model.py 
b/python/flink_agents/integrations/chat_models/openai/openai_chat_model.py
index 381098b0..e810613f 100644
--- a/python/flink_agents/integrations/chat_models/openai/openai_chat_model.py
+++ b/python/flink_agents/integrations/chat_models/openai/openai_chat_model.py
@@ -43,6 +43,8 @@ from 
flink_agents.integrations.chat_models.openai.openai_utils import (
 )
 
 DEFAULT_OPENAI_MODEL = "gpt-4o-mini"
+MAX_OPENAI_TIMEOUT_SECONDS = 2_147_483.647
+MAX_OPENAI_RETRIES = 2_147_483_647
 
 # Models with documented json_schema strict Structured Outputs support. Source 
of
 # truth: https://platform.openai.com/docs/guides/structured-outputs
@@ -129,11 +131,14 @@ class OpenAIChatModelConnection(BaseChatModelConnection):
         default=3,
         description="The maximum number of API retries.",
         ge=0,
+        le=MAX_OPENAI_RETRIES,
     )
     timeout: float = Field(
         default=60.0,
-        description="The timeout, in seconds, for API requests.",
+        description="The timeout, in seconds, for API requests. Set to 0 to 
disable timeouts.",
         ge=0,
+        le=MAX_OPENAI_TIMEOUT_SECONDS,
+        allow_inf_nan=False,
     )
     default_headers: Dict[str, str] | None = Field(
         default=None, description="The default headers for API requests."
@@ -177,6 +182,8 @@ class OpenAIChatModelConnection(BaseChatModelConnection):
 
         self._http_client = http_client
         self._async_http_client = async_http_client
+        if self.timeout == 0 and self._http_client is not None:
+            self._http_client.timeout = httpx.Timeout(None)
 
     @property
     def client(self) -> OpenAI:
@@ -195,7 +202,7 @@ class OpenAIChatModelConnection(BaseChatModelConnection):
             "api_key": self.api_key,
             "base_url": self.api_base_url,
             "max_retries": self.max_retries,
-            "timeout": self.timeout,
+            "timeout": None if self.timeout == 0 else self.timeout,
             "default_headers": self.default_headers,
             "http_client": self._http_client,
         }
diff --git 
a/python/flink_agents/integrations/chat_models/openai/tests/test_openai_chat_model.py
 
b/python/flink_agents/integrations/chat_models/openai/tests/test_openai_chat_model.py
index ff4d2bb3..6eae8273 100644
--- 
a/python/flink_agents/integrations/chat_models/openai/tests/test_openai_chat_model.py
+++ 
b/python/flink_agents/integrations/chat_models/openai/tests/test_openai_chat_model.py
@@ -15,16 +15,21 @@
 #  See the License for the specific language governing permissions and
 # limitations under the License.
 
#################################################################################
+import math
 import os
 from unittest.mock import MagicMock
 
+import httpx
 import pytest
+from pydantic import ValidationError
 
 from flink_agents.api.chat_message import ChatMessage, MessageRole
 from flink_agents.api.resource import Resource, ResourceType
 from flink_agents.api.resource_context import ResourceContext
 from flink_agents.integrations.chat_models.openai.openai_chat_model import (
     DEFAULT_OPENAI_MODEL,
+    MAX_OPENAI_RETRIES,
+    MAX_OPENAI_TIMEOUT_SECONDS,
     OpenAIChatModelConnection,
     OpenAIChatModelSetup,
 )
@@ -121,3 +126,74 @@ def test_default_model_when_omitted() -> None:
     """Verify per-integration default applies when `model` is omitted from 
__init__."""
     setup = OpenAIChatModelSetup(connection="conn")
     assert setup.model == DEFAULT_OPENAI_MODEL
+
+
+def test_connection_default_timeout_and_max_retries() -> None:
+    """Pin canonical connection defaults to prevent silent drift."""
+    conn = OpenAIChatModelConnection(
+        name="test", api_key="fake", api_base_url="http://localhost";
+    )
+    assert conn.timeout == 60.0
+    assert conn.max_retries == 3
+
+
+def test_zero_timeout_disables_client_timeout() -> None:
+    """Keep zero-timeout semantics aligned with the Java OpenAI SDK."""
+    conn = OpenAIChatModelConnection(
+        name="test", api_key="fake", api_base_url="http://localhost";, timeout=0
+    )
+
+    assert conn.client.timeout is None
+    # openai>=3 wraps timeouts in its own Timeout class, so compare
+    # components instead of instances.
+    transport_timeout = conn.client._client.timeout
+    assert transport_timeout.connect is None
+    assert transport_timeout.read is None
+    assert transport_timeout.write is None
+    assert transport_timeout.pool is None
+
+
+def test_zero_timeout_disables_custom_http_client_timeout() -> None:
+    http_client = httpx.Client(timeout=10.0)
+    conn = OpenAIChatModelConnection(
+        name="test",
+        api_key="fake",
+        api_base_url="http://localhost";,
+        timeout=0,
+        http_client=http_client,
+    )
+
+    assert conn.client.timeout is None
+    assert http_client.timeout == httpx.Timeout(None)
+
+    http_client.close()
+
+
[email protected]("timeout", [math.nan, math.inf])
+def test_connection_rejects_non_finite_timeout(timeout: float) -> None:
+    with pytest.raises(ValidationError, match="finite"):
+        OpenAIChatModelConnection(
+            name="test",
+            api_key="fake",
+            api_base_url="http://localhost";,
+            timeout=timeout,
+        )
+
+
[email protected](
+    ("argument", "value"),
+    [
+        ("timeout", MAX_OPENAI_TIMEOUT_SECONDS + 0.001),
+        ("max_retries", MAX_OPENAI_RETRIES + 1),
+    ],
+)
+def test_connection_rejects_values_beyond_java_sdk_limits(
+    argument: str, value: float | int
+) -> None:
+    with pytest.raises(ValidationError, match="less than or equal"):
+        OpenAIChatModelConnection(
+            name="test",
+            api_key="fake",
+            api_base_url="http://localhost";,
+            **{argument: value},
+        )

Reply via email to