Skip to content

Commit b326fbc

Browse files
committed
feat: DeepSeek: support HTTP client timeout configuration
Signed-off-by: yinh <[email protected]>
1 parent d5a3269 commit b326fbc

File tree

5 files changed

+357
-0
lines changed

5 files changed

+357
-0
lines changed

auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/main/java/org/springframework/ai/model/deepseek/autoconfigure/DeepSeekChatAutoConfiguration.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@
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.restclient.RestClientCustomizer;
3839
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
40+
import org.springframework.boot.ssl.SslBundles;
3941
import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration;
4042
import org.springframework.context.annotation.Bean;
4143
import org.springframework.core.retry.RetryTemplate;
@@ -59,6 +61,15 @@
5961
matchIfMissing = true)
6062
public class DeepSeekChatAutoConfiguration {
6163

64+
@Bean
65+
@ConditionalOnMissingBean(name = "deepSeekRestClientCustomizer")
66+
@ConditionalOnProperty(prefix = "spring.ai.deepseek.http-client", name = "enabled", havingValue = "true",
67+
matchIfMissing = true)
68+
public RestClientCustomizer deepSeekRestClientCustomizer(DeepSeekConnectionProperties connectionProperties,
69+
ObjectProvider<SslBundles> sslBundles) {
70+
return new DeepSeekRestClientCustomizer(connectionProperties.getHttpClient(), sslBundles.getIfAvailable());
71+
}
72+
6273
@Bean
6374
@ConditionalOnMissingBean
6475
public DeepSeekChatModel deepSeekChatModel(DeepSeekConnectionProperties commonProperties,

auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/main/java/org/springframework/ai/model/deepseek/autoconfigure/DeepSeekConnectionProperties.java

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

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

19+
import java.time.Duration;
20+
1921
import org.springframework.boot.context.properties.ConfigurationProperties;
22+
import org.springframework.boot.context.properties.NestedConfigurationProperty;
23+
import org.springframework.boot.http.client.HttpClientSettings;
24+
import org.springframework.boot.http.client.HttpRedirects;
25+
import org.springframework.boot.ssl.SslBundle;
26+
import org.springframework.boot.ssl.SslBundles;
2027

2128
/**
2229
* Parent properties for DeepSeek.
@@ -30,8 +37,111 @@ public class DeepSeekConnectionProperties extends DeepSeekParentProperties {
3037

3138
public static final String DEFAULT_BASE_URL = "https://api.deepseek.com";
3239

40+
/**
41+
* HTTP client settings for DeepSeek API calls.
42+
*/
43+
@NestedConfigurationProperty
44+
private HttpClientConfig httpClient = new HttpClientConfig();
45+
3346
public DeepSeekConnectionProperties() {
3447
super.setBaseUrl(DEFAULT_BASE_URL);
3548
}
3649

50+
public HttpClientConfig getHttpClient() {
51+
return this.httpClient;
52+
}
53+
54+
public void setHttpClient(HttpClientConfig httpClient) {
55+
this.httpClient = httpClient;
56+
}
57+
58+
/**
59+
* HTTP client configuration settings. This inner class mirrors the structure of
60+
* Spring Boot's HttpClientSettings to provide full control over HTTP client behavior.
61+
*/
62+
public static class HttpClientConfig {
63+
64+
/**
65+
* Whether to enable custom HTTP client configuration.
66+
*/
67+
private boolean enabled = true;
68+
69+
/**
70+
* Connection timeout.
71+
*/
72+
private Duration connectTimeout = Duration.ofSeconds(10);
73+
74+
/**
75+
* Read timeout.
76+
*/
77+
private Duration readTimeout = Duration.ofSeconds(60);
78+
79+
/**
80+
* HTTP redirect strategy.
81+
*/
82+
private HttpRedirects redirects;
83+
84+
/**
85+
* SSL bundle name for secure connections.
86+
*/
87+
private String sslBundle;
88+
89+
public boolean isEnabled() {
90+
return this.enabled;
91+
}
92+
93+
public void setEnabled(boolean enabled) {
94+
this.enabled = enabled;
95+
}
96+
97+
public Duration getConnectTimeout() {
98+
return this.connectTimeout;
99+
}
100+
101+
public void setConnectTimeout(Duration connectTimeout) {
102+
this.connectTimeout = connectTimeout;
103+
}
104+
105+
public Duration getReadTimeout() {
106+
return this.readTimeout;
107+
}
108+
109+
public void setReadTimeout(Duration readTimeout) {
110+
this.readTimeout = readTimeout;
111+
}
112+
113+
public HttpRedirects getRedirects() {
114+
return this.redirects;
115+
}
116+
117+
public void setRedirects(HttpRedirects redirects) {
118+
this.redirects = redirects;
119+
}
120+
121+
public String getSslBundle() {
122+
return this.sslBundle;
123+
}
124+
125+
public void setSslBundle(String sslBundle) {
126+
this.sslBundle = sslBundle;
127+
}
128+
129+
/**
130+
* Convert to Spring Boot's HttpClientSettings.
131+
* @param sslBundles the SSL bundles registry
132+
* @return HttpClientSettings instance
133+
*/
134+
public HttpClientSettings toHttpClientSettings(SslBundles sslBundles) {
135+
SslBundle bundle = (this.sslBundle != null && sslBundles != null) ? sslBundles.getBundle(this.sslBundle)
136+
: null;
137+
138+
return HttpClientSettings.defaults()
139+
.withConnectTimeout(this.connectTimeout)
140+
.withReadTimeout(this.readTimeout)
141+
.withRedirects(this.redirects)
142+
.withSslBundle(bundle);
143+
}
144+
145+
}
146+
37147
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2023-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.model.deepseek.autoconfigure;
18+
19+
import jakarta.annotation.Nullable;
20+
21+
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
22+
import org.springframework.boot.http.client.HttpClientSettings;
23+
import org.springframework.boot.restclient.RestClientCustomizer;
24+
import org.springframework.boot.ssl.SslBundles;
25+
import org.springframework.http.client.ClientHttpRequestFactory;
26+
import org.springframework.web.client.RestClient;
27+
28+
/**
29+
* This customizer applies HTTP client settings (timeout, SSL, redirects) to
30+
* RestClient.Builder in a non-invasive way, preserving any existing configuration that
31+
* users may have already applied.
32+
*
33+
* @author yinh
34+
*/
35+
public record DeepSeekRestClientCustomizer(DeepSeekConnectionProperties.HttpClientConfig httpClientConfig,
36+
SslBundles sslBundles) implements RestClientCustomizer {
37+
38+
public DeepSeekRestClientCustomizer(DeepSeekConnectionProperties.HttpClientConfig httpClientConfig,
39+
@Nullable SslBundles sslBundles) {
40+
this.httpClientConfig = httpClientConfig;
41+
this.sslBundles = sslBundles;
42+
}
43+
44+
@Override
45+
public void customize(RestClient.Builder restClientBuilder) {
46+
if (!this.httpClientConfig.isEnabled()) {
47+
return;
48+
}
49+
50+
// 将配置转换为 HttpClientSettings
51+
HttpClientSettings settings = this.httpClientConfig.toHttpClientSettings(this.sslBundles);
52+
53+
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryBuilder.detect().build(settings);
54+
55+
restClientBuilder.requestFactory(requestFactory);
56+
}
57+
}

auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/test/java/org/springframework/ai/model/deepseek/autoconfigure/DeepSeekAutoConfigurationIT.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,27 @@ void generateStreaming() {
7373
});
7474
}
7575

76+
@Test
77+
void generateWithCustomTimeout() {
78+
new ApplicationContextRunner()
79+
.withPropertyValues("spring.ai.deepseek.apiKey=" + System.getenv("DEEPSEEK_API_KEY"),
80+
"spring.ai.deepseek.http-client.connect-timeout=5s",
81+
"spring.ai.deepseek.http-client.read-timeout=30s")
82+
.withConfiguration(SpringAiTestAutoConfigurations.of(DeepSeekChatAutoConfiguration.class))
83+
.run(context -> {
84+
DeepSeekChatModel client = context.getBean(DeepSeekChatModel.class);
85+
86+
// Verify that the HTTP client configuration is applied
87+
var connectionProperties = context.getBean(DeepSeekConnectionProperties.class);
88+
assertThat(connectionProperties.getHttpClient().getConnectTimeout().getSeconds()).isEqualTo(5);
89+
assertThat(connectionProperties.getHttpClient().getReadTimeout().getSeconds()).isEqualTo(30);
90+
91+
// Verify that the client can actually make requests with the configured
92+
// timeout
93+
String response = client.call("Hello");
94+
assertThat(response).isNotEmpty();
95+
logger.info("Response with custom timeout: " + response);
96+
});
97+
}
98+
7699
}

0 commit comments

Comments
 (0)