Skip to content

Commit 6a780cc

Browse files
committed
perf(zhipu-ai): Support HTTP client timeout configuration
Signed-off-by: yinh <[email protected]>
1 parent 1f0cdf8 commit 6a780cc

File tree

6 files changed

+195
-12
lines changed

6 files changed

+195
-12
lines changed

auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatAutoConfiguration.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@
3535
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3636
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3737
import org.springframework.boot.context.properties.EnableConfigurationProperties;
38+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
39+
import org.springframework.boot.http.client.HttpClientSettings;
40+
import org.springframework.boot.http.client.autoconfigure.HttpClientSettingsPropertyMapper;
41+
import org.springframework.boot.http.client.reactive.ClientHttpConnectorBuilder;
3842
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
43+
import org.springframework.boot.ssl.SslBundles;
3944
import org.springframework.context.annotation.Bean;
4045
import org.springframework.core.retry.RetryTemplate;
46+
import org.springframework.http.client.ClientHttpRequestFactory;
47+
import org.springframework.http.client.reactive.ClientHttpConnector;
4148
import org.springframework.util.Assert;
4249
import org.springframework.util.StringUtils;
4350
import org.springframework.web.client.ResponseErrorHandler;
@@ -65,12 +72,26 @@ public ZhiPuAiChatModel zhiPuAiChatModel(ZhiPuAiConnectionProperties commonPrope
6572
ObjectProvider<WebClient.Builder> webClientBuilderProvider, RetryTemplate retryTemplate,
6673
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
6774
ObjectProvider<ChatModelObservationConvention> observationConvention, ToolCallingManager toolCallingManager,
68-
ObjectProvider<ToolExecutionEligibilityPredicate> toolExecutionEligibilityPredicate) {
75+
ObjectProvider<ToolExecutionEligibilityPredicate> toolExecutionEligibilityPredicate,
76+
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientSettings> globalHttpClientSettings,
77+
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> factoryBuilder,
78+
ObjectProvider<ClientHttpConnectorBuilder<?>> webConnectorBuilderProvider) {
79+
80+
HttpClientSettingsPropertyMapper mapper = new HttpClientSettingsPropertyMapper(sslBundles.getIfAvailable(),
81+
globalHttpClientSettings.getIfAvailable());
82+
HttpClientSettings httpClientSettings = mapper.map(commonProperties.getHttp());
83+
84+
RestClient.Builder restClientBuilder = restClientBuilderProvider.getIfAvailable(RestClient::builder);
85+
applyRestClientSettings(restClientBuilder, httpClientSettings,
86+
factoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect));
87+
88+
WebClient.Builder webClientBuilder = webClientBuilderProvider.getIfAvailable(WebClient::builder);
89+
applyWebClientSettings(webClientBuilder, httpClientSettings,
90+
webConnectorBuilderProvider.getIfAvailable(ClientHttpConnectorBuilder::detect));
6991

7092
var zhiPuAiApi = zhiPuAiApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
71-
chatProperties.getApiKey(), commonProperties.getApiKey(),
72-
restClientBuilderProvider.getIfAvailable(RestClient::builder),
73-
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler);
93+
chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, webClientBuilder,
94+
responseErrorHandler);
7495

7596
var chatModel = new ZhiPuAiChatModel(zhiPuAiApi, chatProperties.getOptions(), toolCallingManager, retryTemplate,
7697
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
@@ -101,4 +122,16 @@ private ZhiPuAiApi zhiPuAiApi(String baseUrl, String commonBaseUrl, String apiKe
101122

102123
}
103124

125+
private void applyRestClientSettings(RestClient.Builder builder, HttpClientSettings httpClientSettings,
126+
ClientHttpRequestFactoryBuilder<?> factoryBuilder) {
127+
ClientHttpRequestFactory requestFactory = factoryBuilder.build(httpClientSettings);
128+
builder.requestFactory(requestFactory);
129+
}
130+
131+
private void applyWebClientSettings(WebClient.Builder builder, HttpClientSettings httpClientSettings,
132+
ClientHttpConnectorBuilder<?> connectorBuilder) {
133+
ClientHttpConnector connector = connectorBuilder.build(httpClientSettings);
134+
builder.clientConnector(connector);
135+
}
136+
104137
}

auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiConnectionProperties.java

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

1717
package org.springframework.ai.model.zhipuai.autoconfigure;
1818

19+
import java.time.Duration;
20+
21+
import jakarta.annotation.Nullable;
22+
1923
import org.springframework.boot.context.properties.ConfigurationProperties;
24+
import org.springframework.boot.context.properties.NestedConfigurationProperty;
25+
import org.springframework.boot.http.client.HttpRedirects;
26+
import org.springframework.boot.http.client.autoconfigure.HttpClientSettingsProperties;
2027

2128
@ConfigurationProperties(ZhiPuAiConnectionProperties.CONFIG_PREFIX)
2229
public class ZhiPuAiConnectionProperties extends ZhiPuAiParentProperties {
@@ -25,8 +32,47 @@ public class ZhiPuAiConnectionProperties extends ZhiPuAiParentProperties {
2532

2633
public static final String DEFAULT_BASE_URL = "https://open.bigmodel.cn/api/paas";
2734

35+
@NestedConfigurationProperty
36+
private final HttpClientSettingsProperties http = new HttpClientSettingsProperties() {
37+
};
38+
2839
public ZhiPuAiConnectionProperties() {
2940
super.setBaseUrl(DEFAULT_BASE_URL);
3041
}
3142

43+
@Nullable
44+
public HttpRedirects getRedirects() {
45+
return this.http.getRedirects();
46+
}
47+
48+
public void setRedirects(HttpRedirects redirects) {
49+
this.http.setRedirects(redirects);
50+
}
51+
52+
@Nullable
53+
public Duration getConnectTimeout() {
54+
return this.http.getConnectTimeout();
55+
}
56+
57+
public void setConnectTimeout(Duration connectTimeout) {
58+
this.http.setConnectTimeout(connectTimeout);
59+
}
60+
61+
@Nullable
62+
public Duration getReadTimeout() {
63+
return this.http.getReadTimeout();
64+
}
65+
66+
public void setReadTimeout(Duration readTimeout) {
67+
this.http.setReadTimeout(readTimeout);
68+
}
69+
70+
public HttpClientSettingsProperties.Ssl getSsl() {
71+
return this.http.getSsl();
72+
}
73+
74+
public HttpClientSettingsProperties getHttp() {
75+
return this.http;
76+
}
77+
3278
}

auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingAutoConfiguration.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,16 @@
3131
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3232
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3333
import org.springframework.boot.context.properties.EnableConfigurationProperties;
34+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
35+
import org.springframework.boot.http.client.HttpClientSettings;
36+
import org.springframework.boot.http.client.autoconfigure.HttpClientSettingsPropertyMapper;
37+
import org.springframework.boot.http.client.reactive.ClientHttpConnectorBuilder;
3438
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
39+
import org.springframework.boot.ssl.SslBundles;
3540
import org.springframework.context.annotation.Bean;
3641
import org.springframework.core.retry.RetryTemplate;
42+
import org.springframework.http.client.ClientHttpRequestFactory;
43+
import org.springframework.http.client.reactive.ClientHttpConnector;
3744
import org.springframework.util.Assert;
3845
import org.springframework.util.StringUtils;
3946
import org.springframework.web.client.ResponseErrorHandler;
@@ -60,12 +67,26 @@ public ZhiPuAiEmbeddingModel zhiPuAiEmbeddingModel(ZhiPuAiConnectionProperties c
6067
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
6168
ObjectProvider<WebClient.Builder> webClientBuilderProvider, RetryTemplate retryTemplate,
6269
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
63-
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
70+
ObjectProvider<EmbeddingModelObservationConvention> observationConvention,
71+
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientSettings> globalHttpClientSettings,
72+
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> factoryBuilder,
73+
ObjectProvider<ClientHttpConnectorBuilder<?>> webConnectorBuilderProvider) {
74+
75+
HttpClientSettingsPropertyMapper mapper = new HttpClientSettingsPropertyMapper(sslBundles.getIfAvailable(),
76+
globalHttpClientSettings.getIfAvailable());
77+
HttpClientSettings httpClientSettings = mapper.map(commonProperties.getHttp());
78+
79+
RestClient.Builder restClientBuilder = restClientBuilderProvider.getIfAvailable(RestClient::builder);
80+
applyRestClientSettings(restClientBuilder, httpClientSettings,
81+
factoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect));
82+
83+
WebClient.Builder webClientBuilder = webClientBuilderProvider.getIfAvailable(WebClient::builder);
84+
applyWebClientSettings(webClientBuilder, httpClientSettings,
85+
webConnectorBuilderProvider.getIfAvailable(ClientHttpConnectorBuilder::detect));
6486

6587
var zhiPuAiApi = zhiPuAiApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(),
66-
embeddingProperties.getApiKey(), commonProperties.getApiKey(),
67-
restClientBuilderProvider.getIfAvailable(RestClient::builder),
68-
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler);
88+
embeddingProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, webClientBuilder,
89+
responseErrorHandler);
6990

7091
var embeddingModel = new ZhiPuAiEmbeddingModel(zhiPuAiApi, embeddingProperties.getMetadataMode(),
7192
embeddingProperties.getOptions(), retryTemplate,
@@ -95,4 +116,16 @@ private ZhiPuAiApi zhiPuAiApi(String baseUrl, String commonBaseUrl, String apiKe
95116
.build();
96117
}
97118

119+
private void applyRestClientSettings(RestClient.Builder builder, HttpClientSettings httpClientSettings,
120+
ClientHttpRequestFactoryBuilder<?> factoryBuilder) {
121+
ClientHttpRequestFactory requestFactory = factoryBuilder.build(httpClientSettings);
122+
builder.requestFactory(requestFactory);
123+
}
124+
125+
private void applyWebClientSettings(WebClient.Builder builder, HttpClientSettings httpClientSettings,
126+
ClientHttpConnectorBuilder<?> connectorBuilder) {
127+
ClientHttpConnector connector = connectorBuilder.build(httpClientSettings);
128+
builder.clientConnector(connector);
129+
}
130+
98131
}

auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiImageAutoConfiguration.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,14 @@
2828
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2929
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3030
import org.springframework.boot.context.properties.EnableConfigurationProperties;
31+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
32+
import org.springframework.boot.http.client.HttpClientSettings;
33+
import org.springframework.boot.http.client.autoconfigure.HttpClientSettingsPropertyMapper;
3134
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
35+
import org.springframework.boot.ssl.SslBundles;
3236
import org.springframework.context.annotation.Bean;
3337
import org.springframework.core.retry.RetryTemplate;
38+
import org.springframework.http.client.ClientHttpRequestFactory;
3439
import org.springframework.util.Assert;
3540
import org.springframework.util.StringUtils;
3641
import org.springframework.web.client.ResponseErrorHandler;
@@ -53,7 +58,9 @@ public class ZhiPuAiImageAutoConfiguration {
5358
@ConditionalOnMissingBean
5459
public ZhiPuAiImageModel zhiPuAiImageModel(ZhiPuAiConnectionProperties commonProperties,
5560
ZhiPuAiImageProperties imageProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
56-
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler) {
61+
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
62+
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientSettings> globalHttpClientSettings,
63+
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> factoryBuilder) {
5764

5865
String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey()
5966
: commonProperties.getApiKey();
@@ -64,11 +71,24 @@ public ZhiPuAiImageModel zhiPuAiImageModel(ZhiPuAiConnectionProperties commonPro
6471
Assert.hasText(apiKey, "ZhiPuAI API key must be set");
6572
Assert.hasText(baseUrl, "ZhiPuAI base URL must be set");
6673

74+
HttpClientSettingsPropertyMapper mapper = new HttpClientSettingsPropertyMapper(sslBundles.getIfAvailable(),
75+
globalHttpClientSettings.getIfAvailable());
76+
HttpClientSettings httpClientSettings = mapper.map(commonProperties.getHttp());
77+
78+
RestClient.Builder restClientBuilder = restClientBuilderProvider.getIfAvailable(RestClient::builder);
79+
applyRestClientSettings(restClientBuilder, httpClientSettings,
80+
factoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect));
81+
6782
// TODO add ZhiPuAiApi support for image
68-
var zhiPuAiImageApi = new ZhiPuAiImageApi(baseUrl, apiKey,
69-
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
83+
var zhiPuAiImageApi = new ZhiPuAiImageApi(baseUrl, apiKey, restClientBuilder, responseErrorHandler);
7084

7185
return new ZhiPuAiImageModel(zhiPuAiImageApi, imageProperties.getOptions(), retryTemplate);
7286
}
7387

88+
private void applyRestClientSettings(RestClient.Builder builder, HttpClientSettings httpClientSettings,
89+
ClientHttpRequestFactoryBuilder<?> factoryBuilder) {
90+
ClientHttpRequestFactory requestFactory = factoryBuilder.build(httpClientSettings);
91+
builder.requestFactory(requestFactory);
92+
}
93+
7494
}

auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfigurationIT.java

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

1717
package org.springframework.ai.model.zhipuai.autoconfigure;
1818

19+
import java.time.Duration;
1920
import java.util.List;
21+
import java.util.Objects;
2022
import java.util.stream.Collectors;
2123

2224
import org.apache.commons.logging.Log;
@@ -112,4 +114,46 @@ void generateImage() {
112114
});
113115
}
114116

117+
@Test
118+
void generateWithCustomTimeout() {
119+
this.contextRunner
120+
.withPropertyValues("spring.ai.zhipuai.connect-timeout=1s", "spring.ai.zhipuai.read-timeout=1s")
121+
.withConfiguration(SpringAiTestAutoConfigurations.of(ZhiPuAiChatAutoConfiguration.class))
122+
.run(context -> {
123+
ZhiPuAiChatModel client = context.getBean(ZhiPuAiChatModel.class);
124+
125+
var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class);
126+
assertThat(connectionProperties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(5));
127+
assertThat(connectionProperties.getReadTimeout()).isEqualTo(Duration.ofSeconds(30));
128+
129+
String response = client.call("Hello");
130+
assertThat(response).isNotEmpty();
131+
logger.info("Response with custom timeout: " + response);
132+
});
133+
}
134+
135+
@Test
136+
void generateStreamingWithCustomTimeout() {
137+
this.contextRunner
138+
.withPropertyValues("spring.ai.zhipuai.connect-timeout=1s", "spring.ai.zhipuai.read-timeout=1s")
139+
.withConfiguration(SpringAiTestAutoConfigurations.of(ZhiPuAiChatAutoConfiguration.class))
140+
.run(context -> {
141+
ZhiPuAiChatModel client = context.getBean(ZhiPuAiChatModel.class);
142+
143+
// Verify that the HTTP client configuration is applied
144+
var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class);
145+
assertThat(connectionProperties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(1));
146+
assertThat(connectionProperties.getReadTimeout()).isEqualTo(Duration.ofSeconds(1));
147+
148+
Flux<ChatResponse> responseFlux = client.stream(new Prompt(new UserMessage("Hello")));
149+
String response = Objects.requireNonNull(responseFlux.collectList().block())
150+
.stream()
151+
.map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText())
152+
.collect(Collectors.joining());
153+
154+
assertThat(response).isNotEmpty();
155+
logger.info("Response with custom timeout: " + response);
156+
});
157+
}
158+
115159
}

auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java

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

1717
package org.springframework.ai.model.zhipuai.autoconfigure;
1818

19+
import java.time.Duration;
20+
1921
import org.junit.jupiter.api.Test;
2022
import org.skyscreamer.jsonassert.JSONAssert;
2123
import org.skyscreamer.jsonassert.JSONCompareMode;
@@ -76,7 +78,9 @@ public void chatOverrideConnectionProperties() {
7678
"spring.ai.zhipuai.chat.base-url=TEST_BASE_URL2",
7779
"spring.ai.zhipuai.chat.api-key=456",
7880
"spring.ai.zhipuai.chat.options.model=MODEL_XYZ",
79-
"spring.ai.zhipuai.chat.options.temperature=0.55")
81+
"spring.ai.zhipuai.chat.options.temperature=0.55",
82+
"spring.ai.zhipuai.connect-timeout=5s",
83+
"spring.ai.zhipuai.read-timeout=30s")
8084
// @formatter:on
8185
.withConfiguration(SpringAiTestAutoConfigurations.of(ZhiPuAiChatAutoConfiguration.class))
8286
.run(context -> {
@@ -91,6 +95,9 @@ public void chatOverrideConnectionProperties() {
9195

9296
assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
9397
assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55);
98+
99+
assertThat(connectionProperties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(5));
100+
assertThat(connectionProperties.getReadTimeout()).isEqualTo(Duration.ofSeconds(30));
94101
});
95102
}
96103

0 commit comments

Comments
 (0)