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 3df440cdc3f..82520504f72 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 @@ -16,16 +16,26 @@ package org.springframework.ai.model.ollama.autoconfigure; +import org.jspecify.annotations.NonNull; + +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; import org.springframework.ai.ollama.api.OllamaApi; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionMessage; +import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.SpringBootCondition; 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.ConditionContext; +import org.springframework.context.annotation.Conditional; +import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; import org.springframework.web.reactive.function.client.WebClient; @@ -37,11 +47,13 @@ * @author EddĂș MelĂ©ndez * @author Thomas Vitale * @author Ilayaperumal Gopinathan + * @author Nicolas Krier * @since 0.8.0 */ @AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) @ConditionalOnClass(OllamaApi.class) +@Conditional(OllamaApiAutoConfiguration.OllamaChatOrEmbeddingCondition.class) @EnableConfigurationProperties(OllamaConnectionProperties.class) public class OllamaApiAutoConfiguration { @@ -53,7 +65,7 @@ PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectionProper @Bean @ConditionalOnMissingBean - public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails, + OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails, ObjectProvider restClientBuilderProvider, ObjectProvider webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) { return OllamaApi.builder() @@ -79,4 +91,30 @@ public String getBaseUrl() { } + static class OllamaChatOrEmbeddingCondition extends SpringBootCondition { + + @Override + public @NonNull ConditionOutcome getMatchOutcome(@NonNull ConditionContext context, + @NonNull AnnotatedTypeMetadata metadata) { + var messageBuilder = ConditionMessage.forCondition("OllamaChatOrEmbeddingCondition"); + var environment = context.getEnvironment(); + var chatModel = environment.getProperty(SpringAIModelProperties.CHAT_MODEL, SpringAIModels.OLLAMA); + + if (SpringAIModels.OLLAMA.equals(chatModel)) { + return ConditionOutcome.match(messageBuilder.because("Chat model corresponds to Ollama.")); + } + + var embeddingModel = environment.getProperty(SpringAIModelProperties.EMBEDDING_MODEL, + SpringAIModels.OLLAMA); + + if (SpringAIModels.OLLAMA.equals(embeddingModel)) { + return ConditionOutcome.match(messageBuilder.because("Embedding model corresponds to Ollama.")); + } + + return ConditionOutcome + .noMatch(messageBuilder.because("Neither chat model nor embedding model correspond to Ollama.")); + } + + } + } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaModelConfigurationTests.java index 5f8b989a3f0..9c5dc41222e 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaModelConfigurationTests.java @@ -16,10 +16,14 @@ package org.springframework.ai.model.ollama.autoconfigure; +import java.util.List; + import org.junit.jupiter.api.Test; import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.ai.ollama.OllamaEmbeddingModel; +import org.springframework.ai.ollama.api.OllamaApi; +import org.springframework.ai.utils.SpringAiTestAutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -28,11 +32,43 @@ * Unit Tests for Ollama auto-configurations conditional enabling of models. * * @author Ilayaperumal Gopinathan + * @author Nicolas Krier */ -public class OllamaModelConfigurationTests { +class OllamaModelConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); + @Test + void apiActivation() { + this.contextRunner.withConfiguration(SpringAiTestAutoConfigurations.of(OllamaApiAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none") + .run(context -> { + assertThat(context).hasNotFailed(); + assertThat(context).doesNotHaveBean(OllamaApiAutoConfiguration.PropertiesOllamaConnectionDetails.class); + assertThat(context).doesNotHaveBean(OllamaApi.class); + }); + + List.of( + // @formatter:off + this.contextRunner + .withConfiguration(SpringAiTestAutoConfigurations.of(OllamaApiAutoConfiguration.class)), + this.contextRunner + .withConfiguration(SpringAiTestAutoConfigurations.of(OllamaApiAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=ollama", "spring.ai.model.embedding=ollama"), + this.contextRunner + .withConfiguration(SpringAiTestAutoConfigurations.of(OllamaApiAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=ollama", "spring.ai.model.embedding=none"), + this.contextRunner + .withConfiguration(SpringAiTestAutoConfigurations.of(OllamaApiAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=ollama") + // @formatter:on + ).forEach(runner -> runner.run(context -> { + assertThat(context).hasNotFailed(); + assertThat(context).hasSingleBean(OllamaApiAutoConfiguration.PropertiesOllamaConnectionDetails.class); + assertThat(context).hasSingleBean(OllamaApi.class); + })); + } + @Test void chatModelActivation() { this.contextRunner.withConfiguration(BaseOllamaIT.ollamaAutoConfig(OllamaChatAutoConfiguration.class))