Skip to content

Commit 77ca166

Browse files
committed
Implement ChatClient timeout support
Add timeout configuration at request, builder, and ChatOptions levels. Signed-off-by: Hyunsang Han <gustkd3@gmail.com>
1 parent 4bd7d3e commit 77ca166

File tree

20 files changed

+455
-28
lines changed

20 files changed

+455
-28
lines changed

models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatOptions.java

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

1717
package org.springframework.ai.anthropic;
1818

19+
import java.time.Duration;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.HashMap;
@@ -47,6 +48,7 @@
4748
* @author Ilayaperumal Gopinathan
4849
* @author Soby Chacko
4950
* @author Austin Dase
51+
* @author Hyunsang Han
5052
* @since 1.0.0
5153
*/
5254
@JsonInclude(Include.NON_NULL)
@@ -102,6 +104,12 @@ public void setCacheOptions(AnthropicCacheOptions cacheOptions) {
102104
@JsonIgnore
103105
private Map<String, String> httpHeaders = new HashMap<>();
104106

107+
/**
108+
* The timeout duration for the chat request.
109+
*/
110+
@JsonIgnore
111+
private Duration timeout;
112+
105113
// @formatter:on
106114

107115
public static Builder builder() {
@@ -125,6 +133,7 @@ public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions)
125133
.toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null)
126134
.httpHeaders(fromOptions.getHttpHeaders() != null ? new HashMap<>(fromOptions.getHttpHeaders()) : null)
127135
.cacheOptions(fromOptions.getCacheOptions())
136+
.timeout(fromOptions.getTimeout())
128137
.build();
129138
}
130139

@@ -273,6 +282,17 @@ public void setHttpHeaders(Map<String, String> httpHeaders) {
273282
this.httpHeaders = httpHeaders;
274283
}
275284

285+
@Override
286+
@Nullable
287+
@JsonIgnore
288+
public Duration getTimeout() {
289+
return this.timeout;
290+
}
291+
292+
public void setTimeout(Duration timeout) {
293+
this.timeout = timeout;
294+
}
295+
276296
@Override
277297
@SuppressWarnings("unchecked")
278298
public AnthropicChatOptions copy() {
@@ -297,14 +317,14 @@ public boolean equals(Object o) {
297317
&& Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled)
298318
&& Objects.equals(this.toolContext, that.toolContext)
299319
&& Objects.equals(this.httpHeaders, that.httpHeaders)
300-
&& Objects.equals(this.cacheOptions, that.cacheOptions);
320+
&& Objects.equals(this.cacheOptions, that.cacheOptions) && Objects.equals(this.timeout, that.timeout);
301321
}
302322

303323
@Override
304324
public int hashCode() {
305325
return Objects.hash(this.model, this.maxTokens, this.metadata, this.stopSequences, this.temperature, this.topP,
306326
this.topK, this.thinking, this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled,
307-
this.toolContext, this.httpHeaders, this.cacheOptions);
327+
this.toolContext, this.httpHeaders, this.cacheOptions, this.timeout);
308328
}
309329

310330
public static class Builder {
@@ -409,6 +429,11 @@ public Builder cacheOptions(AnthropicCacheOptions cacheOptions) {
409429
return this;
410430
}
411431

432+
public Builder timeout(Duration timeout) {
433+
this.options.timeout = timeout;
434+
return this;
435+
}
436+
412437
public AnthropicChatOptions build() {
413438
return this.options;
414439
}

models/spring-ai-bedrock-converse/src/main/java/org/springframework/ai/bedrock/converse/BedrockChatOptions.java

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

1717
package org.springframework.ai.bedrock.converse;
1818

19+
import java.time.Duration;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.HashMap;
@@ -38,6 +39,7 @@
3839
* The options to be used when sending a chat request to the Bedrock API.
3940
*
4041
* @author Sun Yuhan
42+
* @author Hyunsang Han
4143
*/
4244
@JsonInclude(JsonInclude.Include.NON_NULL)
4345
public class BedrockChatOptions implements ToolCallingChatOptions {
@@ -81,6 +83,9 @@ public class BedrockChatOptions implements ToolCallingChatOptions {
8183
@JsonIgnore
8284
private Boolean internalToolExecutionEnabled;
8385

86+
@JsonIgnore
87+
private Duration timeout;
88+
8489
public static Builder builder() {
8590
return new Builder();
8691
}
@@ -101,6 +106,7 @@ public static BedrockChatOptions fromOptions(BedrockChatOptions fromOptions) {
101106
.toolNames(new HashSet<>(fromOptions.getToolNames()))
102107
.toolContext(new HashMap<>(fromOptions.getToolContext()))
103108
.internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled())
109+
.timeout(fromOptions.getTimeout())
104110
.build();
105111
}
106112

@@ -237,6 +243,17 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut
237243
this.internalToolExecutionEnabled = internalToolExecutionEnabled;
238244
}
239245

246+
@Override
247+
@Nullable
248+
@JsonIgnore
249+
public Duration getTimeout() {
250+
return this.timeout;
251+
}
252+
253+
public void setTimeout(Duration timeout) {
254+
this.timeout = timeout;
255+
}
256+
240257
@Override
241258
@SuppressWarnings("unchecked")
242259
public BedrockChatOptions copy() {
@@ -259,14 +276,15 @@ public boolean equals(Object o) {
259276
&& Objects.equals(this.temperature, that.temperature) && Objects.equals(this.topK, that.topK)
260277
&& Objects.equals(this.topP, that.topP) && Objects.equals(this.toolCallbacks, that.toolCallbacks)
261278
&& Objects.equals(this.toolNames, that.toolNames) && Objects.equals(this.toolContext, that.toolContext)
262-
&& Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled);
279+
&& Objects.equals(this.internalToolExecutionEnabled, that.internalToolExecutionEnabled)
280+
&& Objects.equals(this.timeout, that.timeout);
263281
}
264282

265283
@Override
266284
public int hashCode() {
267285
return Objects.hash(this.model, this.frequencyPenalty, this.maxTokens, this.presencePenalty,
268286
this.requestParameters, this.stopSequences, this.temperature, this.topK, this.topP, this.toolCallbacks,
269-
this.toolNames, this.toolContext, this.internalToolExecutionEnabled);
287+
this.toolNames, this.toolContext, this.internalToolExecutionEnabled, this.timeout);
270288
}
271289

272290
public static class Builder {
@@ -356,6 +374,11 @@ public Builder internalToolExecutionEnabled(@Nullable Boolean internalToolExecut
356374
return this;
357375
}
358376

377+
public Builder timeout(Duration timeout) {
378+
this.options.timeout = timeout;
379+
return this;
380+
}
381+
359382
public BedrockChatOptions build() {
360383
return this.options;
361384
}

models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatOptions.java

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

1717
package org.springframework.ai.deepseek;
1818

19+
import java.time.Duration;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.HashMap;
@@ -43,6 +44,7 @@
4344
* chat completion</a>
4445
*
4546
* @author Geng Rong
47+
* @author Hyunsang Han
4648
*/
4749
@JsonInclude(Include.NON_NULL)
4850
public class DeepSeekChatOptions implements ToolCallingChatOptions {
@@ -143,7 +145,10 @@ public class DeepSeekChatOptions implements ToolCallingChatOptions {
143145
private Set<String> toolNames = new HashSet<>();
144146

145147
@JsonIgnore
146-
private Map<String, Object> toolContext = new HashMap<>();;
148+
private Map<String, Object> toolContext = new HashMap<>();
149+
150+
@JsonIgnore
151+
private Duration timeout;
147152

148153
public static Builder builder() {
149154
return new Builder();
@@ -289,6 +294,17 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut
289294
this.internalToolExecutionEnabled = internalToolExecutionEnabled;
290295
}
291296

297+
@Override
298+
@Nullable
299+
@JsonIgnore
300+
public Duration getTimeout() {
301+
return this.timeout;
302+
}
303+
304+
public void setTimeout(Duration timeout) {
305+
this.timeout = timeout;
306+
}
307+
292308
public Boolean getLogprobs() {
293309
return this.logprobs;
294310
}
@@ -332,7 +348,7 @@ public int hashCode() {
332348
return Objects.hash(this.model, this.frequencyPenalty, this.logprobs, this.topLogprobs,
333349
this.maxTokens, this.presencePenalty, this.responseFormat,
334350
this.stop, this.temperature, this.topP, this.tools, this.toolChoice,
335-
this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, this.toolContext);
351+
this.toolCallbacks, this.toolNames, this.internalToolExecutionEnabled, this.toolContext, this.timeout);
336352
}
337353

338354

@@ -357,7 +373,8 @@ public boolean equals(Object o) {
357373
&& Objects.equals(this.toolCallbacks, other.toolCallbacks)
358374
&& Objects.equals(this.toolNames, other.toolNames)
359375
&& Objects.equals(this.toolContext, other.toolContext)
360-
&& Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled);
376+
&& Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled)
377+
&& Objects.equals(this.timeout, other.timeout);
361378
}
362379

363380
public static DeepSeekChatOptions fromOptions(DeepSeekChatOptions fromOptions) {
@@ -379,6 +396,7 @@ public static DeepSeekChatOptions fromOptions(DeepSeekChatOptions fromOptions) {
379396
.toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null)
380397
.internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled())
381398
.toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null)
399+
.timeout(fromOptions.getTimeout())
382400
.build();
383401
}
384402

@@ -497,6 +515,11 @@ public Builder toolContext(Map<String, Object> toolContext) {
497515
return this;
498516
}
499517

518+
public Builder timeout(Duration timeout) {
519+
this.options.timeout = timeout;
520+
return this;
521+
}
522+
500523
public DeepSeekChatOptions build() {
501524
return this.options;
502525
}

models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatOptions.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2023-2024 the original author or authors.
2+
* Copyright 2023-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.ai.minimax;
1818

19+
import java.time.Duration;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.Collections;
@@ -48,6 +49,7 @@
4849
* @author Thomas Vitale
4950
* @author Ilayaperumal Gopinathan
5051
* @author Alexandros Pappas
52+
* @author Hyunsang Han
5153
* @since 1.0.0 M1
5254
*/
5355
@JsonInclude(Include.NON_NULL)
@@ -156,6 +158,9 @@ public class MiniMaxChatOptions implements ToolCallingChatOptions {
156158
@JsonIgnore
157159
private Boolean internalToolExecutionEnabled;
158160

161+
@JsonIgnore
162+
private Duration timeout;
163+
159164
// @formatter:on
160165

161166
public static Builder builder() {
@@ -352,6 +357,17 @@ public void setInternalToolExecutionEnabled(@Nullable Boolean internalToolExecut
352357
this.internalToolExecutionEnabled = internalToolExecutionEnabled;
353358
}
354359

360+
@Override
361+
@Nullable
362+
@JsonIgnore
363+
public Duration getTimeout() {
364+
return this.timeout;
365+
}
366+
367+
public void setTimeout(Duration timeout) {
368+
this.timeout = timeout;
369+
}
370+
355371
@Override
356372
public Map<String, Object> getToolContext() {
357373
return (this.toolContext != null) ? Collections.unmodifiableMap(this.toolContext) : null;
@@ -512,6 +528,11 @@ public Builder toolContext(Map<String, Object> toolContext) {
512528
return this;
513529
}
514530

531+
public Builder timeout(Duration timeout) {
532+
this.options.timeout = timeout;
533+
return this;
534+
}
535+
515536
public MiniMaxChatOptions build() {
516537
return this.options;
517538
}

models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatOptions.java

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

1717
package org.springframework.ai.mistralai;
1818

19+
import java.time.Duration;
1920
import java.util.ArrayList;
2021
import java.util.Arrays;
2122
import java.util.HashMap;
@@ -46,6 +47,7 @@
4647
* @author Thomas Vitale
4748
* @author Alexandros Pappas
4849
* @author Jason Smith
50+
* @author Hyunsang Han
4951
* @since 0.8.1
5052
*/
5153
@JsonInclude(JsonInclude.Include.NON_NULL)
@@ -160,6 +162,9 @@ public class MistralAiChatOptions implements ToolCallingChatOptions {
160162
@JsonIgnore
161163
private Map<String, Object> toolContext = new HashMap<>();
162164

165+
@JsonIgnore
166+
private Duration timeout;
167+
163168
public static Builder builder() {
164169
return new Builder();
165170
}
@@ -183,6 +188,7 @@ public static MistralAiChatOptions fromOptions(MistralAiChatOptions fromOptions)
183188
.toolNames(fromOptions.getToolNames() != null ? new HashSet<>(fromOptions.getToolNames()) : null)
184189
.internalToolExecutionEnabled(fromOptions.getInternalToolExecutionEnabled())
185190
.toolContext(fromOptions.getToolContext() != null ? new HashMap<>(fromOptions.getToolContext()) : null)
191+
.timeout(fromOptions.getTimeout())
186192
.build();
187193
}
188194

@@ -367,6 +373,17 @@ public void setToolContext(Map<String, Object> toolContext) {
367373
this.toolContext = toolContext;
368374
}
369375

376+
@Override
377+
@Nullable
378+
@JsonIgnore
379+
public Duration getTimeout() {
380+
return this.timeout;
381+
}
382+
383+
public void setTimeout(Duration timeout) {
384+
this.timeout = timeout;
385+
}
386+
370387
@Override
371388
@SuppressWarnings("unchecked")
372389
public MistralAiChatOptions copy() {
@@ -377,7 +394,8 @@ public MistralAiChatOptions copy() {
377394
public int hashCode() {
378395
return Objects.hash(this.model, this.temperature, this.topP, this.maxTokens, this.safePrompt, this.randomSeed,
379396
this.responseFormat, this.stop, this.frequencyPenalty, this.presencePenalty, this.n, this.tools,
380-
this.toolChoice, this.toolCallbacks, this.tools, this.internalToolExecutionEnabled, this.toolContext);
397+
this.toolChoice, this.toolCallbacks, this.tools, this.internalToolExecutionEnabled, this.toolContext,
398+
this.timeout);
381399
}
382400

383401
@Override
@@ -403,7 +421,7 @@ public boolean equals(Object obj) {
403421
&& Objects.equals(this.toolCallbacks, other.toolCallbacks)
404422
&& Objects.equals(this.toolNames, other.toolNames)
405423
&& Objects.equals(this.internalToolExecutionEnabled, other.internalToolExecutionEnabled)
406-
&& Objects.equals(this.toolContext, other.toolContext);
424+
&& Objects.equals(this.toolContext, other.toolContext) && Objects.equals(this.timeout, other.timeout);
407425
}
408426

409427
public static final class Builder {
@@ -518,6 +536,11 @@ public Builder toolContext(Map<String, Object> toolContext) {
518536
return this;
519537
}
520538

539+
public Builder timeout(Duration timeout) {
540+
this.options.timeout = timeout;
541+
return this;
542+
}
543+
521544
public MistralAiChatOptions build() {
522545
return this.options;
523546
}

0 commit comments

Comments
 (0)