Skip to content

Commit 8c7cb3c

Browse files
committed
perf(mistralAi): support HTTP client timeout configuration
Signed-off-by: yinh <[email protected]>
1 parent 182a2c4 commit 8c7cb3c

File tree

8 files changed

+163
-16
lines changed

8 files changed

+163
-16
lines changed

auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingAutoConfiguration.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
3434
import org.springframework.boot.http.client.HttpClientSettings;
3535
import org.springframework.boot.http.client.autoconfigure.HttpClientSettingsPropertyMapper;
36-
import org.springframework.boot.restclient.RestClientCustomizer;
3736
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
3837
import org.springframework.boot.ssl.SslBundles;
3938
import org.springframework.context.annotation.Bean;

auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,17 @@
3434
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3535
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3636
import org.springframework.boot.context.properties.EnableConfigurationProperties;
37+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
38+
import org.springframework.boot.http.client.HttpClientSettings;
39+
import org.springframework.boot.http.client.autoconfigure.HttpClientSettingsPropertyMapper;
40+
import org.springframework.boot.http.client.reactive.ClientHttpConnectorBuilder;
3741
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
42+
import org.springframework.boot.ssl.SslBundles;
3843
import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;
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;
@@ -51,6 +58,7 @@
5158
* @author Christian Tzolov
5259
* @author Thomas Vitale
5360
* @author Ilayaperumal Gopinathan
61+
* @author yinh
5462
* @since 0.8.1
5563
*/
5664
@AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class,
@@ -69,12 +77,26 @@ public MistralAiChatModel mistralAiChatModel(MistralAiCommonProperties commonPro
6977
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
7078
ObjectProvider<ObservationRegistry> observationRegistry,
7179
ObjectProvider<ChatModelObservationConvention> observationConvention,
72-
ObjectProvider<ToolExecutionEligibilityPredicate> mistralAiToolExecutionEligibilityPredicate) {
80+
ObjectProvider<ToolExecutionEligibilityPredicate> mistralAiToolExecutionEligibilityPredicate,
81+
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientSettings> globalHttpClientSettings,
82+
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> factoryBuilder,
83+
ObjectProvider<ClientHttpConnectorBuilder<?>> webConnectorBuilderProvider) {
84+
85+
HttpClientSettingsPropertyMapper mapper = new HttpClientSettingsPropertyMapper(sslBundles.getIfAvailable(),
86+
globalHttpClientSettings.getIfAvailable());
87+
HttpClientSettings httpClientSettings = mapper.map(commonProperties);
88+
89+
RestClient.Builder restClientBuilder = restClientBuilderProvider.getIfAvailable(RestClient::builder);
90+
applyRestClientSettings(restClientBuilder, httpClientSettings,
91+
factoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect));
92+
93+
WebClient.Builder webClientBuilder = webClientBuilderProvider.getIfAvailable(WebClient::builder);
94+
applyWebClientSettings(webClientBuilder, httpClientSettings,
95+
webConnectorBuilderProvider.getIfAvailable(ClientHttpConnectorBuilder::detect));
7396

7497
var mistralAiApi = mistralAiApi(chatProperties.getApiKey(), commonProperties.getApiKey(),
75-
chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
76-
restClientBuilderProvider.getIfAvailable(RestClient::builder),
77-
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler);
98+
chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), restClientBuilder, webClientBuilder,
99+
responseErrorHandler);
78100

79101
var chatModel = MistralAiChatModel.builder()
80102
.mistralAiApi(mistralAiApi)
@@ -110,4 +132,16 @@ private MistralAiApi mistralAiApi(String apiKey, String commonApiKey, String bas
110132
.build();
111133
}
112134

135+
private void applyRestClientSettings(RestClient.Builder builder, HttpClientSettings httpClientSettings,
136+
ClientHttpRequestFactoryBuilder<?> factoryBuilder) {
137+
ClientHttpRequestFactory requestFactory = factoryBuilder.build(httpClientSettings);
138+
builder.requestFactory(requestFactory);
139+
}
140+
141+
private void applyWebClientSettings(WebClient.Builder builder, HttpClientSettings httpClientSettings,
142+
ClientHttpConnectorBuilder<?> connectorBuilder) {
143+
ClientHttpConnector connector = connectorBuilder.build(httpClientSettings);
144+
builder.clientConnector(connector);
145+
}
146+
113147
}

auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiCommonProperties.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.ai.model.mistralai.autoconfigure;
1818

1919
import org.springframework.boot.context.properties.ConfigurationProperties;
20+
import org.springframework.boot.http.client.autoconfigure.HttpClientSettingsProperties;
2021

2122
/**
2223
* Common properties for Mistral AI.
@@ -26,14 +27,30 @@
2627
* @since 0.8.1
2728
*/
2829
@ConfigurationProperties(MistralAiCommonProperties.CONFIG_PREFIX)
29-
public class MistralAiCommonProperties extends MistralAiParentProperties {
30+
public class MistralAiCommonProperties extends HttpClientSettingsProperties {
3031

3132
public static final String CONFIG_PREFIX = "spring.ai.mistralai";
3233

3334
public static final String DEFAULT_BASE_URL = "https://api.mistral.ai";
3435

35-
public MistralAiCommonProperties() {
36-
super.setBaseUrl(DEFAULT_BASE_URL);
36+
private String apiKey;
37+
38+
private String baseUrl = DEFAULT_BASE_URL;
39+
40+
public String getApiKey() {
41+
return this.apiKey;
42+
}
43+
44+
public void setApiKey(String apiKey) {
45+
this.apiKey = apiKey;
46+
}
47+
48+
public String getBaseUrl() {
49+
return this.baseUrl;
50+
}
51+
52+
public void setBaseUrl(String baseUrl) {
53+
this.baseUrl = baseUrl;
3754
}
3855

3956
}

auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,14 @@
3030
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3131
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3232
import org.springframework.boot.context.properties.EnableConfigurationProperties;
33+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
34+
import org.springframework.boot.http.client.HttpClientSettings;
35+
import org.springframework.boot.http.client.autoconfigure.HttpClientSettingsPropertyMapper;
3336
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
37+
import org.springframework.boot.ssl.SslBundles;
3438
import org.springframework.context.annotation.Bean;
3539
import org.springframework.core.retry.RetryTemplate;
40+
import org.springframework.http.client.ClientHttpRequestFactory;
3641
import org.springframework.util.Assert;
3742
import org.springframework.util.StringUtils;
3843
import org.springframework.web.client.ResponseErrorHandler;
@@ -45,6 +50,7 @@
4550
* @author Christian Tzolov
4651
* @author Thomas Vitale
4752
* @author Ilayaperumal Gopinathan
53+
* @author yinh
4854
* @since 0.8.1
4955
*/
5056
@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class })
@@ -60,11 +66,21 @@ public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiCommonProperties
6066
MistralAiEmbeddingProperties embeddingProperties,
6167
ObjectProvider<RestClient.Builder> restClientBuilderProvider, RetryTemplate retryTemplate,
6268
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
63-
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
69+
ObjectProvider<EmbeddingModelObservationConvention> observationConvention,
70+
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientSettings> globalHttpClientSettings,
71+
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> factoryBuilder) {
72+
73+
HttpClientSettingsPropertyMapper mapper = new HttpClientSettingsPropertyMapper(sslBundles.getIfAvailable(),
74+
globalHttpClientSettings.getIfAvailable());
75+
HttpClientSettings httpClientSettings = mapper.map(commonProperties);
76+
77+
RestClient.Builder restClientBuilder = restClientBuilderProvider.getIfAvailable(RestClient::builder);
78+
applyRestClientSettings(restClientBuilder, httpClientSettings,
79+
factoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect));
6480

6581
var mistralAiApi = mistralAiApi(embeddingProperties.getApiKey(), commonProperties.getApiKey(),
66-
embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(),
67-
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
82+
embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(), restClientBuilder,
83+
responseErrorHandler);
6884

6985
var embeddingModel = MistralAiEmbeddingModel.builder()
7086
.mistralAiApi(mistralAiApi)
@@ -96,4 +112,10 @@ private MistralAiApi mistralAiApi(String apiKey, String commonApiKey, String bas
96112
.build();
97113
}
98114

115+
private void applyRestClientSettings(RestClient.Builder builder, HttpClientSettings httpClientSettings,
116+
ClientHttpRequestFactoryBuilder<?> factoryBuilder) {
117+
ClientHttpRequestFactory requestFactory = factoryBuilder.build(httpClientSettings);
118+
builder.requestFactory(requestFactory);
119+
}
120+
99121
}

auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,15 @@
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.boot.webclient.autoconfigure.WebClientAutoConfiguration;
3337
import org.springframework.context.annotation.Bean;
3438
import org.springframework.core.retry.RetryTemplate;
39+
import org.springframework.http.client.ClientHttpRequestFactory;
3540
import org.springframework.util.Assert;
3641
import org.springframework.util.StringUtils;
3742
import org.springframework.web.client.ResponseErrorHandler;
@@ -54,7 +59,9 @@ public class MistralAiModerationAutoConfiguration {
5459
@ConditionalOnMissingBean
5560
public MistralAiModerationModel mistralAiModerationModel(MistralAiCommonProperties commonProperties,
5661
MistralAiModerationProperties moderationProperties, RetryTemplate retryTemplate,
57-
ObjectProvider<RestClient.Builder> restClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
62+
ObjectProvider<RestClient.Builder> restClientBuilderProvider, ResponseErrorHandler responseErrorHandler,
63+
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientSettings> globalHttpClientSettings,
64+
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> factoryBuilder) {
5865

5966
var apiKey = moderationProperties.getApiKey();
6067
var baseUrl = moderationProperties.getBaseUrl();
@@ -65,10 +72,18 @@ public MistralAiModerationModel mistralAiModerationModel(MistralAiCommonProperti
6572
Assert.hasText(resolvedApiKey, "Mistral API key must be set");
6673
Assert.hasText(resoledBaseUrl, "Mistral base URL must be set");
6774

75+
HttpClientSettingsPropertyMapper mapper = new HttpClientSettingsPropertyMapper(sslBundles.getIfAvailable(),
76+
globalHttpClientSettings.getIfAvailable());
77+
HttpClientSettings httpClientSettings = mapper.map(commonProperties);
78+
79+
RestClient.Builder restClientBuilder = restClientBuilderProvider.getIfAvailable(RestClient::builder);
80+
applyRestClientSettings(restClientBuilder, httpClientSettings,
81+
factoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect));
82+
6883
var mistralAiModerationApi = MistralAiModerationApi.builder()
6984
.baseUrl(resoledBaseUrl)
7085
.apiKey(resolvedApiKey)
71-
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
86+
.restClientBuilder(restClientBuilder)
7287
.responseErrorHandler(responseErrorHandler)
7388
.build();
7489

@@ -79,4 +94,10 @@ public MistralAiModerationModel mistralAiModerationModel(MistralAiCommonProperti
7994
.build();
8095
}
8196

97+
private void applyRestClientSettings(RestClient.Builder builder, HttpClientSettings httpClientSettings,
98+
ClientHttpRequestFactoryBuilder<?> factoryBuilder) {
99+
ClientHttpRequestFactory requestFactory = factoryBuilder.build(httpClientSettings);
100+
builder.requestFactory(requestFactory);
101+
}
102+
82103
}

auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrAutoConfiguration.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@
2525
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
2626
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2727
import org.springframework.boot.context.properties.EnableConfigurationProperties;
28+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
29+
import org.springframework.boot.http.client.HttpClientSettings;
30+
import org.springframework.boot.http.client.autoconfigure.HttpClientSettingsPropertyMapper;
2831
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
32+
import org.springframework.boot.ssl.SslBundles;
2933
import org.springframework.context.annotation.Bean;
34+
import org.springframework.http.client.ClientHttpRequestFactory;
3035
import org.springframework.util.Assert;
3136
import org.springframework.util.StringUtils;
3237
import org.springframework.web.client.ResponseErrorHandler;
@@ -47,7 +52,9 @@ public class MistralAiOcrAutoConfiguration {
4752
@Bean
4853
@ConditionalOnMissingBean
4954
public MistralOcrApi mistralOcrApi(MistralAiCommonProperties commonProperties, MistralAiOcrProperties ocrProperties,
50-
ObjectProvider<RestClient.Builder> restClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
55+
ObjectProvider<RestClient.Builder> restClientBuilderProvider, ResponseErrorHandler responseErrorHandler,
56+
ObjectProvider<SslBundles> sslBundles, ObjectProvider<HttpClientSettings> globalHttpClientSettings,
57+
ObjectProvider<ClientHttpRequestFactoryBuilder<?>> factoryBuilder) {
5158

5259
var apiKey = ocrProperties.getApiKey();
5360
var baseUrl = ocrProperties.getBaseUrl();
@@ -58,8 +65,21 @@ public MistralOcrApi mistralOcrApi(MistralAiCommonProperties commonProperties, M
5865
Assert.hasText(resolvedApiKey, "Mistral API key must be set");
5966
Assert.hasText(resolvedBaseUrl, "Mistral base URL must be set");
6067

61-
return new MistralOcrApi(resolvedBaseUrl, resolvedApiKey,
62-
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
68+
HttpClientSettingsPropertyMapper mapper = new HttpClientSettingsPropertyMapper(sslBundles.getIfAvailable(),
69+
globalHttpClientSettings.getIfAvailable());
70+
HttpClientSettings httpClientSettings = mapper.map(commonProperties);
71+
72+
RestClient.Builder restClientBuilder = restClientBuilderProvider.getIfAvailable(RestClient::builder);
73+
applyRestClientSettings(restClientBuilder, httpClientSettings,
74+
factoryBuilder.getIfAvailable(ClientHttpRequestFactoryBuilder::detect));
75+
76+
return new MistralOcrApi(resolvedBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler);
77+
}
78+
79+
private void applyRestClientSettings(RestClient.Builder builder, HttpClientSettings httpClientSettings,
80+
ClientHttpRequestFactoryBuilder<?> factoryBuilder) {
81+
ClientHttpRequestFactory requestFactory = factoryBuilder.build(httpClientSettings);
82+
builder.requestFactory(requestFactory);
6383
}
6484

6585
}

auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfigurationIT.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,18 @@ void embedding() {
9797
});
9898
}
9999

100+
@Test
101+
void generateWithCustomTimeout() {
102+
this.contextRunner.withConfiguration(SpringAiTestAutoConfigurations.of(MistralAiChatAutoConfiguration.class))
103+
.withPropertyValues("spring.ai.mistralai.connect-timeout=1ms", "spring.ai.mistralai.read-timeout=1ms")
104+
.run(context -> {
105+
MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class);
106+
107+
String response = chatModel.call("Hello");
108+
assertThat(response).isNotNull();
109+
110+
logger.info("Response with custom timeout: " + response);
111+
});
112+
}
113+
100114
}

auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrPropertiesTests.java

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

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

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

2123
import org.springframework.ai.mistralai.ocr.MistralOcrApi;
@@ -166,4 +168,22 @@ void ocrActivationViaModelProperty() {
166168
});
167169
}
168170

171+
@Test
172+
public void ocrCustomTimeouts() {
173+
new ApplicationContextRunner().withPropertyValues(
174+
// @formatter:off
175+
"spring.ai.mistralai.api-key=API_KEY",
176+
"spring.ai.mistralai.base-url=TEST_BASE_URL",
177+
"spring.ai.mistralai.connect-timeout=5s",
178+
"spring.ai.mistralai.read-timeout=30s")
179+
// @formatter:on
180+
.withConfiguration(this.autoConfigurations)
181+
.run(context -> {
182+
var connectionProperties = context.getBean(MistralAiCommonProperties.class);
183+
184+
assertThat(connectionProperties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(5));
185+
assertThat(connectionProperties.getReadTimeout()).isEqualTo(Duration.ofSeconds(30));
186+
});
187+
}
188+
169189
}

0 commit comments

Comments
 (0)