From fca1fcbe28cae668e89f7ebcf797b47434751be5 Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Tue, 7 Jul 2026 13:40:57 -0700 Subject: [PATCH 01/16] feat: add shared timeout/max_retries default constants --- .../chatmodels/openai/OpenAIChatCompletionsUtils.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 c9d8c8d9b..8f4a7ca6d 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 @@ -52,6 +52,12 @@ final class OpenAIChatCompletionsUtils { private OpenAIChatCompletionsUtils() {} + /** 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 static final ObjectMapper mapper = new ObjectMapper(); private static final TypeReference> MAP_TYPE = new TypeReference<>() {}; From 566b80a09c0f783ad405cbc3437efc098ae1b43a Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Tue, 7 Jul 2026 13:43:38 -0700 Subject: [PATCH 02/16] fix: use explicit timeout/max_retries in OpenAIConnection --- .../openai/OpenAICompletionsConnection.java | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) 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 29d0dcf78..11b0a2ada 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 @@ -43,6 +43,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; /** * A chat model integration for the OpenAI Chat Completions service using the official Java SDK. @@ -53,8 +54,8 @@ *
  • api_key (required): OpenAI API key *
  • api_base_url (optional): Base URL for OpenAI API (defaults to * https://api.openai.com/v1) - *
  • timeout (optional): Timeout in seconds for API requests - *
  • max_retries (optional): Maximum number of retry attempts (default: 2) + *
  • timeout (optional): Timeout in seconds for API requests (default: 60) + *
  • max_retries (optional): Maximum number of retry attempts (default: 3) *
  • default_headers (optional): Map of default headers to include in all requests *
  • model (optional): Default model to use if not specified in setup * @@ -81,6 +82,8 @@ public class OpenAICompletionsConnection extends BaseChatModelConnection { private static final ObjectMapper mapper = new ObjectMapper(); private final OpenAIClient client; private final String defaultModel; + private final int timeoutSeconds; + private final int maxRetries; public OpenAICompletionsConnection( ResourceDescriptor descriptor, ResourceContext resourceContext) { @@ -98,14 +101,20 @@ public OpenAICompletionsConnection( builder.baseUrl(apiBaseUrl); } - Integer timeoutSeconds = descriptor.getArgument("timeout"); - if (timeoutSeconds != null && timeoutSeconds > 0) { - builder.timeout(Duration.ofSeconds(timeoutSeconds)); + this.timeoutSeconds = + Optional.ofNullable(descriptor.getArgument("timeout")) + .map(Number::intValue) + .orElse(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); + if (this.timeoutSeconds > 0) { + builder.timeout(Duration.ofSeconds(this.timeoutSeconds)); } - Integer maxRetries = descriptor.getArgument("max_retries"); - if (maxRetries != null && maxRetries >= 0) { - builder.maxRetries(maxRetries); + this.maxRetries = + Optional.ofNullable(descriptor.getArgument("max_retries")) + .map(Number::intValue) + .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); + if (this.maxRetries >= 0) { + builder.maxRetries(this.maxRetries); } Map defaultHeaders = descriptor.getArgument("default_headers"); @@ -119,6 +128,16 @@ public OpenAICompletionsConnection( this.client = builder.build(); } + // visible for testing + int getTimeoutSeconds() { + return timeoutSeconds; + } + + // visible for testing + int getMaxRetries() { + return maxRetries; + } + @Override public ChatMessage chat( List messages, List tools, Map modelParams) { From f4063637d4330e28514ebbd524c902cacf0dc66b Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Tue, 7 Jul 2026 13:44:43 -0700 Subject: [PATCH 03/16] fix: use explicit timeout/max_retries in AzureConnection --- .../AzureOpenAIChatModelConnection.java | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) 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 8626b1b80..1a0b81a6c 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 @@ -46,6 +46,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; /** @@ -66,10 +67,9 @@ *

    Optional connection arguments: * *

      - *
    • timeout (Number): seconds before an API call times out; must be greater than 0, - * otherwise ignored (SDK default applies) - *
    • max_retries (Number): retry attempts on failure; must be non-negative, otherwise - * ignored (SDK default applies) + *
    • timeout (Number): seconds before an API call times out; must be greater than 0 + * (default: 60) + *
    • max_retries (Number): retry attempts on failure; must be non-negative (default: 3) *
    • azure_url_path_mode (String): one of {@code "AUTO"}, {@code "LEGACY"}, or {@code * "UNIFIED"} (default {@code "AUTO"}). Controls how the SDK constructs Azure OpenAI request * URLs. In {@code AUTO} mode the SDK only treats the endpoint as Azure when its hostname @@ -99,6 +99,8 @@ public class AzureOpenAIChatModelConnection extends BaseChatModelConnection { Set.of("model", "model_of_azure_deployment", "temperature", "max_tokens", "logprobs"); private final OpenAIClient client; + private final int timeoutSeconds; + private final int maxRetries; public AzureOpenAIChatModelConnection( ResourceDescriptor descriptor, ResourceContext resourceContext) { @@ -125,14 +127,20 @@ public AzureOpenAIChatModelConnection( .credential(AzureApiKeyCredential.create(apiKey)) .azureServiceVersion(AzureOpenAIServiceVersion.fromString(apiVersion)); - Integer timeoutSeconds = descriptor.getArgument("timeout"); - if (timeoutSeconds != null && timeoutSeconds > 0) { - clientBuilder.timeout(Duration.ofSeconds(timeoutSeconds)); + this.timeoutSeconds = + Optional.ofNullable(descriptor.getArgument("timeout")) + .map(Number::intValue) + .orElse(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); + if (this.timeoutSeconds > 0) { + clientBuilder.timeout(Duration.ofSeconds(this.timeoutSeconds)); } - Integer maxRetries = descriptor.getArgument("max_retries"); - if (maxRetries != null && maxRetries >= 0) { - clientBuilder.maxRetries(maxRetries); + this.maxRetries = + Optional.ofNullable(descriptor.getArgument("max_retries")) + .map(Number::intValue) + .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); + if (this.maxRetries >= 0) { + clientBuilder.maxRetries(this.maxRetries); } String azureUrlPathMode = descriptor.getArgument("azure_url_path_mode"); @@ -151,6 +159,16 @@ public AzureOpenAIChatModelConnection( this.client = clientBuilder.build(); } + // visible for testing + int getTimeoutSeconds() { + return timeoutSeconds; + } + + // visible for testing + int getMaxRetries() { + return maxRetries; + } + @Override public ChatMessage chat( List messages, List tools, Map modelParams) { From fb342a33e98135af0f9e8ffc3f4db04665d8512d Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Tue, 7 Jul 2026 13:45:13 -0700 Subject: [PATCH 04/16] test: add OpenAICompletionsConnectionTest --- .../OpenAICompletionsConnectionTest.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnectionTest.java 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 new file mode 100644 index 000000000..d4e1cb686 --- /dev/null +++ b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAICompletionsConnectionTest.java @@ -0,0 +1,87 @@ +/* + * 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 static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Unit tests for {@link OpenAICompletionsConnection} — constructor validation and default + * resolution only, no network access. + */ +class OpenAICompletionsConnectionTest { + + private static final ResourceContext NOOP = ResourceContext.fromGetResource((a, b) -> null); + + private static ResourceDescriptor.Builder connectionDescriptor() { + return ResourceDescriptor.Builder.newBuilder(OpenAICompletionsConnection.class.getName()); + } + + @Test + @DisplayName("Constructor throws when api_key is missing") + void testConstructorMissingApiKey() { + ResourceDescriptor desc = connectionDescriptor().build(); + assertThatThrownBy(() -> new OpenAICompletionsConnection(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(); + OpenAICompletionsConnection conn = new OpenAICompletionsConnection(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(); + OpenAICompletionsConnection conn = new OpenAICompletionsConnection(desc, NOOP); + + assertThat(conn.getTimeoutSeconds()) + .isEqualTo(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(); + OpenAICompletionsConnection conn = new OpenAICompletionsConnection(desc, NOOP); + + assertThat(conn.getTimeoutSeconds()).isEqualTo(120); + assertThat(conn.getMaxRetries()).isEqualTo(5); + } +} From 0a0ded7bbbdf08b2c5f6d09fb0ba99c509d0e1c8 Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Tue, 7 Jul 2026 13:45:32 -0700 Subject: [PATCH 05/16] test: add default assertions to AzureConnectionTest --- .../AzureOpenAIChatModelConnectionTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) 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 60a29729b..eb51ee096 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 @@ -98,6 +98,40 @@ void testConstructorAllRequiredArgs() { 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") + .addInitialArgument("api_version", "2024-02-01") + .addInitialArgument("azure_endpoint", "https://example.openai.azure.com") + .build(); + AzureOpenAIChatModelConnection conn = new AzureOpenAIChatModelConnection(desc, NOOP); + + assertThat(conn.getTimeoutSeconds()) + .isEqualTo(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); + assertThat(conn.getMaxRetries()) + .isEqualTo(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); + } + + @Test + @DisplayName("Explicit timeout and max_retries override the defaults") + void testExplicitTimeoutAndMaxRetriesOverride() { + ResourceDescriptor desc = + connectionDescriptor() + .addInitialArgument("api_key", "test-key") + .addInitialArgument("api_version", "2024-02-01") + .addInitialArgument("azure_endpoint", "https://example.openai.azure.com") + .addInitialArgument("timeout", 120) + .addInitialArgument("max_retries", 5) + .build(); + AzureOpenAIChatModelConnection conn = new AzureOpenAIChatModelConnection(desc, NOOP); + + assertThat(conn.getTimeoutSeconds()).isEqualTo(120); + assertThat(conn.getMaxRetries()).isEqualTo(5); + } + @Test @DisplayName("chat() rejects additional_kwargs that collide with reserved typed fields") void testChatRejectsReservedKeyInAdditionalKwargs() { From bd49d9b442f7e676e9a6270e4e76d3883fe761c2 Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Tue, 7 Jul 2026 13:45:52 -0700 Subject: [PATCH 06/16] test: pin Python connection defaults --- .../chat_models/openai/tests/test_openai_chat_model.py | 9 +++++++++ 1 file changed, 9 insertions(+) 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 ff4d2bb39..168e055a4 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 @@ -121,3 +121,12 @@ 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 From f54f58fb5b552ef689d6cf01b68e5067ebd0c265 Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Tue, 7 Jul 2026 13:55:10 -0700 Subject: [PATCH 07/16] fix: clamp invalid timeout/max_retries to defaults --- .../openai/AzureOpenAIChatModelConnection.java | 16 ++++++++-------- .../openai/OpenAICompletionsConnection.java | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) 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 1a0b81a6c..3c48a6b48 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 @@ -127,21 +127,21 @@ public AzureOpenAIChatModelConnection( .credential(AzureApiKeyCredential.create(apiKey)) .azureServiceVersion(AzureOpenAIServiceVersion.fromString(apiVersion)); - this.timeoutSeconds = + int rawTimeout = Optional.ofNullable(descriptor.getArgument("timeout")) .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); - if (this.timeoutSeconds > 0) { - clientBuilder.timeout(Duration.ofSeconds(this.timeoutSeconds)); - } + this.timeoutSeconds = + rawTimeout > 0 ? rawTimeout : OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS; + clientBuilder.timeout(Duration.ofSeconds(this.timeoutSeconds)); - this.maxRetries = + int rawRetries = Optional.ofNullable(descriptor.getArgument("max_retries")) .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); - if (this.maxRetries >= 0) { - clientBuilder.maxRetries(this.maxRetries); - } + this.maxRetries = + rawRetries >= 0 ? rawRetries : OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES; + clientBuilder.maxRetries(this.maxRetries); String azureUrlPathMode = descriptor.getArgument("azure_url_path_mode"); if (azureUrlPathMode != null && !azureUrlPathMode.isBlank()) { 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 11b0a2ada..fbe861365 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 @@ -101,21 +101,21 @@ public OpenAICompletionsConnection( builder.baseUrl(apiBaseUrl); } - this.timeoutSeconds = + int rawTimeout = Optional.ofNullable(descriptor.getArgument("timeout")) .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); - if (this.timeoutSeconds > 0) { - builder.timeout(Duration.ofSeconds(this.timeoutSeconds)); - } + this.timeoutSeconds = + rawTimeout > 0 ? rawTimeout : OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS; + builder.timeout(Duration.ofSeconds(this.timeoutSeconds)); - this.maxRetries = + int rawRetries = Optional.ofNullable(descriptor.getArgument("max_retries")) .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); - if (this.maxRetries >= 0) { - builder.maxRetries(this.maxRetries); - } + this.maxRetries = + rawRetries >= 0 ? rawRetries : OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES; + builder.maxRetries(this.maxRetries); Map defaultHeaders = descriptor.getArgument("default_headers"); if (defaultHeaders != null && !defaultHeaders.isEmpty()) { From 88f03da829233c0248d6c9211592217f3d56f1c7 Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Tue, 7 Jul 2026 14:36:08 -0700 Subject: [PATCH 08/16] fix: apply spotless formatting to test assertions --- .../chatmodels/openai/AzureOpenAIChatModelConnectionTest.java | 3 +-- .../chatmodels/openai/OpenAICompletionsConnectionTest.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) 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 eb51ee096..e549f5d13 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 @@ -111,8 +111,7 @@ void testDefaultTimeoutAndMaxRetries() { assertThat(conn.getTimeoutSeconds()) .isEqualTo(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); - assertThat(conn.getMaxRetries()) - .isEqualTo(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); + assertThat(conn.getMaxRetries()).isEqualTo(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); } @Test 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 d4e1cb686..250dc3bf7 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 @@ -66,8 +66,7 @@ void testDefaultTimeoutAndMaxRetries() { assertThat(conn.getTimeoutSeconds()) .isEqualTo(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); - assertThat(conn.getMaxRetries()) - .isEqualTo(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); + assertThat(conn.getMaxRetries()).isEqualTo(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); } @Test From 82eb320c7cfc214126e45c5df7b5a406f3531d0a Mon Sep 17 00:00:00 2001 From: rob-9 Date: Wed, 8 Jul 2026 23:55:43 -0700 Subject: [PATCH 09/16] docs: update Java connection default table entries --- docs/content/docs/development/chat_models.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/development/chat_models.md b/docs/content/docs/development/chat_models.md index c7b40ae05..45d19f483 100644 --- a/docs/content/docs/development/chat_models.md +++ b/docs/content/docs/development/chat_models.md @@ -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-02-01"). 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` | int | `60` | Timeout in seconds for API requests; must be greater than 0, otherwise the default is used | +| `max_retries` | int | `3` | Maximum number of API retry attempts; must be non-negative, otherwise the default is used | | `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 >}} @@ -827,8 +827,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, otherwise the default is used | +| `timeout` | int | `60` | Timeout in seconds for API requests; must be greater than 0, otherwise the default is used | | `default_headers` | Map | None | Default headers for API requests | | `model` | String | None | Default model to use if not specified in setup | From 8e6609b46c717e5bc720b97f5e9e360afaa5bd62 Mon Sep 17 00:00:00 2001 From: rob-9 Date: Thu, 9 Jul 2026 09:18:49 -0700 Subject: [PATCH 10/16] ci: retrigger checks (ollama segfault flake) From e8f894a5b5aef3251584ee4b8c42b7acd7c64c9c Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Fri, 10 Jul 2026 13:43:21 -0700 Subject: [PATCH 11/16] refactor: move timeout and max_retries constants above constructor --- .../chatmodels/openai/OpenAIChatCompletionsUtils.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 8f4a7ca6d..509356dd2 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 @@ -50,14 +50,14 @@ */ final class OpenAIChatCompletionsUtils { - private OpenAIChatCompletionsUtils() {} - - /** default timeout in seconds for openai api requests (aligned with python sdk). */ + /** 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). */ + /** Default max retries for OpenAI API requests (aligned with Python SDK). */ static final int DEFAULT_MAX_RETRIES = 3; + private OpenAIChatCompletionsUtils() {} + private static final ObjectMapper mapper = new ObjectMapper(); private static final TypeReference> MAP_TYPE = new TypeReference<>() {}; From abd88f5def7078f5dde9a06f06a56428fbc737ac Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Fri, 10 Jul 2026 13:43:27 -0700 Subject: [PATCH 12/16] fix: reject negative timeout and max_retries instead of clamping --- .../AzureOpenAIChatModelConnection.java | 17 ++++---- .../openai/OpenAICompletionsConnection.java | 15 ++++--- .../OpenAIResponsesModelConnection.java | 39 ++++++++++++++----- 3 files changed, 49 insertions(+), 22 deletions(-) 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 3c48a6b48..141f01b18 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 @@ -67,7 +67,7 @@ *

      Optional connection arguments: * *

        - *
      • timeout (Number): seconds before an API call times out; must be greater than 0 + *
      • timeout (Number): seconds before an API call times out; must be non-negative * (default: 60) *
      • max_retries (Number): retry attempts on failure; must be non-negative (default: 3) *
      • azure_url_path_mode (String): one of {@code "AUTO"}, {@code "LEGACY"}, or {@code @@ -127,20 +127,23 @@ public AzureOpenAIChatModelConnection( .credential(AzureApiKeyCredential.create(apiKey)) .azureServiceVersion(AzureOpenAIServiceVersion.fromString(apiVersion)); - int rawTimeout = + this.timeoutSeconds = Optional.ofNullable(descriptor.getArgument("timeout")) .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); - this.timeoutSeconds = - rawTimeout > 0 ? rawTimeout : OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS; + if (this.timeoutSeconds < 0) { + throw new IllegalArgumentException("timeout must be >= 0, got: " + this.timeoutSeconds); + } clientBuilder.timeout(Duration.ofSeconds(this.timeoutSeconds)); - int rawRetries = + this.maxRetries = Optional.ofNullable(descriptor.getArgument("max_retries")) .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); - this.maxRetries = - rawRetries >= 0 ? rawRetries : OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES; + if (this.maxRetries < 0) { + throw new IllegalArgumentException( + "max_retries must be >= 0, got: " + this.maxRetries); + } clientBuilder.maxRetries(this.maxRetries); String azureUrlPathMode = descriptor.getArgument("azure_url_path_mode"); 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 fbe861365..6ddc458d2 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 @@ -101,20 +101,23 @@ public OpenAICompletionsConnection( builder.baseUrl(apiBaseUrl); } - int rawTimeout = + this.timeoutSeconds = Optional.ofNullable(descriptor.getArgument("timeout")) .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); - this.timeoutSeconds = - rawTimeout > 0 ? rawTimeout : OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS; + if (this.timeoutSeconds < 0) { + throw new IllegalArgumentException("timeout must be >= 0, got: " + this.timeoutSeconds); + } builder.timeout(Duration.ofSeconds(this.timeoutSeconds)); - int rawRetries = + this.maxRetries = Optional.ofNullable(descriptor.getArgument("max_retries")) .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); - this.maxRetries = - rawRetries >= 0 ? rawRetries : OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES; + if (this.maxRetries < 0) { + throw new IllegalArgumentException( + "max_retries must be >= 0, got: " + this.maxRetries); + } builder.maxRetries(this.maxRetries); Map defaultHeaders = descriptor.getArgument("default_headers"); 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 0fcd484b4..c594622f6 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 @@ *
          *
        • api_key (required): OpenAI API key *
        • api_base_url (optional): Base URL for OpenAI API (useful for proxies) - *
        • timeout (optional): Timeout in seconds for API requests - *
        • max_retries (optional): Maximum number of retry attempts (default: 2) + *
        • timeout (optional): Timeout in seconds for API requests; must be non-negative + * (default: 60) + *
        • max_retries (optional): Maximum number of retry attempts; must be non-negative + * (default: 3) *
        • default_headers (optional): Map of default headers to include in all requests *
        • model (optional): Default model to use if not specified in setup *
        @@ -86,6 +88,8 @@ public class OpenAIResponsesModelConnection extends BaseChatModelConnection { private final OpenAIClient client; private final String defaultModel; + private final int timeoutSeconds; + private final int maxRetries; public OpenAIResponsesModelConnection( ResourceDescriptor descriptor, ResourceContext resourceContext) { @@ -103,15 +107,24 @@ public OpenAIResponsesModelConnection( builder.baseUrl(apiBaseUrl); } - Integer timeoutSeconds = descriptor.getArgument("timeout"); - if (timeoutSeconds != null && timeoutSeconds > 0) { - builder.timeout(Duration.ofSeconds(timeoutSeconds)); + this.timeoutSeconds = + Optional.ofNullable(descriptor.getArgument("timeout")) + .map(Number::intValue) + .orElse(OpenAIChatCompletionsUtils.DEFAULT_TIMEOUT_SECONDS); + if (this.timeoutSeconds < 0) { + throw new IllegalArgumentException("timeout must be >= 0, got: " + this.timeoutSeconds); } - - Integer maxRetries = descriptor.getArgument("max_retries"); - if (maxRetries != null && maxRetries >= 0) { - builder.maxRetries(maxRetries); + builder.timeout(Duration.ofSeconds(this.timeoutSeconds)); + + this.maxRetries = + Optional.ofNullable(descriptor.getArgument("max_retries")) + .map(Number::intValue) + .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); + if (this.maxRetries < 0) { + throw new IllegalArgumentException( + "max_retries must be >= 0, got: " + this.maxRetries); } + builder.maxRetries(this.maxRetries); Map defaultHeaders = descriptor.getArgument("default_headers"); if (defaultHeaders != null && !defaultHeaders.isEmpty()) { @@ -453,6 +466,14 @@ private Map toMap(Object value) { return mapper.convertValue(value, MAP_TYPE); } + int getTimeoutSeconds() { + return timeoutSeconds; + } + + int getMaxRetries() { + return maxRetries; + } + @Override public void close() throws Exception { this.client.close(); From 2d507912d4b9f54103ca9b43280986485c85f56a Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Fri, 10 Jul 2026 13:43:33 -0700 Subject: [PATCH 13/16] test: cover negative rejection and zero acceptance for timeout and max_retries --- .../AzureOpenAIChatModelConnectionTest.java | 58 +++++++++++++++++++ .../OpenAICompletionsConnectionTest.java | 50 ++++++++++++++++ 2 files changed, 108 insertions(+) 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 e549f5d13..bb5833796 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 @@ -131,6 +131,64 @@ void testExplicitTimeoutAndMaxRetriesOverride() { assertThat(conn.getMaxRetries()).isEqualTo(5); } + @Test + @DisplayName("Negative timeout throws IllegalArgumentException") + void testNegativeTimeoutThrows() { + ResourceDescriptor desc = + connectionDescriptor() + .addInitialArgument("api_key", "test-key") + .addInitialArgument("api_version", "2024-02-01") + .addInitialArgument("azure_endpoint", "https://example.openai.azure.com") + .addInitialArgument("timeout", -5) + .build(); + assertThatThrownBy(() -> new AzureOpenAIChatModelConnection(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("api_version", "2024-02-01") + .addInitialArgument("azure_endpoint", "https://example.openai.azure.com") + .addInitialArgument("max_retries", -1) + .build(); + assertThatThrownBy(() -> new AzureOpenAIChatModelConnection(desc, NOOP)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("max_retries"); + } + + @Test + @DisplayName("Zero timeout is accepted as valid") + void testZeroTimeoutAccepted() { + ResourceDescriptor desc = + connectionDescriptor() + .addInitialArgument("api_key", "test-key") + .addInitialArgument("api_version", "2024-02-01") + .addInitialArgument("azure_endpoint", "https://example.openai.azure.com") + .addInitialArgument("timeout", 0) + .build(); + AzureOpenAIChatModelConnection conn = new AzureOpenAIChatModelConnection(desc, NOOP); + assertThat(conn.getTimeoutSeconds()).isEqualTo(0); + } + + @Test + @DisplayName("Zero max_retries is accepted as valid") + void testZeroMaxRetriesAccepted() { + ResourceDescriptor desc = + connectionDescriptor() + .addInitialArgument("api_key", "test-key") + .addInitialArgument("api_version", "2024-02-01") + .addInitialArgument("azure_endpoint", "https://example.openai.azure.com") + .addInitialArgument("max_retries", 0) + .build(); + AzureOpenAIChatModelConnection conn = new AzureOpenAIChatModelConnection(desc, NOOP); + assertThat(conn.getMaxRetries()).isEqualTo(0); + } + @Test @DisplayName("chat() rejects additional_kwargs that collide with reserved typed fields") void testChatRejectsReservedKeyInAdditionalKwargs() { 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 250dc3bf7..50bad3a32 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 @@ -83,4 +83,54 @@ void testExplicitOverrides() { assertThat(conn.getTimeoutSeconds()).isEqualTo(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 OpenAICompletionsConnection(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 OpenAICompletionsConnection(desc, NOOP)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("max_retries"); + } + + @Test + @DisplayName("Zero timeout is accepted as valid") + void testZeroTimeoutAccepted() { + ResourceDescriptor desc = + connectionDescriptor() + .addInitialArgument("api_key", "test-key") + .addInitialArgument("timeout", 0) + .build(); + OpenAICompletionsConnection conn = new OpenAICompletionsConnection(desc, NOOP); + assertThat(conn.getTimeoutSeconds()).isEqualTo(0); + } + + @Test + @DisplayName("Zero max_retries is accepted as valid") + void testZeroMaxRetriesAccepted() { + ResourceDescriptor desc = + connectionDescriptor() + .addInitialArgument("api_key", "test-key") + .addInitialArgument("max_retries", 0) + .build(); + OpenAICompletionsConnection conn = new OpenAICompletionsConnection(desc, NOOP); + assertThat(conn.getMaxRetries()).isEqualTo(0); + } } From e2e7786e041e8161df04eab1732077424948e01d Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Fri, 10 Jul 2026 13:43:39 -0700 Subject: [PATCH 14/16] docs: update timeout and max_retries constraints to match validation --- docs/content/docs/development/chat_models.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/docs/development/chat_models.md b/docs/content/docs/development/chat_models.md index 45d19f483..333c6af14 100644 --- a/docs/content/docs/development/chat_models.md +++ b/docs/content/docs/development/chat_models.md @@ -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-02-01"). 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 | `60` | Timeout in seconds for API requests; must be greater than 0, otherwise the default is used | -| `max_retries` | int | `3` | Maximum number of API retry attempts; must be non-negative, otherwise the default is used | +| `timeout` | int | `60` | Timeout in seconds for API requests; must be non-negative | +| `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 >}} @@ -827,8 +827,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 | `3` | Maximum number of API retry attempts; must be non-negative, otherwise the default is used | -| `timeout` | int | `60` | Timeout in seconds for API requests; must be greater than 0, otherwise the default is used | +| `max_retries` | int | `3` | Maximum number of API retry attempts; must be non-negative | +| `timeout` | int | `60` | Timeout in seconds for API requests; must be non-negative | | `default_headers` | Map | None | Default headers for API requests | | `model` | String | None | Default model to use if not specified in setup | @@ -958,8 +958,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` | int | `60` | Timeout in seconds for API requests; must be non-negative | | `default_headers` | Map | None | Default headers for API requests | | `model` | String | None | Default model to use if not specified in setup | From 1f97bc1242b779d865fdcb76817342455d6f1e07 Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Fri, 10 Jul 2026 13:51:08 -0700 Subject: [PATCH 15/16] test: add OpenAIResponsesModelConnectionTest for validation coverage --- .../OpenAIResponsesModelConnectionTest.java | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIResponsesModelConnectionTest.java 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 000000000..9654174ee --- /dev/null +++ b/integrations/chat-models/openai/src/test/java/org/apache/flink/agents/integrations/chatmodels/openai/OpenAIResponsesModelConnectionTest.java @@ -0,0 +1,136 @@ +/* + * 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 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.getTimeoutSeconds()) + .isEqualTo(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.getTimeoutSeconds()).isEqualTo(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("Zero timeout is accepted as valid") + void testZeroTimeoutAccepted() { + ResourceDescriptor desc = + connectionDescriptor() + .addInitialArgument("api_key", "test-key") + .addInitialArgument("timeout", 0) + .build(); + OpenAIResponsesModelConnection conn = new OpenAIResponsesModelConnection(desc, NOOP); + assertThat(conn.getTimeoutSeconds()).isEqualTo(0); + } + + @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); + } +} From cf9be98c5799f31c260a6d9f4769930c4eea8606 Mon Sep 17 00:00:00 2001 From: Robert Ji Date: Fri, 10 Jul 2026 13:56:59 -0700 Subject: [PATCH 16/16] fix: apply spotless formatting --- .../chatmodels/openai/AzureOpenAIChatModelConnection.java | 3 +-- .../chatmodels/openai/OpenAICompletionsConnection.java | 3 +-- .../chatmodels/openai/OpenAIResponsesModelConnection.java | 3 +-- .../chatmodels/openai/OpenAIResponsesModelConnectionTest.java | 3 ++- 4 files changed, 5 insertions(+), 7 deletions(-) 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 141f01b18..1ccf925b8 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,8 +141,7 @@ public AzureOpenAIChatModelConnection( .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); if (this.maxRetries < 0) { - throw new IllegalArgumentException( - "max_retries must be >= 0, got: " + this.maxRetries); + throw new IllegalArgumentException("max_retries must be >= 0, got: " + this.maxRetries); } clientBuilder.maxRetries(this.maxRetries); 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 6ddc458d2..45987ac42 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 @@ -115,8 +115,7 @@ public OpenAICompletionsConnection( .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); if (this.maxRetries < 0) { - throw new IllegalArgumentException( - "max_retries must be >= 0, got: " + this.maxRetries); + throw new IllegalArgumentException("max_retries must be >= 0, got: " + this.maxRetries); } builder.maxRetries(this.maxRetries); 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 c594622f6..4d746255e 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 @@ -121,8 +121,7 @@ public OpenAIResponsesModelConnection( .map(Number::intValue) .orElse(OpenAIChatCompletionsUtils.DEFAULT_MAX_RETRIES); if (this.maxRetries < 0) { - throw new IllegalArgumentException( - "max_retries must be >= 0, got: " + this.maxRetries); + throw new IllegalArgumentException("max_retries must be >= 0, got: " + this.maxRetries); } builder.maxRetries(this.maxRetries); 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 index 9654174ee..f82393469 100644 --- 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 @@ -36,7 +36,8 @@ class OpenAIResponsesModelConnectionTest { private static final ResourceContext NOOP = ResourceContext.fromGetResource((a, b) -> null); private static ResourceDescriptor.Builder connectionDescriptor() { - return ResourceDescriptor.Builder.newBuilder(OpenAIResponsesModelConnection.class.getName()); + return ResourceDescriptor.Builder.newBuilder( + OpenAIResponsesModelConnection.class.getName()); } @Test