Skip to content

Commit 27f3cf5

Browse files
committed
perf(azure-openai): support HTTP client timeout configuration
Signed-off-by: yinh <[email protected]>
1 parent 29eb951 commit 27f3cf5

File tree

4 files changed

+140
-11
lines changed

4 files changed

+140
-11
lines changed

auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderConfiguration.java

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import com.azure.ai.openai.OpenAIClientBuilder;
2424
import com.azure.core.credential.AzureKeyCredential;
2525
import com.azure.core.credential.KeyCredential;
26-
import com.azure.core.util.ClientOptions;
2726
import com.azure.core.util.Header;
27+
import com.azure.core.util.HttpClientOptions;
2828
import com.azure.identity.DefaultAzureCredentialBuilder;
2929

3030
import org.springframework.beans.factory.ObjectProvider;
@@ -56,23 +56,17 @@ public OpenAIClientBuilder openAIClientBuilder(AzureOpenAiConnectionProperties c
5656

5757
final OpenAIClientBuilder clientBuilder;
5858

59+
HttpClientOptions clientOptions = createHttpClientOptions(connectionProperties);
60+
5961
// Connect to OpenAI (e.g. not the Azure OpenAI). The deploymentName property is
6062
// used as OpenAI model name.
6163
if (StringUtils.hasText(connectionProperties.getOpenAiApiKey())) {
6264
clientBuilder = new OpenAIClientBuilder().endpoint("https://api.openai.com/v1")
6365
.credential(new KeyCredential(connectionProperties.getOpenAiApiKey()))
64-
.clientOptions(new ClientOptions().setApplicationId(APPLICATION_ID));
66+
.clientOptions(clientOptions);
6567
applyOpenAIClientBuilderCustomizers(clientBuilder, customizers);
6668
return clientBuilder;
6769
}
68-
69-
Map<String, String> customHeaders = connectionProperties.getCustomHeaders();
70-
List<Header> headers = customHeaders.entrySet()
71-
.stream()
72-
.map(entry -> new Header(entry.getKey(), entry.getValue()))
73-
.collect(Collectors.toList());
74-
ClientOptions clientOptions = new ClientOptions().setApplicationId(APPLICATION_ID).setHeaders(headers);
75-
7670
Assert.hasText(connectionProperties.getEndpoint(), "Endpoint must not be empty");
7771

7872
if (!StringUtils.hasText(connectionProperties.getApiKey())) {
@@ -96,4 +90,44 @@ private void applyOpenAIClientBuilderCustomizers(OpenAIClientBuilder clientBuild
9690
customizers.orderedStream().forEach(customizer -> customizer.customize(clientBuilder));
9791
}
9892

93+
/**
94+
* Create HttpClientOptions
95+
*/
96+
private HttpClientOptions createHttpClientOptions(AzureOpenAiConnectionProperties connectionProperties) {
97+
// Create HttpClientOptions and apply the configuration
98+
HttpClientOptions options = new HttpClientOptions();
99+
100+
options.setApplicationId(APPLICATION_ID);
101+
102+
Map<String, String> customHeaders = connectionProperties.getCustomHeaders();
103+
List<Header> headers = customHeaders.entrySet()
104+
.stream()
105+
.map(entry -> new Header(entry.getKey(), entry.getValue()))
106+
.collect(Collectors.toList());
107+
108+
options.setHeaders(headers);
109+
110+
if (connectionProperties.getConnectTimeout() != null) {
111+
options.setConnectTimeout(connectionProperties.getConnectTimeout());
112+
}
113+
114+
if (connectionProperties.getReadTimeout() != null) {
115+
options.setReadTimeout(connectionProperties.getReadTimeout());
116+
}
117+
118+
if (connectionProperties.getWriteTimeout() != null) {
119+
options.setWriteTimeout(connectionProperties.getWriteTimeout());
120+
}
121+
122+
if (connectionProperties.getResponseTimeout() != null) {
123+
options.setResponseTimeout(connectionProperties.getResponseTimeout());
124+
}
125+
126+
if (connectionProperties.getMaximumConnectionPoolSize() != null) {
127+
options.setMaximumConnectionPoolSize(connectionProperties.getMaximumConnectionPoolSize());
128+
}
129+
130+
return options;
131+
}
132+
99133
}

auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiConnectionProperties.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.ai.model.azure.openai.autoconfigure;
1818

19+
import java.time.Duration;
1920
import java.util.HashMap;
2021
import java.util.Map;
2122

@@ -46,6 +47,31 @@ public class AzureOpenAiConnectionProperties {
4647

4748
private Map<String, String> customHeaders = new HashMap<>();
4849

50+
/**
51+
* HTTP connection timeout
52+
*/
53+
private Duration connectTimeout;
54+
55+
/**
56+
* HTTP read timeout
57+
*/
58+
private Duration readTimeout;
59+
60+
/**
61+
* HTTP write timeout
62+
*/
63+
private Duration writeTimeout;
64+
65+
/**
66+
* HTTP response timeout
67+
*/
68+
private Duration responseTimeout;
69+
70+
/**
71+
* The maximum number of connections in the HTTP connection pool
72+
*/
73+
private Integer maximumConnectionPoolSize;
74+
4975
public String getEndpoint() {
5076
return this.endpoint;
5177
}
@@ -78,4 +104,44 @@ public void setCustomHeaders(Map<String, String> customHeaders) {
78104
this.customHeaders = customHeaders;
79105
}
80106

107+
public Duration getConnectTimeout() {
108+
return this.connectTimeout;
109+
}
110+
111+
public void setConnectTimeout(Duration connectTimeout) {
112+
this.connectTimeout = connectTimeout;
113+
}
114+
115+
public Duration getReadTimeout() {
116+
return this.readTimeout;
117+
}
118+
119+
public void setReadTimeout(Duration readTimeout) {
120+
this.readTimeout = readTimeout;
121+
}
122+
123+
public Duration getWriteTimeout() {
124+
return this.writeTimeout;
125+
}
126+
127+
public void setWriteTimeout(Duration writeTimeout) {
128+
this.writeTimeout = writeTimeout;
129+
}
130+
131+
public Duration getResponseTimeout() {
132+
return this.responseTimeout;
133+
}
134+
135+
public void setResponseTimeout(Duration responseTimeout) {
136+
this.responseTimeout = responseTimeout;
137+
}
138+
139+
public Integer getMaximumConnectionPoolSize() {
140+
return this.maximumConnectionPoolSize;
141+
}
142+
143+
public void setMaximumConnectionPoolSize(Integer maximumConnectionPoolSize) {
144+
this.maximumConnectionPoolSize = maximumConnectionPoolSize;
145+
}
146+
81147
}

auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationIT.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.springframework.util.ReflectionUtils;
5555

5656
import static org.assertj.core.api.Assertions.assertThat;
57+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5758

5859
/**
5960
* @author Christian Tzolov
@@ -288,4 +289,18 @@ void openAIClientBuilderCustomizer() {
288289
});
289290
}
290291

292+
@Test
293+
void connectTimeoutShouldTakeEffect() {
294+
new ApplicationContextRunner().withPropertyValues(
295+
"spring.ai.azure.openai.connect-timeout=1ms"
296+
)
297+
.withConfiguration(SpringAiTestAutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
298+
.run(context -> {
299+
AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class);
300+
301+
assertThatThrownBy(() -> chatModel.call(new Prompt("Hello")))
302+
.isInstanceOf(Exception.class);
303+
});
304+
}
305+
291306
}

auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationPropertyTests.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.ai.model.azure.openai.autoconfigure;
1818

19+
import java.time.Duration;
20+
1921
import org.junit.jupiter.api.Test;
2022

2123
import org.springframework.ai.utils.SpringAiTestAutoConfigurations;
@@ -66,7 +68,13 @@ public void chatPropertiesTest() {
6668
"spring.ai.azure.openai.chat.options.stop=boza,koza",
6769
"spring.ai.azure.openai.chat.options.temperature=0.55",
6870
"spring.ai.azure.openai.chat.options.topP=0.56",
69-
"spring.ai.azure.openai.chat.options.user=userXYZ"
71+
"spring.ai.azure.openai.chat.options.user=userXYZ",
72+
73+
"spring.ai.azure.openai.connect-timeout=10s",
74+
"spring.ai.azure.openai.read-timeout=30s",
75+
"spring.ai.azure.openai.write-timeout=30s",
76+
"spring.ai.azure.openai.response-timeout=60s",
77+
"spring.ai.azure.openai.maximum-connection-pool-size=50"
7078
)
7179
// @formatter:on
7280
.withConfiguration(SpringAiTestAutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class,
@@ -91,6 +99,12 @@ public void chatPropertiesTest() {
9199
assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55);
92100
assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56);
93101

102+
assertThat(connectionProperties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(10));
103+
assertThat(connectionProperties.getReadTimeout()).isEqualTo(Duration.ofSeconds(30));
104+
assertThat(connectionProperties.getWriteTimeout()).isEqualTo(Duration.ofSeconds(30));
105+
assertThat(connectionProperties.getResponseTimeout()).isEqualTo(Duration.ofSeconds(60));
106+
assertThat(connectionProperties.getMaximumConnectionPoolSize()).isEqualTo(50);
107+
94108
assertThat(chatProperties.getOptions().getUser()).isEqualTo("userXYZ");
95109
});
96110
}

0 commit comments

Comments
 (0)