diff --git a/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml b/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml index 4fcb9371d82..e1f010d4a19 100644 --- a/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml +++ b/auto-configurations/common/spring-ai-autoconfigure-retry/pom.xml @@ -61,6 +61,11 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-restclient-test + test + org.mockito diff --git a/auto-configurations/common/spring-ai-autoconfigure-retry/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfiguration.java b/auto-configurations/common/spring-ai-autoconfigure-retry/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfiguration.java index 1d7eed38da9..1a1cef42d90 100644 --- a/auto-configurations/common/spring-ai-autoconfigure-retry/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfiguration.java +++ b/auto-configurations/common/spring-ai-autoconfigure-retry/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfiguration.java @@ -17,7 +17,9 @@ package org.springframework.ai.retry.autoconfigure; import java.io.IOException; +import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,12 +32,13 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; +import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpResponse; import org.springframework.lang.NonNull; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.CollectionUtils; import org.springframework.util.StreamUtils; import org.springframework.web.client.ResponseErrorHandler; @@ -58,21 +61,25 @@ public class SpringAiRetryAutoConfiguration { @Bean @ConditionalOnMissingBean public RetryTemplate retryTemplate(SpringAiRetryProperties properties) { - return RetryTemplate.builder() + RetryPolicy retryPolicy = RetryPolicy.builder() .maxAttempts(properties.getMaxAttempts()) - .retryOn(TransientAiException.class) - .exponentialBackoff(properties.getBackoff().getInitialInterval(), properties.getBackoff().getMultiplier(), - properties.getBackoff().getMaxInterval()) - .withListener(new RetryListener() { - - @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - logger.warn("Retry error. Retry count: {}, Exception: {}", context.getRetryCount(), - throwable.getMessage(), throwable); - } - }) + .includes(TransientAiException.class) + .delay(properties.getBackoff().getInitialInterval()) + .multiplier(properties.getBackoff().getMultiplier()) + .maxDelay(properties.getBackoff().getMaxInterval()) .build(); + + RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); + retryTemplate.setRetryListener(new RetryListener() { + private final AtomicInteger retryCount = new AtomicInteger(0); + + @Override + public void onRetryFailure(RetryPolicy policy, Retryable retryable, Throwable throwable) { + int currentRetries = this.retryCount.incrementAndGet(); + logger.warn("Retry error. Retry count:{}", currentRetries, throwable); + } + }); + return retryTemplate; } @Bean @@ -87,6 +94,12 @@ public boolean hasError(@NonNull ClientHttpResponse response) throws IOException } @Override + public void handleError(@NonNull URI url, @NonNull HttpMethod method, @NonNull ClientHttpResponse response) + throws IOException { + handleError(response); + } + + @SuppressWarnings("removal") public void handleError(@NonNull ClientHttpResponse response) throws IOException { if (!response.getStatusCode().isError()) { return; diff --git a/auto-configurations/common/spring-ai-autoconfigure-retry/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfigurationIT.java b/auto-configurations/common/spring-ai-autoconfigure-retry/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfigurationIT.java index 4b712f0507e..dd4004dbb6b 100644 --- a/auto-configurations/common/spring-ai-autoconfigure-retry/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfigurationIT.java +++ b/auto-configurations/common/spring-ai-autoconfigure-retry/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfigurationIT.java @@ -19,9 +19,9 @@ import org.junit.jupiter.api.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/pom.xml index 62ce7340058..d7a4aae3811 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/pom.xml +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/pom.xml @@ -82,7 +82,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/pom.xml index 57341998a8c..24049480ae1 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/pom.xml +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/pom.xml @@ -88,7 +88,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml index c5844c26a25..028f9e0f273 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/pom.xml @@ -95,6 +95,17 @@ test + + org.springframework.boot + spring-boot-starter-restclient + test + + + org.springframework.boot + spring-boot-starter-webclient + test + + org.springframework.ai spring-ai-autoconfigure-model-anthropic diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java index 72fcafbcec6..a11eead5ec5 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfigurationTests.java @@ -16,14 +16,15 @@ package org.springframework.ai.mcp.server.autoconfigure; -import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider; import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.server.RouterFunction; @@ -46,8 +47,7 @@ void shouldConfigureWebFluxTransportWithCustomObjectMapper() { ObjectMapper objectMapper = context.getBean(ObjectMapper.class); // Verify that the ObjectMapper is configured to ignore unknown properties - assertThat(objectMapper.getDeserializationConfig() - .isEnabled(com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + assertThat(objectMapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); // Test with a JSON payload containing unknown fields // CHECKSTYLE:OFF diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsWithLLMIT.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsWithLLMIT.java index 596f9cb20c3..bd85eb6b35f 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsWithLLMIT.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/test/java/org/springframework/ai/mcp/server/autoconfigure/StreamableMcpAnnotationsWithLLMIT.java @@ -65,9 +65,9 @@ import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.http.server.reactive.HttpHandler; diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml index 9f13c6f89e9..2ebc9c1c59f 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/pom.xml @@ -40,6 +40,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-cassandra + org.springframework.boot @@ -83,13 +87,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - cassandra + testcontainers-cassandra test diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/src/main/java/org/springframework/ai/model/chat/memory/repository/cassandra/autoconfigure/CassandraChatMemoryRepositoryAutoConfiguration.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/src/main/java/org/springframework/ai/model/chat/memory/repository/cassandra/autoconfigure/CassandraChatMemoryRepositoryAutoConfiguration.java index e55bc82cea5..c96330c0f69 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/src/main/java/org/springframework/ai/model/chat/memory/repository/cassandra/autoconfigure/CassandraChatMemoryRepositoryAutoConfiguration.java +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/src/main/java/org/springframework/ai/model/chat/memory/repository/cassandra/autoconfigure/CassandraChatMemoryRepositoryAutoConfiguration.java @@ -22,9 +22,9 @@ import org.springframework.ai.chat.memory.repository.cassandra.CassandraChatMemoryRepositoryConfig; import org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.cassandra.autoconfigure.CassandraAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/src/test/java/org/springframework/ai/model/chat/memory/repository/cassandra/autoconfigure/CassandraChatMemoryRepositoryAutoConfigurationIT.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/src/test/java/org/springframework/ai/model/chat/memory/repository/cassandra/autoconfigure/CassandraChatMemoryRepositoryAutoConfigurationIT.java index 9b619ce9039..50d04eaa3ea 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/src/test/java/org/springframework/ai/model/chat/memory/repository/cassandra/autoconfigure/CassandraChatMemoryRepositoryAutoConfigurationIT.java +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cassandra/src/test/java/org/springframework/ai/model/chat/memory/repository/cassandra/autoconfigure/CassandraChatMemoryRepositoryAutoConfigurationIT.java @@ -31,7 +31,7 @@ import org.springframework.ai.chat.messages.MessageType; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; +import org.springframework.boot.cassandra.autoconfigure.CassandraAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cosmos-db/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cosmos-db/pom.xml index 535fd9f44ed..da7cb85c488 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cosmos-db/pom.xml +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-cosmos-db/pom.xml @@ -81,6 +81,11 @@ ${azure-identity.version} + + org.slf4j + jcl-over-slf4j + + org.springframework.boot diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml index 1ffcc2f324f..a942e7b080e 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/pom.xml @@ -40,6 +40,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-jdbc + org.springframework.boot @@ -59,6 +63,11 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-jdbc-test + test + org.postgresql @@ -68,13 +77,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - postgresql + testcontainers-postgresql test @@ -85,7 +94,7 @@ org.testcontainers - mssqlserver + testcontainers-mssqlserver test diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryAutoConfiguration.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryAutoConfiguration.java index 17bed069754..5d916b1aa79 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryAutoConfiguration.java +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/main/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryAutoConfiguration.java @@ -24,9 +24,9 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.sql.init.OnDatabaseInitializationCondition; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; +import org.springframework.boot.sql.autoconfigure.init.OnDatabaseInitializationCondition; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.java index abfd6927a46..7eed5b474ac 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.java +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT.java @@ -29,8 +29,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationContext; import org.springframework.jdbc.core.JdbcTemplate; @@ -51,10 +52,8 @@ "logging.level.org.springframework.boot.sql.init=DEBUG" }) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY) @ImportAutoConfiguration({ org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration.class, - JdbcChatMemoryRepositoryAutoConfiguration.class, - org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.class, - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class, - SqlInitializationAutoConfiguration.class }) + JdbcChatMemoryRepositoryAutoConfiguration.class, JdbcTemplateAutoConfiguration.class, + DataSourceAutoConfiguration.class }) public class JdbcChatMemoryRepositoryHsqldbAutoConfigurationIT { @Autowired diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPostgresqlAutoConfigurationIT.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPostgresqlAutoConfigurationIT.java index c6bde91fff4..2f7217c6710 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPostgresqlAutoConfigurationIT.java +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositoryPostgresqlAutoConfigurationIT.java @@ -28,8 +28,8 @@ import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializerPostgresqlTests.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializerPostgresqlTests.java index 03542a87d98..7845ef2fa95 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializerPostgresqlTests.java +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySchemaInitializerPostgresqlTests.java @@ -25,8 +25,8 @@ import org.testcontainers.utility.DockerImageName; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySqlServerAutoConfigurationIT.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySqlServerAutoConfigurationIT.java index a3bf69410ac..77095de4216 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySqlServerAutoConfigurationIT.java +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/model/chat/memory/repository/jdbc/autoconfigure/JdbcChatMemoryRepositorySqlServerAutoConfigurationIT.java @@ -33,8 +33,8 @@ import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.model.chat.memory.autoconfigure.ChatMemoryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml index dc0e1ef9373..74377bfe39f 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/pom.xml @@ -40,6 +40,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-neo4j + org.springframework.boot @@ -83,13 +87,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - neo4j + testcontainers-neo4j test diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/src/main/java/org/springframework/ai/model/chat/memory/repository/neo4j/autoconfigure/Neo4jChatMemoryRepositoryAutoConfiguration.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/src/main/java/org/springframework/ai/model/chat/memory/repository/neo4j/autoconfigure/Neo4jChatMemoryRepositoryAutoConfiguration.java index 970cb6be91c..2de15fd2482 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/src/main/java/org/springframework/ai/model/chat/memory/repository/neo4j/autoconfigure/Neo4jChatMemoryRepositoryAutoConfiguration.java +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/src/main/java/org/springframework/ai/model/chat/memory/repository/neo4j/autoconfigure/Neo4jChatMemoryRepositoryAutoConfiguration.java @@ -24,8 +24,8 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration; import org.springframework.context.annotation.Bean; /** diff --git a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/src/test/java/org/springframework/ai/model/chat/memory/repository/neo4j/autoconfigure/Neo4jChatMemoryRepositoryAutoConfigurationIT.java b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/src/test/java/org/springframework/ai/model/chat/memory/repository/neo4j/autoconfigure/Neo4jChatMemoryRepositoryAutoConfigurationIT.java index f60f6f61320..0ba45f83161 100644 --- a/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/src/test/java/org/springframework/ai/model/chat/memory/repository/neo4j/autoconfigure/Neo4jChatMemoryRepositoryAutoConfigurationIT.java +++ b/auto-configurations/models/chat/memory/repository/spring-ai-autoconfigure-model-chat-memory-repository-neo4j/src/test/java/org/springframework/ai/model/chat/memory/repository/neo4j/autoconfigure/Neo4jChatMemoryRepositoryAutoConfigurationIT.java @@ -39,7 +39,7 @@ import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.content.Media; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; +import org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.util.MimeType; diff --git a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml index 5e3ae7629fa..0c071cf380d 100644 --- a/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml +++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml @@ -56,7 +56,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml index 3835531828c..0eee243a13f 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml @@ -60,6 +60,17 @@ true + + org.springframework.boot + spring-boot-starter-webclient + true + + + org.springframework.boot + spring-boot-starter-restclient + true + + org.springframework.boot spring-boot-configuration-processor diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfiguration.java index 3233dd1eee1..0ec1bf2636f 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfiguration.java @@ -33,12 +33,12 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java index f87eb430dfe..20af04ded1c 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java @@ -22,7 +22,7 @@ import org.springframework.ai.anthropic.api.AnthropicApi.ToolChoiceTool; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/BaseAnthropicIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/BaseAnthropicIT.java index c68d9188e10..44282b3614d 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/BaseAnthropicIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/BaseAnthropicIT.java @@ -22,8 +22,8 @@ import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; public abstract class BaseAnthropicIT { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml index 2f36a8c976e..9591e86c7fa 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/pom.xml @@ -60,6 +60,17 @@ true + + org.springframework.boot + spring-boot-starter-webclient + true + + + org.springframework.boot + spring-boot-starter-restclient + true + + org.springframework.boot spring-boot-configuration-processor @@ -72,6 +83,12 @@ true + + org.springframework.boot + spring-boot-starter-webflux + true + + org.springframework.ai diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/main/java/org/springframework/ai/model/deepseek/autoconfigure/DeepSeekChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/main/java/org/springframework/ai/model/deepseek/autoconfigure/DeepSeekChatAutoConfiguration.java index 98aae30a232..eb84d57ee65 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/main/java/org/springframework/ai/model/deepseek/autoconfigure/DeepSeekChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/main/java/org/springframework/ai/model/deepseek/autoconfigure/DeepSeekChatAutoConfiguration.java @@ -34,11 +34,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/test/java/org/springframework/ai/model/deepseek/autoconfigure/BaseDeepSeekIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/test/java/org/springframework/ai/model/deepseek/autoconfigure/BaseDeepSeekIT.java index 9423542a624..1ea7baf7292 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/test/java/org/springframework/ai/model/deepseek/autoconfigure/BaseDeepSeekIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-deepseek/src/test/java/org/springframework/ai/model/deepseek/autoconfigure/BaseDeepSeekIT.java @@ -22,8 +22,8 @@ import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; /** * Base utility class for DeepSeek integration tests. diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/pom.xml index bc09ef1f5b4..af77303b055 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/pom.xml @@ -54,6 +54,17 @@ true + + org.springframework.boot + spring-boot-starter-webclient + true + + + org.springframework.boot + spring-boot-starter-restclient + true + + org.springframework.boot spring-boot-configuration-processor diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/src/main/java/org/springframework/ai/model/elevenlabs/autoconfigure/ElevenLabsAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/src/main/java/org/springframework/ai/model/elevenlabs/autoconfigure/ElevenLabsAutoConfiguration.java index bbff2dcf71e..ad0c20eecf5 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/src/main/java/org/springframework/ai/model/elevenlabs/autoconfigure/ElevenLabsAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/src/main/java/org/springframework/ai/model/elevenlabs/autoconfigure/ElevenLabsAutoConfiguration.java @@ -24,11 +24,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/src/test/java/org/springframework/ai/model/elevenlabs/autoconfigure/ElevenLabsITUtil.java b/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/src/test/java/org/springframework/ai/model/elevenlabs/autoconfigure/ElevenLabsITUtil.java index 9cd2b178856..86debc54233 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/src/test/java/org/springframework/ai/model/elevenlabs/autoconfigure/ElevenLabsITUtil.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-elevenlabs/src/test/java/org/springframework/ai/model/elevenlabs/autoconfigure/ElevenLabsITUtil.java @@ -18,8 +18,8 @@ import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; /** * Utility class for ElevenLabs integration tests. diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/pom.xml index 8bed6c0ea18..2a6b56d4866 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/pom.xml @@ -74,6 +74,11 @@ spring-boot-starter true + + org.springframework.boot + spring-boot-starter-restclient + true + org.springframework.boot @@ -109,13 +114,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - ollama + testcontainers-ollama test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/main/java/org/springframework/ai/model/google/genai/autoconfigure/chat/GoogleGenAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/main/java/org/springframework/ai/model/google/genai/autoconfigure/chat/GoogleGenAiChatAutoConfiguration.java index e8c9b8ea2d1..664b1e3b9be 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/main/java/org/springframework/ai/model/google/genai/autoconfigure/chat/GoogleGenAiChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/main/java/org/springframework/ai/model/google/genai/autoconfigure/chat/GoogleGenAiChatAutoConfiguration.java @@ -42,7 +42,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/main/java/org/springframework/ai/model/google/genai/autoconfigure/embedding/GoogleGenAiTextEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/main/java/org/springframework/ai/model/google/genai/autoconfigure/embedding/GoogleGenAiTextEmbeddingAutoConfiguration.java index 6fd62c663e4..d3825836171 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/main/java/org/springframework/ai/model/google/genai/autoconfigure/embedding/GoogleGenAiTextEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/main/java/org/springframework/ai/model/google/genai/autoconfigure/embedding/GoogleGenAiTextEmbeddingAutoConfiguration.java @@ -31,7 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; /** * Auto-configuration for Google GenAI Text Embedding. diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/test/java/org/springframework/ai/model/google/genai/autoconfigure/chat/tool/FunctionCallWithFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/test/java/org/springframework/ai/model/google/genai/autoconfigure/chat/tool/FunctionCallWithFunctionBeanIT.java index 328e742fcfa..84d5bf71cc7 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/test/java/org/springframework/ai/model/google/genai/autoconfigure/chat/tool/FunctionCallWithFunctionBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-google-genai/src/test/java/org/springframework/ai/model/google/genai/autoconfigure/chat/tool/FunctionCallWithFunctionBeanIT.java @@ -30,7 +30,7 @@ import org.springframework.ai.model.google.genai.autoconfigure.BaseGoogleGenAiIT; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml index 62eff8604f9..2db17c4130b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml @@ -66,6 +66,12 @@ true + + org.springframework.boot + spring-boot-starter-restclient + true + + org.springframework.boot spring-boot-configuration-processor diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatAutoConfiguration.java index b51445491f5..7cebce533fd 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatAutoConfiguration.java @@ -33,10 +33,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingAutoConfiguration.java index e7098339cc2..36e3a82cb00 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingAutoConfiguration.java @@ -29,10 +29,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackInPromptIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackInPromptIT.java index dcf772a0bf7..16b5f011ee5 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackInPromptIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackInPromptIT.java @@ -36,7 +36,7 @@ import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackWithPlainFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackWithPlainFunctionBeanIT.java index 0cb23c746a9..94f0678789c 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackWithPlainFunctionBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackWithPlainFunctionBeanIT.java @@ -37,7 +37,7 @@ import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfigurationIT.java index 543ad444578..723e800e0c2 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfigurationIT.java @@ -34,7 +34,7 @@ import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxFunctionCallbackIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxFunctionCallbackIT.java index 8e41a0641dd..956c3dcedd1 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxFunctionCallbackIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxFunctionCallbackIT.java @@ -36,7 +36,7 @@ import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java index c0751d5e334..44df03c8b7a 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java @@ -27,7 +27,7 @@ import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MinimaxModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MinimaxModelConfigurationTests.java index cf1b622d1ff..6fa8ac46c54 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MinimaxModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MinimaxModelConfigurationTests.java @@ -23,7 +23,7 @@ import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml index d4642a87308..d93f1d3cd21 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml @@ -72,6 +72,17 @@ true + + org.springframework.boot + spring-boot-starter-webclient + true + + + org.springframework.boot + spring-boot-starter-restclient + true + + org.springframework.boot spring-boot-configuration-processor diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java index 9044e82cbbd..8ae982b503f 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java @@ -33,11 +33,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java index ad8a8632ccd..efa30bedd9d 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java @@ -29,10 +29,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java index 778d267019e..cb9d89db164 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java @@ -27,11 +27,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrAutoConfiguration.java index 8a247672507..4a92b82b015 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrAutoConfiguration.java @@ -24,8 +24,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrPropertiesTests.java index 3b494c7f713..1a2ec9a34e5 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiOcrPropertiesTests.java @@ -21,7 +21,7 @@ import org.springframework.ai.mistralai.ocr.MistralOcrApi; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml index ecb49ce4c23..4bbe9e5574f 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml @@ -66,6 +66,17 @@ true + + org.springframework.boot + spring-boot-starter-restclient + true + + + org.springframework.boot + spring-boot-starter-webclient + true + + org.springframework.boot spring-boot-configuration-processor @@ -100,13 +111,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - ollama + testcontainers-ollama test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaApiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaApiAutoConfiguration.java index ed30d787021..3df440cdc3f 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaApiAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaApiAutoConfiguration.java @@ -22,9 +22,9 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java index 34b9ad58346..67bdae15714 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java @@ -36,7 +36,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; /** * {@link AutoConfiguration Auto-configuration} for Ollama Chat model. diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/BaseOllamaIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/BaseOllamaIT.java index 9a3bc2527f4..681e9b0a93b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/BaseOllamaIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/BaseOllamaIT.java @@ -30,7 +30,7 @@ import org.springframework.ai.ollama.management.PullModelStrategy; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.util.Assert; @Testcontainers diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml index ed37e511925..548e9c9d551 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml @@ -71,6 +71,17 @@ true + + org.springframework.boot + spring-boot-starter-webclient + true + + + org.springframework.boot + spring-boot-starter-restclient + true + + org.springframework.boot spring-boot-configuration-processor diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java index 7eff1898fa4..2447478f4fd 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java @@ -16,14 +16,9 @@ package org.springframework.ai.model.openai.autoconfigure; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import org.springframework.http.HttpHeaders; import org.springframework.lang.NonNull; import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; public final class OpenAIAutoConfigurationUtil { @@ -44,12 +39,12 @@ private OpenAIAutoConfigurationUtil() { String organizationId = StringUtils.hasText(modelProperties.getOrganizationId()) ? modelProperties.getOrganizationId() : commonProperties.getOrganizationId(); - Map> connectionHeaders = new HashMap<>(); + HttpHeaders connectionHeaders = new HttpHeaders(); if (StringUtils.hasText(projectId)) { - connectionHeaders.put("OpenAI-Project", List.of(projectId)); + connectionHeaders.add("OpenAI-Project", projectId); } if (StringUtils.hasText(organizationId)) { - connectionHeaders.put("OpenAI-Organization", List.of(organizationId)); + connectionHeaders.add("OpenAI-Organization", organizationId); } Assert.hasText(baseUrl, @@ -59,10 +54,10 @@ private OpenAIAutoConfigurationUtil() { "OpenAI API key must be set. Use the connection property: spring.ai.openai.api-key or spring.ai.openai." + modelType + ".api-key property."); - return new ResolvedConnectionProperties(baseUrl, apiKey, CollectionUtils.toMultiValueMap(connectionHeaders)); + return new ResolvedConnectionProperties(baseUrl, apiKey, connectionHeaders); } - public record ResolvedConnectionProperties(String baseUrl, String apiKey, MultiValueMap headers) { + public record ResolvedConnectionProperties(String baseUrl, String apiKey, HttpHeaders headers) { } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechAutoConfiguration.java index 4d197904e5d..a4623072f90 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechAutoConfiguration.java @@ -28,11 +28,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionAutoConfiguration.java index 7e9b67c70ca..acba2f5d927 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionAutoConfiguration.java @@ -28,11 +28,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatAutoConfiguration.java index f1c1e4ea618..f8f5f801a11 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatAutoConfiguration.java @@ -34,11 +34,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingAutoConfiguration.java index bf9b40e7a65..ac85dbdc248 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingAutoConfiguration.java @@ -30,11 +30,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageAutoConfiguration.java index 07da6969a70..ba7ee1f8c11 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageAutoConfiguration.java @@ -31,11 +31,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationAutoConfiguration.java index bf74afe8fdc..f844f4d5ec8 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationAutoConfiguration.java @@ -28,11 +28,11 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/ChatClientAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/ChatClientAutoConfigurationIT.java index 46dec5cb44c..89bd4f3e373 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/ChatClientAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/ChatClientAutoConfigurationIT.java @@ -29,7 +29,7 @@ import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java index 78e920f7f08..93341ec43ca 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java @@ -42,9 +42,9 @@ import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModelConfigurationTests.java index b5797172932..b4c25940ddb 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModelConfigurationTests.java @@ -28,9 +28,9 @@ import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java index da577eed1ab..a172cfad685 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java @@ -32,9 +32,9 @@ import org.springframework.ai.openai.api.OpenAiAudioApi; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java index 04c2b5813f4..e36e6cc4812 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java @@ -28,9 +28,9 @@ import org.springframework.ai.openai.api.ResponseFormat; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.webclient.autoconfigure.WebClientAutoConfiguration; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java index d7b90d853ff..6797d39afa0 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java @@ -33,7 +33,7 @@ import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java index 06510dfcdbe..60d47c834ce 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java @@ -38,7 +38,7 @@ import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java index 75debfb1069..fccf6708afd 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java @@ -47,7 +47,7 @@ import org.springframework.ai.openai.api.OpenAiApi.ChatModel; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java index a996c4c3e5a..4d7ed8a7459 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java @@ -32,7 +32,7 @@ import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java index 9c917b25fdc..0d74ddc297b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java @@ -39,7 +39,7 @@ import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml index 5ff82851c32..d4d50b6e20b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml @@ -53,6 +53,11 @@ spring-boot-starter true + + org.springframework.boot + spring-boot-starter-jdbc + true + org.springframework.boot @@ -79,6 +84,11 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-jdbc-test + true + org.springframework.boot @@ -88,13 +98,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - postgresql + testcontainers-postgresql test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfiguration.java index 6fcbb4f2949..65d224ab9df 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfiguration.java @@ -23,8 +23,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfigurationIT.java index cb7f54b829a..792ccc8f5a1 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfigurationIT.java @@ -30,8 +30,8 @@ import org.springframework.ai.postgresml.PostgresMlEmbeddingModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; +import org.springframework.boot.jdbc.test.autoconfigure.JdbcTest; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml index 38f9f644b8c..107405ef433 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml @@ -54,6 +54,12 @@ true + + org.springframework.boot + spring-boot-starter-restclient + true + + org.springframework.boot spring-boot-configuration-processor diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java index 119f00c2309..70833cf572a 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java @@ -25,8 +25,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.util.Assert; import org.springframework.util.StringUtils; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml index bc9593a6ffc..3ff975240ad 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml @@ -109,13 +109,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - ollama + testcontainers-ollama test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingAutoConfiguration.java index 56e253dc2d2..8c0370ec84d 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingAutoConfiguration.java @@ -31,7 +31,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; /** * Auto-configuration for Vertex AI Gemini Chat. diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfiguration.java index edb7057c1e6..79b4865ca8e 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfiguration.java @@ -39,7 +39,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml index eafc77142fa..92811dcf397 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml @@ -72,6 +72,12 @@ true + + org.springframework.boot + spring-boot-starter-restclient + true + + org.springframework.boot spring-boot-configuration-processor diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatAutoConfiguration.java index ba9468e5c4f..4023978c697 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatAutoConfiguration.java @@ -34,10 +34,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingAutoConfiguration.java index a80913cdd3d..94ab54dfda9 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingAutoConfiguration.java @@ -30,10 +30,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiImageAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiImageAutoConfiguration.java index ea89f6198cb..d1e876f2afd 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiImageAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiImageAutoConfiguration.java @@ -27,10 +27,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiITUtil.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiITUtil.java index 6c4c0833602..4df8c382ac5 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiITUtil.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiITUtil.java @@ -19,7 +19,7 @@ import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; /** * Utility class for ZhiPuAI integration tests. diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml index dfed881e4de..0f48b2b583c 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/pom.xml @@ -49,6 +49,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-cassandra + org.springframework.boot spring-boot-configuration-processor @@ -83,7 +87,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +97,7 @@ org.testcontainers - cassandra + testcontainers-cassandra test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/src/main/java/org/springframework/ai/vectorstore/cassandra/autoconfigure/CassandraVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/src/main/java/org/springframework/ai/vectorstore/cassandra/autoconfigure/CassandraVectorStoreAutoConfiguration.java index e58ef033ce9..c695b9743eb 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/src/main/java/org/springframework/ai/vectorstore/cassandra/autoconfigure/CassandraVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/src/main/java/org/springframework/ai/vectorstore/cassandra/autoconfigure/CassandraVectorStoreAutoConfiguration.java @@ -30,11 +30,11 @@ import org.springframework.ai.vectorstore.observation.VectorStoreObservationConvention; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; -import org.springframework.boot.autoconfigure.cassandra.DriverConfigLoaderBuilderCustomizer; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.cassandra.autoconfigure.CassandraAutoConfiguration; +import org.springframework.boot.cassandra.autoconfigure.DriverConfigLoaderBuilderCustomizer; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/src/test/java/org/springframework/ai/vectorstore/cassandra/autoconfigure/CassandraVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/src/test/java/org/springframework/ai/vectorstore/cassandra/autoconfigure/CassandraVectorStoreAutoConfigurationIT.java index 0ad388a5b60..7669a6266b5 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/src/test/java/org/springframework/ai/vectorstore/cassandra/autoconfigure/CassandraVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-cassandra/src/test/java/org/springframework/ai/vectorstore/cassandra/autoconfigure/CassandraVectorStoreAutoConfigurationIT.java @@ -37,7 +37,7 @@ import org.springframework.ai.vectorstore.cassandra.CassandraVectorStore; import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; +import org.springframework.boot.cassandra.autoconfigure.CassandraAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml index a92a8d6488b..5f5ef5a74d3 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-chroma/pom.xml @@ -83,7 +83,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +93,7 @@ org.testcontainers - chromadb + testcontainers-chromadb test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml index 11e93742d7e..b60db802756 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/pom.xml @@ -49,6 +49,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-couchbase + org.springframework.boot spring-boot-configuration-processor @@ -83,7 +87,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +97,7 @@ org.testcontainers - couchbase + testcontainers-couchbase test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/src/main/java/org/springframework/ai/vectorstore/couchbase/autoconfigure/CouchbaseSearchVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/src/main/java/org/springframework/ai/vectorstore/couchbase/autoconfigure/CouchbaseSearchVectorStoreAutoConfiguration.java index 25aa0617930..63e31484166 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/src/main/java/org/springframework/ai/vectorstore/couchbase/autoconfigure/CouchbaseSearchVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/src/main/java/org/springframework/ai/vectorstore/couchbase/autoconfigure/CouchbaseSearchVectorStoreAutoConfiguration.java @@ -23,9 +23,9 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.couchbase.autoconfigure.CouchbaseAutoConfiguration; import org.springframework.context.annotation.Bean; /** @@ -49,9 +49,9 @@ public CouchbaseSearchVectorStore vectorStore(CouchbaseSearchVectorStoreProperti mapper.from(properties::getBucketName).whenHasText().to(builder::bucketName); mapper.from(properties::getScopeName).whenHasText().to(builder::scopeName); mapper.from(properties::getCollectionName).whenHasText().to(builder::collectionName); - mapper.from(properties::getDimensions).whenNonNull().to(builder::dimensions); - mapper.from(properties::getSimilarity).whenNonNull().to(builder::similarityFunction); - mapper.from(properties::getOptimization).whenNonNull().to(builder::indexOptimization); + mapper.from(properties::getDimensions).to(builder::dimensions); + mapper.from(properties::getSimilarity).to(builder::similarityFunction); + mapper.from(properties::getOptimization).to(builder::indexOptimization); return builder.initializeSchema(properties.isInitializeSchema()).build(); } diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/src/test/java/org/springframework/ai/vectorstore/couchbase/autoconfigure/CouchbaseSearchVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/src/test/java/org/springframework/ai/vectorstore/couchbase/autoconfigure/CouchbaseSearchVectorStoreAutoConfigurationIT.java index bb8cc8b646c..902f44353a5 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/src/test/java/org/springframework/ai/vectorstore/couchbase/autoconfigure/CouchbaseSearchVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-couchbase/src/test/java/org/springframework/ai/vectorstore/couchbase/autoconfigure/CouchbaseSearchVectorStoreAutoConfigurationIT.java @@ -36,8 +36,7 @@ import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.couchbase.autoconfigure.CouchbaseAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -64,8 +63,8 @@ class CouchbaseSearchVectorStoreAutoConfigurationIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(CouchbaseAutoConfiguration.class, - CouchbaseSearchVectorStoreAutoConfiguration.class, RestClientAutoConfiguration.class, - SpringAiRetryAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class)) + CouchbaseSearchVectorStoreAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, + OpenAiEmbeddingAutoConfiguration.class)) .withPropertyValues("spring.couchbase.connection-string=" + couchbaseContainer.getConnectionString(), "spring.couchbase.username=" + couchbaseContainer.getUsername(), "spring.couchbase.password=" + couchbaseContainer.getPassword(), @@ -113,8 +112,8 @@ public void addAndSearchWithFilters() { public void propertiesTest() { new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(CouchbaseAutoConfiguration.class, - CouchbaseSearchVectorStoreAutoConfiguration.class, RestClientAutoConfiguration.class, - SpringAiRetryAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class)) + CouchbaseSearchVectorStoreAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, + OpenAiEmbeddingAutoConfiguration.class)) .withPropertyValues("spring.couchbase.connection-string=" + couchbaseContainer.getConnectionString(), "spring.couchbase.username=" + couchbaseContainer.getUsername(), "spring.couchbase.password=" + couchbaseContainer.getPassword(), diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml index 30ab3d801c4..807bed275ea 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/pom.xml @@ -49,6 +49,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-elasticsearch + org.springframework.boot spring-boot-configuration-processor @@ -83,7 +87,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +97,7 @@ org.testcontainers - elasticsearch + testcontainers-elasticsearch test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/main/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/main/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfiguration.java index 2c780c94f54..21d9520ea9a 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/main/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/main/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfiguration.java @@ -16,8 +16,8 @@ package org.springframework.ai.vectorstore.elasticsearch.autoconfigure; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import io.micrometer.observation.ObservationRegistry; -import org.elasticsearch.client.RestClient; import org.springframework.ai.embedding.BatchingStrategy; import org.springframework.ai.embedding.EmbeddingModel; @@ -31,9 +31,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.boot.elasticsearch.autoconfigure.ElasticsearchRestClientAutoConfiguration; import org.springframework.context.annotation.Bean; /** @@ -49,7 +49,7 @@ * @since 1.0.0 */ @AutoConfiguration(after = ElasticsearchRestClientAutoConfiguration.class) -@ConditionalOnClass({ ElasticsearchVectorStore.class, EmbeddingModel.class, RestClient.class }) +@ConditionalOnClass({ ElasticsearchVectorStore.class, EmbeddingModel.class, Rest5Client.class }) @EnableConfigurationProperties(ElasticsearchVectorStoreProperties.class) @ConditionalOnProperty(name = SpringAIVectorStoreTypes.TYPE, havingValue = SpringAIVectorStoreTypes.ELASTICSEARCH, matchIfMissing = true) @@ -63,7 +63,7 @@ BatchingStrategy batchingStrategy() { @Bean @ConditionalOnMissingBean - ElasticsearchVectorStore vectorStore(ElasticsearchVectorStoreProperties properties, RestClient restClient, + ElasticsearchVectorStore vectorStore(ElasticsearchVectorStoreProperties properties, Rest5Client restClient, EmbeddingModel embeddingModel, ObjectProvider observationRegistry, ObjectProvider customObservationConvention, BatchingStrategy batchingStrategy) { @@ -71,8 +71,8 @@ ElasticsearchVectorStore vectorStore(ElasticsearchVectorStoreProperties properti PropertyMapper mapper = PropertyMapper.get(); mapper.from(properties::getIndexName).whenHasText().to(elasticsearchVectorStoreOptions::setIndexName); - mapper.from(properties::getDimensions).whenNonNull().to(elasticsearchVectorStoreOptions::setDimensions); - mapper.from(properties::getSimilarity).whenNonNull().to(elasticsearchVectorStoreOptions::setSimilarity); + mapper.from(properties::getDimensions).to(elasticsearchVectorStoreOptions::setDimensions); + mapper.from(properties::getSimilarity).to(elasticsearchVectorStoreOptions::setSimilarity); mapper.from(properties::getEmbeddingFieldName) .whenHasText() .to(elasticsearchVectorStoreOptions::setEmbeddingFieldName); diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java index dd49c2d1599..cadbc42e6cc 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java @@ -42,8 +42,7 @@ import org.springframework.ai.vectorstore.elasticsearch.SimilarityFunction; import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.elasticsearch.autoconfigure.ElasticsearchRestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -58,13 +57,13 @@ class ElasticsearchVectorStoreAutoConfigurationIT { @Container private static final ElasticsearchContainer elasticsearchContainer = new ElasticsearchContainer( - "docker.elastic.co/elasticsearch/elasticsearch:8.16.1") + "docker.elastic.co/elasticsearch/elasticsearch:9.2.0") .withEnv("xpack.security.enabled", "false"); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class, - ElasticsearchVectorStoreAutoConfiguration.class, RestClientAutoConfiguration.class, - SpringAiRetryAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class)) + ElasticsearchVectorStoreAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, + OpenAiEmbeddingAutoConfiguration.class)) .withUserConfiguration(Config.class) .withPropertyValues("spring.elasticsearch.uris=" + elasticsearchContainer.getHttpHostAddress(), "spring.ai.vectorstore.elasticsearch.initializeSchema=true", @@ -131,8 +130,8 @@ public void propertiesTest() { new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class, - ElasticsearchVectorStoreAutoConfiguration.class, RestClientAutoConfiguration.class, - SpringAiRetryAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class)) + ElasticsearchVectorStoreAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, + OpenAiEmbeddingAutoConfiguration.class)) .withPropertyValues("spring.elasticsearch.uris=" + elasticsearchContainer.getHttpHostAddress(), "spring.ai.openai.api-key=" + System.getenv("OPENAI_API_KEY"), "spring.ai.vectorstore.elasticsearch.initializeSchema=true", diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml index 35046e17c81..6339c0e92ef 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/pom.xml @@ -83,7 +83,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -94,7 +94,7 @@ dev.gemfire gemfire-testcontainers - 2.3.0 + ${gemfire.testcontainers.version} test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/src/test/java/org/testcontainers/containers/FailureDetectingExternalResource.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/src/test/java/org/testcontainers/containers/FailureDetectingExternalResource.java new file mode 100644 index 00000000000..68c1cba9d1f --- /dev/null +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-gemfire/src/test/java/org/testcontainers/containers/FailureDetectingExternalResource.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.testcontainers.containers; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.MultipleFailureException; +import org.junit.runners.model.Statement; + +/** + * {@link TestRule} which is called before and after each test, and also is notified on + * success/failure. + * + * This mimics the behaviour of TestWatcher to some degree, but failures occurring in this + * rule do not contribute to the overall failure count (which can otherwise cause strange + * negative test success figures). + */ +public class FailureDetectingExternalResource implements TestRule { + + @Override + public Statement apply(Statement base, Description description) { + + return new Statement() { + @Override + public void evaluate() throws Throwable { + + List errors = new ArrayList(); + + starting(description); + + try { + base.evaluate(); + succeeded(description); + } + catch (Throwable e) { + errors.add(e); + failed(e, description); + } + finally { + finished(description); + } + + MultipleFailureException.assertEmpty(errors); + } + }; + } + + protected void starting(Description description) { + + } + + protected void succeeded(Description description) { + } + + protected void failed(Throwable e, Description description) { + } + + protected void finished(Description description) { + } + +} diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml index d773cc1773f..a4ba5918a58 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/pom.xml @@ -49,6 +49,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-jdbc + org.springframework.boot spring-boot-configuration-processor @@ -83,7 +87,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +97,7 @@ org.testcontainers - mariadb + testcontainers-mariadb test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/src/main/java/org/springframework/ai/vectorstore/mariadb/autoconfigure/MariaDbStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/src/main/java/org/springframework/ai/vectorstore/mariadb/autoconfigure/MariaDbStoreAutoConfiguration.java index 0c9606646f5..33b3d01b2a0 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/src/main/java/org/springframework/ai/vectorstore/mariadb/autoconfigure/MariaDbStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/src/main/java/org/springframework/ai/vectorstore/mariadb/autoconfigure/MariaDbStoreAutoConfiguration.java @@ -31,8 +31,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/src/test/java/org/springframework/ai/vectorstore/mariadb/autoconfigure/MariaDbStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/src/test/java/org/springframework/ai/vectorstore/mariadb/autoconfigure/MariaDbStoreAutoConfigurationIT.java index 09fbfd6ad0b..b708eb559ac 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/src/test/java/org/springframework/ai/vectorstore/mariadb/autoconfigure/MariaDbStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mariadb/src/test/java/org/springframework/ai/vectorstore/mariadb/autoconfigure/MariaDbStoreAutoConfigurationIT.java @@ -39,8 +39,8 @@ import org.springframework.ai.vectorstore.mariadb.MariaDBVectorStore; import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml index a90f238a48f..bd394835d03 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-milvus/pom.xml @@ -83,7 +83,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +93,7 @@ org.testcontainers - milvus + testcontainers-milvus test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml index 6d283e019ca..deab09dde74 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/pom.xml @@ -54,6 +54,16 @@ spring-boot-configuration-processor true + + org.springframework.boot + spring-boot-starter-data-mongodb + true + + + org.springframework.boot + spring-boot-starter-restclient + true + org.springframework.boot spring-boot-autoconfigure-processor @@ -83,7 +93,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +103,7 @@ org.testcontainers - mongodb + testcontainers-mongodb test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/test/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/test/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfigurationIT.java index afd2723b452..7061a617f26 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/test/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/test/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfigurationIT.java @@ -38,9 +38,9 @@ import org.springframework.ai.vectorstore.mongodb.atlas.MongoDBAtlasVectorStore; import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; -import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.data.mongodb.autoconfigure.DataMongoAutoConfiguration; +import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -63,7 +63,7 @@ class MongoDBAtlasVectorStoreAutoConfigurationIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withUserConfiguration(Config.class) - .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, + .withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, DataMongoAutoConfiguration.class, MongoDBAtlasVectorStoreAutoConfiguration.class, RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class)) .withPropertyValues("spring.data.mongodb.database=springaisample", diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml index 94654167324..9600acdf5c5 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/pom.xml @@ -49,6 +49,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-neo4j + org.springframework.boot spring-boot-configuration-processor @@ -83,7 +87,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +97,7 @@ org.testcontainers - neo4j + testcontainers-neo4j test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfiguration.java index c7b5c8db926..c416ad8cad5 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/main/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfiguration.java @@ -30,8 +30,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration; import org.springframework.context.annotation.Bean; /** diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/test/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/test/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfigurationIT.java index fd9077d6c73..47ff53d9987 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/test/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-neo4j/src/test/java/org/springframework/ai/vectorstore/neo4j/autoconfigure/Neo4jVectorStoreAutoConfigurationIT.java @@ -37,7 +37,7 @@ import org.springframework.ai.vectorstore.neo4j.Neo4jVectorStore; import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.neo4j.Neo4jAutoConfiguration; +import org.springframework.boot.neo4j.autoconfigure.Neo4jAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml index 36f569fb9c4..354b02a35a3 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/pom.xml @@ -96,23 +96,23 @@ org.testcontainers - testcontainers + testcontainers-junit-jupiter test - org.testcontainers - junit-jupiter + org.awaitility + awaitility test - org.awaitility - awaitility + org.testcontainers + testcontainers-localstack test org.opensearch opensearch-testcontainers - 2.0.1 + 4.0.0 test @@ -129,7 +129,7 @@ org.testcontainers - localstack + testcontainers-localstack test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreAutoConfigurationIT.java index afe7e178957..92fabb4c83e 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreAutoConfigurationIT.java @@ -26,7 +26,7 @@ import org.junit.jupiter.api.Test; import org.opensearch.client.opensearch.OpenSearchClient; import org.opensearch.client.transport.Transport; -import org.opensearch.testcontainers.OpensearchContainer; +import org.opensearch.testcontainers.OpenSearchContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; @@ -60,7 +60,7 @@ class OpenSearchVectorStoreAutoConfigurationIT { @Container - private static final OpensearchContainer opensearchContainer = new OpensearchContainer<>( + private static final OpenSearchContainer opensearchContainer = new OpenSearchContainer<>( DockerImageName.parse("opensearchproject/opensearch:2.13.0")); private static final String DOCUMENT_INDEX = "auto-spring-ai-document-index"; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreNonAwsFallbackIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreNonAwsFallbackIT.java index ea51b6d0fd4..e59e083db1e 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreNonAwsFallbackIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-opensearch/src/test/java/org/springframework/ai/vectorstore/opensearch/autoconfigure/OpenSearchVectorStoreNonAwsFallbackIT.java @@ -22,7 +22,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; -import org.opensearch.testcontainers.OpensearchContainer; +import org.opensearch.testcontainers.OpenSearchContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; @@ -44,7 +44,7 @@ class OpenSearchVectorStoreNonAwsFallbackIT { @Container - private static final OpensearchContainer opensearchContainer = new OpensearchContainer<>( + private static final OpenSearchContainer opensearchContainer = new OpenSearchContainer<>( DockerImageName.parse("opensearchproject/opensearch:2.13.0")); private static final String DOCUMENT_INDEX = "nonaws-spring-ai-document-index"; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml index 66c6202bd82..f978aadcbb0 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/pom.xml @@ -49,6 +49,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-jdbc + org.springframework.boot spring-boot-configuration-processor @@ -83,7 +87,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +97,7 @@ org.testcontainers - oracle-free + testcontainers-oracle-free test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/src/main/java/org/springframework/ai/vectorstore/oracle/autoconfigure/OracleVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/src/main/java/org/springframework/ai/vectorstore/oracle/autoconfigure/OracleVectorStoreAutoConfiguration.java index eb31a4dda94..2dacb76b2a9 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/src/main/java/org/springframework/ai/vectorstore/oracle/autoconfigure/OracleVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/src/main/java/org/springframework/ai/vectorstore/oracle/autoconfigure/OracleVectorStoreAutoConfiguration.java @@ -31,8 +31,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/src/test/java/org/springframework/ai/vectorstore/oracle/autoconfigure/OracleVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/src/test/java/org/springframework/ai/vectorstore/oracle/autoconfigure/OracleVectorStoreAutoConfigurationIT.java index 1e1261389bd..61ca7179ae8 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/src/test/java/org/springframework/ai/vectorstore/oracle/autoconfigure/OracleVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-oracle/src/test/java/org/springframework/ai/vectorstore/oracle/autoconfigure/OracleVectorStoreAutoConfigurationIT.java @@ -38,8 +38,8 @@ import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.ai.vectorstore.oracle.OracleVectorStore; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml index 9bc06e4b948..4ea2663a80b 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/pom.xml @@ -49,6 +49,10 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-jdbc + org.springframework.boot spring-boot-configuration-processor @@ -67,23 +71,23 @@ test - org.springframework.boot - spring-boot-starter-test + org.testcontainers + testcontainers-junit-jupiter test - org.springframework.boot - spring-boot-testcontainers + org.awaitility + awaitility test - org.testcontainers - testcontainers + org.postgresql + postgresql test org.testcontainers - junit-jupiter + testcontainers-postgresql test @@ -97,10 +101,5 @@ ${project.parent.version} test - - org.testcontainers - postgresql - test - diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/src/main/java/org/springframework/ai/vectorstore/pgvector/autoconfigure/PgVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/src/main/java/org/springframework/ai/vectorstore/pgvector/autoconfigure/PgVectorStoreAutoConfiguration.java index 839d88a37e9..f7f5f297d75 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/src/main/java/org/springframework/ai/vectorstore/pgvector/autoconfigure/PgVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/src/main/java/org/springframework/ai/vectorstore/pgvector/autoconfigure/PgVectorStoreAutoConfiguration.java @@ -31,8 +31,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/src/test/java/org/springframework/ai/vectorstore/pgvector/autoconfigure/PgVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/src/test/java/org/springframework/ai/vectorstore/pgvector/autoconfigure/PgVectorStoreAutoConfigurationIT.java index 3958c109cdb..efca91bf3f7 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/src/test/java/org/springframework/ai/vectorstore/pgvector/autoconfigure/PgVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector/src/test/java/org/springframework/ai/vectorstore/pgvector/autoconfigure/PgVectorStoreAutoConfigurationIT.java @@ -39,8 +39,8 @@ import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.ai.vectorstore.pgvector.PgVectorStore; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml index d37ab34decd..227a86fcc4d 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pinecone/pom.xml @@ -83,7 +83,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml index 4123208f072..089309205c2 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-qdrant/pom.xml @@ -88,17 +88,17 @@ org.testcontainers - testcontainers + testcontainers-qdrant test - org.testcontainers - qdrant + org.awaitility + awaitility test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml index a01969180e1..6b57660413e 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/pom.xml @@ -49,6 +49,11 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-data-redis + true + org.springframework.boot spring-boot-configuration-processor @@ -56,7 +61,7 @@ org.springframework.boot - spring-boot-autoconfigure-processor + spring-boot-data-redis true @@ -83,7 +88,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java index abb5b629bb4..d63719c13c8 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/main/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfiguration.java @@ -34,8 +34,8 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @@ -47,7 +47,7 @@ * @author Soby Chacko * @author Jihoon Kim */ -@AutoConfiguration(after = RedisAutoConfiguration.class) +@AutoConfiguration(after = DataRedisAutoConfiguration.class) @ConditionalOnClass({ JedisPooled.class, JedisConnectionFactory.class, RedisVectorStore.class, EmbeddingModel.class }) @ConditionalOnBean(JedisConnectionFactory.class) @EnableConfigurationProperties(RedisVectorStoreProperties.class) diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/test/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/test/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfigurationIT.java index 40d3bce6e93..9e19525a3db 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/test/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfigurationIT.java +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-redis/src/test/java/org/springframework/ai/vectorstore/redis/autoconfigure/RedisVectorStoreAutoConfigurationIT.java @@ -36,7 +36,7 @@ import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; import org.springframework.ai.vectorstore.redis.RedisVectorStore; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -58,12 +58,14 @@ class RedisVectorStoreAutoConfigurationIT { RedisStackContainer.DEFAULT_IMAGE_NAME.withTag(RedisStackContainer.DEFAULT_TAG)); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class, RedisVectorStoreAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(DataRedisAutoConfiguration.class, RedisVectorStoreAutoConfiguration.class)) .withUserConfiguration(Config.class) .withPropertyValues("spring.data.redis.url=" + redisContainer.getRedisURI()) .withPropertyValues("spring.ai.vectorstore.redis.initialize-schema=true") .withPropertyValues("spring.ai.vectorstore.redis.index=myIdx") - .withPropertyValues("spring.ai.vectorstore.redis.prefix=doc:"); + .withPropertyValues("spring.ai.vectorstore.redis.prefix=doc:") + .withPropertyValues("spring.data.redis.client-type=jedis"); List documents = List.of( new Document(ResourceUtils.getText("classpath:/test/data/spring.ai.txt"), Map.of("spring", "great")), diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml index 31b72e1f345..a78f195e0c0 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-typesense/pom.xml @@ -83,7 +83,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +93,7 @@ org.testcontainers - typesense + testcontainers-typesense test diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml index ef5b555402f..62810f13e96 100644 --- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml +++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate/pom.xml @@ -83,7 +83,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -93,7 +93,7 @@ org.testcontainers - weaviate + testcontainers-weaviate test diff --git a/document-readers/pdf-reader/pom.xml b/document-readers/pdf-reader/pom.xml index 927423f74e0..ad84d10ff84 100644 --- a/document-readers/pdf-reader/pom.xml +++ b/document-readers/pdf-reader/pom.xml @@ -80,10 +80,10 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test - \ No newline at end of file + diff --git a/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml index 99a26be2e85..e3b3434a78f 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml +++ b/memory/repository/spring-ai-model-chat-memory-repository-cassandra/pom.xml @@ -71,13 +71,13 @@ org.testcontainers - cassandra + testcontainers-cassandra test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/memory/repository/spring-ai-model-chat-memory-repository-cassandra/src/test/java/org/springframework/ai/chat/memory/repository/cassandra/CassandraChatMemoryRepositoryIT.java b/memory/repository/spring-ai-model-chat-memory-repository-cassandra/src/test/java/org/springframework/ai/chat/memory/repository/cassandra/CassandraChatMemoryRepositoryIT.java index 455aedcd094..217d23b18c0 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-cassandra/src/test/java/org/springframework/ai/chat/memory/repository/cassandra/CassandraChatMemoryRepositoryIT.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-cassandra/src/test/java/org/springframework/ai/chat/memory/repository/cassandra/CassandraChatMemoryRepositoryIT.java @@ -38,8 +38,6 @@ import org.springframework.ai.chat.messages.MessageType; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -232,7 +230,6 @@ void clear_shouldDeleteMessages() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml index d24854cb3f7..420d77b50fe 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml +++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/pom.xml @@ -127,37 +127,36 @@ org.testcontainers testcontainers-oracle-free - 2.0.1 test org.testcontainers - postgresql + testcontainers-postgresql test org.testcontainers - mariadb + testcontainers-mariadb test org.testcontainers - mysql + testcontainers-mysql test org.testcontainers - mssqlserver + testcontainers-mssqlserver test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/AbstractJdbcChatMemoryRepositoryIT.java b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/AbstractJdbcChatMemoryRepositoryIT.java index 507aee30743..27ac1f2c70e 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/AbstractJdbcChatMemoryRepositoryIT.java +++ b/memory/repository/spring-ai-model-chat-memory-repository-jdbc/src/test/java/org/springframework/ai/chat/memory/repository/jdbc/AbstractJdbcChatMemoryRepositoryIT.java @@ -34,8 +34,8 @@ import org.springframework.ai.chat.messages.UserMessage; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.JdbcTemplateAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ContextConfiguration; diff --git a/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml b/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml index 050d9438840..133df80df78 100644 --- a/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml +++ b/memory/repository/spring-ai-model-chat-memory-repository-neo4j/pom.xml @@ -81,17 +81,17 @@ neo4j-java-driver - - org.testcontainers - neo4j - test - + + org.testcontainers + testcontainers-neo4j + test + - - org.testcontainers - junit-jupiter - test - + + org.testcontainers + testcontainers-junit-jupiter + test + diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java index 40010e11ad9..f7e0567fd03 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/AnthropicChatModel.java @@ -78,11 +78,12 @@ import org.springframework.ai.support.UsageCalculator; import org.springframework.ai.tool.definition.ToolDefinition; import org.springframework.ai.util.json.JsonParser; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; /** @@ -193,8 +194,19 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons this.observationRegistry) .observe(() -> { - ResponseEntity completionEntity = this.retryTemplate.execute( - ctx -> this.anthropicApi.chatCompletionEntity(request, this.getAdditionalHttpHeaders(prompt))); + ResponseEntity completionEntity = null; + try { + completionEntity = this.retryTemplate.execute(() -> this.anthropicApi.chatCompletionEntity(request, + this.getAdditionalHttpHeaders(prompt))); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } AnthropicApi.ChatCompletionResponse completionResponse = completionEntity.getBody(); AnthropicApi.Usage usage = completionResponse.usage(); @@ -523,14 +535,15 @@ else if (mimeType.contains("pdf")) { + ". Supported types are: images (image/*) and PDF documents (application/pdf)"); } - private MultiValueMap getAdditionalHttpHeaders(Prompt prompt) { + private HttpHeaders getAdditionalHttpHeaders(Prompt prompt) { Map headers = new HashMap<>(this.defaultOptions.getHttpHeaders()); if (prompt.getOptions() != null && prompt.getOptions() instanceof AnthropicChatOptions chatOptions) { headers.putAll(chatOptions.getHttpHeaders()); } - return CollectionUtils.toMultiValueMap( - headers.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> List.of(e.getValue())))); + HttpHeaders httpHeaders = new HttpHeaders(); + headers.forEach(httpHeaders::add); + return httpHeaders; } Prompt buildRequestPrompt(Prompt prompt) { 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 497d43acc4a..e18c4d38801 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 @@ -48,8 +48,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; @@ -148,6 +146,20 @@ private AnthropicApi(String baseUrl, String completionsPath, ApiKey anthropicApi .build(); } + /** + * Create a new client api. + * @param completionsPath path to append to the base URL. + * @param restClient RestClient instance. + * @param webClient WebClient instance. + * @param apiKey Anthropic api Key. + */ + public AnthropicApi(String completionsPath, RestClient restClient, WebClient webClient, ApiKey apiKey) { + this.completionsPath = completionsPath; + this.restClient = restClient; + this.webClient = webClient; + this.apiKey = apiKey; + } + /** * Creates a model response for the given chat conversation. * @param chatRequest The chat completion request. @@ -155,7 +167,7 @@ private AnthropicApi(String baseUrl, String completionsPath, ApiKey anthropicApi * status code and headers. */ public ResponseEntity chatCompletionEntity(ChatCompletionRequest chatRequest) { - return chatCompletionEntity(chatRequest, new LinkedMultiValueMap<>()); + return chatCompletionEntity(chatRequest, new HttpHeaders()); } /** @@ -166,7 +178,7 @@ public ResponseEntity chatCompletionEntity(ChatCompletio * status code and headers. */ public ResponseEntity chatCompletionEntity(ChatCompletionRequest chatRequest, - MultiValueMap additionalHttpHeader) { + HttpHeaders additionalHttpHeader) { Assert.notNull(chatRequest, "The request body can not be null."); Assert.isTrue(!chatRequest.stream(), "Request must set the stream property to false."); @@ -192,7 +204,7 @@ public ResponseEntity chatCompletionEntity(ChatCompletio * @return Returns a {@link Flux} stream from chat completion chunks. */ public Flux chatCompletionStream(ChatCompletionRequest chatRequest) { - return chatCompletionStream(chatRequest, new LinkedMultiValueMap<>()); + return chatCompletionStream(chatRequest, new HttpHeaders()); } /** @@ -203,7 +215,7 @@ public Flux chatCompletionStream(ChatCompletionRequest c * @return Returns a {@link Flux} stream from chat completion chunks. */ public Flux chatCompletionStream(ChatCompletionRequest chatRequest, - MultiValueMap additionalHttpHeader) { + HttpHeaders additionalHttpHeader) { Assert.notNull(chatRequest, "The request body can not be null."); Assert.isTrue(chatRequest.stream(), "Request must set the stream property to true."); @@ -256,7 +268,7 @@ public Flux chatCompletionStream(ChatCompletionRequest c } private void addDefaultHeadersIfMissing(HttpHeaders headers) { - if (!headers.containsKey(HEADER_X_API_KEY)) { + if (!headers.containsHeader(HEADER_X_API_KEY)) { String apiKeyValue = this.apiKey.getValue(); if (StringUtils.hasText(apiKeyValue)) { headers.add(HEADER_X_API_KEY, apiKeyValue); diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java index 910f572d208..382dbe6f64f 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java @@ -40,7 +40,7 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; @@ -171,7 +171,7 @@ public AnthropicApi anthropicApi() { public AnthropicChatModel anthropicChatModel(AnthropicApi anthropicApi, TestObservationRegistry observationRegistry) { return new AnthropicChatModel(anthropicApi, AnthropicChatOptions.builder().build(), - ToolCallingManager.builder().build(), RetryTemplate.defaultInstance(), observationRegistry); + ToolCallingManager.builder().build(), new RetryTemplate(), observationRegistry); } } diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiBuilderTests.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiBuilderTests.java index 1f42a35a67e..6b2a1caf8d1 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiBuilderTests.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/api/AnthropicApiBuilderTests.java @@ -37,8 +37,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -304,7 +302,7 @@ void dynamicApiKeyRestClientWithAdditionalApiKeyHeader() throws InterruptedExcep .temperature(0.8) .messages(List.of(chatCompletionMessage)) .build(); - MultiValueMap additionalHeaders = new LinkedMultiValueMap<>(); + var additionalHeaders = new HttpHeaders(); additionalHeaders.add("x-api-key", "additional-key"); ResponseEntity response = api.chatCompletionEntity(request, additionalHeaders); @@ -403,7 +401,7 @@ void dynamicApiKeyWebClientWithAdditionalApiKey() throws InterruptedException { .messages(List.of(chatCompletionMessage)) .stream(true) .build(); - MultiValueMap additionalHeaders = new LinkedMultiValueMap<>(); + var additionalHeaders = new HttpHeaders(); additionalHeaders.add("x-api-key", "additional-key"); api.chatCompletionStream(request, additionalHeaders).collectList().block(); diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java index fba44ffd4ce..c89a1c0c812 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/DeepSeekChatModel.java @@ -66,8 +66,9 @@ import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.support.UsageCalculator; import org.springframework.ai.tool.definition.ToolDefinition; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -165,8 +166,18 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons this.observationRegistry) .observe(() -> { - ResponseEntity completionEntity = this.retryTemplate - .execute(ctx -> this.deepSeekApi.chatCompletionEntity(request)); + ResponseEntity completionEntity = null; + try { + completionEntity = this.retryTemplate.execute(() -> this.deepSeekApi.chatCompletionEntity(request)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } var chatCompletion = completionEntity.getBody(); diff --git a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekApi.java b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekApi.java index 13415829854..13667f0e3d5 100644 --- a/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekApi.java +++ b/models/spring-ai-deepseek/src/main/java/org/springframework/ai/deepseek/api/DeepSeekApi.java @@ -41,8 +41,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -67,7 +65,7 @@ public class DeepSeekApi { private final WebClient webClient; - private DeepSeekStreamFunctionCallingHelper chunkMerger = new DeepSeekStreamFunctionCallingHelper(); + private final DeepSeekStreamFunctionCallingHelper chunkMerger = new DeepSeekStreamFunctionCallingHelper(); /** * Create a new chat completion api. @@ -80,7 +78,7 @@ public class DeepSeekApi { * @param webClientBuilder WebClient builder. * @param responseErrorHandler Response error handler. */ - public DeepSeekApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, String completionsPath, + public DeepSeekApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, String completionsPath, String betaPrefixPath, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) { @@ -90,21 +88,39 @@ public DeepSeekApi(String baseUrl, ApiKey apiKey, MultiValueMap this.completionsPath = completionsPath; this.betaPrefixPath = betaPrefixPath; - // @formatter:off + Consumer 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) .defaultStatusHandler(responseErrorHandler) .build(); - this.webClient = webClientBuilder - .baseUrl(baseUrl) - .defaultHeaders(finalHeaders) - .build(); // @formatter:on + this.webClient = webClientBuilder.baseUrl(baseUrl).defaultHeaders(finalHeaders).build(); + + } + + /** + * Create a new chat completion api. + * @param completionsPath the path to the chat completions endpoint. + * @param betaPrefixPath the prefix path to the beta feature endpoint. + * @param restClient RestClient instance. + * @param webClient WebClient instance. + */ + public DeepSeekApi(String completionsPath, String betaPrefixPath, RestClient restClient, WebClient webClient) { + + Assert.hasText(completionsPath, "Completions Path must not be null"); + Assert.hasText(betaPrefixPath, "Beta feature path must not be null"); + Assert.notNull(restClient, "RestClient must not be null"); + Assert.notNull(webClient, "WebClient must not be null"); + + this.completionsPath = completionsPath; + this.betaPrefixPath = betaPrefixPath; + this.restClient = restClient; + this.webClient = webClient; } /** @@ -132,7 +148,7 @@ public ResponseEntity chatCompletionEntity(ChatCompletionRequest * @return Returns a {@link Flux} stream from chat completion chunks. */ public Flux chatCompletionStream(ChatCompletionRequest chatRequest) { - return chatCompletionStream(chatRequest, new LinkedMultiValueMap<>()); + return chatCompletionStream(chatRequest, new HttpHeaders()); } /** @@ -144,7 +160,7 @@ public Flux chatCompletionStream(ChatCompletionRequest chat * @return Returns a {@link Flux} stream from chat completion chunks. */ public Flux chatCompletionStream(ChatCompletionRequest chatRequest, - MultiValueMap additionalHttpHeader) { + HttpHeaders additionalHttpHeader) { Assert.notNull(chatRequest, "The request body can not be null."); Assert.isTrue(chatRequest.stream(), "Request must set the stream property to true."); @@ -153,7 +169,7 @@ public Flux chatCompletionStream(ChatCompletionRequest chat 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) @@ -911,7 +927,7 @@ public static final class Builder { private ApiKey apiKey; - private MultiValueMap headers = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private String completionsPath = org.springframework.ai.deepseek.api.common.DeepSeekConstants.DEFAULT_COMPLETIONS_PATH; @@ -941,7 +957,7 @@ public Builder apiKey(String simpleApiKey) { return this; } - public Builder headers(MultiValueMap headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "headers cannot be null"); this.headers = headers; return this; diff --git a/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekRetryTests.java b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekRetryTests.java index 35f24eaadeb..511ee734806 100644 --- a/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekRetryTests.java +++ b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/DeepSeekRetryTests.java @@ -34,11 +34,11 @@ import org.springframework.ai.deepseek.api.DeepSeekApi.ChatCompletionRequest; import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.TransientAiException; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import org.springframework.http.ResponseEntity; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -62,7 +62,7 @@ public class DeepSeekRetryTests { public void beforeEach() { RetryTemplate retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - retryTemplate.registerListener(this.retryListener); + retryTemplate.setRetryListener(this.retryListener); this.chatModel = DeepSeekChatModel.builder() .deepSeekApi(this.deepSeekApi) @@ -88,7 +88,7 @@ public void deepSeekChatTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -116,7 +116,7 @@ public void deepSeekChatStreamTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -134,14 +134,15 @@ private static class TestRetryListener implements RetryListener { int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void beforeRetry(final RetryPolicy retryPolicy, final Retryable retryable) { + // Count each retry attempt + this.onErrorRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } } diff --git a/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/chat/DeepSeekChatModelObservationIT.java b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/chat/DeepSeekChatModelObservationIT.java index 00e8732dade..b2900e479cb 100644 --- a/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/chat/DeepSeekChatModelObservationIT.java +++ b/models/spring-ai-deepseek/src/test/java/org/springframework/ai/deepseek/chat/DeepSeekChatModelObservationIT.java @@ -40,7 +40,7 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.ai.chat.observation.ChatModelObservationDocumentation.HighCardinalityKeyNames; @@ -172,7 +172,7 @@ public DeepSeekApi deepSeekApi() { public DeepSeekChatModel deepSeekChatModel(DeepSeekApi deepSeekApi, TestObservationRegistry observationRegistry) { return new DeepSeekChatModel(deepSeekApi, DeepSeekChatOptions.builder().build(), - ToolCallingManager.builder().build(), RetryTemplate.defaultInstance(), observationRegistry); + ToolCallingManager.builder().build(), new RetryTemplate(), observationRegistry); } } diff --git a/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/ElevenLabsTextToSpeechModel.java b/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/ElevenLabsTextToSpeechModel.java index 68ed07568a8..b8220e87065 100644 --- a/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/ElevenLabsTextToSpeechModel.java +++ b/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/ElevenLabsTextToSpeechModel.java @@ -29,7 +29,8 @@ import org.springframework.ai.audio.tts.TextToSpeechResponse; import org.springframework.ai.elevenlabs.api.ElevenLabsApi; import org.springframework.ai.retry.RetryUtils; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -73,15 +74,26 @@ public static Builder builder() { public TextToSpeechResponse call(TextToSpeechPrompt prompt) { RequestContext requestContext = prepareRequest(prompt); - byte[] audioData = this.retryTemplate.execute(context -> { - var response = this.elevenLabsApi.textToSpeech(requestContext.request, requestContext.voiceId, - requestContext.queryParameters); - if (response.getBody() == null) { - logger.warn("No speech response returned for request: {}", requestContext.request); - return new byte[0]; + byte[] audioData = null; + try { + audioData = this.retryTemplate.execute(() -> { + var response = this.elevenLabsApi.textToSpeech(requestContext.request, requestContext.voiceId, + requestContext.queryParameters); + if (response.getBody() == null) { + logger.warn("No speech response returned for request: {}", requestContext.request); + return new byte[0]; + } + return response.getBody(); + }); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); } - return response.getBody(); - }); + } return new TextToSpeechResponse(List.of(new Speech(audioData))); } @@ -90,9 +102,19 @@ public TextToSpeechResponse call(TextToSpeechPrompt prompt) { public Flux stream(TextToSpeechPrompt prompt) { RequestContext requestContext = prepareRequest(prompt); - return this.retryTemplate.execute(context -> this.elevenLabsApi - .textToSpeechStream(requestContext.request, requestContext.voiceId, requestContext.queryParameters) - .map(entity -> new TextToSpeechResponse(List.of(new Speech(entity.getBody()))))); + try { + return this.retryTemplate.execute(() -> this.elevenLabsApi + .textToSpeechStream(requestContext.request, requestContext.voiceId, requestContext.queryParameters) + .map(entity -> new TextToSpeechResponse(List.of(new Speech(entity.getBody()))))); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } } private RequestContext prepareRequest(TextToSpeechPrompt prompt) { diff --git a/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/api/ElevenLabsApi.java b/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/api/ElevenLabsApi.java index 10ce0349070..2f20727e17c 100644 --- a/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/api/ElevenLabsApi.java +++ b/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/api/ElevenLabsApi.java @@ -33,7 +33,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; @@ -62,15 +61,14 @@ public final class ElevenLabsApi { * @param webClientBuilder A builder for the Spring WebClient. * @param responseErrorHandler A custom error handler for API responses. */ - private ElevenLabsApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, - RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, - ResponseErrorHandler responseErrorHandler) { + private ElevenLabsApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, RestClient.Builder restClientBuilder, + WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) { Consumer jsonContentHeaders = h -> { if (!(apiKey instanceof NoopApiKey)) { h.set("xi-api-key", apiKey.getValue()); } - h.addAll(headers); + h.addAll(HttpHeaders.readOnlyHttpHeaders(headers)); h.setContentType(MediaType.APPLICATION_JSON); }; @@ -82,6 +80,16 @@ private ElevenLabsApi(String baseUrl, ApiKey apiKey, MultiValueMap headers = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private RestClient.Builder restClientBuilder = RestClient.builder(); @@ -357,7 +365,7 @@ public Builder apiKey(String simpleApiKey) { return this; } - public Builder headers(MultiValueMap headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "headers cannot be null"); this.headers = headers; return this; diff --git a/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/api/ElevenLabsVoicesApi.java b/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/api/ElevenLabsVoicesApi.java index 3f6f6377937..766191a8a73 100644 --- a/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/api/ElevenLabsVoicesApi.java +++ b/models/spring-ai-elevenlabs/src/main/java/org/springframework/ai/elevenlabs/api/ElevenLabsVoicesApi.java @@ -32,8 +32,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; @@ -56,13 +54,13 @@ public class ElevenLabsVoicesApi { * @param restClientBuilder A builder for the Spring RestClient. * @param responseErrorHandler A custom error handler for API responses. */ - public ElevenLabsVoicesApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { + public ElevenLabsVoicesApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, RestClient.Builder restClientBuilder, + ResponseErrorHandler responseErrorHandler) { Consumer jsonContentHeaders = h -> { if (!(apiKey instanceof NoopApiKey)) { h.set("xi-api-key", apiKey.getValue()); } - h.addAll(headers); + h.addAll(HttpHeaders.readOnlyHttpHeaders(headers)); h.setContentType(MediaType.APPLICATION_JSON); }; @@ -73,6 +71,14 @@ public ElevenLabsVoicesApi(String baseUrl, ApiKey apiKey, MultiValueMap headers = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private RestClient.Builder restClientBuilder = RestClient.builder(); @@ -423,7 +429,7 @@ public Builder apiKey(String simpleApiKey) { return this; } - public Builder headers(MultiValueMap headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "headers cannot be null"); this.headers = headers; return this; diff --git a/models/spring-ai-google-genai-embedding/src/main/java/org/springframework/ai/google/genai/text/GoogleGenAiTextEmbeddingModel.java b/models/spring-ai-google-genai-embedding/src/main/java/org/springframework/ai/google/genai/text/GoogleGenAiTextEmbeddingModel.java index 46c87cd6862..049720082bf 100644 --- a/models/spring-ai-google-genai-embedding/src/main/java/org/springframework/ai/google/genai/text/GoogleGenAiTextEmbeddingModel.java +++ b/models/spring-ai-google-genai-embedding/src/main/java/org/springframework/ai/google/genai/text/GoogleGenAiTextEmbeddingModel.java @@ -46,7 +46,8 @@ import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.observation.conventions.AiProvider; import org.springframework.ai.retry.RetryUtils; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -168,8 +169,19 @@ public EmbeddingResponse call(EmbeddingRequest request) { } // Call the embedding API with retry - EmbedContentResponse embeddingResponse = this.retryTemplate - .execute(context -> this.genAiClient.models.embedContent(modelName, validTexts, config)); + EmbedContentResponse embeddingResponse = null; + try { + embeddingResponse = this.retryTemplate + .execute(() -> this.genAiClient.models.embedContent(modelName, validTexts, config)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } // Process the response // Note: We need to handle the case where some texts were filtered out diff --git a/models/spring-ai-google-genai-embedding/src/test/java/org/springframework/ai/google/genai/text/GoogleGenAiTextEmbeddingRetryTests.java b/models/spring-ai-google-genai-embedding/src/test/java/org/springframework/ai/google/genai/text/GoogleGenAiTextEmbeddingRetryTests.java index 4dc9fce14c5..6d2136aca1a 100644 --- a/models/spring-ai-google-genai-embedding/src/test/java/org/springframework/ai/google/genai/text/GoogleGenAiTextEmbeddingRetryTests.java +++ b/models/spring-ai-google-genai-embedding/src/test/java/org/springframework/ai/google/genai/text/GoogleGenAiTextEmbeddingRetryTests.java @@ -36,10 +36,10 @@ import org.springframework.ai.google.genai.GoogleGenAiEmbeddingConnectionDetails; import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.TransientAiException; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -75,7 +75,7 @@ public class GoogleGenAiTextEmbeddingRetryTests { public void setUp() throws Exception { this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - this.retryTemplate.registerListener(this.retryListener); + this.retryTemplate.setRetryListener(this.retryListener); // Create a mock Client and use reflection to set the models field this.mockGenAiClient = mock(Client.class); @@ -114,7 +114,7 @@ public void vertexAiEmbeddingTransientError() { assertThat(result).isNotNull(); assertThat(result.getResults()).hasSize(1); assertThat(result.getResults().get(0).getOutput()).isEqualTo(new float[] { 9.9f, 8.8f }); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); verify(this.mockModels, times(3)).embedContent(anyString(), any(List.class), any(EmbedContentConfig.class)); @@ -143,14 +143,15 @@ private static class TestRetryListener implements RetryListener { int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void beforeRetry(final RetryPolicy retryPolicy, final Retryable retryable) { + // Count each retry attempt + this.onErrorRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } } diff --git a/models/spring-ai-google-genai-embedding/src/test/java/org/springframework/ai/google/genai/text/TestGoogleGenAiTextEmbeddingModel.java b/models/spring-ai-google-genai-embedding/src/test/java/org/springframework/ai/google/genai/text/TestGoogleGenAiTextEmbeddingModel.java index 44a06031afc..9836f63ec30 100644 --- a/models/spring-ai-google-genai-embedding/src/test/java/org/springframework/ai/google/genai/text/TestGoogleGenAiTextEmbeddingModel.java +++ b/models/spring-ai-google-genai-embedding/src/test/java/org/springframework/ai/google/genai/text/TestGoogleGenAiTextEmbeddingModel.java @@ -17,7 +17,7 @@ package org.springframework.ai.google.genai.text; import org.springframework.ai.google.genai.GoogleGenAiEmbeddingConnectionDetails; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; /** * Test implementation of GoogleGenAiTextEmbeddingModel that uses a mock connection for diff --git a/models/spring-ai-google-genai/src/main/java/org/springframework/ai/google/genai/GoogleGenAiChatModel.java b/models/spring-ai-google-genai/src/main/java/org/springframework/ai/google/genai/GoogleGenAiChatModel.java index 8e38008e859..70f5c1385dc 100644 --- a/models/spring-ai-google-genai/src/main/java/org/springframework/ai/google/genai/GoogleGenAiChatModel.java +++ b/models/spring-ai-google-genai/src/main/java/org/springframework/ai/google/genai/GoogleGenAiChatModel.java @@ -87,8 +87,9 @@ import org.springframework.ai.support.UsageCalculator; import org.springframework.ai.tool.definition.ToolDefinition; import org.springframework.beans.factory.DisposableBean; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.lang.NonNull; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -404,30 +405,41 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry) - .observe(() -> this.retryTemplate.execute(context -> { - - var geminiRequest = createGeminiRequest(prompt); - - GenerateContentResponse generateContentResponse = this.getContentResponse(geminiRequest); + .observe(() -> { + try { + return this.retryTemplate.execute(() -> { + + var geminiRequest = createGeminiRequest(prompt); + + GenerateContentResponse generateContentResponse = this.getContentResponse(geminiRequest); + + List generations = generateContentResponse.candidates() + .orElse(List.of()) + .stream() + .map(this::responseCandidateToGeneration) + .flatMap(List::stream) + .toList(); + + var usage = generateContentResponse.usageMetadata(); + GoogleGenAiChatOptions options = (GoogleGenAiChatOptions) prompt.getOptions(); + Usage currentUsage = (usage.isPresent()) ? getDefaultUsage(usage.get(), options) + : getDefaultUsage(null, options); + Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(currentUsage, previousChatResponse); + ChatResponse chatResponse = new ChatResponse(generations, + toChatResponseMetadata(cumulativeUsage, generateContentResponse.modelVersion().get())); + + observationContext.setResponse(chatResponse); + return chatResponse; + }); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } - List generations = generateContentResponse.candidates() - .orElse(List.of()) - .stream() - .map(this::responseCandidateToGeneration) - .flatMap(List::stream) - .toList(); - - var usage = generateContentResponse.usageMetadata(); - GoogleGenAiChatOptions options = (GoogleGenAiChatOptions) prompt.getOptions(); - Usage currentUsage = (usage.isPresent()) ? getDefaultUsage(usage.get(), options) - : getDefaultUsage(null, options); - Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(currentUsage, previousChatResponse); - ChatResponse chatResponse = new ChatResponse(generations, - toChatResponseMetadata(cumulativeUsage, generateContentResponse.modelVersion().get())); - - observationContext.setResponse(chatResponse); - return chatResponse; - })); + throw new RuntimeException(e); + } + }); if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); diff --git a/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiChatModelCachedContentTests.java b/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiChatModelCachedContentTests.java index af4ea6679e0..f2340cc79d6 100644 --- a/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiChatModelCachedContentTests.java +++ b/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiChatModelCachedContentTests.java @@ -36,7 +36,7 @@ import org.springframework.ai.google.genai.cache.GoogleGenAiCachedContent; import org.springframework.ai.google.genai.cache.GoogleGenAiCachedContentService; import org.springframework.ai.retry.RetryUtils; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; diff --git a/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiChatModelExtendedUsageTests.java b/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiChatModelExtendedUsageTests.java index 27e3ccf24a5..1b75b991a3a 100644 --- a/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiChatModelExtendedUsageTests.java +++ b/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiChatModelExtendedUsageTests.java @@ -41,7 +41,7 @@ import org.springframework.ai.google.genai.metadata.GoogleGenAiTrafficType; import org.springframework.ai.google.genai.metadata.GoogleGenAiUsage; import org.springframework.ai.retry.RetryUtils; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; diff --git a/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiRetryTests.java b/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiRetryTests.java index 4170c992c64..901036959bc 100644 --- a/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiRetryTests.java +++ b/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/GoogleGenAiRetryTests.java @@ -27,10 +27,10 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.ai.retry.RetryUtils; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; /** * @author Mark Pollack @@ -55,7 +55,7 @@ public class GoogleGenAiRetryTests { public void setUp() { this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - this.retryTemplate.registerListener(this.retryListener); + this.retryTemplate.setRetryListener(this.retryListener); this.chatModel = new org.springframework.ai.google.genai.TestGoogleGenAiGeminiChatModel(this.genAiClient, GoogleGenAiChatOptions.builder() @@ -95,14 +95,15 @@ private static class TestRetryListener implements RetryListener { int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void beforeRetry(final RetryPolicy retryPolicy, final Retryable retryable) { + // Count each retry attempt + this.onErrorRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } } diff --git a/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/TestGoogleGenAiGeminiChatModel.java b/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/TestGoogleGenAiGeminiChatModel.java index 6c63133fd1f..2730b73e501 100644 --- a/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/TestGoogleGenAiGeminiChatModel.java +++ b/models/spring-ai-google-genai/src/test/java/org/springframework/ai/google/genai/TestGoogleGenAiGeminiChatModel.java @@ -20,7 +20,7 @@ import com.google.genai.types.GenerateContentResponse; import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; /** * @author Mark Pollack diff --git a/models/spring-ai-huggingface/pom.xml b/models/spring-ai-huggingface/pom.xml index fae8ecbd802..9c02044c248 100644 --- a/models/spring-ai-huggingface/pom.xml +++ b/models/spring-ai-huggingface/pom.xml @@ -90,7 +90,7 @@ io.swagger.codegen.v3 swagger-codegen-maven-plugin - 3.0.64 + 3.0.75 diff --git a/models/spring-ai-huggingface/src/main/resources/handlebars/Java/libraries/resttemplate/ApiClient.mustache b/models/spring-ai-huggingface/src/main/resources/handlebars/Java/libraries/resttemplate/ApiClient.mustache new file mode 100644 index 00000000000..b08e07bd54f --- /dev/null +++ b/models/spring-ai-huggingface/src/main/resources/handlebars/Java/libraries/resttemplate/ApiClient.mustache @@ -0,0 +1,651 @@ +package {{invokerPackage}}; + +{{#withXml}} +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; +{{/withXml}} +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.InvalidMediaTypeException; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.RequestEntity.BodyBuilder; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +{{#withXml}} +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; +{{/withXml}} +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; +{{#threetenbp}} +import org.threeten.bp.*; +import com.fasterxml.jackson.datatype.threetenbp.ThreeTenModule; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import com.fasterxml.jackson.databind.ObjectMapper; +{{/threetenbp}} + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TimeZone; + +import {{invokerPackage}}.auth.Authentication; +import {{invokerPackage}}.auth.HttpBasicAuth; +import {{invokerPackage}}.auth.ApiKeyAuth; +import {{invokerPackage}}.auth.OAuth; + +{{>generatedAnnotation}} +@Component("{{invokerPackage}}.ApiClient") +public class ApiClient { + public enum CollectionFormat { + CSV(","), TSV("\t"), SSV(" "), PIPES("|"), MULTI(null); + + private final String separator; + private CollectionFormat(String separator) { + this.separator = separator; + } + + private String collectionToString(Collection collection) { + return StringUtils.collectionToDelimitedString(collection, separator); + } + } + + private boolean debugging = false; + + private HttpHeaders defaultHeaders = new HttpHeaders(); + + private String basePath = "{{basePath}}"; + + private RestTemplate restTemplate; + + private Map authentications; + + private DateFormat dateFormat; + + public ApiClient() { + this.restTemplate = buildRestTemplate(); + init(); + } + + @Autowired + public ApiClient(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + init(); + } + + protected void init() { + // Use RFC3339 format for date and datetime. + // See http://xml2rfc.ietf.org/public/rfc/html/rfc3339.html#anchor14 + this.dateFormat = new RFC3339DateFormat(); + + // Use UTC as the default time zone. + this.dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + // Set default User-Agent. + setUserAgent("Java-SDK"); + + // Setup authentications (key: authentication name, value: authentication). + authentications = new HashMap();{{#authMethods}}{{#isBasic}} + authentications.put("{{name}}", new HttpBasicAuth());{{/isBasic}}{{#isApiKey}} + authentications.put("{{name}}", new ApiKeyAuth({{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{^isKeyInHeader}}"query"{{/isKeyInHeader}}, "{{keyParamName}}"));{{/isApiKey}}{{#isOAuth}} + authentications.put("{{name}}", new OAuth());{{/isOAuth}}{{/authMethods}} + // Prevent the authentications from being modified. + authentications = Collections.unmodifiableMap(authentications); + } + + /** + * Get the current base path + * @return String the base path + */ + public String getBasePath() { + return basePath; + } + + /** + * Set the base path, which should include the host + * @param basePath the base path + * @return ApiClient this client + */ + public ApiClient setBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + /** + * Get authentications (key: authentication name, value: authentication). + * @return Map the currently configured authentication types + */ + public Map getAuthentications() { + return authentications; + } + + /** + * Get authentication for the given name. + * + * @param authName The authentication name + * @return The authentication, null if not found + */ + public Authentication getAuthentication(String authName) { + return authentications.get(authName); + } + + /** + * Helper method to set username for the first HTTP basic authentication. + * @param username the username + */ + public void setUsername(String username) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setUsername(username); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set password for the first HTTP basic authentication. + * @param password the password + */ + public void setPassword(String password) { + for (Authentication auth : authentications.values()) { + if (auth instanceof HttpBasicAuth) { + ((HttpBasicAuth) auth).setPassword(password); + return; + } + } + throw new RuntimeException("No HTTP basic authentication configured!"); + } + + /** + * Helper method to set API key value for the first API key authentication. + * @param apiKey the API key + */ + public void setApiKey(String apiKey) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKey(apiKey); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set API key prefix for the first API key authentication. + * @param apiKeyPrefix the API key prefix + */ + public void setApiKeyPrefix(String apiKeyPrefix) { + for (Authentication auth : authentications.values()) { + if (auth instanceof ApiKeyAuth) { + ((ApiKeyAuth) auth).setApiKeyPrefix(apiKeyPrefix); + return; + } + } + throw new RuntimeException("No API key authentication configured!"); + } + + /** + * Helper method to set access token for the first OAuth2 authentication. + * @param accessToken the access token + */ + public void setAccessToken(String accessToken) { + for (Authentication auth : authentications.values()) { + if (auth instanceof OAuth) { + ((OAuth) auth).setAccessToken(accessToken); + return; + } + } + throw new RuntimeException("No OAuth2 authentication configured!"); + } + + /** + * Set the User-Agent header's value (by adding to the default header map). + * @param userAgent the user agent string + * @return ApiClient this client + */ + public ApiClient setUserAgent(String userAgent) { + addDefaultHeader("User-Agent", userAgent); + return this; + } + + /** + * Add a default header. + * + * @param name The header's name + * @param value The header's value + * @return ApiClient this client + */ + public ApiClient addDefaultHeader(String name, String value) { + if (defaultHeaders.containsHeader(name)) { + defaultHeaders.remove(name); + } + defaultHeaders.add(name, value); + return this; + } + + public void setDebugging(boolean debugging) { + List currentInterceptors = this.restTemplate.getInterceptors(); + if(debugging) { + if (currentInterceptors == null) { + currentInterceptors = new ArrayList(); + } + ClientHttpRequestInterceptor interceptor = new ApiClientHttpRequestInterceptor(); + currentInterceptors.add(interceptor); + this.restTemplate.setInterceptors(currentInterceptors); + } else { + if (currentInterceptors != null && !currentInterceptors.isEmpty()) { + Iterator iter = currentInterceptors.iterator(); + while (iter.hasNext()) { + ClientHttpRequestInterceptor interceptor = iter.next(); + if (interceptor instanceof ApiClientHttpRequestInterceptor) { + iter.remove(); + } + } + this.restTemplate.setInterceptors(currentInterceptors); + } + } + this.debugging = debugging; + } + + /** + * Check that whether debugging is enabled for this API client. + * @return boolean true if this client is enabled for debugging, false otherwise + */ + public boolean isDebugging() { + return debugging; + } + + /** + * Get the date format used to parse/format date parameters. + * @return DateFormat format + */ + public DateFormat getDateFormat() { + return dateFormat; + } + + /** + * Set the date format used to parse/format date parameters. + * @param dateFormat Date format + * @return API client + */ + public ApiClient setDateFormat(DateFormat dateFormat) { + this.dateFormat = dateFormat; + {{#threetenbp}} + for(HttpMessageConverter converter:restTemplate.getMessageConverters()){ + if(converter instanceof AbstractJackson2HttpMessageConverter){ + ObjectMapper mapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper(); + mapper.setDateFormat(dateFormat); + } + } + {{/threetenbp}} + return this; + } + + /** + * Parse the given string into Date object. + */ + public Date parseDate(String str) { + try { + return dateFormat.parse(str); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + + /** + * Format the given Date object into string. + */ + public String formatDate(Date date) { + return dateFormat.format(date); + } + + /** + * Format the given parameter object into string. + * @param param the object to convert + * @return String the parameter represented as a String + */ + public String parameterToString(Object param) { + if (param == null) { + return ""; + } else if (param instanceof Date) { + return formatDate( (Date) param); + } else if (param instanceof Collection) { + StringBuilder b = new StringBuilder(); + for(Object o : (Collection) param) { + if(b.length() > 0) { + b.append(","); + } + b.append(String.valueOf(o)); + } + return b.toString(); + } else { + return String.valueOf(param); + } + } + + /** + * Converts a parameter to a {@link MultiValueMap} for use in REST requests + * @param collectionFormat The format to convert to + * @param name The name of the parameter + * @param value The parameter's value + * @return a Map containing the String value(s) of the input parameter + */ + public MultiValueMap parameterToMultiValueMap(CollectionFormat collectionFormat, String name, Object value) { + final MultiValueMap params = new LinkedMultiValueMap(); + + if (name == null || name.isEmpty() || value == null) { + return params; + } + + if(collectionFormat == null) { + collectionFormat = CollectionFormat.CSV; + } + + Collection valueCollection = null; + if (value instanceof Collection) { + valueCollection = (Collection) value; + } else { + params.add(name, parameterToString(value)); + return params; + } + + if (valueCollection.isEmpty()){ + return params; + } + + if (collectionFormat.equals(CollectionFormat.MULTI)) { + for (Object item : valueCollection) { + params.add(name, parameterToString(item)); + } + return params; + } + + List values = new ArrayList(); + for(Object o : valueCollection) { + values.add(parameterToString(o)); + } + params.add(name, collectionFormat.collectionToString(values)); + + return params; + } + + /** + * Check if the given {@code String} is a JSON MIME. + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents JSON, false otherwise + */ + public boolean isJsonMime(String mediaType) { + // "* / *" is default to JSON + if ("*/*".equals(mediaType)) { + return true; + } + + try { + return isJsonMime(MediaType.parseMediaType(mediaType)); + } catch (InvalidMediaTypeException e) { + } + return false; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * @param mediaType the input MediaType + * @return boolean true if the MediaType represents JSON, false otherwise + */ + public boolean isJsonMime(MediaType mediaType) { + return mediaType != null && (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || mediaType.getSubtype().matches("^.*\\+json[;]?\\s*$")); + } + + /** + * Select the Accept header's value from the given accepts array: + * if JSON exists in the given array, use it; + * otherwise use all of them (joining into a string) + * + * @param accepts The accepts array to select from + * @return List The list of MediaTypes to use for the Accept header + */ + public List selectHeaderAccept(String[] accepts) { + if (accepts.length == 0) { + return null; + } + for (String accept : accepts) { + MediaType mediaType = MediaType.parseMediaType(accept); + if (isJsonMime(mediaType)) { + return Collections.singletonList(mediaType); + } + } + return MediaType.parseMediaTypes(StringUtils.arrayToCommaDelimitedString(accepts)); + } + + /** + * Select the Content-Type header's value from the given array: + * if JSON exists in the given array, use it; + * otherwise use the first one of the array. + * + * @param contentTypes The Content-Type array to select from + * @return MediaType The Content-Type header to use. If the given array is empty, JSON will be used. + */ + public MediaType selectHeaderContentType(String[] contentTypes) { + if (contentTypes.length == 0) { + return MediaType.APPLICATION_JSON; + } + for (String contentType : contentTypes) { + MediaType mediaType = MediaType.parseMediaType(contentType); + if (isJsonMime(mediaType)) { + return mediaType; + } + } + return MediaType.parseMediaType(contentTypes[0]); + } + + /** + * Select the body to use for the request + * @param obj the body object + * @param formParams the form parameters + * @param contentType the content type of the request + * @return Object the selected body + */ + protected Object selectBody(Object obj, MultiValueMap formParams, MediaType contentType) { + boolean isForm = MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType) || MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType); + return isForm ? formParams : obj; + } + + /** + * Invoke API by sending HTTP request with the given options. + * + * @param the return type to use + * @param path The sub-path of the HTTP URL + * @param method The request method + * @param queryParams The query parameters + * @param body The request body object + * @param headerParams The header parameters + * @param formParams The form parameters + * @param accept The request's Accept header + * @param contentType The request's Content-Type header + * @param authNames The authentications to apply + * @param returnType The return type into which to deserialize the response + * @return ResponseEntity<T> The response of the chosen type + */ + public ResponseEntity invokeAPI(String path, HttpMethod method, MultiValueMap queryParams, Object body, HttpHeaders headerParams, MultiValueMap formParams, List accept, MediaType contentType, String[] authNames, ParameterizedTypeReference returnType) throws RestClientException { + updateParamsForAuth(authNames, queryParams, headerParams); + + final UriComponentsBuilder builder = UriComponentsBuilder.fromPath(basePath).path(path); + if (queryParams != null) { + builder.queryParams(queryParams); + } + + final BodyBuilder requestBuilder = RequestEntity.method(method, builder.build().toUri()); + if(accept != null) { + requestBuilder.accept(accept.toArray(new MediaType[accept.size()])); + } + if(contentType != null) { + requestBuilder.contentType(contentType); + } + + addHeadersToRequest(headerParams, requestBuilder); + addHeadersToRequest(defaultHeaders, requestBuilder); + + RequestEntity requestEntity = requestBuilder.body(selectBody(body, formParams, contentType)); + + ResponseEntity responseEntity = restTemplate.exchange(requestEntity, returnType); + + if (responseEntity.getStatusCode().is2xxSuccessful()) { + return responseEntity; + } else { + // The error handler built into the RestTemplate should handle 400 and 500 series errors. + throw new RestClientException("API returned " + responseEntity.getStatusCode() + " and it wasn't handled by the RestTemplate error handler"); + } + } + + /** + * Add headers to the request that is being built + * @param headers The headers to add + * @param requestBuilder The current request + */ + protected void addHeadersToRequest(HttpHeaders headers, BodyBuilder requestBuilder) { + for (Entry> entry : headers.headerSet()) { + List values = entry.getValue(); + for(String value : values) { + if (value != null) { + requestBuilder.header(entry.getKey(), value); + } + } + } + } + + /** + * Build the RestTemplate used to make HTTP requests. + * @return RestTemplate + */ + protected RestTemplate buildRestTemplate() { + {{#withXml}}List> messageConverters = new ArrayList>(); + messageConverters.add(new MappingJackson2HttpMessageConverter()); + XmlMapper xmlMapper = new XmlMapper(); + xmlMapper.configure(ToXmlGenerator.Feature.WRITE_XML_DECLARATION, true); + messageConverters.add(new MappingJackson2XmlHttpMessageConverter(xmlMapper)); + + RestTemplate restTemplate = new RestTemplate(messageConverters); + {{/withXml}}{{^withXml}}RestTemplate restTemplate = new RestTemplate();{{/withXml}} + {{#threetenbp}} + for(HttpMessageConverter converter:restTemplate.getMessageConverters()){ + if(converter instanceof AbstractJackson2HttpMessageConverter){ + ObjectMapper mapper = ((AbstractJackson2HttpMessageConverter)converter).getObjectMapper(); + ThreeTenModule module = new ThreeTenModule(); + module.addDeserializer(Instant.class, CustomInstantDeserializer.INSTANT); + module.addDeserializer(OffsetDateTime.class, CustomInstantDeserializer.OFFSET_DATE_TIME); + module.addDeserializer(ZonedDateTime.class, CustomInstantDeserializer.ZONED_DATE_TIME); + mapper.registerModule(module); + } + } + {{/threetenbp}} + // This allows us to read the response more than once - Necessary for debugging. + restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(restTemplate.getRequestFactory())); + return restTemplate; + } + + /** + * Update query and header parameters based on authentication settings. + * + * @param authNames The authentications to apply + * @param queryParams The query parameters + * @param headerParams The header parameters + */ + private void updateParamsForAuth(String[] authNames, MultiValueMap queryParams, HttpHeaders headerParams) { + for (String authName : authNames) { + Authentication auth = authentications.get(authName); + if (auth == null) { + throw new RestClientException("Authentication undefined: " + authName); + } + auth.applyToParams(queryParams, headerParams); + } + } + + private class ApiClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { + private final Log log = LogFactory.getLog(ApiClientHttpRequestInterceptor.class); + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { + logRequest(request, body); + ClientHttpResponse response = execution.execute(request, body); + logResponse(response); + return response; + } + + private void logRequest(HttpRequest request, byte[] body) throws UnsupportedEncodingException { + log.info("URI: " + request.getURI()); + log.info("HTTP Method: " + request.getMethod()); + log.info("HTTP Headers: " + headersToString(request.getHeaders())); + log.info("Request Body: " + new String(body, StandardCharsets.UTF_8)); + } + + private void logResponse(ClientHttpResponse response) throws IOException { + log.info("HTTP Status Code: " + response.getStatusCode().value()); + log.info("Status Text: " + response.getStatusText()); + log.info("HTTP Headers: " + headersToString(response.getHeaders())); + log.info("Response Body: " + bodyToString(response.getBody())); + } + + private String headersToString(HttpHeaders headers) { + StringBuilder builder = new StringBuilder(); + for(Entry> entry : headers.headerSet()) { + builder.append(entry.getKey()).append("=["); + for(String value : entry.getValue()) { + builder.append(value).append(","); + } + builder.setLength(builder.length() - 1); // Get rid of trailing comma + builder.append("],"); + } + builder.setLength(builder.length() - 1); // Get rid of trailing comma + return builder.toString(); + } + + private String bodyToString(InputStream body) throws IOException { + StringBuilder builder = new StringBuilder(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(body, StandardCharsets.UTF_8)); + String line = bufferedReader.readLine(); + while (line != null) { + builder.append(line).append(System.lineSeparator()); + line = bufferedReader.readLine(); + } + bufferedReader.close(); + return builder.toString(); + } + } +} \ No newline at end of file diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java index 5c771b2f5db..fa72c428c86 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxChatModel.java @@ -68,8 +68,9 @@ import org.springframework.ai.model.tool.internal.ToolCallReactiveContextHolder; import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.tool.definition.ToolDefinition; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -253,8 +254,18 @@ public ChatResponse call(Prompt prompt) { this.observationRegistry) .observe(() -> { - ResponseEntity completionEntity = this.retryTemplate - .execute(ctx -> this.miniMaxApi.chatCompletionEntity(request)); + ResponseEntity completionEntity = null; + try { + completionEntity = this.retryTemplate.execute(() -> this.miniMaxApi.chatCompletionEntity(request)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } var chatCompletion = completionEntity.getBody(); @@ -328,8 +339,18 @@ public Flux stream(Prompt prompt) { return Flux.deferContextual(contextView -> { ChatCompletionRequest request = createRequest(requestPrompt, true); - Flux completionChunks = this.retryTemplate - .execute(ctx -> this.miniMaxApi.chatCompletionStream(request)); + Flux completionChunks = null; + try { + completionChunks = this.retryTemplate.execute(() -> this.miniMaxApi.chatCompletionStream(request)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } // For chunked responses, only the first chunk contains the choice role. // The rest of the chunks with same ID share the same role. diff --git a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxEmbeddingModel.java b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxEmbeddingModel.java index 5dd0077fed3..ab631ee1825 100644 --- a/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxEmbeddingModel.java +++ b/models/spring-ai-minimax/src/main/java/org/springframework/ai/minimax/MiniMaxEmbeddingModel.java @@ -40,7 +40,8 @@ import org.springframework.ai.minimax.api.MiniMaxApiConstants; import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.retry.RetryUtils; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -165,8 +166,19 @@ public EmbeddingResponse call(EmbeddingRequest request) { .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry) .observe(() -> { - MiniMaxApi.EmbeddingList apiEmbeddingResponse = this.retryTemplate - .execute(ctx -> this.miniMaxApi.embeddings(apiRequest).getBody()); + MiniMaxApi.EmbeddingList apiEmbeddingResponse = null; + try { + apiEmbeddingResponse = this.retryTemplate + .execute(() -> this.miniMaxApi.embeddings(apiRequest).getBody()); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } if (apiEmbeddingResponse == null) { logger.warn("No embeddings returned for request: {}", request); diff --git a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/api/MiniMaxRetryTests.java b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/api/MiniMaxRetryTests.java index 9f165e27c0d..32d0baccc95 100644 --- a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/api/MiniMaxRetryTests.java +++ b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/api/MiniMaxRetryTests.java @@ -45,11 +45,11 @@ import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.TransientAiException; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import org.springframework.http.ResponseEntity; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -78,7 +78,7 @@ public class MiniMaxRetryTests { public void beforeEach() { this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - this.retryTemplate.registerListener(this.retryListener); + this.retryTemplate.setRetryListener(this.retryListener); this.chatModel = new MiniMaxChatModel(this.miniMaxApi, MiniMaxChatOptions.builder().build(), ToolCallingManager.builder().build(), this.retryTemplate); @@ -103,7 +103,7 @@ public void miniMaxChatTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -131,7 +131,7 @@ public void miniMaxChatStreamTransientError() { assertThat(result).isNotNull(); assertThat(result.collectList().block().get(0).getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -158,7 +158,7 @@ public void miniMaxEmbeddingTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput()).isEqualTo(new float[] { 9.9f, 8.8f }); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -171,21 +171,22 @@ public void miniMaxEmbeddingNonTransientError() { .call(new org.springframework.ai.embedding.EmbeddingRequest(List.of("text1", "text2"), options))); } - private class TestRetryListener implements RetryListener { + private static class TestRetryListener implements RetryListener { int onErrorRetryCount = 0; int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void beforeRetry(final RetryPolicy retryPolicy, final Retryable retryable) { + // Count each retry attempt + this.onErrorRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } } diff --git a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/embedding/MiniMaxEmbeddingModelObservationIT.java b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/embedding/MiniMaxEmbeddingModelObservationIT.java index 0b5df195dd3..b502515ae15 100644 --- a/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/embedding/MiniMaxEmbeddingModelObservationIT.java +++ b/models/spring-ai-minimax/src/test/java/org/springframework/ai/minimax/embedding/MiniMaxEmbeddingModelObservationIT.java @@ -37,7 +37,7 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation.HighCardinalityKeyNames; @@ -106,7 +106,7 @@ public MiniMaxApi minimaxApi() { public MiniMaxEmbeddingModel minimaxEmbeddingModel(MiniMaxApi minimaxApi, TestObservationRegistry observationRegistry) { return new MiniMaxEmbeddingModel(minimaxApi, MetadataMode.EMBED, MiniMaxEmbeddingOptions.builder().build(), - RetryTemplate.defaultInstance(), observationRegistry); + new RetryTemplate(), observationRegistry); } } diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java index f7314603ec3..3d03ff182e2 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiChatModel.java @@ -69,8 +69,9 @@ import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.support.UsageCalculator; import org.springframework.ai.tool.definition.ToolDefinition; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeType; @@ -199,8 +200,19 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons this.observationRegistry) .observe(() -> { - ResponseEntity completionEntity = this.retryTemplate - .execute(ctx -> this.mistralAiApi.chatCompletionEntity(request)); + ResponseEntity completionEntity = null; + try { + completionEntity = this.retryTemplate + .execute(() -> this.mistralAiApi.chatCompletionEntity(request)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } ChatCompletion chatCompletion = completionEntity.getBody(); @@ -272,8 +284,18 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha observation.parentObservation(contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null)).start(); - Flux completionChunks = this.retryTemplate - .execute(ctx -> this.mistralAiApi.chatCompletionStream(request)); + Flux completionChunks = null; + try { + completionChunks = this.retryTemplate.execute(() -> this.mistralAiApi.chatCompletionStream(request)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } // For chunked responses, only the first chunk contains the choice role. // The rest of the chunks with same ID share the same role. diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingModel.java index 8650fca10b7..a782ce6a179 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/MistralAiEmbeddingModel.java @@ -39,7 +39,8 @@ import org.springframework.ai.mistralai.api.MistralAiApi; import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.retry.RetryUtils; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; /** @@ -140,8 +141,19 @@ public EmbeddingResponse call(EmbeddingRequest request) { .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry) .observe(() -> { - var apiEmbeddingResponse = this.retryTemplate - .execute(ctx -> this.mistralAiApi.embeddings(apiRequest).getBody()); + MistralAiApi.EmbeddingList apiEmbeddingResponse = null; + try { + apiEmbeddingResponse = this.retryTemplate + .execute(() -> this.mistralAiApi.embeddings(apiRequest).getBody()); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } if (apiEmbeddingResponse == null) { logger.warn("No embeddings returned for request: {}", request); diff --git a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/moderation/MistralAiModerationModel.java b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/moderation/MistralAiModerationModel.java index 0717520d766..281dbb0a809 100644 --- a/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/moderation/MistralAiModerationModel.java +++ b/models/spring-ai-mistral-ai/src/main/java/org/springframework/ai/mistralai/moderation/MistralAiModerationModel.java @@ -34,8 +34,9 @@ import org.springframework.ai.moderation.ModerationResponse; import org.springframework.ai.moderation.ModerationResult; import org.springframework.ai.retry.RetryUtils; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import static org.springframework.ai.mistralai.api.MistralAiModerationApi.MistralAiModerationRequest; @@ -81,27 +82,39 @@ public MistralAiModerationModel(MistralAiModerationApi mistralAiModerationApi, R @Override public ModerationResponse call(ModerationPrompt moderationPrompt) { - return this.retryTemplate.execute(ctx -> { + try { + return this.retryTemplate.execute(() -> { - var instructions = moderationPrompt.getInstructions().getText(); + var instructions = moderationPrompt.getInstructions().getText(); - var moderationRequest = new MistralAiModerationRequest(instructions); + var moderationRequest = new MistralAiModerationRequest(instructions); - if (this.defaultOptions != null) { - moderationRequest = ModelOptionsUtils.merge(this.defaultOptions, moderationRequest, - MistralAiModerationRequest.class); + if (this.defaultOptions != null) { + moderationRequest = ModelOptionsUtils.merge(this.defaultOptions, moderationRequest, + MistralAiModerationRequest.class); + } + else { + // moderationPrompt.getOptions() never null but model can be empty, + // cause + // by ModerationPrompt constructor + moderationRequest = ModelOptionsUtils.merge( + toMistralAiModerationOptions(moderationPrompt.getOptions()), moderationRequest, + MistralAiModerationRequest.class); + } + + var moderationResponseEntity = this.mistralAiModerationApi.moderate(moderationRequest); + + return convertResponse(moderationResponseEntity, moderationRequest); + }); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; } else { - // moderationPrompt.getOptions() never null but model can be empty, cause - // by ModerationPrompt constructor - moderationRequest = ModelOptionsUtils.merge(toMistralAiModerationOptions(moderationPrompt.getOptions()), - moderationRequest, MistralAiModerationRequest.class); + throw new RuntimeException(e.getCause()); } - - var moderationResponseEntity = this.mistralAiModerationApi.moderate(moderationRequest); - - return convertResponse(moderationResponseEntity, moderationRequest); - }); + } } private ModerationResponse convertResponse(ResponseEntity moderationResponseEntity, diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java index 5d8a30d309c..03ac09d993c 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java @@ -38,7 +38,7 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.StringUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -190,7 +190,7 @@ public MistralAiChatModel mistralAiChatModel(MistralAiApi mistralAiApi, return MistralAiChatModel.builder() .mistralAiApi(mistralAiApi) .defaultOptions(MistralAiChatOptions.builder().build()) - .retryTemplate(RetryTemplate.defaultInstance()) + .retryTemplate(new RetryTemplate()) .observationRegistry(observationRegistry) .build(); } diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiEmbeddingModelObservationIT.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiEmbeddingModelObservationIT.java index 8341c0fa22e..f3287c9fc3e 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiEmbeddingModelObservationIT.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiEmbeddingModelObservationIT.java @@ -34,7 +34,7 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation.HighCardinalityKeyNames; @@ -110,7 +110,7 @@ public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiApi mistralAiApi return MistralAiEmbeddingModel.builder() .mistralAiApi(mistralAiApi) .options(MistralAiEmbeddingOptions.builder().build()) - .retryTemplate(RetryTemplate.defaultInstance()) + .retryTemplate(new RetryTemplate()) .observationRegistry(observationRegistry) .build(); } diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiRetryTests.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiRetryTests.java index 9709a3783ca..f3d38e813ed 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiRetryTests.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiRetryTests.java @@ -40,11 +40,11 @@ import org.springframework.ai.mistralai.api.MistralAiApi.EmbeddingRequest; import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.TransientAiException; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import org.springframework.http.ResponseEntity; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -75,7 +75,7 @@ public class MistralAiRetryTests { public void beforeEach() { this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - this.retryTemplate.registerListener(this.retryListener); + this.retryTemplate.setRetryListener(this.retryListener); this.chatModel = MistralAiChatModel.builder() .mistralAiApi(this.mistralAiApi) @@ -110,7 +110,7 @@ public void mistralAiChatTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -139,7 +139,7 @@ public void mistralAiChatStreamTransientError() { assertThat(result).isNotNull(); assertThat(result.collectList().block().get(0).getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -167,7 +167,7 @@ public void mistralAiEmbeddingTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput()).isEqualTo(new float[] { 9.9f, 8.8f }); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -189,7 +189,7 @@ public void mistralAiChatMixedTransientAndNonTransientErrors() { assertThrows(RuntimeException.class, () -> this.chatModel.call(new Prompt("text"))); // Should have 1 retry attempt before hitting non-transient error - assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); + assertThat(this.retryListener.onErrorRetryCount).isEqualTo(1); } private static class TestRetryListener implements RetryListener { @@ -199,14 +199,15 @@ private static class TestRetryListener implements RetryListener { int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void beforeRetry(final RetryPolicy retryPolicy, final Retryable retryable) { + // Count each retry attempt + this.onErrorRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } } diff --git a/models/spring-ai-ollama/pom.xml b/models/spring-ai-ollama/pom.xml index 673064e4bb1..71f678d041b 100644 --- a/models/spring-ai-ollama/pom.xml +++ b/models/spring-ai-ollama/pom.xml @@ -103,15 +103,15 @@ test - - org.testcontainers - junit-jupiter - test - + + org.testcontainers + testcontainers-junit-jupiter + test + org.testcontainers - ollama + testcontainers-ollama test diff --git a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java index 7cb87eb8f3b..969454ee8b3 100644 --- a/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java +++ b/models/spring-ai-ollama/src/main/java/org/springframework/ai/ollama/OllamaChatModel.java @@ -70,7 +70,8 @@ import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.tool.definition.ToolDefinition; import org.springframework.ai.util.json.JsonParser; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -246,7 +247,18 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon this.observationRegistry) .observe(() -> { - OllamaApi.ChatResponse ollamaResponse = this.retryTemplate.execute(ctx -> this.chatApi.chat(request)); + OllamaApi.ChatResponse ollamaResponse = null; + try { + ollamaResponse = this.retryTemplate.execute(() -> this.chatApi.chat(request)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } List toolCalls = ollamaResponse.message().toolCalls() == null ? List.of() : ollamaResponse.message() diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java index 693f892e940..232269fa97f 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaChatModelMultimodalIT.java @@ -35,10 +35,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ClassPathResource; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import org.springframework.util.MimeTypeUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -93,19 +93,21 @@ public OllamaApi ollamaApi() { @Bean public OllamaChatModel ollamaChat(OllamaApi ollamaApi) { - RetryTemplate retryTemplate = RetryTemplate.builder() + RetryPolicy retryPolicy = RetryPolicy.builder() .maxAttempts(1) - .retryOn(TransientAiException.class) - .fixedBackoff(Duration.ofSeconds(1)) - .withListener(new RetryListener() { - - @Override - public void onError(RetryContext context, - RetryCallback callback, Throwable throwable) { - logger.warn("Retry error. Retry count:" + context.getRetryCount(), throwable); - } - }) + .includes(TransientAiException.class) + .delay(Duration.ofSeconds(1)) .build(); + + RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); + retryTemplate.setRetryListener(new RetryListener() { + + @Override + public void onRetryFailure(final RetryPolicy policy, final Retryable retryable, + final Throwable throwable) { + logger.warn("Retry error. Retry count:" + (throwable.getSuppressed().length + 1), throwable); + } + }); return OllamaChatModel.builder() .ollamaApi(ollamaApi) .defaultOptions(OllamaChatOptions.builder().model(MODEL).temperature(0.9).build()) diff --git a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaRetryTests.java b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaRetryTests.java index 323c969c6fa..a744aead9d1 100644 --- a/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaRetryTests.java +++ b/models/spring-ai-ollama/src/test/java/org/springframework/ai/ollama/OllamaRetryTests.java @@ -35,10 +35,10 @@ import org.springframework.ai.retry.NonTransientAiException; import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.TransientAiException; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import org.springframework.web.client.ResourceAccessException; import static org.assertj.core.api.Assertions.assertThat; @@ -71,7 +71,7 @@ class OllamaRetryTests { public void beforeEach() { this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - this.retryTemplate.registerListener(this.retryListener); + this.retryTemplate.setRetryListener(this.retryListener); this.chatModel = OllamaChatModel.builder() .ollamaApi(this.ollamaApi) @@ -96,7 +96,7 @@ void ollamaChatTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -130,7 +130,7 @@ void ollamaChatNonTransientErrorShouldNotRetry() { .hasMessage("Model not found"); assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(0); - assertThat(this.retryListener.onErrorRetryCount).isEqualTo(1); + assertThat(this.retryListener.onErrorRetryCount).isEqualTo(0); verify(this.ollamaApi, times(1)).chat(isA(OllamaApi.ChatRequest.class)); } @@ -202,14 +202,15 @@ private static class TestRetryListener implements RetryListener { int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void beforeRetry(final RetryPolicy retryPolicy, final Retryable retryable) { + // Count each retry attempt + this.onErrorRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } } diff --git a/models/spring-ai-openai/pom.xml b/models/spring-ai-openai/pom.xml index 3e534615f2b..db5df8407d4 100644 --- a/models/spring-ai-openai/pom.xml +++ b/models/spring-ai-openai/pom.xml @@ -80,6 +80,12 @@ + + org.springframework.boot + spring-boot-restclient-test + test + + org.springframework.ai spring-ai-test @@ -101,13 +107,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - ollama + testcontainers-ollama test diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioSpeechModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioSpeechModel.java index 759eac07e09..d83e4c8caae 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioSpeechModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioSpeechModel.java @@ -31,8 +31,8 @@ import org.springframework.ai.openai.metadata.audio.OpenAiAudioSpeechResponseMetadata; import org.springframework.ai.openai.metadata.support.OpenAiResponseHeaderExtractor; import org.springframework.ai.retry.RetryUtils; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -127,8 +127,13 @@ public SpeechResponse call(SpeechPrompt speechPrompt) { OpenAiAudioApi.SpeechRequest speechRequest = createRequest(speechPrompt); - ResponseEntity speechEntity = this.retryTemplate - .execute(ctx -> this.audioApi.createSpeech(speechRequest)); + ResponseEntity speechEntity; + try { + speechEntity = this.retryTemplate.execute(() -> this.audioApi.createSpeech(speechRequest)); + } + catch (Exception e) { + throw new RuntimeException("Error calling OpenAI audio speech API", e); + } var speech = speechEntity.getBody(); @@ -154,8 +159,13 @@ public Flux stream(SpeechPrompt speechPrompt) { OpenAiAudioApi.SpeechRequest speechRequest = createRequest(speechPrompt); - Flux> speechEntity = this.retryTemplate - .execute(ctx -> this.audioApi.stream(speechRequest)); + Flux> speechEntity; + try { + speechEntity = this.retryTemplate.execute(() -> this.audioApi.stream(speechRequest)); + } + catch (Exception e) { + throw new RuntimeException("Error calling OpenAI audio speech streaming API", e); + } return speechEntity.map(entity -> new SpeechResponse(new Speech(entity.getBody()), new OpenAiAudioSpeechResponseMetadata(OpenAiResponseHeaderExtractor.extractAiResponseHeaders(entity)))); diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioTranscriptionModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioTranscriptionModel.java index 365b25cffb8..db5508b23c0 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioTranscriptionModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiAudioTranscriptionModel.java @@ -30,8 +30,8 @@ import org.springframework.ai.openai.metadata.support.OpenAiResponseHeaderExtractor; import org.springframework.ai.retry.RetryUtils; import org.springframework.core.io.Resource; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; /** @@ -112,8 +112,14 @@ public AudioTranscriptionResponse call(AudioTranscriptionPrompt transcriptionPro if (request.responseFormat().isJsonType()) { - ResponseEntity transcriptionEntity = this.retryTemplate - .execute(ctx -> this.audioApi.createTranscription(request, StructuredResponse.class)); + ResponseEntity transcriptionEntity; + try { + transcriptionEntity = this.retryTemplate + .execute(() -> this.audioApi.createTranscription(request, StructuredResponse.class)); + } + catch (Exception e) { + throw new RuntimeException("Error calling OpenAI transcription API", e); + } var transcription = transcriptionEntity.getBody(); @@ -133,8 +139,14 @@ public AudioTranscriptionResponse call(AudioTranscriptionPrompt transcriptionPro } else { - ResponseEntity transcriptionEntity = this.retryTemplate - .execute(ctx -> this.audioApi.createTranscription(request, String.class)); + ResponseEntity transcriptionEntity; + try { + transcriptionEntity = this.retryTemplate + .execute(() -> this.audioApi.createTranscription(request, String.class)); + } + catch (Exception e) { + throw new RuntimeException("Error calling OpenAI transcription API", e); + } var transcription = transcriptionEntity.getBody(); diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java index 246b7893c4a..3de07809929 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiChatModel.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; @@ -78,13 +77,13 @@ import org.springframework.ai.tool.definition.ToolDefinition; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.Resource; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; -import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; /** @@ -196,8 +195,14 @@ public ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespons this.observationRegistry) .observe(() -> { - ResponseEntity completionEntity = this.retryTemplate - .execute(ctx -> this.openAiApi.chatCompletionEntity(request, getAdditionalHttpHeaders(prompt))); + ResponseEntity completionEntity; + try { + completionEntity = this.retryTemplate + .execute(() -> this.openAiApi.chatCompletionEntity(request, getAdditionalHttpHeaders(prompt))); + } + catch (Exception e) { + throw new RuntimeException("Error calling OpenAI chat completion API", e); + } var chatCompletion = completionEntity.getBody(); @@ -402,14 +407,15 @@ public Flux internalStream(Prompt prompt, ChatResponse previousCha }); } - private MultiValueMap getAdditionalHttpHeaders(Prompt prompt) { + private HttpHeaders getAdditionalHttpHeaders(Prompt prompt) { Map headers = new HashMap<>(this.defaultOptions.getHttpHeaders()); if (prompt.getOptions() != null && prompt.getOptions() instanceof OpenAiChatOptions chatOptions) { headers.putAll(chatOptions.getHttpHeaders()); } - return CollectionUtils.toMultiValueMap( - headers.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> List.of(e.getValue())))); + HttpHeaders httpHeaders = new HttpHeaders(); + headers.forEach(httpHeaders::add); + return httpHeaders; } private Generation buildGeneration(Choice choice, Map metadata, ChatCompletionRequest request) { diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingModel.java index 47c06ac5a72..d5c3cb347d4 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiEmbeddingModel.java @@ -42,7 +42,7 @@ import org.springframework.ai.openai.api.OpenAiApi.EmbeddingList; import org.springframework.ai.openai.api.common.OpenAiApiConstants; import org.springframework.ai.retry.RetryUtils; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; /** @@ -164,8 +164,14 @@ public EmbeddingResponse call(EmbeddingRequest request) { .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry) .observe(() -> { - EmbeddingList apiEmbeddingResponse = this.retryTemplate - .execute(ctx -> this.openAiApi.embeddings(apiRequest).getBody()); + EmbeddingList apiEmbeddingResponse; + try { + apiEmbeddingResponse = this.retryTemplate + .execute(() -> this.openAiApi.embeddings(apiRequest).getBody()); + } + catch (Exception e) { + throw new RuntimeException("Error calling OpenAI embedding API", e); + } if (apiEmbeddingResponse == null) { logger.warn("No embeddings returned for request: {}", request); diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageModel.java index 68354662548..d850c6ceef2 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiImageModel.java @@ -38,8 +38,8 @@ import org.springframework.ai.openai.api.common.OpenAiApiConstants; import org.springframework.ai.openai.metadata.OpenAiImageGenerationMetadata; import org.springframework.ai.retry.RetryUtils; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; /** @@ -141,8 +141,14 @@ public ImageResponse call(ImagePrompt imagePrompt) { .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry) .observe(() -> { - ResponseEntity imageResponseEntity = this.retryTemplate - .execute(ctx -> this.openAiImageApi.createImage(imageRequest)); + ResponseEntity imageResponseEntity; + try { + imageResponseEntity = this.retryTemplate + .execute(() -> this.openAiImageApi.createImage(imageRequest)); + } + catch (Exception e) { + throw new RuntimeException("Error calling OpenAI image API", e); + } ImageResponse imageResponse = convertResponse(imageResponseEntity, imageRequest); diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiModerationModel.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiModerationModel.java index 8e00c24b430..5a22be70ad0 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiModerationModel.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/OpenAiModerationModel.java @@ -34,8 +34,8 @@ import org.springframework.ai.moderation.ModerationResult; import org.springframework.ai.openai.api.OpenAiModerationApi; import org.springframework.ai.retry.RetryUtils; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; /** @@ -77,28 +77,34 @@ public OpenAiModerationModel withDefaultOptions(OpenAiModerationOptions defaultO @Override public ModerationResponse call(ModerationPrompt moderationPrompt) { - return this.retryTemplate.execute(ctx -> { + try { + return this.retryTemplate.execute(() -> { - String instructions = moderationPrompt.getInstructions().getText(); + String instructions = moderationPrompt.getInstructions().getText(); - OpenAiModerationApi.OpenAiModerationRequest moderationRequest = new OpenAiModerationApi.OpenAiModerationRequest( - instructions); + OpenAiModerationApi.OpenAiModerationRequest moderationRequest = new OpenAiModerationApi.OpenAiModerationRequest( + instructions); - if (this.defaultOptions != null) { - moderationRequest = ModelOptionsUtils.merge(this.defaultOptions, moderationRequest, - OpenAiModerationApi.OpenAiModerationRequest.class); - } + if (this.defaultOptions != null) { + moderationRequest = ModelOptionsUtils.merge(this.defaultOptions, moderationRequest, + OpenAiModerationApi.OpenAiModerationRequest.class); + } - if (moderationPrompt.getOptions() != null) { - moderationRequest = ModelOptionsUtils.merge(toOpenAiModerationOptions(moderationPrompt.getOptions()), - moderationRequest, OpenAiModerationApi.OpenAiModerationRequest.class); - } + if (moderationPrompt.getOptions() != null) { + moderationRequest = ModelOptionsUtils.merge( + toOpenAiModerationOptions(moderationPrompt.getOptions()), moderationRequest, + OpenAiModerationApi.OpenAiModerationRequest.class); + } - ResponseEntity moderationResponseEntity = this.openAiModerationApi - .createModeration(moderationRequest); + ResponseEntity moderationResponseEntity = this.openAiModerationApi + .createModeration(moderationRequest); - return convertResponse(moderationResponseEntity, moderationRequest); - }); + return convertResponse(moderationResponseEntity, moderationRequest); + }); + } + catch (Exception e) { + throw new RuntimeException("Error calling OpenAI moderation API", e); + } } private ModerationResponse convertResponse( 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 070d4e4b5c6..49acf9b7242 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 @@ -48,8 +48,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -102,7 +100,7 @@ public static Builder builder() { private final ApiKey apiKey; - private final MultiValueMap headers; + private final HttpHeaders headers; private final String completionsPath; @@ -114,7 +112,7 @@ public static Builder builder() { private final WebClient webClient; - private OpenAiStreamFunctionCallingHelper chunkMerger = new OpenAiStreamFunctionCallingHelper(); + private final OpenAiStreamFunctionCallingHelper chunkMerger = new OpenAiStreamFunctionCallingHelper(); /** * Create a new chat completion api. @@ -127,8 +125,8 @@ public static Builder builder() { * @param webClientBuilder WebClient builder. * @param responseErrorHandler Response error handler. */ - public OpenAiApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, String completionsPath, - String embeddingsPath, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, + public OpenAiApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, String completionsPath, String embeddingsPath, + RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) { this.baseUrl = baseUrl; this.apiKey = apiKey; @@ -159,6 +157,29 @@ public OpenAiApi(String baseUrl, ApiKey apiKey, MultiValueMap he .build(); // @formatter:on } + /** + * Create a new chat completion api. + * @param baseUrl api base URL. + * @param apiKey OpenAI apiKey. + * @param headers the http headers to use. + * @param completionsPath the path to the chat completions endpoint. + * @param embeddingsPath the path to the embeddings endpoint. + * @param restClient RestClient instance. + * @param webClient WebClient instance. + * @param responseErrorHandler Response error handler. + */ + public OpenAiApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, String completionsPath, String embeddingsPath, + ResponseErrorHandler responseErrorHandler, RestClient restClient, WebClient webClient) { + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.headers = headers; + this.completionsPath = completionsPath; + this.embeddingsPath = embeddingsPath; + this.responseErrorHandler = responseErrorHandler; + this.restClient = restClient; + this.webClient = webClient; + } + /** * Returns a string containing all text values from the given media content list. Only * elements of type "text" are processed and concatenated in order. @@ -182,7 +203,7 @@ public static String getTextContent(List con * and headers. */ public ResponseEntity chatCompletionEntity(ChatCompletionRequest chatRequest) { - return chatCompletionEntity(chatRequest, new LinkedMultiValueMap<>()); + return chatCompletionEntity(chatRequest, new HttpHeaders()); } /** @@ -194,7 +215,7 @@ public ResponseEntity chatCompletionEntity(ChatCompletionRequest * and headers. */ public ResponseEntity chatCompletionEntity(ChatCompletionRequest chatRequest, - MultiValueMap additionalHttpHeader) { + HttpHeaders additionalHttpHeader) { Assert.notNull(chatRequest, REQUEST_BODY_NULL_MESSAGE); Assert.isTrue(!chatRequest.stream(), STREAM_FALSE_MESSAGE); @@ -220,7 +241,7 @@ public ResponseEntity chatCompletionEntity(ChatCompletionRequest * @return Returns a {@link Flux} stream from chat completion chunks. */ public Flux chatCompletionStream(ChatCompletionRequest chatRequest) { - return chatCompletionStream(chatRequest, new LinkedMultiValueMap<>()); + return chatCompletionStream(chatRequest, new HttpHeaders()); } /** @@ -232,7 +253,7 @@ public Flux chatCompletionStream(ChatCompletionRequest chat * @return Returns a {@link Flux} stream from chat completion chunks. */ public Flux chatCompletionStream(ChatCompletionRequest chatRequest, - MultiValueMap additionalHttpHeader) { + HttpHeaders additionalHttpHeader) { Assert.notNull(chatRequest, REQUEST_BODY_NULL_MESSAGE); Assert.isTrue(chatRequest.stream(), "Request must set the stream property to true."); @@ -328,7 +349,7 @@ public ResponseEntity> embeddings(EmbeddingRequest< } private void addDefaultHeadersIfMissing(HttpHeaders headers) { - if (!headers.containsKey(HttpHeaders.AUTHORIZATION) && !(this.apiKey instanceof NoopApiKey)) { + if (headers.get(HttpHeaders.AUTHORIZATION) == null && !(this.apiKey instanceof NoopApiKey)) { headers.setBearerAuth(this.apiKey.getValue()); } } @@ -342,7 +363,7 @@ ApiKey getApiKey() { return this.apiKey; } - MultiValueMap getHeaders() { + HttpHeaders getHeaders() { return this.headers; } @@ -2000,7 +2021,8 @@ public Builder() { public Builder(OpenAiApi api) { this.baseUrl = api.getBaseUrl(); this.apiKey = api.getApiKey(); - this.headers = new LinkedMultiValueMap<>(api.getHeaders()); + this.headers = new HttpHeaders(); + this.headers.addAll(api.getHeaders()); this.completionsPath = api.getCompletionsPath(); this.embeddingsPath = api.getEmbeddingsPath(); this.restClientBuilder = api.restClient != null ? api.restClient.mutate() : RestClient.builder(); @@ -2012,7 +2034,7 @@ public Builder(OpenAiApi api) { private ApiKey apiKey; - private MultiValueMap headers = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private String completionsPath = "/v1/chat/completions"; @@ -2041,7 +2063,7 @@ public Builder apiKey(String simpleApiKey) { return this; } - public Builder headers(MultiValueMap headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "headers cannot be null"); this.headers = headers; return this; 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 cd89852d244..2de106ab54b 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 @@ -69,11 +69,10 @@ public class OpenAiAudioApi { * @param webClientBuilder WebClient builder. * @param responseErrorHandler Response error handler. */ - public OpenAiAudioApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, - RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, - ResponseErrorHandler responseErrorHandler) { + public OpenAiAudioApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, RestClient.Builder restClientBuilder, + WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) { - Consumer authHeaders = h -> h.addAll(headers); + Consumer authHeaders = h -> h.addAll(HttpHeaders.readOnlyHttpHeaders(headers)); // @formatter:off this.restClient = restClientBuilder.clone() @@ -98,6 +97,16 @@ public OpenAiAudioApi(String baseUrl, ApiKey apiKey, MultiValueMap headers = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private RestClient.Builder restClientBuilder = RestClient.builder(); @@ -868,7 +877,7 @@ public Builder apiKey(String simpleApiKey) { return this; } - public Builder headers(MultiValueMap headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "headers cannot be null"); this.headers = headers; return this; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiFileApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiFileApi.java index 535b6a9a043..98eb5ae15e7 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiFileApi.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiFileApi.java @@ -49,8 +49,8 @@ public class OpenAiFileApi { private final RestClient restClient; - public OpenAiFileApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { + public OpenAiFileApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, RestClient.Builder restClientBuilder, + ResponseErrorHandler responseErrorHandler) { Consumer authHeaders = h -> h.addAll(headers); this.restClient = restClientBuilder.clone() @@ -65,6 +65,10 @@ public OpenAiFileApi(String baseUrl, ApiKey apiKey, MultiValueMap headers = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private RestClient.Builder restClientBuilder = RestClient.builder(); @@ -384,7 +388,7 @@ public Builder apiKey(String simpleApiKey) { return this; } - public Builder headers(MultiValueMap headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "headers cannot be null"); this.headers = headers; return this; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java index fe82e30e56b..378653f6e2f 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiImageApi.java @@ -31,8 +31,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; @@ -60,7 +58,7 @@ public class OpenAiImageApi { * @param restClientBuilder the rest client builder to use. * @param responseErrorHandler the response error handler to use. */ - public OpenAiImageApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, String imagesPath, + public OpenAiImageApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, String imagesPath, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { // @formatter:off @@ -68,7 +66,7 @@ public OpenAiImageApi(String baseUrl, ApiKey apiKey, MultiValueMap { h.setContentType(MediaType.APPLICATION_JSON); - h.addAll(headers); + h.addAll(HttpHeaders.readOnlyHttpHeaders(headers)); }) .defaultStatusHandler(responseErrorHandler) .defaultRequest(requestHeadersSpec -> { @@ -82,6 +80,16 @@ public OpenAiImageApi(String baseUrl, ApiKey apiKey, MultiValueMap createImage(OpenAiImageRequest openAiImageRequest) { Assert.notNull(openAiImageRequest, "Image request cannot be null."); Assert.hasLength(openAiImageRequest.prompt(), "Prompt cannot be empty."); @@ -168,7 +176,7 @@ public static final class Builder { private ApiKey apiKey; - private MultiValueMap headers = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private RestClient.Builder restClientBuilder = RestClient.builder(); @@ -200,7 +208,7 @@ public Builder apiKey(String simpleApiKey) { return this; } - public Builder headers(MultiValueMap headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "headers cannot be null"); this.headers = headers; return this; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java index a90bd1aad8e..8c97a90f7ff 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiModerationApi.java @@ -19,8 +19,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.model.ApiKey; import org.springframework.ai.model.NoopApiKey; @@ -31,8 +29,6 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; @@ -49,29 +45,23 @@ public class OpenAiModerationApi { public static final String DEFAULT_MODERATION_MODEL = "omni-moderation-latest"; - private static final String DEFAULT_BASE_URL = "https://api.openai.com"; - private final RestClient restClient; - private final ObjectMapper objectMapper; - /** * Create a new OpenAI Moderation API with the provided base URL. * @param baseUrl the base URL for the OpenAI API. * @param apiKey OpenAI apiKey. * @param restClientBuilder the rest client builder to use. */ - public OpenAiModerationApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { - - this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + public OpenAiModerationApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, RestClient.Builder restClientBuilder, + ResponseErrorHandler responseErrorHandler) { // @formatter:off this.restClient = restClientBuilder.clone() .baseUrl(baseUrl) .defaultHeaders(h -> { h.setContentType(MediaType.APPLICATION_JSON); - h.addAll(headers); + h.addAll(HttpHeaders.readOnlyHttpHeaders(headers)); }) .defaultStatusHandler(responseErrorHandler) .defaultRequest(requestHeadersSpec -> { @@ -82,6 +72,14 @@ public OpenAiModerationApi(String baseUrl, ApiKey apiKey, MultiValueMap createModeration(OpenAiModerationRequest openAiModerationRequest) { Assert.notNull(openAiModerationRequest, "Moderation request cannot be null."); Assert.hasLength(openAiModerationRequest.prompt(), "Prompt cannot be empty."); @@ -178,7 +176,7 @@ public static final class Builder { private ApiKey apiKey; - private MultiValueMap headers = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private RestClient.Builder restClientBuilder = RestClient.builder(); @@ -202,7 +200,7 @@ public Builder apiKey(String simpleApiKey) { return this; } - public Builder headers(MultiValueMap headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "headers cannot be null"); this.headers = headers; return this; diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/support/OpenAiResponseHeaderExtractor.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/support/OpenAiResponseHeaderExtractor.java index 7a4d344755d..b7ad61960a3 100644 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/support/OpenAiResponseHeaderExtractor.java +++ b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/metadata/support/OpenAiResponseHeaderExtractor.java @@ -71,22 +71,18 @@ public static RateLimit extractAiResponseHeaders(ResponseEntity response) { private static Duration getHeaderAsDuration(ResponseEntity response, String headerName) { var headers = response.getHeaders(); - if (headers.containsKey(headerName)) { - var values = headers.get(headerName); - if (!CollectionUtils.isEmpty(values)) { - return DurationFormatter.TIME_UNIT.parse(values.get(0)); - } + var values = headers.get(headerName); + if (!CollectionUtils.isEmpty(values)) { + return DurationFormatter.TIME_UNIT.parse(values.get(0)); } return null; } private static Long getHeaderAsLong(ResponseEntity response, String headerName) { var headers = response.getHeaders(); - if (headers.containsKey(headerName)) { - var values = headers.get(headerName); - if (!CollectionUtils.isEmpty(values)) { - return parseLong(headerName, values.get(0)); - } + var values = headers.get(headerName); + if (!CollectionUtils.isEmpty(values)) { + return parseLong(headerName, values.get(0)); } return null; } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java index ce54f0031f4..e3cb1514932 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiApiBuilderTests.java @@ -38,8 +38,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -67,7 +65,7 @@ void testMinimalBuilder() { @Test void testFullBuilder() { - MultiValueMap headers = new LinkedMultiValueMap<>(); + HttpHeaders headers = new HttpHeaders(); headers.add("Custom-Header", "test-value"); RestClient.Builder restClientBuilder = RestClient.builder(); WebClient.Builder webClientBuilder = WebClient.builder(); @@ -193,7 +191,7 @@ void testNullApiKeyValue() { @Test void testBuilderMethodChaining() { - MultiValueMap headers = new LinkedMultiValueMap<>(); + HttpHeaders headers = new HttpHeaders(); headers.add("Test-Header", "test-value"); OpenAiApi api = OpenAiApi.builder() @@ -212,7 +210,7 @@ void testBuilderMethodChaining() { @Test void testCustomHeadersPreservation() { - MultiValueMap customHeaders = new LinkedMultiValueMap<>(); + HttpHeaders customHeaders = new HttpHeaders(); customHeaders.add("X-Custom-Header", "custom-value"); customHeaders.add("X-Organization", "org-123"); customHeaders.add("User-Agent", "Custom-Client/1.0"); @@ -224,7 +222,7 @@ void testCustomHeadersPreservation() { @Test void testComplexMultiValueHeaders() { - MultiValueMap multiHeaders = new LinkedMultiValueMap<>(); + HttpHeaders multiHeaders = new HttpHeaders(); multiHeaders.add("Accept", "application/json"); multiHeaders.add("Accept", "text/plain"); multiHeaders.add("Cache-Control", "no-cache"); @@ -278,7 +276,7 @@ void testDifferentApiKeyTypes() { @Test void testBuilderCreatesIndependentInstances() { - MultiValueMap sharedHeaders = new LinkedMultiValueMap<>(); + HttpHeaders sharedHeaders = new HttpHeaders(); sharedHeaders.add("X-Shared", "value"); OpenAiApi.Builder builder = OpenAiApi.builder() @@ -294,8 +292,8 @@ void testBuilderCreatesIndependentInstances() { OpenAiApi api2 = builder.build(); // Both APIs should have the modified headers since they share the same reference - assertThat(api1.getHeaders()).containsKey("X-Modified"); - assertThat(api2.getHeaders()).containsKey("X-Modified"); + assertThat(api1.getHeaders().containsHeader("X-Modified")).isTrue(); + assertThat(api2.getHeaders().containsHeader("X-Modified")).isTrue(); } @Test @@ -312,23 +310,23 @@ void testMutatePreservesResponseErrorHandler() { @Test void testMutateCreatesIndependentHeaders() { - MultiValueMap headers = new LinkedMultiValueMap<>(); + HttpHeaders headers = new HttpHeaders(); headers.add("X-Original", "value1"); OpenAiApi original = OpenAiApi.builder().apiKey(TEST_API_KEY).headers(headers).build(); - MultiValueMap newHeaders = new LinkedMultiValueMap<>(); + HttpHeaders newHeaders = new HttpHeaders(); newHeaders.add("X-New", "value2"); OpenAiApi mutated = original.mutate().headers(newHeaders).build(); // Original headers should be unchanged - assertThat(original.getHeaders()).containsKey("X-Original"); - assertThat(original.getHeaders()).doesNotContainKey("X-New"); + assertThat(original.getHeaders().containsHeader("X-Original")).isTrue(); + assertThat(original.getHeaders().containsHeader("X-New")).isFalse(); // Mutated should have new headers - assertThat(mutated.getHeaders()).doesNotContainKey("X-Original"); - assertThat(mutated.getHeaders()).containsKey("X-New"); + assertThat(mutated.getHeaders().containsHeader("X-Original")).isFalse(); + assertThat(mutated.getHeaders().containsHeader("X-New")).isTrue(); } @Test @@ -446,7 +444,7 @@ void dynamicApiKeyRestClientWithAdditionalAuthorizationHeader() throws Interrupt OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest( List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, false); - MultiValueMap additionalHeaders = new LinkedMultiValueMap<>(); + HttpHeaders additionalHeaders = new HttpHeaders(); additionalHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer additional-key"); ResponseEntity response = api.chatCompletionEntity(request, additionalHeaders); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @@ -543,7 +541,7 @@ void dynamicApiKeyWebClientWithAdditionalAuthorizationHeader() throws Interrupte OpenAiApi.ChatCompletionMessage.Role.USER); OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest( List.of(chatCompletionMessage), "gpt-3.5-turbo", 0.8, true); - MultiValueMap additionalHeaders = new LinkedMultiValueMap<>(); + HttpHeaders additionalHeaders = new HttpHeaders(); additionalHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer additional-key"); List response = api.chatCompletionStream(request, additionalHeaders) .collectList() diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiChatModelMutateTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiChatModelMutateTests.java index 36ca255710e..cd549a9bebb 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiChatModelMutateTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiChatModelMutateTests.java @@ -20,7 +20,7 @@ import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.util.LinkedMultiValueMap; +import org.springframework.http.HttpHeaders; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -88,12 +88,12 @@ void mutateDoesNotAffectOriginal() { @Test void mutateHeadersCreatesDistinctHeaders() { - OpenAiApi mutatedApi = this.baseApi.mutate() - .headers(new LinkedMultiValueMap<>(java.util.Map.of("X-Test", java.util.List.of("value")))) - .build(); + HttpHeaders headers = new HttpHeaders(); + headers.add("X-Test", "value"); + OpenAiApi mutatedApi = this.baseApi.mutate().headers(headers).build(); - assertThat(mutatedApi.getHeaders()).containsKey("X-Test"); - assertThat(this.baseApi.getHeaders()).doesNotContainKey("X-Test"); + assertThat(mutatedApi.getHeaders().get("X-Test")).isNotNull(); + assertThat(this.baseApi.getHeaders().get("X-Test")).isNull(); } @Test @@ -129,7 +129,7 @@ void mutateAndCloneAreEquivalent() { @Test void testApiMutateWithComplexHeaders() { - LinkedMultiValueMap complexHeaders = new LinkedMultiValueMap<>(); + HttpHeaders complexHeaders = new HttpHeaders(); complexHeaders.add("Authorization", "Bearer custom-token"); complexHeaders.add("X-Custom-Header", "value1"); complexHeaders.add("X-Custom-Header", "value2"); @@ -137,9 +137,9 @@ void testApiMutateWithComplexHeaders() { OpenAiApi mutatedApi = this.baseApi.mutate().headers(complexHeaders).build(); - assertThat(mutatedApi.getHeaders()).containsKey("Authorization"); - assertThat(mutatedApi.getHeaders()).containsKey("X-Custom-Header"); - assertThat(mutatedApi.getHeaders()).containsKey("User-Agent"); + assertThat(mutatedApi.getHeaders().get("Authorization")).isNotNull(); + assertThat(mutatedApi.getHeaders().get("X-Custom-Header")).isNotNull(); + assertThat(mutatedApi.getHeaders().get("User-Agent")).isNotNull(); assertThat(mutatedApi.getHeaders().get("X-Custom-Header")).hasSize(2); } @@ -155,11 +155,11 @@ void testMutateWithEmptyOptions() { @Test void testApiMutateWithEmptyHeaders() { - LinkedMultiValueMap emptyHeaders = new LinkedMultiValueMap<>(); + HttpHeaders emptyHeaders = new HttpHeaders(); OpenAiApi mutatedApi = this.baseApi.mutate().headers(emptyHeaders).build(); - assertThat(mutatedApi.getHeaders()).isEmpty(); + assertThat(mutatedApi.getHeaders().isEmpty()).isTrue(); } @Test diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiFileApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiFileApiBuilderTests.java index 143fd9eaa68..0bcbf856db9 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiFileApiBuilderTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiFileApiBuilderTests.java @@ -36,8 +36,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; @@ -65,7 +63,7 @@ void testMinimalBuilder() { @Test void testFullBuilder() { - MultiValueMap headers = new LinkedMultiValueMap<>(); + HttpHeaders headers = new HttpHeaders(); headers.add("Custom-Header", "test-value"); RestClient.Builder restClientBuilder = RestClient.builder(); ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class); diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java index ecd506277d3..2d567807abc 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/api/OpenAiAudioApiBuilderTests.java @@ -37,8 +37,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -65,7 +63,7 @@ void testMinimalBuilder() { @Test void testFullBuilder() { - MultiValueMap headers = new LinkedMultiValueMap<>(); + HttpHeaders headers = new HttpHeaders(); headers.add("Custom-Header", "test-value"); RestClient.Builder restClientBuilder = RestClient.builder(); WebClient.Builder webClientBuilder = WebClient.builder(); diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/speech/OpenAiSpeechModelWithSpeechResponseMetadataTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/speech/OpenAiSpeechModelWithSpeechResponseMetadataTests.java index 51df242073d..e71e4ed94f4 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/speech/OpenAiSpeechModelWithSpeechResponseMetadataTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/speech/OpenAiSpeechModelWithSpeechResponseMetadataTests.java @@ -30,7 +30,7 @@ import org.springframework.ai.openai.metadata.support.OpenAiApiResponseHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; +import org.springframework.boot.restclient.test.autoconfigure.RestClientTest; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/transcription/OpenAiAudioTranscriptionModelTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/transcription/OpenAiAudioTranscriptionModelTests.java index ea9b3d930c3..411a339fd69 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/transcription/OpenAiAudioTranscriptionModelTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/transcription/OpenAiAudioTranscriptionModelTests.java @@ -28,14 +28,14 @@ import org.springframework.ai.openai.api.OpenAiAudioApi.TranscriptResponseFormat; import org.springframework.ai.retry.RetryUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; +import org.springframework.boot.restclient.test.autoconfigure.RestClientTest; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; -import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -123,9 +123,8 @@ static class Config { @Bean public OpenAiAudioApi openAiAudioApi(RestClient.Builder builder) { - return new OpenAiAudioApi("https://api.openai.com", new SimpleApiKey("test-api-key"), - new LinkedMultiValueMap<>(), builder, WebClient.builder(), - RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER); + return new OpenAiAudioApi("https://api.openai.com", new SimpleApiKey("test-api-key"), new HttpHeaders(), + builder, WebClient.builder(), RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER); } @Bean diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/transcription/OpenAiTranscriptionModelWithTranscriptionResponseMetadataTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/transcription/OpenAiTranscriptionModelWithTranscriptionResponseMetadataTests.java index 8b2dff36e4e..056e7c78c0b 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/transcription/OpenAiTranscriptionModelWithTranscriptionResponseMetadataTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/audio/transcription/OpenAiTranscriptionModelWithTranscriptionResponseMetadataTests.java @@ -33,7 +33,7 @@ import org.springframework.ai.openai.metadata.support.OpenAiApiResponseHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; +import org.springframework.boot.restclient.test.autoconfigure.RestClientTest; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/MessageTypeContentTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/MessageTypeContentTests.java index 0c949a15f71..3a176ffaef4 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/MessageTypeContentTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/MessageTypeContentTests.java @@ -41,10 +41,10 @@ import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionChunk; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest; import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; -import org.springframework.util.MultiValueMap; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -66,7 +66,7 @@ public class MessageTypeContentTests { ArgumentCaptor pomptCaptor; @Captor - ArgumentCaptor> headersCaptor; + ArgumentCaptor headersCaptor; Flux fluxResponse = Flux.generate( () -> new ChatCompletionChunk("id", List.of(), 0L, "model", null, "fp", "object", null), (state, sink) -> { @@ -89,7 +89,7 @@ public void systemMessageSimpleContentType() { this.chatModel.call(new Prompt(List.of(new SystemMessage("test message")))); validateStringContent(this.pomptCaptor.getValue()); - assertThat(this.headersCaptor.getValue()).isEmpty(); + assertThat(this.headersCaptor.getValue().isEmpty()).isTrue(); } @Test @@ -112,7 +112,7 @@ public void streamUserMessageSimpleContentType() { this.chatModel.stream(new Prompt(List.of(new UserMessage("test message")))).subscribe(); validateStringContent(this.pomptCaptor.getValue()); - assertThat(this.headersCaptor.getValue()).isEmpty(); + assertThat(this.headersCaptor.getValue().isEmpty()).isTrue(); } private void validateStringContent(ChatCompletionRequest chatCompletionRequest) { @@ -207,7 +207,7 @@ public void userMessageWithEmptyMediaList() { .build()))); validateStringContent(this.pomptCaptor.getValue()); - assertThat(this.headersCaptor.getValue()).isEmpty(); + assertThat(this.headersCaptor.getValue().isEmpty()).isTrue(); } @Test @@ -308,7 +308,7 @@ public void streamWithMultipleMessagesAndMedia() { // User message should be complex assertThat(request.messages().get(1).rawContent()).isInstanceOf(List.class); - assertThat(this.headersCaptor.getValue()).isEmpty(); + assertThat(this.headersCaptor.getValue().isEmpty()).isTrue(); } // Helper method for testing different image formats diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelObservationIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelObservationIT.java index 3b73adf7f0b..fa2daa3962f 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelObservationIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelObservationIT.java @@ -40,7 +40,8 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.ai.chat.observation.ChatModelObservationDocumentation.HighCardinalityKeyNames; @@ -175,7 +176,8 @@ public OpenAiApi openAiApi() { @Bean public OpenAiChatModel openAiChatModel(OpenAiApi openAiApi, TestObservationRegistry observationRegistry) { return new OpenAiChatModel(openAiApi, OpenAiChatOptions.builder().build(), - ToolCallingManager.builder().build(), RetryTemplate.defaultInstance(), observationRegistry); + ToolCallingManager.builder().build(), new RetryTemplate(RetryPolicy.withDefaults()), + observationRegistry); } } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelWithChatResponseMetadataTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelWithChatResponseMetadataTests.java index 1e9815513c3..0f37d78e01c 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelWithChatResponseMetadataTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiChatModelWithChatResponseMetadataTests.java @@ -35,7 +35,7 @@ import org.springframework.ai.openai.metadata.support.OpenAiApiResponseHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; +import org.springframework.boot.restclient.test.autoconfigure.RestClientTest; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -213,11 +213,11 @@ private String getJson(boolean includeLogprobs) { static class Config { @Bean - public OpenAiApi chatCompletionApi(RestClient.Builder builder, WebClient.Builder webClientBuilder) { + public OpenAiApi chatCompletionApi(RestClient.Builder builder) { return OpenAiApi.builder() .apiKey(TEST_API_KEY) .restClientBuilder(builder) - .webClientBuilder(webClientBuilder) + .webClientBuilder(WebClient.builder()) .build(); } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiRetryTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiRetryTests.java index e19e82640b2..293437a0dc4 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiRetryTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiRetryTests.java @@ -65,11 +65,11 @@ import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.TransientAiException; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import org.springframework.http.ResponseEntity; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -109,7 +109,7 @@ public class OpenAiRetryTests { public void beforeEach() { this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - this.retryTemplate.registerListener(this.retryListener); + this.retryTemplate.setRetryListener(this.retryListener); this.chatModel = OpenAiChatModel.builder() .openAiApi(this.openAiApi) @@ -145,8 +145,8 @@ public void openAiChatTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); - assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); + assertThat(this.retryListener.retryCount).isEqualTo(2); } @Test @@ -174,8 +174,8 @@ public void openAiChatStreamTransientError() { assertThat(result).isNotNull(); assertThat(result.collectList().block().get(0).getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); - assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); + assertThat(this.retryListener.retryCount).isEqualTo(2); } @Test @@ -202,8 +202,8 @@ public void openAiEmbeddingTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput()).isEqualTo(new float[] { 9.9f, 8.8f }); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); - assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); + assertThat(this.retryListener.retryCount).isEqualTo(2); } @Test @@ -229,8 +229,8 @@ public void openAiAudioTranscriptionTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput()).isEqualTo(expectedResponse.text()); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); - assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); + assertThat(this.retryListener.retryCount).isEqualTo(2); } @Test @@ -256,8 +256,8 @@ public void openAiImageTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getUrl()).isEqualTo("url678"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); - assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); + assertThat(this.retryListener.retryCount).isEqualTo(2); } @Test @@ -270,19 +270,19 @@ public void openAiImageNonTransientError() { private static class TestRetryListener implements RetryListener { - int onErrorRetryCount = 0; + int retryCount = 0; int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void beforeRetry(RetryPolicy retryPolicy, Retryable retryable) { + this.retryCount++; } } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiStreamingFinishReasonTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiStreamingFinishReasonTests.java index 3dc59444e82..d1f8dee4929 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiStreamingFinishReasonTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/chat/OpenAiStreamingFinishReasonTests.java @@ -40,7 +40,7 @@ import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionMessage.Role; import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest; import org.springframework.ai.retry.RetryUtils; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/OpenAiEmbeddingModelObservationIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/OpenAiEmbeddingModelObservationIT.java index aa76a67f7a5..5d3cd477653 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/OpenAiEmbeddingModelObservationIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/embedding/OpenAiEmbeddingModelObservationIT.java @@ -37,7 +37,8 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation.HighCardinalityKeyNames; @@ -111,7 +112,7 @@ public OpenAiApi openAiApi() { public OpenAiEmbeddingModel openAiEmbeddingModel(OpenAiApi openAiApi, TestObservationRegistry observationRegistry) { return new OpenAiEmbeddingModel(openAiApi, MetadataMode.EMBED, OpenAiEmbeddingOptions.builder().build(), - RetryTemplate.defaultInstance(), observationRegistry); + new RetryTemplate(RetryPolicy.withDefaults()), observationRegistry); } } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelNoOpApiKeysIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelNoOpApiKeysIT.java index 61160a08a1f..bbd3373f826 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelNoOpApiKeysIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelNoOpApiKeysIT.java @@ -31,7 +31,8 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -70,7 +71,7 @@ public OpenAiImageApi openAiImageApi() { @Bean public OpenAiImageModel openAiImageModel(OpenAiImageApi openAiImageApi) { return new OpenAiImageModel(openAiImageApi, OpenAiImageOptions.builder().build(), - RetryTemplate.defaultInstance(), TestObservationRegistry.create()); + new RetryTemplate(RetryPolicy.withDefaults()), TestObservationRegistry.create()); } } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelObservationIT.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelObservationIT.java index 37dc7abcdba..8a6fb12b13b 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelObservationIT.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelObservationIT.java @@ -34,7 +34,8 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.ai.image.observation.ImageModelObservationDocumentation.HighCardinalityKeyNames; @@ -105,7 +106,7 @@ public OpenAiImageApi openAiImageApi() { public OpenAiImageModel openAiImageModel(OpenAiImageApi openAiImageApi, TestObservationRegistry observationRegistry) { return new OpenAiImageModel(openAiImageApi, OpenAiImageOptions.builder().build(), - RetryTemplate.defaultInstance(), observationRegistry); + new RetryTemplate(RetryPolicy.withDefaults()), observationRegistry); } } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelWithImageResponseMetadataTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelWithImageResponseMetadataTests.java index 14dda5ccd22..9530d45e01d 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelWithImageResponseMetadataTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/OpenAiImageModelWithImageResponseMetadataTests.java @@ -32,7 +32,7 @@ import org.springframework.ai.openai.metadata.support.OpenAiApiResponseHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; +import org.springframework.boot.restclient.test.autoconfigure.RestClientTest; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java index 50bfd71fef3..6121893973a 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/image/api/OpenAiImageApiBuilderTests.java @@ -37,8 +37,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; @@ -64,7 +62,7 @@ void testMinimalBuilder() { @Test void testFullBuilder() { - MultiValueMap headers = new LinkedMultiValueMap<>(); + HttpHeaders headers = new HttpHeaders(); headers.add("Custom-Header", "test-value"); RestClient.Builder restClientBuilder = RestClient.builder(); ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class); diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/OpenAiModerationModelTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/OpenAiModerationModelTests.java index ec599b03df9..002f5a84b37 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/OpenAiModerationModelTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/OpenAiModerationModelTests.java @@ -36,7 +36,7 @@ import org.springframework.ai.retry.RetryUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; +import org.springframework.boot.restclient.test.autoconfigure.RestClientTest; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java index 262eb21e05b..e7712e97d97 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/moderation/api/OpenAiModerationApiBuilderTests.java @@ -37,8 +37,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; @@ -64,7 +62,7 @@ void testMinimalBuilder() { @Test void testFullBuilder() { - MultiValueMap headers = new LinkedMultiValueMap<>(); + HttpHeaders headers = new HttpHeaders(); headers.add("Custom-Header", "test-value"); RestClient.Builder restClientBuilder = RestClient.builder(); ResponseErrorHandler errorHandler = mock(ResponseErrorHandler.class); diff --git a/models/spring-ai-postgresml/pom.xml b/models/spring-ai-postgresml/pom.xml index caff7c1e3d1..a978654b030 100644 --- a/models/spring-ai-postgresml/pom.xml +++ b/models/spring-ai-postgresml/pom.xml @@ -52,6 +52,11 @@ runtime + + org.springframework.boot + spring-boot-starter-jdbc + + org.springframework spring-jdbc @@ -63,6 +68,11 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-jdbc-test + test + org.springframework.boot @@ -72,13 +82,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - postgresql + testcontainers-postgresql test diff --git a/models/spring-ai-postgresml/src/test/java/org/springframework/ai/postgresml/PostgresMlEmbeddingModelIT.java b/models/spring-ai-postgresml/src/test/java/org/springframework/ai/postgresml/PostgresMlEmbeddingModelIT.java index f682823d41a..06f4ef93c36 100644 --- a/models/spring-ai-postgresml/src/test/java/org/springframework/ai/postgresml/PostgresMlEmbeddingModelIT.java +++ b/models/spring-ai-postgresml/src/test/java/org/springframework/ai/postgresml/PostgresMlEmbeddingModelIT.java @@ -40,8 +40,8 @@ import org.springframework.ai.postgresml.PostgresMlEmbeddingModel.VectorType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; -import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest; +import org.springframework.boot.jdbc.test.autoconfigure.AutoConfigureTestDatabase; +import org.springframework.boot.jdbc.test.autoconfigure.JdbcTest; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.jdbc.core.JdbcTemplate; diff --git a/models/spring-ai-transformers/pom.xml b/models/spring-ai-transformers/pom.xml index 08ef9835cca..f08cbe367c6 100644 --- a/models/spring-ai-transformers/pom.xml +++ b/models/spring-ai-transformers/pom.xml @@ -99,7 +99,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingModel.java b/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingModel.java index 4bef9d1145b..380ed5035ae 100644 --- a/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingModel.java +++ b/models/spring-ai-vertex-ai-embedding/src/main/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingModel.java @@ -50,7 +50,8 @@ import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingUtils; import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingUtils.TextInstanceBuilder; import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingUtils.TextParametersBuilder; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -139,7 +140,7 @@ public EmbeddingResponse call(EmbeddingRequest request) { (VertexAiTextEmbeddingOptions) options); PredictResponse embeddingResponse = this.retryTemplate - .execute(context -> getPredictResponse(client, predictRequestBuilder)); + .execute(() -> getPredictResponse(client, predictRequestBuilder)); int index = 0; int totalTokenCount = 0; @@ -163,6 +164,14 @@ public EmbeddingResponse call(EmbeddingRequest request) { return response; } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } }); } diff --git a/models/spring-ai-vertex-ai-embedding/src/test/java/org/springframework/ai/vertexai/embedding/text/TestVertexAiTextEmbeddingModel.java b/models/spring-ai-vertex-ai-embedding/src/test/java/org/springframework/ai/vertexai/embedding/text/TestVertexAiTextEmbeddingModel.java index e8627f3d625..36853d0bca7 100644 --- a/models/spring-ai-vertex-ai-embedding/src/test/java/org/springframework/ai/vertexai/embedding/text/TestVertexAiTextEmbeddingModel.java +++ b/models/spring-ai-vertex-ai-embedding/src/test/java/org/springframework/ai/vertexai/embedding/text/TestVertexAiTextEmbeddingModel.java @@ -23,7 +23,7 @@ import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; public class TestVertexAiTextEmbeddingModel extends VertexAiTextEmbeddingModel { diff --git a/models/spring-ai-vertex-ai-embedding/src/test/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingRetryTests.java b/models/spring-ai-vertex-ai-embedding/src/test/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingRetryTests.java index 6d88ef6e958..08f4295dbcd 100644 --- a/models/spring-ai-vertex-ai-embedding/src/test/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingRetryTests.java +++ b/models/spring-ai-vertex-ai-embedding/src/test/java/org/springframework/ai/vertexai/embedding/text/VertexAiTextEmbeddingRetryTests.java @@ -36,10 +36,10 @@ import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.TransientAiException; import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -76,7 +76,7 @@ public class VertexAiTextEmbeddingRetryTests { public void setUp() { this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - this.retryTemplate.registerListener(this.retryListener); + this.retryTemplate.setRetryListener(this.retryListener); this.embeddingModel = new TestVertexAiTextEmbeddingModel(this.mockConnectionDetails, VertexAiTextEmbeddingOptions.builder().build(), this.retryTemplate); @@ -123,7 +123,7 @@ public void vertexAiEmbeddingTransientError() { assertThat(result).isNotNull(); assertThat(result.getResults()).hasSize(1); assertThat(result.getResults().get(0).getOutput()).isEqualTo(new float[] { 9.9f, 8.8f }); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); verify(this.mockPredictRequestBuilder, times(3)).build(); @@ -163,14 +163,15 @@ private static class TestRetryListener implements RetryListener { int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void beforeRetry(final RetryPolicy retryPolicy, final Retryable retryable) { + // Count each retry attempt + this.onErrorRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } } diff --git a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java index 3a55ee58611..c7b95ddad88 100644 --- a/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java +++ b/models/spring-ai-vertex-ai-gemini/src/main/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiChatModel.java @@ -91,8 +91,9 @@ import org.springframework.ai.vertexai.gemini.schema.VertexAiSchemaConverter; import org.springframework.ai.vertexai.gemini.schema.VertexToolCallingManager; import org.springframework.beans.factory.DisposableBean; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.lang.NonNull; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -389,28 +390,45 @@ private ChatResponse internalCall(Prompt prompt, ChatResponse previousChatRespon ChatResponse response = ChatModelObservationDocumentation.CHAT_MODEL_OPERATION .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry) - .observe(() -> this.retryTemplate.execute(context -> { - - var geminiRequest = createGeminiRequest(prompt); - - GenerateContentResponse generateContentResponse = this.getContentResponse(geminiRequest); + .observe(() -> { + try { + return this.retryTemplate.execute(() -> { + + var geminiRequest = createGeminiRequest(prompt); + + GenerateContentResponse generateContentResponse = this.getContentResponse(geminiRequest); + + List generations = generateContentResponse.getCandidatesList() + .stream() + .map(this::responseCandidateToGeneration) + .flatMap(List::stream) + .toList(); + + GenerateContentResponse.UsageMetadata usage = generateContentResponse.getUsageMetadata(); + Usage currentUsage = (usage != null) + ? new DefaultUsage(usage.getPromptTokenCount(), usage.getCandidatesTokenCount()) + : new EmptyUsage(); + Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(currentUsage, previousChatResponse); + ChatResponse chatResponse = new ChatResponse(generations, + toChatResponseMetadata(cumulativeUsage)); + + observationContext.setResponse(chatResponse); + return chatResponse; + }); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } - List generations = generateContentResponse.getCandidatesList() - .stream() - .map(this::responseCandidateToGeneration) - .flatMap(List::stream) - .toList(); - - GenerateContentResponse.UsageMetadata usage = generateContentResponse.getUsageMetadata(); - Usage currentUsage = (usage != null) - ? new DefaultUsage(usage.getPromptTokenCount(), usage.getCandidatesTokenCount()) - : new EmptyUsage(); - Usage cumulativeUsage = UsageCalculator.getCumulativeUsage(currentUsage, previousChatResponse); - ChatResponse chatResponse = new ChatResponse(generations, toChatResponseMetadata(cumulativeUsage)); - - observationContext.setResponse(chatResponse); - return chatResponse; - })); + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } + }); if (this.toolExecutionEligibilityPredicate.isToolExecutionRequired(prompt.getOptions(), response)) { var toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/TestVertexAiGeminiChatModel.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/TestVertexAiGeminiChatModel.java index 33af68c57d2..e19e39fccb0 100644 --- a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/TestVertexAiGeminiChatModel.java +++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/TestVertexAiGeminiChatModel.java @@ -23,7 +23,7 @@ import com.google.cloud.vertexai.generativeai.GenerativeModel; import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; /** * @author Mark Pollack diff --git a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiRetryTests.java b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiRetryTests.java index 79ac33982c3..2b1b88d9a76 100644 --- a/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiRetryTests.java +++ b/models/spring-ai-vertex-ai-gemini/src/test/java/org/springframework/ai/vertexai/gemini/VertexAiGeminiRetryTests.java @@ -35,10 +35,10 @@ import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.retry.TransientAiException; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -68,7 +68,7 @@ public class VertexAiGeminiRetryTests { public void setUp() { this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - this.retryTemplate.registerListener(this.retryListener); + this.retryTemplate.setRetryListener(this.retryListener); this.chatModel = new TestVertexAiGeminiChatModel(this.vertexAI, VertexAiGeminiChatOptions.builder() @@ -101,7 +101,7 @@ public void vertexAiGeminiChatTransientError() throws IOException { // Assertions assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()).isEqualTo("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -168,7 +168,6 @@ public void vertexAiGeminiChatMaxRetriesExceeded() throws Exception { // Should throw the last TransientAiException after exhausting retries assertThrows(TransientAiException.class, () -> this.chatModel.call(new Prompt("test prompt"))); - // Verify retry attempts were made assertThat(this.retryListener.onErrorRetryCount).isGreaterThan(0); } @@ -249,7 +248,7 @@ public void vertexAiGeminiChatAlternatingErrorsAndSuccess() throws Exception { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()).isEqualTo("Success after alternating errors"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -260,14 +259,15 @@ private static class TestRetryListener implements RetryListener { int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void beforeRetry(final RetryPolicy retryPolicy, final Retryable retryable) { + // Count each retry attempt + this.onErrorRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } } diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java index 2c9ff3e54ff..4c6e0225bf9 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiChatModel.java @@ -70,8 +70,9 @@ import org.springframework.ai.zhipuai.api.ZhiPuAiApi.ChatCompletionMessage.ToolCall; import org.springframework.ai.zhipuai.api.ZhiPuAiApi.ChatCompletionRequest; import org.springframework.ai.zhipuai.api.ZhiPuApiConstants; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.MimeType; @@ -260,8 +261,18 @@ public ChatResponse call(Prompt prompt) { this.observationRegistry) .observe(() -> { - ResponseEntity completionEntity = this.retryTemplate - .execute(ctx -> this.zhiPuAiApi.chatCompletionEntity(request)); + ResponseEntity completionEntity = null; + try { + completionEntity = this.retryTemplate.execute(() -> this.zhiPuAiApi.chatCompletionEntity(request)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } var chatCompletion = completionEntity.getBody(); @@ -319,8 +330,18 @@ public Flux stream(Prompt prompt) { Prompt requestPrompt = buildRequestPrompt(prompt); ChatCompletionRequest request = createRequest(requestPrompt, true); - Flux completionChunks = this.retryTemplate - .execute(ctx -> this.zhiPuAiApi.chatCompletionStream(request)); + Flux completionChunks = null; + try { + completionChunks = this.retryTemplate.execute(() -> this.zhiPuAiApi.chatCompletionStream(request)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } // For chunked responses, only the first chunk contains the choice role. // The rest of the chunks with same ID share the same role. diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingModel.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingModel.java index c1ec94262e1..7a60ddb883c 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingModel.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiEmbeddingModel.java @@ -41,7 +41,9 @@ import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.zhipuai.api.ZhiPuAiApi; import org.springframework.ai.zhipuai.api.ZhiPuApiConstants; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -165,8 +167,19 @@ public EmbeddingResponse call(EmbeddingRequest request) { .observation(this.observationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, this.observationRegistry) .observe(() -> { - var embeddingResponse = this.retryTemplate - .execute(ctx -> this.zhiPuAiApi.embeddings(zhipuEmbeddingRequest)); + ResponseEntity> embeddingResponse = null; + try { + embeddingResponse = this.retryTemplate + .execute(() -> this.zhiPuAiApi.embeddings(zhipuEmbeddingRequest)); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } if (embeddingResponse == null || embeddingResponse.getBody() == null || CollectionUtils.isEmpty(embeddingResponse.getBody().data())) { diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiImageModel.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiImageModel.java index 406221e7d8a..e88231a9929 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiImageModel.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/ZhiPuAiImageModel.java @@ -30,8 +30,9 @@ import org.springframework.ai.model.ModelOptionsUtils; import org.springframework.ai.retry.RetryUtils; import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.ResponseEntity; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; /** @@ -71,30 +72,40 @@ public ZhiPuAiImageOptions getDefaultOptions() { @Override public ImageResponse call(ImagePrompt imagePrompt) { - return this.retryTemplate.execute(ctx -> { + try { + return this.retryTemplate.execute(() -> { - String instructions = imagePrompt.getInstructions().get(0).getText(); + String instructions = imagePrompt.getInstructions().get(0).getText(); - ZhiPuAiImageApi.ZhiPuAiImageRequest imageRequest = new ZhiPuAiImageApi.ZhiPuAiImageRequest(instructions, - ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL); + ZhiPuAiImageApi.ZhiPuAiImageRequest imageRequest = new ZhiPuAiImageApi.ZhiPuAiImageRequest(instructions, + ZhiPuAiImageApi.DEFAULT_IMAGE_MODEL); - if (this.defaultOptions != null) { - imageRequest = ModelOptionsUtils.merge(this.defaultOptions, imageRequest, - ZhiPuAiImageApi.ZhiPuAiImageRequest.class); - } + if (this.defaultOptions != null) { + imageRequest = ModelOptionsUtils.merge(this.defaultOptions, imageRequest, + ZhiPuAiImageApi.ZhiPuAiImageRequest.class); + } - if (imagePrompt.getOptions() != null) { - imageRequest = ModelOptionsUtils.merge(toZhiPuAiImageOptions(imagePrompt.getOptions()), imageRequest, - ZhiPuAiImageApi.ZhiPuAiImageRequest.class); - } + if (imagePrompt.getOptions() != null) { + imageRequest = ModelOptionsUtils.merge(toZhiPuAiImageOptions(imagePrompt.getOptions()), + imageRequest, ZhiPuAiImageApi.ZhiPuAiImageRequest.class); + } - // Make the request - ResponseEntity imageResponseEntity = this.zhiPuAiImageApi - .createImage(imageRequest); + // Make the request + ResponseEntity imageResponseEntity = this.zhiPuAiImageApi + .createImage(imageRequest); - // Convert to org.springframework.ai.model derived ImageResponse data type - return convertResponse(imageResponseEntity, imageRequest); - }); + // Convert to org.springframework.ai.model derived ImageResponse data type + return convertResponse(imageResponseEntity, imageRequest); + }); + } + catch (RetryException e) { + if (e.getCause() instanceof RuntimeException r) { + throw r; + } + else { + throw new RuntimeException(e.getCause()); + } + } } private ImageResponse convertResponse(ResponseEntity imageResponseEntity, diff --git a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/api/ZhiPuAiApi.java b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/api/ZhiPuAiApi.java index f07d93a159c..ee1cabffe85 100644 --- a/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/api/ZhiPuAiApi.java +++ b/models/spring-ai-zhipuai/src/main/java/org/springframework/ai/zhipuai/api/ZhiPuAiApi.java @@ -42,8 +42,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -85,7 +83,7 @@ public static Builder builder() { private final ApiKey apiKey; - private final MultiValueMap headers; + private final HttpHeaders headers; private final String completionsPath; @@ -143,7 +141,7 @@ public ZhiPuAiApi(String baseUrl, String zhiPuAiToken, RestClient.Builder restCl @Deprecated public ZhiPuAiApi(String baseUrl, String zhiPuAiToken, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { - this(baseUrl, new SimpleApiKey(zhiPuAiToken), new LinkedMultiValueMap<>(), DEFAULT_COMPLETIONS_PATH, + this(baseUrl, new SimpleApiKey(zhiPuAiToken), new HttpHeaders(), DEFAULT_COMPLETIONS_PATH, DEFAULT_EMBEDDINGS_PATH, restClientBuilder, WebClient.builder(), responseErrorHandler); } @@ -158,7 +156,7 @@ public ZhiPuAiApi(String baseUrl, String zhiPuAiToken, RestClient.Builder restCl * @param webClientBuilder WebClient builder. * @param responseErrorHandler Response error handler. */ - private ZhiPuAiApi(String baseUrl, ApiKey apiKey, MultiValueMap headers, String completionsPath, + private ZhiPuAiApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, String completionsPath, String embeddingsPath, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) { Assert.hasText(completionsPath, "Completions Path must not be null"); @@ -190,6 +188,33 @@ private ZhiPuAiApi(String baseUrl, ApiKey apiKey, MultiValueMap .build(); // @formatter:on } + /** + * Create a new chat completion api. + * @param baseUrl api base URL. + * @param apiKey ZhiPuAI apiKey. + * @param headers the http headers to use. + * @param completionsPath the path to the chat completions endpoint. + * @param embeddingsPath the path to the embeddings endpoint. + * @param restClient RestClient instance. + * @param webClient WebClient instance. + * @param responseErrorHandler Response error handler. + */ + public ZhiPuAiApi(String baseUrl, ApiKey apiKey, HttpHeaders headers, String completionsPath, String embeddingsPath, + ResponseErrorHandler responseErrorHandler, RestClient restClient, WebClient webClient) { + Assert.hasText(completionsPath, "Completions Path must not be null"); + Assert.hasText(embeddingsPath, "Embeddings Path must not be null"); + Assert.notNull(headers, "Headers must not be null"); + + this.baseUrl = baseUrl; + this.apiKey = apiKey; + this.headers = headers; + this.completionsPath = completionsPath; + this.embeddingsPath = embeddingsPath; + this.responseErrorHandler = responseErrorHandler; + this.restClient = restClient; + this.webClient = webClient; + } + public static String getTextContent(List content) { return content.stream() .filter(c -> "text".equals(c.type())) @@ -204,7 +229,7 @@ public static String getTextContent(List con * and headers. */ public ResponseEntity chatCompletionEntity(ChatCompletionRequest chatRequest) { - return chatCompletionEntity(chatRequest, new LinkedMultiValueMap<>()); + return chatCompletionEntity(chatRequest, new HttpHeaders()); } /** @@ -214,7 +239,7 @@ public ResponseEntity chatCompletionEntity(ChatCompletionRequest * and headers. */ public ResponseEntity chatCompletionEntity(ChatCompletionRequest chatRequest, - MultiValueMap additionalHttpHeader) { + HttpHeaders additionalHttpHeader) { Assert.notNull(chatRequest, "The request body can not be null."); Assert.isTrue(!chatRequest.stream(), "Request must set the stream property to false."); @@ -239,7 +264,7 @@ public ResponseEntity chatCompletionEntity(ChatCompletionRequest * @return Returns a {@link Flux} stream from chat completion chunks. */ public Flux chatCompletionStream(ChatCompletionRequest chatRequest) { - return chatCompletionStream(chatRequest, new LinkedMultiValueMap<>()); + return chatCompletionStream(chatRequest, new HttpHeaders()); } /** @@ -249,7 +274,7 @@ public Flux chatCompletionStream(ChatCompletionRequest chat * @return Returns a {@link Flux} stream from chat completion chunks. */ public Flux chatCompletionStream(ChatCompletionRequest chatRequest, - MultiValueMap additionalHttpHeader) { + HttpHeaders additionalHttpHeader) { Assert.notNull(chatRequest, "The request body can not be null."); Assert.isTrue(chatRequest.stream(), "Request must set the stream property to true."); @@ -330,7 +355,7 @@ public ResponseEntity> embeddings(EmbeddingRequest< } private void addDefaultHeadersIfMissing(HttpHeaders headers) { - if (!headers.containsKey(HttpHeaders.AUTHORIZATION) && !(this.apiKey instanceof NoopApiKey)) { + if (headers.get(HttpHeaders.AUTHORIZATION) == null && !(this.apiKey instanceof NoopApiKey)) { headers.setBearerAuth(this.apiKey.getValue()); } } @@ -344,7 +369,7 @@ ApiKey getApiKey() { return this.apiKey; } - MultiValueMap getHeaders() { + HttpHeaders getHeaders() { return this.headers; } @@ -1216,7 +1241,8 @@ private Builder() { public Builder(ZhiPuAiApi api) { this.baseUrl = api.getBaseUrl(); this.apiKey = api.getApiKey(); - this.headers = new LinkedMultiValueMap<>(api.getHeaders()); + this.headers = new HttpHeaders(); + this.headers.addAll(api.getHeaders()); this.completionsPath = api.getCompletionsPath(); this.embeddingsPath = api.getEmbeddingsPath(); this.restClientBuilder = api.restClient != null ? api.restClient.mutate() : RestClient.builder(); @@ -1228,7 +1254,7 @@ public Builder(ZhiPuAiApi api) { private ApiKey apiKey; - private MultiValueMap headers = new LinkedMultiValueMap<>(); + private HttpHeaders headers = new HttpHeaders(); private String completionsPath = DEFAULT_COMPLETIONS_PATH; @@ -1257,7 +1283,7 @@ public Builder apiKey(String simpleApiKey) { return this; } - public Builder headers(MultiValueMap headers) { + public Builder headers(HttpHeaders headers) { Assert.notNull(headers, "headers cannot be null"); this.headers = headers; return this; diff --git a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/api/ZhiPuAiApiBuilderTests.java b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/api/ZhiPuAiApiBuilderTests.java index a6409e70c20..5b1f0d9795a 100644 --- a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/api/ZhiPuAiApiBuilderTests.java +++ b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/api/ZhiPuAiApiBuilderTests.java @@ -37,8 +37,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -66,7 +64,7 @@ void testMinimalBuilder() { @Test void testFullBuilder() { - MultiValueMap headers = new LinkedMultiValueMap<>(); + var headers = new HttpHeaders(); headers.add("Custom-Header", "test-value"); RestClient.Builder restClientBuilder = RestClient.builder(); WebClient.Builder webClientBuilder = WebClient.builder(); @@ -232,7 +230,7 @@ void dynamicApiKeyRestClientWithAdditionalAuthorizationHeader() throws Interrupt ZhiPuAiApi.ChatCompletionRequest request = new ZhiPuAiApi.ChatCompletionRequest( List.of(chatCompletionMessage), "glm-4-flash", 0.8, false); - MultiValueMap additionalHeaders = new LinkedMultiValueMap<>(); + var additionalHeaders = new HttpHeaders(); additionalHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer additional-key"); ResponseEntity response = api.chatCompletionEntity(request, additionalHeaders); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); @@ -289,7 +287,7 @@ void dynamicApiKeyWebClientWithAdditionalAuthorizationHeader() throws Interrupte ZhiPuAiApi.ChatCompletionMessage.Role.USER); ZhiPuAiApi.ChatCompletionRequest request = new ZhiPuAiApi.ChatCompletionRequest( List.of(chatCompletionMessage), "glm-4-flash", 0.8, true); - MultiValueMap additionalHeaders = new LinkedMultiValueMap<>(); + var additionalHeaders = new HttpHeaders(); additionalHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer additional-key"); List response = api.chatCompletionStream(request, additionalHeaders) .collectList() diff --git a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/api/ZhiPuAiRetryTests.java b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/api/ZhiPuAiRetryTests.java index 327a7f45329..7746360829c 100644 --- a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/api/ZhiPuAiRetryTests.java +++ b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/api/ZhiPuAiRetryTests.java @@ -52,11 +52,11 @@ import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi.Data; import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi.ZhiPuAiImageRequest; import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi.ZhiPuAiImageResponse; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import org.springframework.http.ResponseEntity; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -88,7 +88,7 @@ public class ZhiPuAiRetryTests { public void beforeEach() { this.retryTemplate = RetryUtils.SHORT_RETRY_TEMPLATE; this.retryListener = new TestRetryListener(); - this.retryTemplate.registerListener(this.retryListener); + this.retryTemplate.setRetryListener(this.retryListener); this.chatModel = new ZhiPuAiChatModel(this.zhiPuAiApi, ZhiPuAiChatOptions.builder().build(), this.retryTemplate); @@ -115,7 +115,7 @@ public void zhiPuAiChatTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -144,7 +144,7 @@ public void zhiPuAiChatStreamTransientError() { assertThat(result).isNotNull(); assertThat(result.collectList().block().get(0).getResult().getOutput().getText()).isSameAs("Response"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -174,7 +174,7 @@ public void zhiPuAiEmbeddingTransientError() { assertThat(result).isNotNull(); // choose the first result assertThat(result.getResult().getOutput()).isEqualTo(new float[] { 9.9f, 8.8f }); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -201,7 +201,7 @@ public void zhiPuAiImageTransientError() { assertThat(result).isNotNull(); assertThat(result.getResult().getOutput().getUrl()).isEqualTo("url678"); - assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(2); + assertThat(this.retryListener.onSuccessRetryCount).isEqualTo(1); assertThat(this.retryListener.onErrorRetryCount).isEqualTo(2); } @@ -213,21 +213,22 @@ public void zhiPuAiImageNonTransientError() { () -> this.imageModel.call(new ImagePrompt(List.of(new ImageMessage("Image Message"))))); } - private class TestRetryListener implements RetryListener { + private static class TestRetryListener implements RetryListener { int onErrorRetryCount = 0; int onSuccessRetryCount = 0; @Override - public void onSuccess(RetryContext context, RetryCallback callback, T result) { - this.onSuccessRetryCount = context.getRetryCount(); + public void beforeRetry(final RetryPolicy retryPolicy, final Retryable retryable) { + // Count each retry attempt + this.onErrorRetryCount++; } @Override - public void onError(RetryContext context, RetryCallback callback, - Throwable throwable) { - this.onErrorRetryCount = context.getRetryCount(); + public void onRetrySuccess(final RetryPolicy retryPolicy, final Retryable retryable, final Object result) { + // Count successful retries - we increment when we succeed after a failure + this.onSuccessRetryCount++; } } diff --git a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatModelObservationIT.java b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatModelObservationIT.java index 953c7c3bb4e..a01fc35ec0d 100644 --- a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatModelObservationIT.java +++ b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/chat/ZhiPuAiChatModelObservationIT.java @@ -39,7 +39,7 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.ai.chat.observation.ChatModelObservationDocumentation.HighCardinalityKeyNames; @@ -164,8 +164,8 @@ public ZhiPuAiApi zhiPuAiApi() { @Bean public ZhiPuAiChatModel zhiPuAiChatModel(ZhiPuAiApi zhiPuAiApi, TestObservationRegistry observationRegistry) { - return new ZhiPuAiChatModel(zhiPuAiApi, ZhiPuAiChatOptions.builder().build(), - RetryTemplate.defaultInstance(), observationRegistry); + return new ZhiPuAiChatModel(zhiPuAiApi, ZhiPuAiChatOptions.builder().build(), new RetryTemplate(), + observationRegistry); } } diff --git a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/embedding/ZhiPuAiEmbeddingModelObservationIT.java b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/embedding/ZhiPuAiEmbeddingModelObservationIT.java index f6a33037566..11f3bc70f75 100644 --- a/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/embedding/ZhiPuAiEmbeddingModelObservationIT.java +++ b/models/spring-ai-zhipuai/src/test/java/org/springframework/ai/zhipuai/embedding/ZhiPuAiEmbeddingModelObservationIT.java @@ -37,7 +37,7 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; +import org.springframework.core.retry.RetryTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation.HighCardinalityKeyNames; @@ -107,7 +107,7 @@ public ZhiPuAiEmbeddingModel zhiPuAiEmbeddingModel(ZhiPuAiApi zhiPuAiApi, TestObservationRegistry observationRegistry) { return new ZhiPuAiEmbeddingModel(zhiPuAiApi, MetadataMode.EMBED, ZhiPuAiEmbeddingOptions.builder().model(ZhiPuAiApi.DEFAULT_EMBEDDING_MODEL).build(), - RetryTemplate.defaultInstance(), observationRegistry); + new RetryTemplate(), observationRegistry); } } diff --git a/pom.xml b/pom.xml index c72871481bd..aff74d8e334 100644 --- a/pom.xml +++ b/pom.xml @@ -267,13 +267,14 @@ ${java.version} ${java.version} ${java.version} + 2.2.21 - 3.5.7 + 4.0.0-RC1 4.3.4 1.0.0-beta.16 1.1.0 - 1.9.25 + 2.2.21 2.31.65 @@ -301,8 +302,8 @@ 2.20.11 24.09 2.5.8 - 2.3.0 - 1.20.4 + 2.3.3 + 2.0.1 4.0.1 4.29.3 @@ -328,11 +329,12 @@ 4.12.0 + 5.5.6 4.1.0 - 0.15.0-SNAPSHOT - 0.6.0-SNAPSHOT + 0.15.0 + 0.6.0 4.13.1 @@ -433,7 +435,7 @@ org.jetbrains.kotlin kotlin-maven-plugin - ${kotlin.version} + ${kotlin.compiler.version} ${java.version} true @@ -934,6 +936,20 @@ pom import + + org.testcontainers + testcontainers-bom + ${testcontainers.version} + pom + import + + + io.rest-assured + rest-assured-bom + ${rest-assured-bom.version} + pom + import + io.modelcontextprotocol.sdk mcp-bom diff --git a/spring-ai-client-chat/src/main/kotlin/org/springframework/ai/chat/client/ChatClientExtensions.kt b/spring-ai-client-chat/src/main/kotlin/org/springframework/ai/chat/client/ChatClientExtensions.kt index 40a4c6ffd84..7128603bbc7 100644 --- a/spring-ai-client-chat/src/main/kotlin/org/springframework/ai/chat/client/ChatClientExtensions.kt +++ b/spring-ai-client-chat/src/main/kotlin/org/springframework/ai/chat/client/ChatClientExtensions.kt @@ -25,8 +25,8 @@ import org.springframework.core.ParameterizedTypeReference * @author Josh Long */ -inline fun ChatClient.CallResponseSpec.entity(): T = +inline fun ChatClient.CallResponseSpec.entity(): T = entity(object : ParameterizedTypeReference() {}) as T -inline fun ChatClient.CallResponseSpec.responseEntity(): ResponseEntity = +inline fun ChatClient.CallResponseSpec.responseEntity(): ResponseEntity = responseEntity(object : ParameterizedTypeReference() {}) diff --git a/spring-ai-integration-tests/pom.xml b/spring-ai-integration-tests/pom.xml index 8ff01b1baad..c4c6c6feb91 100644 --- a/spring-ai-integration-tests/pom.xml +++ b/spring-ai-integration-tests/pom.xml @@ -47,6 +47,16 @@ spring-boot-starter-web test + + org.springframework.boot + spring-boot-starter-restclient + test + + + org.springframework.boot + spring-boot-starter-webclient + test + io.micrometer @@ -111,7 +121,7 @@ org.testcontainers - postgresql + testcontainers-postgresql test diff --git a/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/tool/ToolCallingManagerTests.java b/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/tool/ToolCallingManagerTests.java index 89621cc8cb1..bac0082e718 100644 --- a/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/tool/ToolCallingManagerTests.java +++ b/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/tool/ToolCallingManagerTests.java @@ -17,6 +17,7 @@ package org.springframework.ai.integration.tests.tool; import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; @@ -107,7 +108,7 @@ private void runExplicitToolCallingExecutionWithOptions(ChatOptions chatOptions, } private void runExplicitToolCallingExecutionWithOptionsStream(ChatOptions chatOptions, Prompt prompt) { - ChatResponse chatResponse = this.openAiChatModel.stream(prompt).flatMap(response -> { + String joinedTextResponse = this.openAiChatModel.stream(prompt).flatMap(response -> { if (response.hasToolCalls()) { ToolExecutionResult toolExecutionResult = this.toolCallingManager.executeToolCalls(prompt, response); @@ -117,14 +118,13 @@ private void runExplicitToolCallingExecutionWithOptionsStream(ChatOptions chatOp .anyMatch(m -> m instanceof ToolResponseMessage)).isTrue(); Prompt secondPrompt = new Prompt(toolExecutionResult.conversationHistory(), chatOptions); - // return openAiChatModel.stream(secondPrompt); - return Flux.just(this.openAiChatModel.call(secondPrompt)); + return this.openAiChatModel.stream(secondPrompt); } return Flux.just(response); - }).blockLast(); + }).mapNotNull(it -> it.getResult().getOutput().getText()).collect(Collectors.joining()).block(); - assertThat(chatResponse).isNotNull(); - assertThat(chatResponse.getResult().getOutput().getText()).isNotEmpty() + assertThat(joinedTextResponse).isNotNull(); + assertThat(joinedTextResponse).isNotEmpty() .contains("His Dark Materials") .contains("The Lion, the Witch and the Wardrob") .contains("The Hobbit") diff --git a/spring-ai-model/src/test/kotlin/org/springframework/ai/tool/resolution/TypeResolverHelperKotlinIT.kt b/spring-ai-model/src/test/kotlin/org/springframework/ai/tool/resolution/TypeResolverHelperKotlinIT.kt index b7671a89b98..4f1fc4de843 100644 --- a/spring-ai-model/src/test/kotlin/org/springframework/ai/tool/resolution/TypeResolverHelperKotlinIT.kt +++ b/spring-ai-model/src/test/kotlin/org/springframework/ai/tool/resolution/TypeResolverHelperKotlinIT.kt @@ -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.getName()); } class Outer { diff --git a/spring-ai-retry/pom.xml b/spring-ai-retry/pom.xml index 20393c99ae0..1706fe61276 100644 --- a/spring-ai-retry/pom.xml +++ b/spring-ai-retry/pom.xml @@ -42,11 +42,6 @@ - - org.springframework.retry - spring-retry - - org.springframework spring-web diff --git a/spring-ai-retry/src/main/java/org/springframework/ai/retry/NonTransientAiException.java b/spring-ai-retry/src/main/java/org/springframework/ai/retry/NonTransientAiException.java index 44c405ca6d8..c82762f60e0 100644 --- a/spring-ai-retry/src/main/java/org/springframework/ai/retry/NonTransientAiException.java +++ b/spring-ai-retry/src/main/java/org/springframework/ai/retry/NonTransientAiException.java @@ -26,11 +26,20 @@ */ public class NonTransientAiException extends RuntimeException { - public NonTransientAiException(String message) { + /** + * Constructor with message. + * @param message the exception message + */ + public NonTransientAiException(final String message) { super(message); } - public NonTransientAiException(String message, Throwable cause) { + /** + * Constructor with message and cause. + * @param message the exception message + * @param cause the exception cause + */ + public NonTransientAiException(final String message, final Throwable cause) { super(message, cause); } diff --git a/spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java b/spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java index 3dc125fd2a8..2a1f3c97c73 100644 --- a/spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java +++ b/spring-ai-retry/src/main/java/org/springframework/ai/retry/RetryUtils.java @@ -20,17 +20,18 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Duration; +import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.retry.RetryListener; +import org.springframework.core.retry.RetryPolicy; +import org.springframework.core.retry.RetryTemplate; +import org.springframework.core.retry.Retryable; import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpResponse; import org.springframework.lang.NonNull; -import org.springframework.retry.RetryCallback; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryListener; -import org.springframework.retry.support.RetryTemplate; import org.springframework.util.StreamUtils; import org.springframework.web.client.ResourceAccessException; import org.springframework.web.client.ResponseErrorHandler; @@ -45,29 +46,44 @@ */ public abstract class RetryUtils { + private static final int DEFAULT_MAX_ATTEMPTS = 10; + + private static final long DEFAULT_INITIAL_INTERVAL = 2000; + + private static final int DEFAULT_MULTIPLIER = 5; + + private static final long DEFAULT_MAX_INTERVAL = 3 * 60000; + + private static final long SHORT_INITIAL_INTERVAL = 100; + + private static final Logger LOGGER = LoggerFactory.getLogger(RetryUtils.class); + + /** + * Default ResponseErrorHandler implementation. + */ public static final ResponseErrorHandler DEFAULT_RESPONSE_ERROR_HANDLER = new ResponseErrorHandler() { @Override - public boolean hasError(@NonNull ClientHttpResponse response) throws IOException { + public boolean hasError(final @NonNull ClientHttpResponse response) throws IOException { return response.getStatusCode().isError(); } @Override - public void handleError(URI url, HttpMethod method, @NonNull ClientHttpResponse response) throws IOException { + public void handleError(final URI url, final HttpMethod method, final @NonNull ClientHttpResponse response) + throws IOException { handleError(response); } - @Override @SuppressWarnings("removal") - public void handleError(@NonNull ClientHttpResponse response) throws IOException { + public void handleError(final @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); - /** + /* * Thrown on 4xx client errors, such as 401 - Incorrect API key provided, * 401 - You must be a member of an organization to use the API, 429 - - * Rate limit reached for requests, 429 - You exceeded your current quota - * , please check your plan and billing details. + * Rate limit reached for requests, 429 - You exceeded your current quota, + * please check your plan and billing details. */ if (response.getStatusCode().is4xxClientError()) { throw new NonTransientAiException(message); @@ -75,42 +91,68 @@ public void handleError(@NonNull ClientHttpResponse response) throws IOException throw new TransientAiException(message); } } + }; - private static final Logger logger = LoggerFactory.getLogger(RetryUtils.class); + /** + * Default RetryTemplate with exponential backoff configuration. + */ + public static final RetryTemplate DEFAULT_RETRY_TEMPLATE = createDefaultRetryTemplate(); + + /** + * Short RetryTemplate for testing scenarios. + */ + public static final RetryTemplate SHORT_RETRY_TEMPLATE = createShortRetryTemplate(); - public static final RetryTemplate DEFAULT_RETRY_TEMPLATE = RetryTemplate.builder() - .maxAttempts(10) - .retryOn(TransientAiException.class) - .retryOn(ResourceAccessException.class) - .exponentialBackoff(Duration.ofMillis(2000), 5, Duration.ofMillis(3 * 60000L)) - .withListener(new RetryListener() { + private static RetryTemplate createDefaultRetryTemplate() { + RetryPolicy retryPolicy = RetryPolicy.builder() + .maxAttempts(DEFAULT_MAX_ATTEMPTS) + .includes(TransientAiException.class) + .includes(ResourceAccessException.class) + .delay(Duration.ofMillis(DEFAULT_INITIAL_INTERVAL)) + .multiplier(DEFAULT_MULTIPLIER) + .maxDelay(Duration.ofMillis(DEFAULT_MAX_INTERVAL)) + .build(); + + RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); + retryTemplate.setRetryListener(new RetryListener() { + private final AtomicInteger retryCount = new AtomicInteger(0); @Override - public void onError(RetryContext context, - RetryCallback callback, Throwable throwable) { - logger.warn("Retry error. Retry count:{}", context.getRetryCount(), throwable); + public void onRetryFailure(final RetryPolicy policy, final Retryable retryable, + final Throwable throwable) { + int currentRetries = this.retryCount.incrementAndGet(); + LOGGER.warn("Retry error. Retry count:{}", currentRetries, throwable); } - }) - .build(); + }); + return retryTemplate; + } /** - * Useful in testing scenarios where you don't want to wait long for retry and now - * show stack trace + * Useful in testing scenarios where you don't want to wait long for retry and don't + * need to show stack trace. + * @return a RetryTemplate with short delays */ - public static final RetryTemplate SHORT_RETRY_TEMPLATE = RetryTemplate.builder() - .maxAttempts(10) - .retryOn(TransientAiException.class) - .retryOn(ResourceAccessException.class) - .fixedBackoff(Duration.ofMillis(100)) - .withListener(new RetryListener() { + private static RetryTemplate createShortRetryTemplate() { + RetryPolicy retryPolicy = RetryPolicy.builder() + .maxAttempts(DEFAULT_MAX_ATTEMPTS) + .includes(TransientAiException.class) + .includes(ResourceAccessException.class) + .delay(Duration.ofMillis(SHORT_INITIAL_INTERVAL)) + .build(); + + RetryTemplate retryTemplate = new RetryTemplate(retryPolicy); + retryTemplate.setRetryListener(new RetryListener() { + private final AtomicInteger retryCount = new AtomicInteger(0); @Override - public void onError(RetryContext context, - RetryCallback callback, Throwable throwable) { - logger.warn("Retry error. Retry count:{}", context.getRetryCount()); + public void onRetryFailure(final RetryPolicy policy, final Retryable retryable, + final Throwable throwable) { + int currentRetries = this.retryCount.incrementAndGet(); + LOGGER.warn("Retry error. Retry count:{}", currentRetries, throwable); } - }) - .build(); + }); + return retryTemplate; + } } diff --git a/spring-ai-retry/src/main/java/org/springframework/ai/retry/TransientAiException.java b/spring-ai-retry/src/main/java/org/springframework/ai/retry/TransientAiException.java index 95b6e37f668..90d43fe0e5c 100644 --- a/spring-ai-retry/src/main/java/org/springframework/ai/retry/TransientAiException.java +++ b/spring-ai-retry/src/main/java/org/springframework/ai/retry/TransientAiException.java @@ -19,18 +19,27 @@ /** * Root of the hierarchy of Model access exceptions that are considered transient - where * a previously failed operation might be able to succeed when the operation is retried - * without any intervention by application-level functionality. + * without any intervention. * * @author Christian Tzolov * @since 0.8.1 */ public class TransientAiException extends RuntimeException { - public TransientAiException(String message) { + /** + * Constructor with message. + * @param message the exception message + */ + public TransientAiException(final String message) { super(message); } - public TransientAiException(String message, Throwable cause) { + /** + * Constructor with message and cause. + * @param message the exception message + * @param cause the exception cause + */ + public TransientAiException(final String message, final Throwable cause) { super(message, cause); } diff --git a/spring-ai-retry/src/test/java/org/springframework/ai/retry/RetryUtilsTests.java b/spring-ai-retry/src/test/java/org/springframework/ai/retry/RetryUtilsTests.java index 1541edbc47a..3007ec16bfc 100644 --- a/spring-ai-retry/src/test/java/org/springframework/ai/retry/RetryUtilsTests.java +++ b/spring-ai-retry/src/test/java/org/springframework/ai/retry/RetryUtilsTests.java @@ -24,10 +24,11 @@ import org.junit.jupiter.api.Test; +import org.springframework.core.retry.RetryException; +import org.springframework.core.retry.RetryTemplate; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpResponse; -import org.springframework.retry.support.RetryTemplate; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -98,20 +99,20 @@ void shortRetryTemplateRetries() { AtomicInteger counter = new AtomicInteger(0); RetryTemplate template = RetryUtils.SHORT_RETRY_TEMPLATE; - assertThrows(TransientAiException.class, () -> template.execute(cb -> { + assertThrows(RetryException.class, () -> template.execute(() -> { counter.incrementAndGet(); throw new TransientAiException("test fail"); })); - assertEquals(10, counter.get()); + assertEquals(11, counter.get()); } @Test - void shortRetryTemplateSucceedsBeforeMaxAttempts() { + void shortRetryTemplateSucceedsBeforeMaxAttempts() throws RetryException { AtomicInteger counter = new AtomicInteger(0); RetryTemplate template = RetryUtils.SHORT_RETRY_TEMPLATE; - String result = template.execute(cb -> { + String result = template.execute(() -> { if (counter.incrementAndGet() < 5) { throw new TransientAiException("test fail"); } diff --git a/spring-ai-spring-boot-docker-compose/pom.xml b/spring-ai-spring-boot-docker-compose/pom.xml index 642db247343..891cd2b6033 100644 --- a/spring-ai-spring-boot-docker-compose/pom.xml +++ b/spring-ai-spring-boot-docker-compose/pom.xml @@ -101,7 +101,10 @@ org.springframework.boot spring-boot-starter - + + org.springframework.boot + spring-boot-starter-mongodb + org.springframework.boot spring-boot-docker-compose @@ -198,7 +201,7 @@ true - + org.springframework.ai @@ -212,11 +215,17 @@ spring-boot-starter-web test + org.springframework.boot spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-tomcat + test + org.testcontainers testcontainers diff --git a/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/mongo/MongoDbAtlasLocalDockerComposeConnectionDetailsFactory.java b/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/mongo/MongoDbAtlasLocalDockerComposeConnectionDetailsFactory.java index 7c04ff256bf..2af6245fdb5 100644 --- a/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/mongo/MongoDbAtlasLocalDockerComposeConnectionDetailsFactory.java +++ b/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/mongo/MongoDbAtlasLocalDockerComposeConnectionDetailsFactory.java @@ -18,10 +18,10 @@ import com.mongodb.ConnectionString; -import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails; import org.springframework.boot.docker.compose.core.RunningService; import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; +import org.springframework.boot.mongodb.autoconfigure.MongoConnectionDetails; /** * A {@link DockerComposeConnectionDetailsFactory} implementation that creates diff --git a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/mongo/MongoDbAtlasLocalDockerComposeConnectionDetailsFactoryIT.java b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/mongo/MongoDbAtlasLocalDockerComposeConnectionDetailsFactoryIT.java index a85e7438c09..99f0ac06cf5 100644 --- a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/mongo/MongoDbAtlasLocalDockerComposeConnectionDetailsFactoryIT.java +++ b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/mongo/MongoDbAtlasLocalDockerComposeConnectionDetailsFactoryIT.java @@ -19,8 +19,8 @@ import org.junit.jupiter.api.Test; import org.testcontainers.utility.DockerImageName; -import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails; import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIT; +import org.springframework.boot.mongodb.autoconfigure.MongoConnectionDetails; import static org.assertj.core.api.Assertions.assertThat; diff --git a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIT.java b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIT.java index 26d26baccbb..1b42dcb9213 100644 --- a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIT.java +++ b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/boot/docker/compose/service/connection/test/AbstractDockerComposeIT.java @@ -32,8 +32,8 @@ import org.springframework.boot.SpringApplicationShutdownHandlers; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; -import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.testsupport.DisabledIfProcessUnavailable; +import org.springframework.boot.web.server.autoconfigure.servlet.ServletWebServerConfiguration; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; @@ -97,7 +97,7 @@ private File transformedComposeFile(File composeFile, DockerImageName imageName) } @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration(ServletWebServerFactoryAutoConfiguration.class) + @ImportAutoConfiguration(ServletWebServerConfiguration.class) static class Config { } diff --git a/spring-ai-spring-boot-testcontainers/pom.xml b/spring-ai-spring-boot-testcontainers/pom.xml index 0823a1623ef..4b45d9573f5 100644 --- a/spring-ai-spring-boot-testcontainers/pom.xml +++ b/spring-ai-spring-boot-testcontainers/pom.xml @@ -121,6 +121,11 @@ org.springframework.boot spring-boot-starter + + org.springframework.boot + spring-boot-starter-data-mongodb + + org.springframework.boot @@ -239,6 +244,11 @@ test + + org.springframework.boot + spring-boot-starter-restclient-test + test + org.springframework.boot spring-boot-starter-jdbc @@ -253,13 +263,12 @@ org.testcontainers testcontainers - 1.21.3 true org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -304,56 +313,56 @@ org.testcontainers - qdrant + testcontainers-qdrant true org.testcontainers - weaviate + testcontainers-weaviate true org.testcontainers - chromadb + testcontainers-chromadb true org.testcontainers - localstack + testcontainers-localstack true org.testcontainers - milvus + testcontainers-milvus true org.testcontainers - mongodb + testcontainers-mongodb true org.testcontainers - ollama + testcontainers-ollama true org.testcontainers - typesense + testcontainers-typesense true org.opensearch opensearch-testcontainers - 2.0.1 + 4.0.0 true diff --git a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/mongo/MongoDbAtlasLocalContainerConnectionDetailsFactory.java b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/mongo/MongoDbAtlasLocalContainerConnectionDetailsFactory.java index 32bd405326e..91a5c5a6cf1 100644 --- a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/mongo/MongoDbAtlasLocalContainerConnectionDetailsFactory.java +++ b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/mongo/MongoDbAtlasLocalContainerConnectionDetailsFactory.java @@ -22,7 +22,7 @@ import com.mongodb.ConnectionString; import org.testcontainers.mongodb.MongoDBAtlasLocalContainer; -import org.springframework.boot.autoconfigure.mongo.MongoConnectionDetails; +import org.springframework.boot.mongodb.autoconfigure.MongoConnectionDetails; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionSource; diff --git a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/opensearch/OpenSearchContainerConnectionDetailsFactory.java b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/opensearch/OpenSearchContainerConnectionDetailsFactory.java index 022c9a14b40..f369c2aa5df 100644 --- a/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/opensearch/OpenSearchContainerConnectionDetailsFactory.java +++ b/spring-ai-spring-boot-testcontainers/src/main/java/org/springframework/ai/testcontainers/service/connection/opensearch/OpenSearchContainerConnectionDetailsFactory.java @@ -18,7 +18,7 @@ import java.util.List; -import org.opensearch.testcontainers.OpensearchContainer; +import org.opensearch.testcontainers.OpenSearchContainer; import org.springframework.ai.vectorstore.opensearch.autoconfigure.OpenSearchConnectionDetails; import org.springframework.boot.testcontainers.service.connection.ContainerConnectionDetailsFactory; @@ -28,11 +28,11 @@ * @author Eddú Meléndez */ class OpenSearchContainerConnectionDetailsFactory - extends ContainerConnectionDetailsFactory, OpenSearchConnectionDetails> { + extends ContainerConnectionDetailsFactory, OpenSearchConnectionDetails> { @Override public OpenSearchConnectionDetails getContainerConnectionDetails( - ContainerConnectionSource> source) { + ContainerConnectionSource> source) { return new OpenSearchContainerConnectionDetails(source); } @@ -40,9 +40,9 @@ public OpenSearchConnectionDetails getContainerConnectionDetails( * {@link OpenSearchConnectionDetails} backed by a {@link ContainerConnectionSource}. */ private static final class OpenSearchContainerConnectionDetails - extends ContainerConnectionDetails> implements OpenSearchConnectionDetails { + extends ContainerConnectionDetails> implements OpenSearchConnectionDetails { - private OpenSearchContainerConnectionDetails(ContainerConnectionSource> source) { + private OpenSearchContainerConnectionDetails(ContainerConnectionSource> source) { super(source); } diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/mongo/MongoDbAtlasLocalContainerConnectionDetailsFactoryIT.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/mongo/MongoDbAtlasLocalContainerConnectionDetailsFactoryIT.java index 6cd28c8982e..28afd58b24b 100644 --- a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/mongo/MongoDbAtlasLocalContainerConnectionDetailsFactoryIT.java +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/mongo/MongoDbAtlasLocalContainerConnectionDetailsFactoryIT.java @@ -35,8 +35,8 @@ import org.springframework.ai.vectorstore.mongodb.autoconfigure.MongoDBAtlasVectorStoreAutoConfiguration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration; -import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration; +import org.springframework.boot.data.mongodb.autoconfigure.DataMongoAutoConfiguration; +import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -94,7 +94,7 @@ public void addAndSearch() throws InterruptedException { } @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration({ MongoAutoConfiguration.class, MongoDataAutoConfiguration.class, + @ImportAutoConfiguration({ MongoAutoConfiguration.class, DataMongoAutoConfiguration.class, MongoDBAtlasVectorStoreAutoConfiguration.class }) static class Config { diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactoryIT.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactoryIT.java index d3217fc2c8c..6c9dc470f93 100644 --- a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactoryIT.java +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/ollama/OllamaContainerConnectionDetailsFactoryIT.java @@ -33,7 +33,7 @@ import org.springframework.ai.ollama.OllamaEmbeddingModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration; import org.springframework.boot.testcontainers.service.connection.ServiceConnection; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.TestPropertySource; diff --git a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/opensearch/OpenSearchContainerConnectionDetailsFactoryIT.java b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/opensearch/OpenSearchContainerConnectionDetailsFactoryIT.java index 81294dbd6c1..5374b2b3fd3 100644 --- a/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/opensearch/OpenSearchContainerConnectionDetailsFactoryIT.java +++ b/spring-ai-spring-boot-testcontainers/src/test/java/org/springframework/ai/testcontainers/service/connection/opensearch/OpenSearchContainerConnectionDetailsFactoryIT.java @@ -23,7 +23,7 @@ import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; -import org.opensearch.testcontainers.OpensearchContainer; +import org.opensearch.testcontainers.OpenSearchContainer; import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.regions.Region; @@ -126,8 +126,8 @@ public EmbeddingModel embeddingModel() { @Bean @ServiceConnection - OpensearchContainer opensearch() { - return new OpensearchContainer<>(OpenSearchImage.DEFAULT_IMAGE); + OpenSearchContainer opensearch() { + return new OpenSearchContainer<>(OpenSearchImage.DEFAULT_IMAGE); } } diff --git a/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml b/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml index 5ff2b29f296..62928a9a116 100644 --- a/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml +++ b/vector-stores/spring-ai-azure-cosmos-db-store/pom.xml @@ -81,13 +81,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - azure + testcontainers-azure diff --git a/vector-stores/spring-ai-cassandra-store/pom.xml b/vector-stores/spring-ai-cassandra-store/pom.xml index 84682622655..b2121119f19 100644 --- a/vector-stores/spring-ai-cassandra-store/pom.xml +++ b/vector-stores/spring-ai-cassandra-store/pom.xml @@ -53,6 +53,11 @@ java-driver-query-builder + + org.apache.commons + commons-lang3 + + org.springframework.ai @@ -76,13 +81,13 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - cassandra + testcontainers-cassandra test diff --git a/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/CassandraRichSchemaVectorStoreIT.java b/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/CassandraRichSchemaVectorStoreIT.java index 0b345fcd99d..e5d6c3b5123 100644 --- a/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/CassandraRichSchemaVectorStoreIT.java +++ b/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/CassandraRichSchemaVectorStoreIT.java @@ -33,6 +33,7 @@ import com.datastax.oss.driver.api.core.servererrors.InvalidQueryException; import com.datastax.oss.driver.api.core.servererrors.SyntaxError; import com.datastax.oss.driver.api.core.type.DataTypes; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -40,7 +41,6 @@ import org.testcontainers.cassandra.CassandraContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.springframework.ai.document.Document; import org.springframework.ai.document.DocumentMetadata; @@ -49,8 +49,6 @@ import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.cassandra.CassandraVectorStore.SchemaColumn; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -609,7 +607,6 @@ private void executeCqlFile(ApplicationContext context, String filename) throws } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean diff --git a/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/CassandraVectorStoreIT.java b/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/CassandraVectorStoreIT.java index 81dd8a2df35..dc05a181ff8 100644 --- a/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/CassandraVectorStoreIT.java +++ b/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/CassandraVectorStoreIT.java @@ -49,8 +49,6 @@ import org.springframework.ai.vectorstore.cassandra.CassandraVectorStore.SchemaColumnTags; import org.springframework.ai.vectorstore.filter.Filter; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -603,7 +601,6 @@ void throwsExceptionOnInvalidIndexNameWithSchemaValidation() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean diff --git a/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/WikiVectorStoreExample.java b/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/WikiVectorStoreExample.java index fc4e4070104..9b9e8ac8f9e 100644 --- a/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/WikiVectorStoreExample.java +++ b/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/cassandra/WikiVectorStoreExample.java @@ -31,8 +31,6 @@ import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.cassandra.CassandraVectorStore.SchemaColumn; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -78,7 +76,6 @@ void search() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean diff --git a/vector-stores/spring-ai-chroma-store/pom.xml b/vector-stores/spring-ai-chroma-store/pom.xml index a49cdf0d37a..f21756c2814 100644 --- a/vector-stores/spring-ai-chroma-store/pom.xml +++ b/vector-stores/spring-ai-chroma-store/pom.xml @@ -65,13 +65,13 @@ org.testcontainers - chromadb + testcontainers-chromadb test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-couchbase-store/pom.xml b/vector-stores/spring-ai-couchbase-store/pom.xml index 50b583b7b68..7fc1840a29e 100644 --- a/vector-stores/spring-ai-couchbase-store/pom.xml +++ b/vector-stores/spring-ai-couchbase-store/pom.xml @@ -61,12 +61,12 @@ org.testcontainers - couchbase + testcontainers-couchbase test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-couchbase-store/src/test/java/org/springframework/ai/vectorstore/CouchbaseSearchVectorStoreIT.java b/vector-stores/spring-ai-couchbase-store/src/test/java/org/springframework/ai/vectorstore/CouchbaseSearchVectorStoreIT.java index 03b63577531..1bbf50256f6 100644 --- a/vector-stores/spring-ai-couchbase-store/src/test/java/org/springframework/ai/vectorstore/CouchbaseSearchVectorStoreIT.java +++ b/vector-stores/spring-ai-couchbase-store/src/test/java/org/springframework/ai/vectorstore/CouchbaseSearchVectorStoreIT.java @@ -44,8 +44,6 @@ import org.springframework.ai.vectorstore.filter.Filter; import org.springframework.ai.vectorstore.testcontainer.CouchbaseContainerMetadata; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -283,7 +281,6 @@ void getNativeClientTest() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean diff --git a/vector-stores/spring-ai-elasticsearch-store/pom.xml b/vector-stores/spring-ai-elasticsearch-store/pom.xml index 7c64cbb1c5a..06d5f14fc50 100644 --- a/vector-stores/spring-ai-elasticsearch-store/pom.xml +++ b/vector-stores/spring-ai-elasticsearch-store/pom.xml @@ -78,13 +78,13 @@ org.testcontainers - elasticsearch + testcontainers-elasticsearch test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java index fc77e4f01f5..28446f75377 100644 --- a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java +++ b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStore.java @@ -32,10 +32,10 @@ import co.elastic.clients.elasticsearch.core.search.Hit; import co.elastic.clients.json.jackson.JacksonJsonpMapper; import co.elastic.clients.transport.Version; -import co.elastic.clients.transport.rest_client.RestClientTransport; +import co.elastic.clients.transport.rest5_client.Rest5ClientTransport; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import org.elasticsearch.client.RestClient; import org.springframework.ai.document.Document; import org.springframework.ai.document.DocumentMetadata; @@ -168,7 +168,7 @@ protected ElasticsearchVectorStore(Builder builder) { this.filterExpressionConverter = builder.filterExpressionConverter; String version = Version.VERSION == null ? "Unknown" : Version.VERSION.toString(); - this.elasticsearchClient = new ElasticsearchClient(new RestClientTransport(builder.restClient, + this.elasticsearchClient = new ElasticsearchClient(new Rest5ClientTransport(builder.restClient, new JacksonJsonpMapper( new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)))) .withTransportOptions(t -> t.addHeader("user-agent", "spring-ai elastic-java/" + version)); @@ -369,13 +369,13 @@ public Optional getNativeClient() { * Creates a new builder instance for ElasticsearchVectorStore. * @return a new ElasticsearchBuilder instance */ - public static Builder builder(RestClient restClient, EmbeddingModel embeddingModel) { + public static Builder builder(Rest5Client restClient, EmbeddingModel embeddingModel) { return new Builder(restClient, embeddingModel); } public static class Builder extends AbstractVectorStoreBuilder { - private final RestClient restClient; + private final Rest5Client restClient; private ElasticsearchVectorStoreOptions options = new ElasticsearchVectorStoreOptions(); @@ -388,7 +388,7 @@ public static class Builder extends AbstractVectorStoreBuilder { * @param restClient the Elasticsearch REST client * @param embeddingModel the Embedding Model to be used */ - public Builder(RestClient restClient, EmbeddingModel embeddingModel) { + public Builder(Rest5Client restClient, EmbeddingModel embeddingModel) { super(embeddingModel); Assert.notNull(restClient, "RestClient must not be null"); this.restClient = restClient; diff --git a/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStoreIT.java b/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStoreIT.java index 5734d369967..070fb138b0f 100644 --- a/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStoreIT.java +++ b/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStoreIT.java @@ -17,6 +17,7 @@ package org.springframework.ai.vectorstore.elasticsearch; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.ZonedDateTime; @@ -33,12 +34,12 @@ import co.elastic.clients.elasticsearch.cat.indices.IndicesRecord; import co.elastic.clients.elasticsearch.indices.stats.IndicesStats; import co.elastic.clients.json.jackson.JacksonJsonpMapper; -import co.elastic.clients.transport.rest_client.RestClientTransport; +import co.elastic.clients.transport.rest5_client.Rest5ClientTransport; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.http.HttpHost; +import org.apache.hc.core5.http.HttpHost; import org.awaitility.Awaitility; -import org.elasticsearch.client.RestClient; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -58,8 +59,6 @@ import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -108,7 +107,7 @@ void cleanDatabase() { getContextRunner().run(context -> { // deleting indices and data before following tests ElasticsearchClient elasticsearchClient = context.getBean(ElasticsearchClient.class); - List indices = elasticsearchClient.cat().indices().valueBody().stream().map(IndicesRecord::index).toList(); + List indices = elasticsearchClient.cat().indices().indices().stream().map(IndicesRecord::index).toList(); if (!indices.isEmpty()) { elasticsearchClient.indices().delete(del -> del.index(indices)); } @@ -474,16 +473,15 @@ public void getNativeClientTest() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean("vectorStore_cosine") - public ElasticsearchVectorStore vectorStoreDefault(EmbeddingModel embeddingModel, RestClient restClient) { + public ElasticsearchVectorStore vectorStoreDefault(EmbeddingModel embeddingModel, Rest5Client restClient) { return ElasticsearchVectorStore.builder(restClient, embeddingModel).initializeSchema(true).build(); } @Bean("vectorStore_l2_norm") - public ElasticsearchVectorStore vectorStoreL2(EmbeddingModel embeddingModel, RestClient restClient) { + public ElasticsearchVectorStore vectorStoreL2(EmbeddingModel embeddingModel, Rest5Client restClient) { ElasticsearchVectorStoreOptions options = new ElasticsearchVectorStoreOptions(); options.setIndexName("index_l2"); options.setSimilarity(SimilarityFunction.l2_norm); @@ -494,7 +492,7 @@ public ElasticsearchVectorStore vectorStoreL2(EmbeddingModel embeddingModel, Res } @Bean("vectorStore_dot_product") - public ElasticsearchVectorStore vectorStoreDotProduct(EmbeddingModel embeddingModel, RestClient restClient) { + public ElasticsearchVectorStore vectorStoreDotProduct(EmbeddingModel embeddingModel, Rest5Client restClient) { ElasticsearchVectorStoreOptions options = new ElasticsearchVectorStoreOptions(); options.setIndexName("index_dot_product"); options.setSimilarity(SimilarityFunction.dot_product); @@ -505,7 +503,7 @@ public ElasticsearchVectorStore vectorStoreDotProduct(EmbeddingModel embeddingMo } @Bean("vectorStore_custom_embedding_field") - public ElasticsearchVectorStore vectorStoreCustomField(EmbeddingModel embeddingModel, RestClient restClient) { + public ElasticsearchVectorStore vectorStoreCustomField(EmbeddingModel embeddingModel, Rest5Client restClient) { ElasticsearchVectorStoreOptions options = new ElasticsearchVectorStoreOptions(); options.setEmbeddingFieldName("custom_embedding_field"); return ElasticsearchVectorStore.builder(restClient, embeddingModel) @@ -520,13 +518,13 @@ public EmbeddingModel embeddingModel() { } @Bean - RestClient restClient() { - return RestClient.builder(HttpHost.create(elasticsearchContainer.getHttpHostAddress())).build(); + Rest5Client restClient() throws URISyntaxException { + return Rest5Client.builder(HttpHost.create(elasticsearchContainer.getHttpHostAddress())).build(); } @Bean - ElasticsearchClient elasticsearchClient(RestClient restClient) { - return new ElasticsearchClient(new RestClientTransport(restClient, new JacksonJsonpMapper( + ElasticsearchClient elasticsearchClient(Rest5Client restClient) { + return new ElasticsearchClient(new Rest5ClientTransport(restClient, new JacksonJsonpMapper( new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)))); } diff --git a/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStoreObservationIT.java b/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStoreObservationIT.java index f11623671c4..9b7b5dd31fe 100644 --- a/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/elasticsearch/ElasticsearchVectorStoreObservationIT.java @@ -17,6 +17,7 @@ package org.springframework.ai.vectorstore.elasticsearch; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.List; @@ -26,15 +27,15 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.cat.indices.IndicesRecord; import co.elastic.clients.json.jackson.JacksonJsonpMapper; -import co.elastic.clients.transport.rest_client.RestClientTransport; +import co.elastic.clients.transport.rest5_client.Rest5ClientTransport; +import co.elastic.clients.transport.rest5_client.low_level.Rest5Client; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.tck.TestObservationRegistry; import io.micrometer.observation.tck.TestObservationRegistryAssert; -import org.apache.http.HttpHost; +import org.apache.hc.core5.http.HttpHost; import org.awaitility.Awaitility; -import org.elasticsearch.client.RestClient; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -57,8 +58,6 @@ import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.HighCardinalityKeyNames; import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.LowCardinalityKeyNames; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -111,7 +110,7 @@ void cleanDatabase() { getContextRunner().run(context -> { // deleting indices and data before following tests ElasticsearchClient elasticsearchClient = context.getBean(ElasticsearchClient.class); - List indices = elasticsearchClient.cat().indices().valueBody().stream().map(IndicesRecord::index).toList(); + List indices = elasticsearchClient.cat().indices().indices().stream().map(IndicesRecord::index).toList(); if (!indices.isEmpty()) { elasticsearchClient.indices().delete(del -> del.index(indices)); } @@ -198,7 +197,6 @@ void observationVectorStoreAddAndQueryOperations() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class Config { @Bean @@ -207,7 +205,7 @@ public TestObservationRegistry observationRegistry() { } @Bean - public ElasticsearchVectorStore vectorStoreDefault(EmbeddingModel embeddingModel, RestClient restClient, + public ElasticsearchVectorStore vectorStoreDefault(EmbeddingModel embeddingModel, Rest5Client restClient, ObservationRegistry observationRegistry) { return ElasticsearchVectorStore.builder(restClient, embeddingModel) .initializeSchema(true) @@ -224,13 +222,13 @@ public EmbeddingModel embeddingModel() { } @Bean - RestClient restClient() { - return RestClient.builder(HttpHost.create(elasticsearchContainer.getHttpHostAddress())).build(); + Rest5Client restClient() throws URISyntaxException { + return Rest5Client.builder(HttpHost.create(elasticsearchContainer.getHttpHostAddress())).build(); } @Bean - ElasticsearchClient elasticsearchClient(RestClient restClient) { - return new ElasticsearchClient(new RestClientTransport(restClient, new JacksonJsonpMapper( + ElasticsearchClient elasticsearchClient(Rest5Client restClient) { + return new ElasticsearchClient(new Rest5ClientTransport(restClient, new JacksonJsonpMapper( new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)))); } diff --git a/vector-stores/spring-ai-gemfire-store/pom.xml b/vector-stores/spring-ai-gemfire-store/pom.xml index 6641c94b8ac..60af16df8cf 100644 --- a/vector-stores/spring-ai-gemfire-store/pom.xml +++ b/vector-stores/spring-ai-gemfire-store/pom.xml @@ -100,7 +100,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-gemfire-store/src/test/java/org/testcontainers/containers/FailureDetectingExternalResource.java b/vector-stores/spring-ai-gemfire-store/src/test/java/org/testcontainers/containers/FailureDetectingExternalResource.java new file mode 100644 index 00000000000..68c1cba9d1f --- /dev/null +++ b/vector-stores/spring-ai-gemfire-store/src/test/java/org/testcontainers/containers/FailureDetectingExternalResource.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.testcontainers.containers; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.MultipleFailureException; +import org.junit.runners.model.Statement; + +/** + * {@link TestRule} which is called before and after each test, and also is notified on + * success/failure. + * + * This mimics the behaviour of TestWatcher to some degree, but failures occurring in this + * rule do not contribute to the overall failure count (which can otherwise cause strange + * negative test success figures). + */ +public class FailureDetectingExternalResource implements TestRule { + + @Override + public Statement apply(Statement base, Description description) { + + return new Statement() { + @Override + public void evaluate() throws Throwable { + + List errors = new ArrayList(); + + starting(description); + + try { + base.evaluate(); + succeeded(description); + } + catch (Throwable e) { + errors.add(e); + failed(e, description); + } + finally { + finished(description); + } + + MultipleFailureException.assertEmpty(errors); + } + }; + } + + protected void starting(Description description) { + + } + + protected void succeeded(Description description) { + } + + protected void failed(Throwable e, Description description) { + } + + protected void finished(Description description) { + } + +} diff --git a/vector-stores/spring-ai-mariadb-store/pom.xml b/vector-stores/spring-ai-mariadb-store/pom.xml index cb440f3561d..599842a0d99 100644 --- a/vector-stores/spring-ai-mariadb-store/pom.xml +++ b/vector-stores/spring-ai-mariadb-store/pom.xml @@ -49,8 +49,8 @@ - org.springframework - spring-jdbc + org.springframework.boot + spring-boot-starter-jdbc @@ -87,13 +87,13 @@ org.testcontainers - mariadb + testcontainers-mariadb test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreCustomNamesIT.java b/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreCustomNamesIT.java index a6113538103..3311fae71b1 100644 --- a/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreCustomNamesIT.java +++ b/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreCustomNamesIT.java @@ -32,9 +32,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreIT.java b/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreIT.java index b2bfd96e3ed..f9e5456d3af 100644 --- a/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreIT.java +++ b/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreIT.java @@ -53,9 +53,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreObservationIT.java b/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreObservationIT.java index d716fe057b4..9451a1c4041 100644 --- a/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreObservationIT.java +++ b/vector-stores/spring-ai-mariadb-store/src/test/java/org/springframework/ai/vectorstore/mariadb/MariaDBStoreObservationIT.java @@ -47,9 +47,9 @@ import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.LowCardinalityKeyNames; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; diff --git a/vector-stores/spring-ai-milvus-store/pom.xml b/vector-stores/spring-ai-milvus-store/pom.xml index e5031cb6388..33efbed1a77 100644 --- a/vector-stores/spring-ai-milvus-store/pom.xml +++ b/vector-stores/spring-ai-milvus-store/pom.xml @@ -70,22 +70,21 @@ test - org.springframework.boot spring-boot-starter-test test - - org.testcontainers - milvus - test - + + org.testcontainers + testcontainers-milvus + test + org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreCustomFieldNamesIT.java b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreCustomFieldNamesIT.java index 56ac2deda91..710bda8a5a3 100644 --- a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreCustomFieldNamesIT.java +++ b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreCustomFieldNamesIT.java @@ -41,8 +41,6 @@ import org.springframework.ai.vectorstore.VectorStore; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -204,7 +202,6 @@ void searchWithAutoIdEnabled(String metricType) { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) static class TestApplication { @Value("${test.spring.ai.vectorstore.milvus.metricType}") diff --git a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreIT.java b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreIT.java index eb0bf4e515f..7e0b7df5805 100644 --- a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreIT.java +++ b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/milvus/MilvusVectorStoreIT.java @@ -56,8 +56,6 @@ import org.springframework.ai.vectorstore.filter.Filter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -355,7 +353,6 @@ void getNativeClientTest() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Value("${test.spring.ai.vectorstore.milvus.metricType}") diff --git a/vector-stores/spring-ai-mongodb-atlas-store/pom.xml b/vector-stores/spring-ai-mongodb-atlas-store/pom.xml index 1cca946cef9..bac1f379668 100644 --- a/vector-stores/spring-ai-mongodb-atlas-store/pom.xml +++ b/vector-stores/spring-ai-mongodb-atlas-store/pom.xml @@ -71,12 +71,12 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - mongodb + testcontainers-mongodb test diff --git a/vector-stores/spring-ai-neo4j-store/pom.xml b/vector-stores/spring-ai-neo4j-store/pom.xml index 478f57f1793..7f868ed9f64 100644 --- a/vector-stores/spring-ai-neo4j-store/pom.xml +++ b/vector-stores/spring-ai-neo4j-store/pom.xml @@ -86,13 +86,13 @@ org.testcontainers - neo4j + testcontainers-neo4j test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreIT.java b/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreIT.java index d338c71e556..a1a3bc5027d 100644 --- a/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreIT.java +++ b/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreIT.java @@ -45,8 +45,6 @@ import org.springframework.ai.vectorstore.filter.Filter; import org.springframework.ai.vectorstore.filter.FilterExpressionTextParser; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; @@ -369,7 +367,6 @@ void vectorIndexDimensionsDefaultAndOverwriteWorks() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean diff --git a/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreObservationIT.java b/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreObservationIT.java index d58d7fd5c87..1f12f9e1be8 100644 --- a/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/neo4j/Neo4jVectorStoreObservationIT.java @@ -49,8 +49,6 @@ import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.HighCardinalityKeyNames; import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.LowCardinalityKeyNames; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -166,7 +164,6 @@ void observationVectorStoreAddAndQueryOperations() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class Config { @Bean diff --git a/vector-stores/spring-ai-opensearch-store/pom.xml b/vector-stores/spring-ai-opensearch-store/pom.xml index 84387f1a7ec..8ac21ebe310 100644 --- a/vector-stores/spring-ai-opensearch-store/pom.xml +++ b/vector-stores/spring-ai-opensearch-store/pom.xml @@ -97,7 +97,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test @@ -106,7 +106,7 @@ micrometer-observation-test test - + diff --git a/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/opensearch/OpenSearchVectorStoreIT.java b/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/opensearch/OpenSearchVectorStoreIT.java index 0b6b845dad5..172e781b7a2 100644 --- a/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/opensearch/OpenSearchVectorStoreIT.java +++ b/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/opensearch/OpenSearchVectorStoreIT.java @@ -54,8 +54,6 @@ import org.springframework.ai.vectorstore.filter.Filter; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -811,7 +809,6 @@ public void approximateSearchThresholdTest(String similarityFunction) { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean diff --git a/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/opensearch/OpenSearchVectorStoreObservationIT.java b/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/opensearch/OpenSearchVectorStoreObservationIT.java index 802f0d052a5..0d83263e74d 100644 --- a/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/opensearch/OpenSearchVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/opensearch/OpenSearchVectorStoreObservationIT.java @@ -53,8 +53,6 @@ import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.HighCardinalityKeyNames; import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.LowCardinalityKeyNames; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -195,7 +193,6 @@ void observationVectorStoreAddAndQueryOperations() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) static class Config { @Bean diff --git a/vector-stores/spring-ai-oracle-store/pom.xml b/vector-stores/spring-ai-oracle-store/pom.xml index 700e507407a..d9c8e44f0e3 100644 --- a/vector-stores/spring-ai-oracle-store/pom.xml +++ b/vector-stores/spring-ai-oracle-store/pom.xml @@ -68,6 +68,11 @@ simplefan + + org.springframework.boot + spring-boot-starter-jdbc + + org.springframework spring-jdbc @@ -103,13 +108,13 @@ org.testcontainers - oracle-free + testcontainers-oracle-free test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/oracle/OracleVectorStoreIT.java b/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/oracle/OracleVectorStoreIT.java index e690fc19bfa..c65c9e7b47a 100644 --- a/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/oracle/OracleVectorStoreIT.java +++ b/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/oracle/OracleVectorStoreIT.java @@ -52,9 +52,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/oracle/OracleVectorStoreObservationIT.java b/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/oracle/OracleVectorStoreObservationIT.java index cc155227e0f..01414b9ea5d 100644 --- a/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/oracle/OracleVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/oracle/OracleVectorStoreObservationIT.java @@ -48,9 +48,9 @@ import org.springframework.ai.vectorstore.oracle.OracleVectorStore.OracleVectorStoreDistanceType; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/vector-stores/spring-ai-pgvector-store/pom.xml b/vector-stores/spring-ai-pgvector-store/pom.xml index 92251350a7d..9e14d7f4d00 100644 --- a/vector-stores/spring-ai-pgvector-store/pom.xml +++ b/vector-stores/spring-ai-pgvector-store/pom.xml @@ -58,6 +58,11 @@ spring-jdbc + + org.springframework.boot + spring-boot-starter-jdbc + + org.postgresql postgresql @@ -105,13 +110,13 @@ org.testcontainers - postgresql + testcontainers-postgresql test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreAutoTruncationIT.java b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreAutoTruncationIT.java index 94fb52d97a0..f69dab9b019 100644 --- a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreAutoTruncationIT.java +++ b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreAutoTruncationIT.java @@ -41,9 +41,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreCustomNamesIT.java b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreCustomNamesIT.java index 012504bcc92..e173763f39b 100644 --- a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreCustomNamesIT.java +++ b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreCustomNamesIT.java @@ -35,9 +35,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreIT.java b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreIT.java index 01da433b099..77fce3f010b 100644 --- a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreIT.java +++ b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreIT.java @@ -56,9 +56,9 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; diff --git a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreObservationIT.java b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreObservationIT.java index 3792dd3d705..d8c7e0be66b 100644 --- a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/pgvector/PgVectorStoreObservationIT.java @@ -49,9 +49,9 @@ import org.springframework.ai.vectorstore.pgvector.PgVectorStore.PgIndexType; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; diff --git a/vector-stores/spring-ai-qdrant-store/pom.xml b/vector-stores/spring-ai-qdrant-store/pom.xml index 81fac48e3d2..dea478b047c 100644 --- a/vector-stores/spring-ai-qdrant-store/pom.xml +++ b/vector-stores/spring-ai-qdrant-store/pom.xml @@ -81,13 +81,13 @@ org.testcontainers - qdrant + testcontainers-qdrant test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-redis-store/pom.xml b/vector-stores/spring-ai-redis-store/pom.xml index 6a536d76561..5b869cf3603 100644 --- a/vector-stores/spring-ai-redis-store/pom.xml +++ b/vector-stores/spring-ai-redis-store/pom.xml @@ -54,6 +54,10 @@ org.springframework.data spring-data-redis + + org.springframework.boot + spring-boot-starter-data-redis + redis.clients @@ -89,7 +93,7 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test diff --git a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreIT.java b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreIT.java index 7fcccfdeedb..f00920d20d2 100644 --- a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreIT.java +++ b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreIT.java @@ -44,9 +44,7 @@ import org.springframework.ai.vectorstore.redis.RedisVectorStore.MetadataField; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -68,7 +66,7 @@ class RedisVectorStoreIT extends BaseVectorStoreTests { RedisStackContainer.DEFAULT_IMAGE_NAME.withTag(RedisStackContainer.DEFAULT_TAG)); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(DataRedisAutoConfiguration.class)) .withUserConfiguration(TestApplication.class) .withPropertyValues("spring.data.redis.url=" + redisContainer.getRedisURI()); @@ -317,7 +315,6 @@ void getNativeClientTest() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean diff --git a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreObservationIT.java b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreObservationIT.java index 53e11eeb750..84501971935 100644 --- a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/redis/RedisVectorStoreObservationIT.java @@ -47,7 +47,7 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; +import org.springframework.boot.data.redis.autoconfigure.DataRedisAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -67,7 +67,7 @@ public class RedisVectorStoreObservationIT { RedisStackContainer.DEFAULT_IMAGE_NAME.withTag(RedisStackContainer.DEFAULT_TAG)); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(DataRedisAutoConfiguration.class)) .withUserConfiguration(Config.class) .withPropertyValues("spring.data.redis.url=" + redisContainer.getRedisURI()); diff --git a/vector-stores/spring-ai-typesense-store/pom.xml b/vector-stores/spring-ai-typesense-store/pom.xml index 5b7c650efeb..4a2bcc3fbe3 100644 --- a/vector-stores/spring-ai-typesense-store/pom.xml +++ b/vector-stores/spring-ai-typesense-store/pom.xml @@ -78,17 +78,16 @@ org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test org.testcontainers - typesense + testcontainers-typesense test - io.micrometer micrometer-observation-test diff --git a/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStoreIT.java b/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStoreIT.java index 696e2349631..cd843461670 100644 --- a/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStoreIT.java +++ b/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/typesense/TypesenseVectorStoreIT.java @@ -45,8 +45,6 @@ import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.filter.Filter; import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.core.io.DefaultResourceLoader; @@ -300,7 +298,6 @@ void getNativeClientTest() { } @SpringBootConfiguration - @EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class) public static class TestApplication { @Bean diff --git a/vector-stores/spring-ai-weaviate-store/pom.xml b/vector-stores/spring-ai-weaviate-store/pom.xml index 577e02f3216..fad5b29272e 100644 --- a/vector-stores/spring-ai-weaviate-store/pom.xml +++ b/vector-stores/spring-ai-weaviate-store/pom.xml @@ -86,13 +86,13 @@ org.testcontainers - weaviate + testcontainers-weaviate test org.testcontainers - junit-jupiter + testcontainers-junit-jupiter test