From a7f311d1a0e9301667d34f5e27b542fa87617ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dariusz=20J=C4=99drzejczyk?= Date: Wed, 7 May 2025 11:57:49 +0200 Subject: [PATCH] Fix mutating global RestClient and WebClient builders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the builders for HTTP clients are mutable and shared, they should only be configured with globally applicable settings. The current use leaks specific details into other usages and affects newly instantiated clients. This PR applies the clone() method right before mutation happens as it probably is the strategy that avoids multiple unnecessary copies. Signed-off-by: Dariusz Jędrzejczyk --- .../org/springframework/ai/anthropic/api/AnthropicApi.java | 6 ++++-- .../java/org/springframework/ai/minimax/api/MiniMaxApi.java | 2 +- .../org/springframework/ai/mistralai/api/MistralAiApi.java | 6 +++++- .../java/org/springframework/ai/ollama/api/OllamaApi.java | 2 ++ .../java/org/springframework/ai/openai/api/OpenAiApi.java | 5 ++++- .../org/springframework/ai/openai/api/OpenAiAudioApi.java | 5 +++-- .../java/org/springframework/ai/zhipuai/api/ZhiPuAiApi.java | 2 +- 7 files changed, 20 insertions(+), 8 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java index a5aa780a0f9..d7c1aacd268 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java @@ -119,12 +119,14 @@ private AnthropicApi(String baseUrl, String completionsPath, String anthropicApi this.completionsPath = completionsPath; - this.restClient = restClientBuilder.baseUrl(baseUrl) + this.restClient = restClientBuilder.clone() + .baseUrl(baseUrl) .defaultHeaders(jsonContentHeaders) .defaultStatusHandler(responseErrorHandler) .build(); - this.webClient = webClientBuilder.baseUrl(baseUrl) + this.webClient = webClientBuilder.clone() + .baseUrl(baseUrl) .defaultHeaders(jsonContentHeaders) .defaultStatusHandler(HttpStatusCode::isError, resp -> resp.bodyToMono(String.class) diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/api/MiniMaxApi.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/api/MiniMaxApi.java index 0d6dabaa8a1..eeb6c859a03 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/api/MiniMaxApi.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/api/MiniMaxApi.java @@ -115,7 +115,7 @@ public MiniMaxApi(String baseUrl, String miniMaxToken, RestClient.Builder restCl .defaultStatusHandler(responseErrorHandler) .build(); - this.webClient = WebClient.builder() + this.webClient = WebClient.builder() // FIXME: use a bean instead .baseUrl(baseUrl) .defaultHeaders(authHeaders) .build(); diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java index 7b32a5911bd..68d9afb60f4 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/api/MistralAiApi.java @@ -112,7 +112,11 @@ public MistralAiApi(String baseUrl, String mistralAiApiKey, RestClient.Builder r .defaultStatusHandler(responseErrorHandler) .build(); - this.webClient = WebClient.builder().baseUrl(baseUrl).defaultHeaders(jsonContentHeaders).build(); + this.webClient = WebClient.builder().baseUrl(baseUrl).defaultHeaders(jsonContentHeaders).build(); // FIXME: + // use + // a + // bean + // instead } /** diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java index 02f1e8816e2..ec3f0cc50ff 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/api/OllamaApi.java @@ -80,11 +80,13 @@ private OllamaApi(String baseUrl, RestClient.Builder restClientBuilder, WebClien }; this.restClient = restClientBuilder.baseUrl(baseUrl) + .clone() .defaultHeaders(defaultHeaders) .defaultStatusHandler(responseErrorHandler) .build(); this.webClient = webClientBuilder + .clone() .baseUrl(baseUrl) .defaultHeaders(defaultHeaders) .build(); diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java index 3cc5d5b4864..6a0bbde3f75 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApi.java @@ -115,12 +115,15 @@ public OpenAiApi(String baseUrl, ApiKey apiKey, MultiValueMap he h.setContentType(MediaType.APPLICATION_JSON); h.addAll(headers); }; - this.restClient = restClientBuilder.baseUrl(baseUrl) + this.restClient = restClientBuilder + .clone() + .baseUrl(baseUrl) .defaultHeaders(finalHeaders) .defaultStatusHandler(responseErrorHandler) .build(); this.webClient = webClientBuilder + .clone() .baseUrl(baseUrl) .defaultHeaders(finalHeaders) .build(); // @formatter:on diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java index 8974081b2f8..21775d5e7aa 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiAudioApi.java @@ -77,12 +77,13 @@ public OpenAiAudioApi(String baseUrl, ApiKey apiKey, MultiValueMap