Spring Boot 4.0 changes

Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
This commit is contained in:
Ilayaperumal Gopinathan
2025-05-28 21:17:13 +01:00
parent 925f176e20
commit 3de5c3fea9
14 changed files with 32 additions and 23 deletions

View File

@@ -17,6 +17,7 @@
package org.springframework.ai.retry.autoconfigure;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
@@ -30,6 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.retry.RetryCallback;
@@ -87,7 +89,8 @@ public class SpringAiRetryAutoConfiguration {
}
@Override
public void handleError(@NonNull ClientHttpResponse response) throws IOException {
public void handleError(URI uri, HttpMethod httpMethod, @NonNull ClientHttpResponse response)
throws IOException {
if (!response.getStatusCode().isError()) {
return;
}

View File

@@ -162,7 +162,7 @@ public final class AnthropicApi {
return this.restClient.post()
.uri(this.completionsPath)
.headers(headers -> headers.addAll(additionalHttpHeader))
.headers(headers -> headers.addAll(HttpHeaders.readOnlyHttpHeaders(additionalHttpHeader)))
.body(chatRequest)
.retrieve()
.toEntity(ChatCompletionResponse.class);
@@ -198,7 +198,7 @@ public final class AnthropicApi {
return this.webClient.post()
.uri(this.completionsPath)
.headers(headers -> headers.addAll(additionalHttpHeader))
.headers(headers -> headers.addAll(HttpHeaders.readOnlyHttpHeaders(additionalHttpHeader)))
.body(Mono.just(chatRequest), ChatCompletionRequest.class)
.retrieve()
.bodyToFlux(String.class)

View File

@@ -94,7 +94,7 @@ public class DeepSeekApi {
Consumer<HttpHeaders> finalHeaders = h -> {
h.setBearerAuth(apiKey.getValue());
h.setContentType(MediaType.APPLICATION_JSON);
h.addAll(headers);
h.addAll(HttpHeaders.readOnlyHttpHeaders(headers));
};
this.restClient = restClientBuilder.baseUrl(baseUrl)
.defaultHeaders(finalHeaders)
@@ -153,7 +153,7 @@ public class DeepSeekApi {
return this.webClient.post()
.uri(this.getEndpoint(chatRequest))
.headers(headers -> headers.addAll(additionalHttpHeader))
.headers(headers -> headers.addAll(HttpHeaders.readOnlyHttpHeaders(additionalHttpHeader)))
.body(Mono.just(chatRequest), ChatCompletionRequest.class)
.retrieve()
.bodyToFlux(String.class)

View File

@@ -51,7 +51,7 @@
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.2.15</version>
<version>2.2.32</version>
</dependency>
<dependency>

View File

@@ -133,7 +133,7 @@ public class OpenAiApi {
}
h.setContentType(MediaType.APPLICATION_JSON);
h.addAll(headers);
h.addAll(HttpHeaders.readOnlyHttpHeaders(headers));
};
this.restClient = restClientBuilder.clone()
.baseUrl(baseUrl)
@@ -181,7 +181,7 @@ public class OpenAiApi {
return this.restClient.post()
.uri(this.completionsPath)
.headers(headers -> headers.addAll(additionalHttpHeader))
.headers(headers -> headers.addAll(HttpHeaders.readOnlyHttpHeaders(additionalHttpHeader)))
.body(chatRequest)
.retrieve()
.toEntity(ChatCompletion.class);
@@ -215,7 +215,7 @@ public class OpenAiApi {
return this.webClient.post()
.uri(this.completionsPath)
.headers(headers -> headers.addAll(additionalHttpHeader))
.headers(headers -> headers.addAll(HttpHeaders.readOnlyHttpHeaders(additionalHttpHeader)))
.body(Mono.just(chatRequest), ChatCompletionRequest.class)
.retrieve()
.bodyToFlux(String.class)

View File

@@ -74,7 +74,7 @@ public class OpenAiAudioApi {
if (!(apiKey instanceof NoopApiKey)) {
h.setBearerAuth(apiKey.getValue());
}
h.addAll(headers);
h.addAll(HttpHeaders.readOnlyHttpHeaders(headers));
// h.setContentType(MediaType.APPLICATION_JSON);
};

View File

@@ -27,6 +27,7 @@ import org.springframework.ai.model.NoopApiKey;
import org.springframework.ai.model.SimpleApiKey;
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
@@ -68,7 +69,7 @@ public class OpenAiImageApi {
h.setBearerAuth(apiKey.getValue());
}
h.setContentType(MediaType.APPLICATION_JSON);
h.addAll(headers);
h.addAll(HttpHeaders.readOnlyHttpHeaders(headers));
})
.defaultStatusHandler(responseErrorHandler)
.build();

View File

@@ -27,6 +27,7 @@ import org.springframework.ai.model.NoopApiKey;
import org.springframework.ai.model.SimpleApiKey;
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
@@ -69,7 +70,7 @@ public class OpenAiModerationApi {
h.setBearerAuth(apiKey.getValue());
}
h.setContentType(MediaType.APPLICATION_JSON);
h.addAll(headers);
h.addAll(HttpHeaders.readOnlyHttpHeaders(headers));
}).defaultStatusHandler(responseErrorHandler).build();
}

View File

@@ -71,7 +71,7 @@ public final class OpenAiResponseHeaderExtractor {
private static Duration getHeaderAsDuration(ResponseEntity<?> response, String headerName) {
var headers = response.getHeaders();
if (headers.containsKey(headerName)) {
if (headers.containsHeader(headerName)) {
var values = headers.get(headerName);
if (!CollectionUtils.isEmpty(values)) {
return DurationFormatter.TIME_UNIT.parse(values.get(0));
@@ -82,7 +82,7 @@ public final class OpenAiResponseHeaderExtractor {
private static Long getHeaderAsLong(ResponseEntity<?> response, String headerName) {
var headers = response.getHeaders();
if (headers.containsKey(headerName)) {
if (headers.containsHeader(headerName)) {
var values = headers.get(headerName);
if (!CollectionUtils.isEmpty(values)) {
return parseLong(headerName, values.get(0));

View File

@@ -162,7 +162,7 @@
<module>models/spring-ai-azure-openai</module>
<module>models/spring-ai-bedrock</module>
<module>models/spring-ai-bedrock-converse</module>
<module>models/spring-ai-huggingface</module>
<!-- <module>models/spring-ai-huggingface</module>-->
<module>models/spring-ai-minimax</module>
<module>models/spring-ai-mistral-ai</module>
<module>models/spring-ai-oci-genai</module>
@@ -253,12 +253,12 @@
<kotlin.compiler.jvmTarget>${java.version}</kotlin.compiler.jvmTarget>
<!-- production dependencies -->
<spring-boot.version>3.4.5</spring-boot.version>
<spring-boot.version>4.0.0-SNAPSHOT</spring-boot.version>
<ST4.version>4.3.4</ST4.version>
<azure-open-ai-client.version>1.0.0-beta.16</azure-open-ai-client.version>
<jtokkit.version>1.1.0</jtokkit.version>
<victools.version>4.37.0</victools.version>
<kotlin.version>1.9.25</kotlin.version>
<kotlin.version>2.1.0</kotlin.version>
<!-- NOTE: keep bedrockruntime and awssdk versions aligned -->
<bedrockruntime.version>2.31.26</bedrockruntime.version>

View File

@@ -25,8 +25,8 @@ import org.springframework.core.ParameterizedTypeReference
* @author Josh Long
*/
inline fun <reified T> ChatClient.CallResponseSpec.entity(): T =
inline fun <reified T : Any> ChatClient.CallResponseSpec.entity(): T =
entity(object : ParameterizedTypeReference<T>() {}) as T
inline fun <reified T> ChatClient.CallResponseSpec.responseEntity(): ResponseEntity<ChatResponse, T> =
responseEntity(object : ParameterizedTypeReference<T>() {})
inline fun <reified T : Any> ChatClient.CallResponseSpec.responseEntity(): ResponseEntity<ChatResponse, T> =
responseEntity(object : ParameterizedTypeReference<T>() {})

View File

@@ -39,7 +39,7 @@ class TypeResolverHelperKotlinIT {
val functionType = TypeResolverHelper.resolveBeanType(this.applicationContext, beanName);
val functionInputClass = TypeResolverHelper.getFunctionArgumentType(functionType, 0).rawClass;
assertThat(functionInputClass).isNotNull();
assertThat(functionInputClass.typeName).isEqualTo(WeatherRequest::class.java.getName());
assertThat(functionInputClass!!.typeName).isEqualTo(WeatherRequest::class.java.name)
}
class Outer {

View File

@@ -17,12 +17,14 @@
package org.springframework.ai.retry;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.retry.RetryCallback;
@@ -49,7 +51,7 @@ public abstract class RetryUtils {
}
@Override
public void handleError(@NonNull ClientHttpResponse response) throws IOException {
public void handleError(URI uri, HttpMethod method, @NonNull ClientHttpResponse response) throws IOException {
if (response.getStatusCode().isError()) {
String error = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
String message = String.format("%s - %s", response.getStatusCode().value(), error);

View File

@@ -24,6 +24,7 @@ import java.util.Optional;
import java.util.stream.Collectors;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.mapping.DenseVectorSimilarity;
import co.elastic.clients.elasticsearch.core.BulkRequest;
import co.elastic.clients.elasticsearch.core.BulkResponse;
import co.elastic.clients.elasticsearch.core.SearchResponse;
@@ -331,7 +332,8 @@ public class ElasticsearchVectorStore extends AbstractObservationVectorStore imp
this.elasticsearchClient.indices()
.create(cr -> cr.index(this.options.getIndexName())
.mappings(map -> map.properties(this.options.getEmbeddingFieldName(),
p -> p.denseVector(dv -> dv.similarity(this.options.getSimilarity().toString())
p -> p.denseVector(dv -> dv
.similarity(DenseVectorSimilarity.valueOf(this.options.getSimilarity().toString()))
.dims(this.options.getDimensions())))));
}
catch (IOException e) {