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 890375ca2..4030432cd 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml @@ -39,21 +39,18 @@ org.springframework.ai spring-ai-autoconfigure-model-tool ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfiguration.java similarity index 96% rename from auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfiguration.java index eb064bec9..dd4ae062b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfiguration.java @@ -56,9 +56,11 @@ import org.springframework.web.reactive.function.client.WebClient; ToolCallingAutoConfiguration.class }) @EnableConfigurationProperties({ AnthropicChatProperties.class, AnthropicConnectionProperties.class }) @ConditionalOnClass(AnthropicApi.class) +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.ANTHROPIC, + matchIfMissing = true) @ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class, WebClientAutoConfiguration.class }) -public class AnthropicAutoConfiguration { +public class AnthropicChatAutoConfiguration { @Bean @ConditionalOnMissingBean @@ -74,8 +76,6 @@ public class AnthropicAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.ANTHROPIC, - matchIfMissing = true) public AnthropicChatModel anthropicChatModel(AnthropicApi anthropicApi, AnthropicChatProperties chatProperties, RetryTemplate retryTemplate, ToolCallingManager toolCallingManager, ObjectProvider observationRegistry, diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 45f613d48..88fef7ef0 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.anthropic.autoconfigure.AnthropicAutoConfiguration +org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfigurationIT.java similarity index 93% rename from auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfigurationIT.java rename to auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfigurationIT.java index a46ff4022..bed6487cd 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatAutoConfigurationIT.java @@ -39,13 +39,13 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".*") -public class AnthropicAutoConfigurationIT { +public class AnthropicChatAutoConfigurationIT { - private static final Log logger = LogFactory.getLog(AnthropicAutoConfigurationIT.class); + private static final Log logger = LogFactory.getLog(AnthropicChatAutoConfigurationIT.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY")) - .withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(AnthropicChatAutoConfiguration.class)); @Test void call() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicModelConfigurationTests.java index 387556f6f..d9e6d3b5c 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicModelConfigurationTests.java @@ -25,7 +25,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * Unit Tests for {@link AnthropicAutoConfiguration}'s conditional enabling of models. + * Unit Tests for {@link AnthropicChatAutoConfiguration}'s conditional enabling of models. * * @author Ilayaperumal Gopinathan */ @@ -33,7 +33,7 @@ public class AnthropicModelConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY")) - .withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(AnthropicChatAutoConfiguration.class)); @Test void chatModelActivation() { @@ -42,7 +42,7 @@ public class AnthropicModelConfigurationTests { }); this.contextRunner.withPropertyValues("spring.ai.model.chat=none").run(context -> { - assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(AnthropicChatModel.class)).isEmpty(); }); 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 186437c48..9edaf5b2c 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 @@ -44,7 +44,7 @@ public class AnthropicPropertiesTests { "spring.ai.anthropic.chat.options.temperature=0.55") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) + RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(AnthropicChatProperties.class); var connectionProperties = context.getBean(AnthropicConnectionProperties.class); @@ -80,7 +80,7 @@ public class AnthropicPropertiesTests { ) // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) + RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(AnthropicChatProperties.class); var connectionProperties = context.getBean(AnthropicConnectionProperties.class); @@ -104,7 +104,7 @@ public class AnthropicPropertiesTests { // It is enabled by default new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) + RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(AnthropicChatModel.class)).isNotEmpty(); @@ -113,7 +113,7 @@ public class AnthropicPropertiesTests { // Explicitly enable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.anthropic.chat.enabled=true") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) + RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(AnthropicChatModel.class)).isNotEmpty(); @@ -122,7 +122,7 @@ public class AnthropicPropertiesTests { // Explicitly disable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.model.chat=none") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) + RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(AnthropicChatModel.class)).isEmpty(); }); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java index 740cf066a..5a3bdc115 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java @@ -27,13 +27,12 @@ import org.slf4j.LoggerFactory; import org.springframework.ai.anthropic.AnthropicChatModel; import org.springframework.ai.anthropic.AnthropicChatOptions; import org.springframework.ai.anthropic.api.AnthropicApi; -import org.springframework.ai.model.anthropic.autoconfigure.AnthropicAutoConfiguration; +import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; import org.springframework.ai.model.anthropic.autoconfigure.tool.MockWeatherService.Request; import org.springframework.ai.model.anthropic.autoconfigure.tool.MockWeatherService.Response; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.function.FunctionCallingOptions; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -50,7 +49,7 @@ class FunctionCallWithFunctionBeanIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY")) - .withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(AnthropicChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java index a8fd3cc8c..b7e55c4d0 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java @@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory; import org.springframework.ai.anthropic.AnthropicChatModel; import org.springframework.ai.anthropic.AnthropicChatOptions; import org.springframework.ai.anthropic.api.AnthropicApi; -import org.springframework.ai.model.anthropic.autoconfigure.AnthropicAutoConfiguration; +import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; @@ -43,7 +43,7 @@ public class FunctionCallWithPromptFunctionIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY")) - .withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(AnthropicChatAutoConfiguration.class)); @Test void functionCallTest() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml index 595864404..e324901bd 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml @@ -39,35 +39,30 @@ org.springframework.ai spring-ai-autoconfigure-model-tool ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-image-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAudioTranscriptionAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAudioTranscriptionAutoConfiguration.java new file mode 100644 index 000000000..0e853fdae --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAudioTranscriptionAutoConfiguration.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; + +import com.azure.ai.openai.OpenAIClientBuilder; + +import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel; +import org.springframework.ai.azure.openai.AzureOpenAiChatModel; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +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.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * {@link AutoConfiguration Auto-configuration} for Azure OpenAI. + * + * @author Piotr Olaszewski + * @author Soby Chacko + * @author Manuel Andreo Garcia + * @author Ilayaperumal Gopinathan + */ +@AutoConfiguration +@ConditionalOnClass({ AzureOpenAiAudioTranscriptionModel.class }) +@EnableConfigurationProperties(AzureOpenAiAudioTranscriptionProperties.class) +@ConditionalOnProperty(name = SpringAIModelProperties.AUDIO_TRANSCRIPTION_MODEL, + havingValue = SpringAIModels.AZURE_OPENAI, matchIfMissing = true) +@ImportAutoConfiguration(classes = AzureOpenAiClientBuilderAutoConfiguration.class) +public class AzureOpenAiAudioTranscriptionAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public AzureOpenAiAudioTranscriptionModel azureOpenAiAudioTranscriptionModel(OpenAIClientBuilder openAIClient, + AzureOpenAiAudioTranscriptionProperties audioProperties) { + return new AzureOpenAiAudioTranscriptionModel(openAIClient.buildClient(), audioProperties.getOptions()); + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java deleted file mode 100644 index c442995b1..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import com.azure.ai.openai.OpenAIClientBuilder; -import com.azure.core.credential.AzureKeyCredential; -import com.azure.core.credential.KeyCredential; -import com.azure.core.credential.TokenCredential; -import com.azure.core.util.ClientOptions; -import com.azure.core.util.Header; -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.model.SpringAIModelProperties; -import org.springframework.ai.model.SpringAIModels; -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel; -import org.springframework.ai.azure.openai.AzureOpenAiChatModel; -import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; -import org.springframework.ai.azure.openai.AzureOpenAiImageModel; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -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.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * {@link AutoConfiguration Auto-configuration} for Azure OpenAI. - * - * @author Piotr Olaszewski - * @author Soby Chacko - * @author Manuel Andreo Garcia - * @author Ilayaperumal Gopinathan - */ -@AutoConfiguration(after = { ToolCallingAutoConfiguration.class }) -@ConditionalOnClass({ OpenAIClientBuilder.class, AzureOpenAiChatModel.class }) -@EnableConfigurationProperties({ AzureOpenAiChatProperties.class, AzureOpenAiEmbeddingProperties.class, - AzureOpenAiConnectionProperties.class, AzureOpenAiImageOptionsProperties.class, - AzureOpenAiAudioTranscriptionProperties.class }) -@ImportAutoConfiguration(classes = { ToolCallingAutoConfiguration.class }) -public class AzureOpenAiAutoConfiguration { - - private static final String APPLICATION_ID = "spring-ai"; - - @Bean - @ConditionalOnMissingBean // ({ OpenAIClient.class, TokenCredential.class }) - public OpenAIClientBuilder openAIClientBuilder(AzureOpenAiConnectionProperties connectionProperties, - ObjectProvider customizers) { - - if (StringUtils.hasText(connectionProperties.getApiKey())) { - - Assert.hasText(connectionProperties.getEndpoint(), "Endpoint must not be empty"); - - Map customHeaders = connectionProperties.getCustomHeaders(); - List
headers = customHeaders.entrySet() - .stream() - .map(entry -> new Header(entry.getKey(), entry.getValue())) - .collect(Collectors.toList()); - ClientOptions clientOptions = new ClientOptions().setApplicationId(APPLICATION_ID).setHeaders(headers); - OpenAIClientBuilder clientBuilder = new OpenAIClientBuilder().endpoint(connectionProperties.getEndpoint()) - .credential(new AzureKeyCredential(connectionProperties.getApiKey())) - .clientOptions(clientOptions); - applyOpenAIClientBuilderCustomizers(clientBuilder, customizers); - return clientBuilder; - } - - // Connect to OpenAI (e.g. not the Azure OpenAI). The deploymentName property is - // used as OpenAI model name. - if (StringUtils.hasText(connectionProperties.getOpenAiApiKey())) { - OpenAIClientBuilder clientBuilder = new OpenAIClientBuilder().endpoint("https://api.openai.com/v1") - .credential(new KeyCredential(connectionProperties.getOpenAiApiKey())) - .clientOptions(new ClientOptions().setApplicationId(APPLICATION_ID)); - applyOpenAIClientBuilderCustomizers(clientBuilder, customizers); - return clientBuilder; - } - - throw new IllegalArgumentException("Either API key or OpenAI API key must not be empty"); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(TokenCredential.class) - public OpenAIClientBuilder openAIClientWithTokenCredential(AzureOpenAiConnectionProperties connectionProperties, - TokenCredential tokenCredential, ObjectProvider customizers) { - - Assert.notNull(tokenCredential, "TokenCredential must not be null"); - Assert.hasText(connectionProperties.getEndpoint(), "Endpoint must not be empty"); - - OpenAIClientBuilder clientBuilder = new OpenAIClientBuilder().endpoint(connectionProperties.getEndpoint()) - .credential(tokenCredential) - .clientOptions(new ClientOptions().setApplicationId(APPLICATION_ID)); - applyOpenAIClientBuilderCustomizers(clientBuilder, customizers); - return clientBuilder; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, - matchIfMissing = true) - public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, - AzureOpenAiChatProperties chatProperties, ToolCallingManager toolCallingManager, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var chatModel = AzureOpenAiChatModel.builder() - .openAIClientBuilder(openAIClientBuilder) - .defaultOptions(chatProperties.getOptions()) - .toolCallingManager(toolCallingManager) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .build(); - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, - matchIfMissing = true) - public AzureOpenAiEmbeddingModel azureOpenAiEmbeddingModel(OpenAIClientBuilder openAIClient, - AzureOpenAiEmbeddingProperties embeddingProperties, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var embeddingModel = new AzureOpenAiEmbeddingModel(openAIClient.buildClient(), - embeddingProperties.getMetadataMode(), embeddingProperties.getOptions(), - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, - matchIfMissing = true) - public AzureOpenAiImageModel azureOpenAiImageModel(OpenAIClientBuilder openAIClientBuilder, - AzureOpenAiImageOptionsProperties imageProperties) { - - return new AzureOpenAiImageModel(openAIClientBuilder.buildClient(), imageProperties.getOptions()); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.AUDIO_TRANSCRIPTION_MODEL, - havingValue = SpringAIModels.AZURE_OPENAI, matchIfMissing = true) - public AzureOpenAiAudioTranscriptionModel azureOpenAiAudioTranscriptionModel(OpenAIClientBuilder openAIClient, - AzureOpenAiAudioTranscriptionProperties audioProperties) { - return new AzureOpenAiAudioTranscriptionModel(openAIClient.buildClient(), audioProperties.getOptions()); - } - - private void applyOpenAIClientBuilderCustomizers(OpenAIClientBuilder clientBuilder, - ObjectProvider customizers) { - customizers.orderedStream().forEach(customizer -> customizer.customize(clientBuilder)); - } - - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatAutoConfiguration.java similarity index 50% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatAutoConfiguration.java index 0a9591ed2..ce067c58e 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2024 the original author or authors. + * Copyright 2023-2024 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. @@ -14,73 +14,59 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.converse; +package org.springframework.ai.model.azure.openai.autoconfigure; +import com.azure.ai.openai.OpenAIClientBuilder; import io.micrometer.observation.ObservationRegistry; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; -import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; -import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; -import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionConfiguration; -import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionProperties; -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.bedrock.converse.BedrockProxyChatModel; +import org.springframework.ai.azure.openai.AzureOpenAiChatModel; import org.springframework.ai.chat.observation.ChatModelObservationConvention; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.ai.model.tool.ToolCallingManager; +import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 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.context.properties.EnableConfigurationProperties; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; /** - * {@link AutoConfiguration Auto-configuration} for Bedrock Converse Proxy Chat Client. + * {@link AutoConfiguration Auto-configuration} for Azure OpenAI. * - * Leverages the Spring Cloud AWS to resolve the {@link AwsCredentialsProvider}. - * - * @author Christian Tzolov - * @author Wei Jiang + * @author Piotr Olaszewski + * @author Soby Chacko + * @author Manuel Andreo Garcia + * @author Ilayaperumal Gopinathan */ @AutoConfiguration(after = { ToolCallingAutoConfiguration.class }) -@EnableConfigurationProperties({ BedrockConverseProxyChatProperties.class, BedrockAwsConnectionConfiguration.class }) -@ConditionalOnClass({ BedrockProxyChatModel.class, BedrockRuntimeClient.class, BedrockRuntimeAsyncClient.class }) -@ConditionalOnProperty(prefix = BedrockConverseProxyChatProperties.CONFIG_PREFIX, name = "enabled", - havingValue = "true", matchIfMissing = true) -@Import(BedrockAwsConnectionConfiguration.class) -@ImportAutoConfiguration({ ToolCallingAutoConfiguration.class }) -public class BedrockConverseProxyChatAutoConfiguration { +@ConditionalOnClass({ AzureOpenAiChatModel.class }) +@EnableConfigurationProperties({ AzureOpenAiChatProperties.class }) +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, + matchIfMissing = true) +@ImportAutoConfiguration( + classes = { AzureOpenAiClientBuilderAutoConfiguration.class, ToolCallingAutoConfiguration.class }) +public class AzureOpenAiChatAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public BedrockProxyChatModel bedrockProxyChatModel(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockAwsConnectionProperties connectionProperties, - BedrockConverseProxyChatProperties chatProperties, ToolCallingManager toolCallingManager, + public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, + AzureOpenAiChatProperties chatProperties, ToolCallingManager toolCallingManager, ObjectProvider observationRegistry, - ObjectProvider observationConvention, - ObjectProvider bedrockRuntimeClient, - ObjectProvider bedrockRuntimeAsyncClient) { + ObjectProvider observationConvention) { - var chatModel = BedrockProxyChatModel.builder() - .credentialsProvider(credentialsProvider) - .region(regionProvider.getRegion()) - .timeout(connectionProperties.getTimeout()) + var chatModel = AzureOpenAiChatModel.builder() + .openAIClientBuilder(openAIClientBuilder) .defaultOptions(chatProperties.getOptions()) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) .toolCallingManager(toolCallingManager) - .bedrockRuntimeClient(bedrockRuntimeClient.getIfAvailable()) - .bedrockRuntimeAsyncClient(bedrockRuntimeAsyncClient.getIfAvailable()) + .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) .build(); - observationConvention.ifAvailable(chatModel::setObservationConvention); return chatModel; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderAutoConfiguration.java similarity index 51% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderAutoConfiguration.java index f01962f43..4dafd1ced 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderAutoConfiguration.java @@ -26,27 +26,13 @@ import com.azure.core.credential.KeyCredential; import com.azure.core.credential.TokenCredential; import com.azure.core.util.ClientOptions; import com.azure.core.util.Header; -import io.micrometer.observation.ObservationRegistry; -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel; -import org.springframework.ai.azure.openai.AzureOpenAiChatModel; -import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; -import org.springframework.ai.azure.openai.AzureOpenAiImageModel; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 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.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -59,13 +45,10 @@ import org.springframework.util.StringUtils; * @author Manuel Andreo Garcia * @author Ilayaperumal Gopinathan */ -@AutoConfiguration(after = { ToolCallingAutoConfiguration.class }) -@ConditionalOnClass({ OpenAIClientBuilder.class, AzureOpenAiChatModel.class }) -@EnableConfigurationProperties({ AzureOpenAiChatProperties.class, AzureOpenAiEmbeddingProperties.class, - AzureOpenAiConnectionProperties.class, AzureOpenAiImageOptionsProperties.class, - AzureOpenAiAudioTranscriptionProperties.class }) -@ImportAutoConfiguration(classes = { ToolCallingAutoConfiguration.class }) -public class AzureOpenAiAutoConfiguration { +@AutoConfiguration +@ConditionalOnClass({ OpenAIClientBuilder.class }) +@EnableConfigurationProperties(AzureOpenAiConnectionProperties.class) +public class AzureOpenAiClientBuilderAutoConfiguration { private static final String APPLICATION_ID = "spring-ai"; @@ -120,71 +103,6 @@ public class AzureOpenAiAutoConfiguration { return clientBuilder; } - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = AzureOpenAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder, - AzureOpenAiChatProperties chatProperties, ToolCallingManager toolCallingManager, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var chatModel = AzureOpenAiChatModel.builder() - .openAIClientBuilder(openAIClientBuilder) - .defaultOptions(chatProperties.getOptions()) - .toolCallingManager(toolCallingManager) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .build(); - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = AzureOpenAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", - havingValue = "true", matchIfMissing = true) - public AzureOpenAiEmbeddingModel azureOpenAiEmbeddingModel(OpenAIClientBuilder openAIClient, - AzureOpenAiEmbeddingProperties embeddingProperties, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var embeddingModel = new AzureOpenAiEmbeddingModel(openAIClient.buildClient(), - embeddingProperties.getMetadataMode(), embeddingProperties.getOptions(), - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - - } - - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = AzureOpenAiImageOptionsProperties.CONFIG_PREFIX, name = "enabled", - havingValue = "true", matchIfMissing = true) - public AzureOpenAiImageModel azureOpenAiImageClient(OpenAIClientBuilder openAIClientBuilder, - AzureOpenAiImageOptionsProperties imageProperties) { - - return new AzureOpenAiImageModel(openAIClientBuilder.buildClient(), imageProperties.getOptions()); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = AzureOpenAiAudioTranscriptionProperties.CONFIG_PREFIX, name = "enabled", - havingValue = "true", matchIfMissing = true) - public AzureOpenAiAudioTranscriptionModel azureOpenAiAudioTranscriptionModel(OpenAIClientBuilder openAIClient, - AzureOpenAiAudioTranscriptionProperties audioProperties) { - return new AzureOpenAiAudioTranscriptionModel(openAIClient.buildClient(), audioProperties.getOptions()); - } - private void applyOpenAIClientBuilderCustomizers(OpenAIClientBuilder clientBuilder, ObjectProvider customizers) { customizers.orderedStream().forEach(customizer -> customizer.customize(clientBuilder)); diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingAutoConfiguration.java similarity index 50% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingAutoConfiguration.java index 495428766..13dc388bd 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingAutoConfiguration.java @@ -14,16 +14,18 @@ * limitations under the License. */ -package org.springframework.ai.model.transformers.autoconfigure; +package org.springframework.ai.model.azure.openai.autoconfigure; -import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer; -import ai.onnxruntime.OrtSession; +import com.azure.ai.openai.OpenAIClientBuilder; import io.micrometer.observation.ObservationRegistry; +import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.transformers.TransformersEmbeddingModel; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -31,41 +33,35 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.context.annotation.Bean; /** - * {@link AutoConfiguration Auto-configuration} for Transformers Embedding Model. + * {@link AutoConfiguration Auto-configuration} for Azure OpenAI. * - * @author Christian Tzolov + * @author Piotr Olaszewski + * @author Soby Chacko + * @author Manuel Andreo Garcia + * @author Ilayaperumal Gopinathan */ @AutoConfiguration -@EnableConfigurationProperties({ TransformersEmbeddingModelProperties.class }) -@ConditionalOnClass({ OrtSession.class, HuggingFaceTokenizer.class, TransformersEmbeddingModel.class }) -public class TransformersEmbeddingModelAutoConfiguration { +@ConditionalOnClass({ AzureOpenAiEmbeddingModel.class }) +@EnableConfigurationProperties({ AzureOpenAiEmbeddingProperties.class }) +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, + matchIfMissing = true) +@ImportAutoConfiguration(classes = AzureOpenAiClientBuilderAutoConfiguration.class) +public class AzureOpenAiEmbeddingAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = TransformersEmbeddingModelProperties.CONFIG_PREFIX, name = "enabled", - havingValue = "true", matchIfMissing = true) - public TransformersEmbeddingModel embeddingModel(TransformersEmbeddingModelProperties properties, - ObjectProvider observationRegistry, + public AzureOpenAiEmbeddingModel azureOpenAiEmbeddingModel(OpenAIClientBuilder openAIClient, + AzureOpenAiEmbeddingProperties embeddingProperties, ObjectProvider observationRegistry, ObjectProvider observationConvention) { - TransformersEmbeddingModel embeddingModel = new TransformersEmbeddingModel(properties.getMetadataMode(), + var embeddingModel = new AzureOpenAiEmbeddingModel(openAIClient.buildClient(), + embeddingProperties.getMetadataMode(), embeddingProperties.getOptions(), observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - embeddingModel.setDisableCaching(!properties.getCache().isEnabled()); - embeddingModel.setResourceCacheDirectory(properties.getCache().getDirectory()); - - embeddingModel.setTokenizerResource(properties.getTokenizer().getUri()); - embeddingModel.setTokenizerOptions(properties.getTokenizer().getOptions()); - - embeddingModel.setModelResource(properties.getOnnx().getModelUri()); - - embeddingModel.setGpuDeviceId(properties.getOnnx().getGpuDeviceId()); - - embeddingModel.setModelOutputName(properties.getOnnx().getModelOutputName()); - observationConvention.ifAvailable(embeddingModel::setObservationConvention); return embeddingModel; + } } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiImageAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiImageAutoConfiguration.java new file mode 100644 index 000000000..df74ab365 --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiImageAutoConfiguration.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; + +import com.azure.ai.openai.OpenAIClientBuilder; + +import org.springframework.ai.azure.openai.AzureOpenAiImageModel; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +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.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * {@link AutoConfiguration Auto-configuration} for Azure OpenAI. + * + * @author Piotr Olaszewski + * @author Soby Chacko + * @author Manuel Andreo Garcia + * @author Ilayaperumal Gopinathan + */ +@AutoConfiguration +@ConditionalOnClass(AzureOpenAiImageModel.class) +@ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, + matchIfMissing = true) +@EnableConfigurationProperties(AzureOpenAiImageOptionsProperties.class) +@ImportAutoConfiguration(classes = AzureOpenAiClientBuilderAutoConfiguration.class) +public class AzureOpenAiImageAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public AzureOpenAiImageModel azureOpenAiImageModel(OpenAIClientBuilder openAIClientBuilder, + AzureOpenAiImageOptionsProperties imageProperties) { + + return new AzureOpenAiImageModel(openAIClientBuilder.buildClient(), imageProperties.getOptions()); + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 130083802..c16af6362 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration +org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration +org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration +org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiImageAutoConfiguration +org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAudioTranscriptionAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationIT.java index 4cd87697c..6dd72c57a 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationIT.java @@ -35,11 +35,8 @@ import com.azure.core.http.HttpResponse; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAIClientBuilderCustomizer; - import reactor.core.publisher.Flux; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel; import org.springframework.ai.azure.openai.AzureOpenAiChatModel; import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; @@ -86,7 +83,7 @@ class AzureOpenAiAutoConfigurationIT { "spring.ai.azure.openai.embedding.options.deployment-name=" + EMBEDDING_MODEL_NAME, "spring.ai.azure.openai.audio.transcription.options.deployment-name=" + System.getenv("AZURE_OPENAI_TRANSCRIPTION_DEPLOYMENT_NAME") // @formatter:on - ).withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)); + ); private final Message systemMessage = new SystemPromptTemplate(""" You are a helpful AI assistant. Your name is {name}. @@ -100,16 +97,17 @@ class AzureOpenAiAutoConfigurationIT { @Test void chatCompletion() { - this.contextRunner.run(context -> { - AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - ChatResponse response = chatModel.call(new Prompt(List.of(this.userMessage, this.systemMessage))); - assertThat(response.getResult().getOutput().getText()).contains("Blackbeard"); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) + .run(context -> { + AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); + ChatResponse response = chatModel.call(new Prompt(List.of(this.userMessage, this.systemMessage))); + assertThat(response.getResult().getOutput().getText()).contains("Blackbeard"); + }); } @Test void httpRequestContainsUserAgentAndCustomHeaders() { - this.contextRunner + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) .withPropertyValues("spring.ai.azure.openai.custom-headers.foo=bar", "spring.ai.azure.openai.custom-headers.fizz=buzz") .run(context -> { @@ -136,100 +134,137 @@ class AzureOpenAiAutoConfigurationIT { @Test void chatCompletionStreaming() { - this.contextRunner.run(context -> { + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) + .run(context -> { - AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); + AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - Flux response = chatModel.stream(new Prompt(List.of(this.userMessage, this.systemMessage))); + Flux response = chatModel + .stream(new Prompt(List.of(this.userMessage, this.systemMessage))); - List responses = response.collectList().block(); - assertThat(responses.size()).isGreaterThan(10); + List responses = response.collectList().block(); + assertThat(responses.size()).isGreaterThan(10); - String stitchedResponseContent = responses.stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); + String stitchedResponseContent = responses.stream() + .map(ChatResponse::getResults) + .flatMap(List::stream) + .map(Generation::getOutput) + .map(AssistantMessage::getText) + .collect(Collectors.joining()); - assertThat(stitchedResponseContent).contains("Blackbeard"); - }); + assertThat(stitchedResponseContent).contains("Blackbeard"); + }); } @Test void embedding() { - this.contextRunner.run(context -> { - AzureOpenAiEmbeddingModel embeddingModel = context.getBean(AzureOpenAiEmbeddingModel.class); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) + .run(context -> { + AzureOpenAiEmbeddingModel embeddingModel = context.getBean(AzureOpenAiEmbeddingModel.class); - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); + EmbeddingResponse embeddingResponse = embeddingModel + .embedForResponse(List.of("Hello World", "World is big and salvation is near")); + assertThat(embeddingResponse.getResults()).hasSize(2); + assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); + assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); + + assertThat(embeddingModel.dimensions()).isEqualTo(1536); + }); - assertThat(embeddingModel.dimensions()).isEqualTo(1536); - }); } @Test @EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_TRANSCRIPTION_DEPLOYMENT_NAME", matches = ".+") void transcribe() { - this.contextRunner.run(context -> { - AzureOpenAiAudioTranscriptionModel transcriptionModel = context - .getBean(AzureOpenAiAudioTranscriptionModel.class); - Resource audioFile = new ClassPathResource("/speech/jfk.flac"); - String response = transcriptionModel.call(audioFile); - assertThat(response).isEqualTo( - "And so my fellow Americans, ask not what your country can do for you, ask what you can do for your country."); - }); + this.contextRunner + .withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class)) + .run(context -> { + AzureOpenAiAudioTranscriptionModel transcriptionModel = context + .getBean(AzureOpenAiAudioTranscriptionModel.class); + Resource audioFile = new ClassPathResource("/speech/jfk.flac"); + String response = transcriptionModel.call(audioFile); + assertThat(response).isEqualTo( + "And so my fellow Americans, ask not what your country can do for you, ask what you can do for your country."); + }); } @Test void chatActivation() { // Disable the chat auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.chat.enabled=false") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty()); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=none") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); + }); // The chat auto-configuration is enabled by default. - this.contextRunner.run(context -> assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty()); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty(); + }); // Explicitly enable the chat auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.chat.enabled=true") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty()); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=azure-openai") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty(); + }); } @Test void embeddingActivation() { // Disable the embedding auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.embedding.enabled=false") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty()); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=none") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isEmpty(); + }); // The embedding auto-configuration is enabled by default. - this.contextRunner - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty()); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty(); + }); // Explicitly enable the embedding auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.embedding.enabled=true") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty()); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=azure-openai") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty(); + }); } @Test void audioTranscriptionActivation() { // Disable the transcription auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.audio.transcription.enabled=false") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty()); + this.contextRunner + .withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.audio.transcription=none") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isEmpty(); + }); // The transcription auto-configuration is enabled by default. this.contextRunner + .withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class)) .run(context -> assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty()); // Explicitly enable the transcription auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.audio.transcription.enabled=true") + this.contextRunner + .withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.audio.transcription=azure-openai") .run(context -> assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty()); } @@ -237,7 +272,7 @@ class AzureOpenAiAutoConfigurationIT { void openAIClientBuilderCustomizer() { AtomicBoolean firstCustomizationApplied = new AtomicBoolean(false); AtomicBoolean secondCustomizationApplied = new AtomicBoolean(false); - this.contextRunner + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) .withBean("first", AzureOpenAIClientBuilderCustomizer.class, () -> clientBuilder -> firstCustomizationApplied.set(true)) .withBean("second", AzureOpenAIClientBuilderCustomizer.class, diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationPropertyTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationPropertyTests.java index df965f34e..f166bf284 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationPropertyTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationPropertyTests.java @@ -18,10 +18,6 @@ package org.springframework.ai.model.azure.openai.autoconfigure; import org.junit.jupiter.api.Test; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatProperties; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiConnectionProperties; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -40,7 +36,7 @@ public class AzureOpenAiAutoConfigurationPropertyTests { .withPropertyValues("spring.ai.azure.openai.api-key=TEST_API_KEY", "spring.ai.azure.openai.endpoint=TEST_ENDPOINT", "spring.ai.azure.openai.embedding.options.deployment-name=MODEL_XYZ") - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(AzureOpenAiEmbeddingProperties.class); var connectionProperties = context.getBean(AzureOpenAiConnectionProperties.class); @@ -72,7 +68,8 @@ public class AzureOpenAiAutoConfigurationPropertyTests { "spring.ai.azure.openai.chat.options.user=userXYZ" ) // @formatter:on - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class, + AzureOpenAiEmbeddingAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(AzureOpenAiChatProperties.class); var connectionProperties = context.getBean(AzureOpenAiConnectionProperties.class); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiDirectOpenAiAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiDirectOpenAiAutoConfigurationIT.java index 99c4ed05c..a3dff4617 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiDirectOpenAiAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiDirectOpenAiAutoConfigurationIT.java @@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import reactor.core.publisher.Flux; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; import org.springframework.ai.azure.openai.AzureOpenAiChatModel; import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; import org.springframework.ai.chat.messages.AssistantMessage; @@ -60,7 +59,9 @@ public class AzureOpenAiDirectOpenAiAutoConfigurationIT { "spring.ai.azure.openai.chat.options.maxTokens=123", "spring.ai.azure.openai.embedding.options.deployment-name=" + EMBEDDING_MODEL_NAME // @formatter:on - ).withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)); + ) + .withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class, + AzureOpenAiEmbeddingAutoConfiguration.class)); private final Message systemMessage = new SystemPromptTemplate(""" You are a helpful AI assistant. Your name is {name}. diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiModelConfigurationTests.java index 19a2ce674..7c3a933f2 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiModelConfigurationTests.java @@ -29,38 +29,42 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * Unit Tests for {@link AzureOpenAiAutoConfiguration}'s conditional enabling of models. + * Unit Tests for Azure OpenAI auto-configurations conditional enabling of models. * * @author Ilayaperumal Gopinathan */ @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") public class AzureOpenAiModelConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.azure.openai.openai-api-key=" + System.getenv("OPENAI_API_KEY"), - "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( + "spring.ai.azure.openai.openai-api-key=" + System.getenv("OPENAI_API_KEY"), + "spring.ai.openai.base-url=TEST_BASE_URL"); @Test void chatModelActivation() { - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.chat=none").run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=none") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.chat=azure-openai").run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=azure-openai") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); + }); - this.contextRunner + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) .withPropertyValues("spring.ai.model.chat=azure-openai", "spring.ai.model.embedding=none", "spring.ai.model.image=none", "spring.ai.model.audio.speech=none", "spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none") @@ -74,24 +78,29 @@ public class AzureOpenAiModelConfigurationTests { @Test void embeddingModelActivation() { - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=none") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.embedding=azure-openai").run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=azure-openai") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); + }); - this.contextRunner + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) .withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=azure-openai", "spring.ai.model.image=none", "spring.ai.model.audio.speech=none", "spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none") @@ -105,24 +114,29 @@ public class AzureOpenAiModelConfigurationTests { @Test void imageModelActivation() { - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.image=none").run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.image=none") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.image=azure-openai").run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.image=azure-openai") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); + }); - this.contextRunner + this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class)) .withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none", "spring.ai.model.image=azure-openai", "spring.ai.model.audio.speech=none", "spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none") @@ -136,24 +150,33 @@ public class AzureOpenAiModelConfigurationTests { @Test void audioTranscriptionModelActivation() { - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); - }); - - this.contextRunner.withPropertyValues("spring.ai.model.audio.transcription=none").run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); - }); - - this.contextRunner.withPropertyValues("spring.ai.model.audio.transcription=azure-openai").run(context -> { - assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); - }); + this.contextRunner + .withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); + }); this.contextRunner + .withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.audio.transcription=none") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); + }); + + this.contextRunner + .withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.audio.transcription=azure-openai") + .run(context -> { + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); + }); + + this.contextRunner + .withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class)) .withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none", "spring.ai.model.image=none", "spring.ai.model.audio.speech=none", "spring.ai.model.audio.transcription=azure-openai", "spring.ai.model.moderation=none") diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java index 5f560d174..e4a17e2a7 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java @@ -24,13 +24,13 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; import org.springframework.ai.azure.openai.AzureOpenAiChatModel; import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -51,7 +51,7 @@ class FunctionCallWithFunctionBeanIT { "spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"), "spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT")) // @formatter:onn - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionWrapperIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionWrapperIT.java index b809da5dd..c8c61ae15 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionWrapperIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionWrapperIT.java @@ -23,12 +23,12 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; import org.springframework.ai.azure.openai.AzureOpenAiChatModel; import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -49,7 +49,7 @@ public class FunctionCallWithFunctionWrapperIT { "spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"), "spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT")) // @formatter:onn - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java index 90e5d7a9b..8f2d63b8e 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java @@ -23,12 +23,12 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; import org.springframework.ai.azure.openai.AzureOpenAiChatModel; import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -46,7 +46,7 @@ public class FunctionCallWithPromptFunctionIT { "spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"), "spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT")) // @formatter:onn - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)); @Test void functionCallTest() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml index 7c0112362..11d42f694 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml @@ -46,35 +46,30 @@ org.springframework.ai spring-ai-autoconfigure-model-tool ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-image-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatProperties.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatProperties.java deleted file mode 100644 index 0428a9c90..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatProperties.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2024-2024 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.springframework.ai.model.bedrock.autoconfigure.converse; - -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; -import org.springframework.util.Assert; - -/** - * Configuration properties for Bedrock Converse. - * - * @author Christian Tzolov - * @since 1.0.0 - */ -@ConfigurationProperties(BedrockConverseProxyChatProperties.CONFIG_PREFIX) -public class BedrockConverseProxyChatProperties { - - public static final String CONFIG_PREFIX = "spring.ai.bedrock.converse.chat"; - - /** - * Enable Bedrock Converse chat model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private ToolCallingChatOptions options = ToolCallingChatOptions.builder() - .temperature(0.7) - .maxTokens(300) - .topK(10) - .build(); - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public ToolCallingChatOptions getOptions() { - return this.options; - } - - public void setOptions(ToolCallingChatOptions options) { - Assert.notNull(options, "FunctionCallingOptions must not be null"); - this.options = options; - } - -} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingProperties.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingProperties.java deleted file mode 100644 index 48c452ea4..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingProperties.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.titan; - -import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel.InputType; -import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi.TitanEmbeddingModel; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Bedrock Titan Embedding autoconfiguration properties. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -@ConfigurationProperties(BedrockTitanEmbeddingProperties.CONFIG_PREFIX) -public class BedrockTitanEmbeddingProperties { - - public static final String CONFIG_PREFIX = "spring.ai.bedrock.titan.embedding"; - - /** - * Enable Bedrock Titan Embedding Model. False by default. - */ - private boolean enabled = false; - - /** - * Bedrock Titan Embedding generative name. Defaults to 'amazon.titan-embed-image-v1'. - */ - private String model = TitanEmbeddingModel.TITAN_EMBED_IMAGE_V1.id(); - - /** - * Titan Embedding API input types. Could be either text or image (encoded in base64). - * Defaults to {@link InputType#IMAGE}. - */ - private InputType inputType = InputType.IMAGE; - - public static String getConfigPrefix() { - return CONFIG_PREFIX; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getModel() { - return this.model; - } - - public void setModel(String model) { - this.model = model; - } - - public InputType getInputType() { - return this.inputType; - } - - public void setInputType(InputType inputType) { - this.inputType = inputType; - } - -} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereEmbeddingAutoConfiguration.java similarity index 98% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereEmbeddingAutoConfiguration.java index 57765ad55..2b07482d4 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereEmbeddingAutoConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.cohere; +package org.springframework.ai.model.bedrock.cohere.autoconfigure; import com.fasterxml.jackson.databind.ObjectMapper; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingProperties.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereEmbeddingProperties.java similarity index 97% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingProperties.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereEmbeddingProperties.java index 131a9832a..f12412770 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingProperties.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereEmbeddingProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.cohere; +package org.springframework.ai.model.bedrock.cohere.autoconfigure; import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingOptions; import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingModel; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfiguration.java similarity index 98% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfiguration.java index e063bc3af..9aed1da08 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.converse; +package org.springframework.ai.model.bedrock.converse.autoconfigure; import io.micrometer.observation.ObservationRegistry; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatProperties.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatProperties.java similarity index 96% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatProperties.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatProperties.java index 0428a9c90..b7231551b 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatProperties.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.converse; +package org.springframework.ai.model.bedrock.converse.autoconfigure; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanEmbeddingAutoConfiguration.java similarity index 98% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanEmbeddingAutoConfiguration.java index 474ec9030..efb5c2637 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanEmbeddingAutoConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.titan; +package org.springframework.ai.model.bedrock.titan.autoconfigure; import com.fasterxml.jackson.databind.ObjectMapper; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingProperties.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanEmbeddingProperties.java similarity index 97% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingProperties.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanEmbeddingProperties.java index 48c452ea4..05ec385c6 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingProperties.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanEmbeddingProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.titan; +package org.springframework.ai.model.bedrock.titan.autoconfigure; import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel.InputType; import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi.TitanEmbeddingModel; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 21b64d10d..be2a4839c 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,6 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -cohere.org.springframework.ai.model.bedrock.autoconfigure.BedrockCohereEmbeddingAutoConfiguration -titan.org.springframework.ai.model.bedrock.autoconfigure.BedrockTitanEmbeddingAutoConfiguration -converse.org.springframework.ai.model.bedrock.autoconfigure.BedrockConverseProxyChatAutoConfiguration +org.springframework.ai.model.bedrock.cohere.autoconfigure.BedrockCohereEmbeddingAutoConfiguration +org.springframework.ai.model.bedrock.titan.autoconfigure.BedrockTitanEmbeddingAutoConfiguration +org.springframework.ai.model.bedrock.converse.autoconfigure.BedrockConverseProxyChatAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithFunctionBeanIT.java deleted file mode 100644 index 4ad9486a4..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithFunctionBeanIT.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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.springframework.ai.model.bedrock.autoconfigure.converse.tool; - -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils; -import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials; -import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration; -import org.springframework.ai.bedrock.converse.BedrockProxyChatModel; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -@RequiresAwsCredentials -class FunctionCallWithFunctionBeanIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionBeanIT.class); - - private final ApplicationContextRunner contextRunner = BedrockTestUtils.getContextRunner() - .withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - - this.contextRunner - .withPropertyValues( - "spring.ai.bedrock.converse.chat.options.model=" + "anthropic.claude-3-5-sonnet-20240620-v1:0") - .run(context -> { - - BedrockProxyChatModel chatModel = context.getBean(BedrockProxyChatModel.class); - - var userMessage = new UserMessage( - "What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius."); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - ToolCallingChatOptions.builder().toolNames("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - response = chatModel.call(new Prompt(List.of(userMessage), - ToolCallingChatOptions.builder().toolNames("weatherFunction3").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Test - void functionStreamTest() { - - this.contextRunner - .withPropertyValues( - "spring.ai.bedrock.converse.chat.options.model=" + "anthropic.claude-3-5-sonnet-20240620-v1:0") - .run(context -> { - - BedrockProxyChatModel chatModel = context.getBean(BedrockProxyChatModel.class); - - var userMessage = new UserMessage( - "What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius."); - - Flux responses = chatModel.stream(new Prompt(List.of(userMessage), - ToolCallingChatOptions.builder().toolNames("weatherFunction").build())); - - String content = responses.collectList() - .block() - .stream() - .filter(cr -> cr.getResult() != null) - .map(cr -> cr.getResult().getOutput().getText()) - .collect(Collectors.joining()); - - logger.info("Response: {}", content); - assertThat(content).contains("30", "10", "15"); - - }); - } - - @Configuration - static class Config { - - @Bean - @Description("Get the weather in location. Return temperature in 36°F or 36°C format.") - public Function weatherFunction() { - return new MockWeatherService(); - } - - // Relies on the Request's JsonClassDescription annotation to provide the - // function description. - @Bean - public Function weatherFunction3() { - MockWeatherService weatherService = new MockWeatherService(); - return (weatherService::apply); - } - - } - -} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/MockWeatherService.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/MockWeatherService.java deleted file mode 100644 index 29081ec2f..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/MockWeatherService.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.converse.tool; - -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - -/** - * Mock 3rd party weather service. - * - * @author Christian Tzolov - */ -public class MockWeatherService implements Function { - - @Override - public Response apply(Request request) { - - double temperature = 0; - if (request.location().contains("Paris")) { - temperature = 15; - } - else if (request.location().contains("Tokyo")) { - temperature = 10; - } - else if (request.location().contains("San Francisco")) { - temperature = 30; - } - - return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); - } - - /** - * Temperature units. - */ - public enum Unit { - - /** - * Celsius. - */ - C("metric"), - /** - * Fahrenheit. - */ - F("imperial"); - - /** - * Human readable unit name. - */ - public final String unitName; - - Unit(String text) { - this.unitName = text; - } - - } - - /** - * Weather Function request. - */ - @JsonInclude(Include.NON_NULL) - @JsonClassDescription("Weather API request") - public record Request(@JsonProperty(required = true, - value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, - @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { - - } - - /** - * Weather Function response. - */ - public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity, - Unit unit) { - - } - -} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereEmbeddingAutoConfigurationIT.java similarity index 98% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereEmbeddingAutoConfigurationIT.java index 4e6f0c4eb..127f0b3d2 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereEmbeddingAutoConfigurationIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.cohere; +package org.springframework.ai.model.bedrock.cohere.autoconfigure; import java.util.List; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereModelConfigurationTests.java similarity index 86% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereModelConfigurationTests.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereModelConfigurationTests.java index 019261105..7690cb9f7 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/cohere/autoconfigure/BedrockCohereModelConfigurationTests.java @@ -14,15 +14,12 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.cohere; +package org.springframework.ai.model.bedrock.cohere.autoconfigure; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingModel; -import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel; -import org.springframework.ai.model.bedrock.autoconfigure.titan.BedrockTitanEmbeddingAutoConfiguration; -import org.springframework.ai.model.bedrock.autoconfigure.titan.BedrockTitanEmbeddingProperties; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseModelConfigurationTests.java similarity index 89% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseModelConfigurationTests.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseModelConfigurationTests.java index 0192e85d9..9abd3712d 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseModelConfigurationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.converse; +package org.springframework.ai.model.bedrock.converse.autoconfigure; import org.junit.jupiter.api.Test; @@ -25,9 +25,8 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * Unit Tests for - * {@link org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration}'s - * conditional enabling of models. + * Unit Tests for {@link BedrockConverseProxyChatAutoConfiguration}'s conditional enabling + * of models. * * @author Ilayaperumal Gopinathan */ diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfigurationIT.java similarity index 97% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfigurationIT.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfigurationIT.java index f89cda503..8f101a357 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatAutoConfigurationIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.converse; +package org.springframework.ai.model.bedrock.converse.autoconfigure; import java.util.List; import java.util.stream.Collectors; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatPropertiesTests.java similarity index 98% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatPropertiesTests.java index 65913a7f0..73db74a3d 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/BedrockConverseProxyChatPropertiesTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.converse; +package org.springframework.ai.model.bedrock.converse.autoconfigure; import org.junit.jupiter.api.Test; diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java similarity index 96% rename from spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithFunctionBeanIT.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java index 4ad9486a4..ee837f271 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithFunctionBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.converse.tool; +package org.springframework.ai.model.bedrock.converse.autoconfigure.tool; import java.util.List; import java.util.function.Function; @@ -27,7 +27,7 @@ import reactor.core.publisher.Flux; import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils; import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials; -import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration; +import org.springframework.ai.model.bedrock.converse.autoconfigure.BedrockConverseProxyChatAutoConfiguration; import org.springframework.ai.bedrock.converse.BedrockProxyChatModel; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithPromptFunctionIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java similarity index 93% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithPromptFunctionIT.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java index 95c595370..efff6f331 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithPromptFunctionIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.converse.tool; +package org.springframework.ai.model.bedrock.converse.autoconfigure.tool; import java.util.List; @@ -24,7 +24,7 @@ import org.slf4j.LoggerFactory; import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils; import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials; -import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration; +import org.springframework.ai.model.bedrock.converse.autoconfigure.BedrockConverseProxyChatAutoConfiguration; import org.springframework.ai.bedrock.converse.BedrockProxyChatModel; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/MockWeatherService.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/tool/MockWeatherService.java similarity index 96% rename from spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/MockWeatherService.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/tool/MockWeatherService.java index 645ab7060..3047b7ce4 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/MockWeatherService.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/converse/autoconfigure/tool/MockWeatherService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.anthropic.autoconfigure.tool; +package org.springframework.ai.model.bedrock.converse.autoconfigure.tool; import java.util.function.Function; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanEmbeddingAutoConfigurationIT.java similarity index 98% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanEmbeddingAutoConfigurationIT.java index 8764167dc..24584382b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanEmbeddingAutoConfigurationIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.titan; +package org.springframework.ai.model.bedrock.titan.autoconfigure; import java.util.Base64; import java.util.List; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanModelConfigurationTests.java similarity index 86% rename from auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanModelConfigurationTests.java rename to auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanModelConfigurationTests.java index 83d194a9e..443ae6d1d 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/titan/autoconfigure/BedrockTitanModelConfigurationTests.java @@ -14,15 +14,12 @@ * limitations under the License. */ -package org.springframework.ai.model.bedrock.autoconfigure.titan; +package org.springframework.ai.model.bedrock.titan.autoconfigure; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; -import org.springframework.ai.bedrock.converse.BedrockProxyChatModel; import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel; -import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration; -import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatProperties; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml index 09da01d8f..75235f4db 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml @@ -39,7 +39,6 @@ org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java index 9b937deab..8b8526d9c 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java @@ -29,12 +29,12 @@ import org.springframework.context.annotation.Bean; @AutoConfiguration @ConditionalOnClass(HuggingfaceChatModel.class) @EnableConfigurationProperties(HuggingfaceChatProperties.class) +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.HUGGINGFACE, + matchIfMissing = true) public class HuggingfaceChatAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.HUGGINGFACE, - matchIfMissing = true) public HuggingfaceChatModel huggingfaceChatModel(HuggingfaceChatProperties huggingfaceChatProperties) { return new HuggingfaceChatModel(huggingfaceChatProperties.getApiKey(), huggingfaceChatProperties.getUrl()); } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceModelConfigurationTests.java index 835036219..7d9a4a8c3 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceModelConfigurationTests.java @@ -42,7 +42,7 @@ public class HuggingfaceModelConfigurationTests { }); this.contextRunner.withPropertyValues("spring.ai.model.chat=none").run(context -> { - assertThat(context.getBeansOfType(HuggingfaceChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(HuggingfaceChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(HuggingfaceChatModel.class)).isEmpty(); }); 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 0ead4e2ab..6c8a26e97 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml @@ -39,28 +39,24 @@ org.springframework.ai spring-ai-autoconfigure-model-tool ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatAutoConfiguration.java similarity index 73% rename from auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatAutoConfiguration.java index f74e3ba21..7406ce43b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatAutoConfiguration.java @@ -20,17 +20,15 @@ import java.util.List; import io.micrometer.observation.ObservationRegistry; +import org.springframework.ai.chat.observation.ChatModelObservationConvention; +import org.springframework.ai.minimax.MiniMaxChatModel; +import org.springframework.ai.minimax.api.MiniMaxApi; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.minimax.MiniMaxChatModel; -import org.springframework.ai.minimax.MiniMaxEmbeddingModel; -import org.springframework.ai.minimax.api.MiniMaxApi; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallback; import org.springframework.ai.model.function.FunctionCallbackResolver; +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.ConditionalOnClass; @@ -47,20 +45,20 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; /** - * {@link AutoConfiguration Auto-configuration} for MiniMax Chat and Embedding Models. + * {@link AutoConfiguration Auto-configuration} for MiniMax Chat Model. * * @author Geng Rong + * @author Ilayaperumal Gopinathan */ @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) @ConditionalOnClass(MiniMaxApi.class) -@EnableConfigurationProperties({ MiniMaxConnectionProperties.class, MiniMaxChatProperties.class, - MiniMaxEmbeddingProperties.class }) -public class MiniMaxAutoConfiguration { +@EnableConfigurationProperties({ MiniMaxConnectionProperties.class, MiniMaxChatProperties.class }) +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MINIMAX, + matchIfMissing = true) +public class MiniMaxChatAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MINIMAX, - matchIfMissing = true) public MiniMaxChatModel miniMaxChatModel(MiniMaxConnectionProperties commonProperties, MiniMaxChatProperties chatProperties, ObjectProvider restClientBuilderProvider, List toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver, @@ -79,29 +77,6 @@ public class MiniMaxAutoConfiguration { return chatModel; } - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MINIMAX, - matchIfMissing = true) - public MiniMaxEmbeddingModel miniMaxEmbeddingModel(MiniMaxConnectionProperties commonProperties, - MiniMaxEmbeddingProperties embeddingProperties, - ObjectProvider restClientBuilderProvider, RetryTemplate retryTemplate, - ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var miniMaxApi = miniMaxApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(), - embeddingProperties.getApiKey(), commonProperties.getApiKey(), - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - - var embeddingModel = new MiniMaxEmbeddingModel(miniMaxApi, embeddingProperties.getMetadataMode(), - embeddingProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - } - private MiniMaxApi miniMaxApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingAutoConfiguration.java similarity index 62% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingAutoConfiguration.java index caea39372..e7098339c 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingAutoConfiguration.java @@ -16,19 +16,14 @@ package org.springframework.ai.model.minimax.autoconfigure; -import java.util.List; - import io.micrometer.observation.ObservationRegistry; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.minimax.MiniMaxChatModel; import org.springframework.ai.minimax.MiniMaxEmbeddingModel; import org.springframework.ai.minimax.api.MiniMaxApi; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackResolver; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +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.ConditionalOnClass; @@ -36,7 +31,6 @@ 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.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; @@ -45,42 +39,20 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; /** - * {@link AutoConfiguration Auto-configuration} for MiniMax Chat and Embedding Models. + * {@link AutoConfiguration Auto-configuration} for MiniMax Embedding Model. * * @author Geng Rong + * @author Ilayaperumal Gopinathan */ @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) @ConditionalOnClass(MiniMaxApi.class) -@EnableConfigurationProperties({ MiniMaxConnectionProperties.class, MiniMaxChatProperties.class, - MiniMaxEmbeddingProperties.class }) -public class MiniMaxAutoConfiguration { +@EnableConfigurationProperties({ MiniMaxConnectionProperties.class, MiniMaxEmbeddingProperties.class }) +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MINIMAX, + matchIfMissing = true) +public class MiniMaxEmbeddingAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = MiniMaxChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public MiniMaxChatModel miniMaxChatModel(MiniMaxConnectionProperties commonProperties, - MiniMaxChatProperties chatProperties, ObjectProvider restClientBuilderProvider, - List toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver, - RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var miniMaxApi = miniMaxApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), - chatProperties.getApiKey(), commonProperties.getApiKey(), - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - - var chatModel = new MiniMaxChatModel(miniMaxApi, chatProperties.getOptions(), functionCallbackResolver, - toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(chatModel::setObservationConvention); - return chatModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = MiniMaxEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) public MiniMaxEmbeddingModel miniMaxEmbeddingModel(MiniMaxConnectionProperties commonProperties, MiniMaxEmbeddingProperties embeddingProperties, ObjectProvider restClientBuilderProvider, RetryTemplate retryTemplate, @@ -112,12 +84,4 @@ public class MiniMaxAutoConfiguration { return new MiniMaxApi(resolvedBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler); } - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 1f671df33..edf614f0b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.minimax.autoconfigure.MiniMaxAutoConfiguration +org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration +org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration 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 f86e76185..c4982ba90 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 @@ -25,7 +25,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; @@ -34,6 +33,7 @@ import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.minimax.MiniMaxChatModel; import org.springframework.ai.minimax.MiniMaxChatOptions; import org.springframework.ai.model.function.FunctionCallback; +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.test.context.runner.ApplicationContextRunner; @@ -51,7 +51,7 @@ public class FunctionCallbackInPromptIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)); + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)); @Test void functionCallTest() { 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 734a0b8a7..b4e3d6a9b 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 @@ -55,7 +55,7 @@ class FunctionCallbackWithPlainFunctionBeanIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) .withUserConfiguration(Config.class); // FIXME: multiple function calls may stop prematurely due to model performance 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 2b82ba7d4..86f52f0a2 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 @@ -47,51 +47,58 @@ public class MiniMaxAutoConfigurationIT { private static final Log logger = LogFactory.getLog(MiniMaxAutoConfigurationIT.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)); + .withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY")); @Test void generate() { - this.contextRunner.run(context -> { - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); + this.contextRunner + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) + .run(context -> { + MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); + String response = chatModel.call("Hello"); + assertThat(response).isNotEmpty(); + logger.info("Response: " + response); + }); } @Test void generateStreaming() { - this.contextRunner.run(context -> { - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - String response = responseFlux.collectList() - .block() - .stream() - .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) - .collect(Collectors.joining()); + this.contextRunner + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) + .run(context -> { + MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); + Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); + String response = responseFlux.collectList() + .block() + .stream() + .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) + .collect(Collectors.joining()); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); + assertThat(response).isNotEmpty(); + logger.info("Response: " + response); + }); } @Test void embedding() { - this.contextRunner.run(context -> { - MiniMaxEmbeddingModel embeddingModel = context.getBean(MiniMaxEmbeddingModel.class); + this.contextRunner + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class)) + .run(context -> { + MiniMaxEmbeddingModel embeddingModel = context.getBean(MiniMaxEmbeddingModel.class); - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); + EmbeddingResponse embeddingResponse = embeddingModel + .embedForResponse(List.of("Hello World", "World is big and salvation is near")); + assertThat(embeddingResponse.getResults()).hasSize(2); + assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); + assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - assertThat(embeddingModel.dimensions()).isEqualTo(1536); - }); + assertThat(embeddingModel.dimensions()).isEqualTo(1536); + }); } } 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 5bf102959..50c2bee69 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 @@ -25,7 +25,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; @@ -34,6 +33,7 @@ import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.minimax.MiniMaxChatModel; import org.springframework.ai.minimax.MiniMaxChatOptions; import org.springframework.ai.model.function.FunctionCallback; +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.test.context.runner.ApplicationContextRunner; @@ -53,7 +53,7 @@ public class MiniMaxFunctionCallbackIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test 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 962b2e114..f897867cb 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 @@ -50,7 +50,7 @@ public class MiniMaxPropertiesTests { "spring.ai.minimax.chat.options.temperature=0.55") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(MiniMaxChatProperties.class); var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); @@ -79,7 +79,7 @@ public class MiniMaxPropertiesTests { "spring.ai.minimax.chat.options.temperature=0.55") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(MiniMaxChatProperties.class); var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); @@ -105,7 +105,7 @@ public class MiniMaxPropertiesTests { "spring.ai.minimax.embedding.options.model=MODEL_XYZ") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class)) .run(context -> { var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class); var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); @@ -132,7 +132,7 @@ public class MiniMaxPropertiesTests { "spring.ai.minimax.embedding.options.model=MODEL_XYZ") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class)) .run(context -> { var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class); var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); @@ -199,17 +199,14 @@ public class MiniMaxPropertiesTests { ) // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(MiniMaxChatProperties.class); var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); - var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class); assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("embo-01"); - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5); assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); @@ -248,7 +245,7 @@ public class MiniMaxPropertiesTests { ) // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class)) .run(context -> { var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class); @@ -267,16 +264,16 @@ public class MiniMaxPropertiesTests { .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL", "spring.ai.model.embedding=none") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty(); }); new ApplicationContextRunner() .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty(); @@ -286,7 +283,7 @@ public class MiniMaxPropertiesTests { .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL", "spring.ai.minimax.embedding.enabled=true") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty(); @@ -299,16 +296,16 @@ public class MiniMaxPropertiesTests { .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL", "spring.ai.model.chat=none") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isEmpty(); }); new ApplicationContextRunner() .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty(); @@ -318,7 +315,7 @@ public class MiniMaxPropertiesTests { .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL", "spring.ai.model.chat=minimax") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) + RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty(); 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 d1cdd2709..eff69c6c8 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 @@ -27,57 +27,59 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * Unit Tests for {@link MiniMaxAutoConfiguration}'s conditional enabling of models. + * Unit Tests for MiniMax auto-configurations' conditional enabling of models. * * @author Ilayaperumal Gopinathan */ public class MinimaxModelConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(MiniMaxAutoConfiguration.class, SpringAiRetryAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class)) .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL"); @Test void chatModelActivation() { - this.contextRunner.run(context -> { + this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxChatAutoConfiguration.class)).run(context -> { assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty(); }); - this.contextRunner.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none") + this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none") .run(context -> { - assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty(); }); - this.contextRunner.withPropertyValues("spring.ai.model.chat=minimax", "spring.ai.model.embedding=none") + this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=minimax", "spring.ai.model.embedding=none") .run(context -> { assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty(); }); } @Test void embeddingModelActivation() { - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxEmbeddingAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> { - assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=none") + .run(context -> { + assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.embedding=minimax").run(context -> { - assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=minimax") + .run(context -> { + assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty(); + }); } } 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 e75110f3f..d62018c33 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 @@ -39,35 +39,30 @@ org.springframework.ai spring-ai-autoconfigure-model-tool ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-image-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java similarity index 73% rename from auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java index 77bab0242..c67a43856 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java @@ -18,18 +18,16 @@ package org.springframework.ai.model.mistralai.autoconfigure; import io.micrometer.observation.ObservationRegistry; +import org.springframework.ai.chat.observation.ChatModelObservationConvention; +import org.springframework.ai.mistralai.MistralAiChatModel; +import org.springframework.ai.mistralai.api.MistralAiApi; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.mistralai.MistralAiChatModel; -import org.springframework.ai.mistralai.MistralAiEmbeddingModel; -import org.springframework.ai.mistralai.api.MistralAiApi; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.ai.model.tool.ToolCallingManager; +import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; @@ -47,49 +45,26 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; /** - * {@link AutoConfiguration Auto-configuration} for Mistral AI. + * Chat {@link AutoConfiguration Auto-configuration} for Mistral AI. * * @author Ricken Bazolo * @author Christian Tzolov * @author Thomas Vitale + * @author Ilayaperumal Gopinathan * @since 0.8.1 */ @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -@EnableConfigurationProperties({ MistralAiEmbeddingProperties.class, MistralAiCommonProperties.class, - MistralAiChatProperties.class }) +@EnableConfigurationProperties({ MistralAiCommonProperties.class, MistralAiChatProperties.class }) +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MISTRAL, + matchIfMissing = true) @ConditionalOnClass(MistralAiApi.class) @ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -public class MistralAiAutoConfiguration { +public class MistralAiChatAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MISTRAL, - matchIfMissing = true) - public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiCommonProperties commonProperties, - MistralAiEmbeddingProperties embeddingProperties, - ObjectProvider restClientBuilderProvider, RetryTemplate retryTemplate, - ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var mistralAiApi = mistralAiApi(embeddingProperties.getApiKey(), commonProperties.getApiKey(), - embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(), - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - - var embeddingModel = new MistralAiEmbeddingModel(mistralAiApi, embeddingProperties.getMetadataMode(), - embeddingProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MISTRAL, - matchIfMissing = true) public MistralAiChatModel mistralAiChatModel(MistralAiCommonProperties commonProperties, MistralAiChatProperties chatProperties, ObjectProvider restClientBuilderProvider, ToolCallingManager toolCallingManager, RetryTemplate retryTemplate, diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java similarity index 59% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java index b58a75200..db0605dc3 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingAutoConfiguration.java @@ -18,16 +18,12 @@ package org.springframework.ai.model.mistralai.autoconfigure; import io.micrometer.observation.ObservationRegistry; -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.mistralai.MistralAiChatModel; import org.springframework.ai.mistralai.MistralAiEmbeddingModel; import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.tool.ToolCallingManager; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; @@ -36,7 +32,6 @@ 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.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; @@ -45,26 +40,24 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; /** - * {@link AutoConfiguration Auto-configuration} for Mistral AI. + * Embedding {@link AutoConfiguration Auto-configuration} for Mistral AI. * * @author Ricken Bazolo * @author Christian Tzolov * @author Thomas Vitale + * @author Ilayaperumal Gopinathan * @since 0.8.1 */ -@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, - ToolCallingAutoConfiguration.class }) -@EnableConfigurationProperties({ MistralAiEmbeddingProperties.class, MistralAiCommonProperties.class, - MistralAiChatProperties.class }) +@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) +@EnableConfigurationProperties({ MistralAiCommonProperties.class, MistralAiEmbeddingProperties.class }) @ConditionalOnClass(MistralAiApi.class) -@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - ToolCallingAutoConfiguration.class }) -public class MistralAiAutoConfiguration { +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MISTRAL, + matchIfMissing = true) +@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class }) +public class MistralAiEmbeddingAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = MistralAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiCommonProperties commonProperties, MistralAiEmbeddingProperties embeddingProperties, ObjectProvider restClientBuilderProvider, RetryTemplate retryTemplate, @@ -84,33 +77,6 @@ public class MistralAiAutoConfiguration { return embeddingModel; } - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = MistralAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public MistralAiChatModel mistralAiChatModel(MistralAiCommonProperties commonProperties, - MistralAiChatProperties chatProperties, ObjectProvider restClientBuilderProvider, - ToolCallingManager toolCallingManager, RetryTemplate retryTemplate, - ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var mistralAiApi = mistralAiApi(chatProperties.getApiKey(), commonProperties.getApiKey(), - chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - - var chatModel = MistralAiChatModel.builder() - .mistralAiApi(mistralAiApi) - .defaultOptions(chatProperties.getOptions()) - .toolCallingManager(toolCallingManager) - .retryTemplate(retryTemplate) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .build(); - - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - private MistralAiApi mistralAiApi(String apiKey, String commonApiKey, String baseUrl, String commonBaseUrl, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { @@ -123,12 +89,4 @@ public class MistralAiAutoConfiguration { return new MistralAiApi(resoledBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler); } - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 6436596f9..167a445dc 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration +org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration +org.springframework.ai.model.mistralai.autoconfigure.MistralAiEmbeddingAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfigurationIT.java index b7bb85226..4759d322d 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfigurationIT.java @@ -38,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Christian Tzolov + * @author Ilayaperumal Gopinathan * @since 0.8.1 */ @EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".*") @@ -46,50 +47,52 @@ public class MistralAiAutoConfigurationIT { private static final Log logger = LogFactory.getLog(MistralAiAutoConfigurationIT.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class)); + .withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY")); @Test void generate() { - this.contextRunner.run(context -> { - MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class)) + .run(context -> { + MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); + String response = chatModel.call("Hello"); + assertThat(response).isNotEmpty(); + logger.info("Response: " + response); + }); } @Test void generateStreaming() { - this.contextRunner.run(context -> { - MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - String response = responseFlux.collectList() - .block() - .stream() - .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) - .collect(Collectors.joining()); + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class)) + .run(context -> { + MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); + Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); + String response = responseFlux.collectList() + .block() + .stream() + .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) + .collect(Collectors.joining()); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); + assertThat(response).isNotEmpty(); + logger.info("Response: " + response); + }); } @Test void embedding() { - this.contextRunner.run(context -> { - MistralAiEmbeddingModel embeddingModel = context.getBean(MistralAiEmbeddingModel.class); + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiEmbeddingAutoConfiguration.class)) + .run(context -> { + MistralAiEmbeddingModel embeddingModel = context.getBean(MistralAiEmbeddingModel.class); - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); + EmbeddingResponse embeddingResponse = embeddingModel + .embedForResponse(List.of("Hello World", "World is big and salvation is near")); + assertThat(embeddingResponse.getResults()).hasSize(2); + assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); + assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - assertThat(embeddingModel.dimensions()).isEqualTo(1024); - }); + assertThat(embeddingModel.dimensions()).isEqualTo(1024); + }); } } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiPropertiesTests.java index dd4eae9ab..eb444a7e9 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiPropertiesTests.java @@ -38,7 +38,7 @@ public class MistralAiPropertiesTests { .withPropertyValues("spring.ai.mistralai.base-url=TEST_BASE_URL", "spring.ai.mistralai.api-key=abc123", "spring.ai.mistralai.embedding.options.model=MODEL_XYZ") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, MistralAiEmbeddingAutoConfiguration.class)) .run(context -> { var embeddingProperties = context.getBean(MistralAiEmbeddingProperties.class); var connectionProperties = context.getBean(MistralAiCommonProperties.class); @@ -87,7 +87,7 @@ public class MistralAiPropertiesTests { "spring.ai.mistralai.api-key=abc123", "spring.ai.mistralai.embedding.base-url=TEST_BASE_URL2", "spring.ai.mistralai.embedding.api-key=456", "spring.ai.mistralai.embedding.options.model=MODEL_XYZ") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, MistralAiChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(MistralAiChatProperties.class); @@ -108,7 +108,7 @@ public class MistralAiPropertiesTests { "spring.ai.mistralai.api-key=abc123", "spring.ai.mistralai.embedding.base-url=TEST_BASE_URL2", "spring.ai.mistralai.embedding.api-key=456", "spring.ai.mistralai.embedding.options.model=MODEL_XYZ") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, MistralAiEmbeddingAutoConfiguration.class)) .run(context -> { var embeddingProperties = context.getBean(MistralAiEmbeddingProperties.class); var connectionProperties = context.getBean(MistralAiCommonProperties.class); @@ -132,7 +132,7 @@ public class MistralAiPropertiesTests { "spring.ai.mistralai.embedding.options.model=MODEL_XYZ", "spring.ai.mistralai.embedding.options.encodingFormat=MyEncodingFormat") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, MistralAiEmbeddingAutoConfiguration.class)) .run(context -> { var connectionProperties = context.getBean(MistralAiCommonProperties.class); var embeddingProperties = context.getBean(MistralAiEmbeddingProperties.class); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralModelConfigurationTests.java index fb4d5013d..0f01a27d1 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralModelConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralModelConfigurationTests.java @@ -26,57 +26,64 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * Unit Tests for {@link MistralAiAutoConfiguration}'s conditional enabling of models. + * Unit Tests for Mistral AI auto-configurations conditional enabling of models. * * @author Ilayaperumal Gopinathan */ public class MistralModelConfigurationTests { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class)) .withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY")); @Test void chatModelActivation() { - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MistralAiChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty(); - }); - - this.contextRunner.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none") - .run(context -> { - assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MistralAiChatModel.class)).isEmpty(); - assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty(); - }); - - this.contextRunner.withPropertyValues("spring.ai.model.chat=mistral", "spring.ai.model.embedding=none") + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(MistralAiChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty(); + }); + + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none") + .run(context -> { + assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiChatModel.class)).isEmpty(); + }); + + this.contextRunner + .withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class, + MistralAiEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=mistral", "spring.ai.model.embedding=none") + .run(context -> { + assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MistralAiChatModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty(); }); } @Test void embeddingModelActivation() { - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiEmbeddingAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> { - assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=none") + .run(context -> { + assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.embedding=mistral").run(context -> { - assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=mistral") + .run(context -> { + assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty(); + }); } } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanIT.java index bd7ed874a..bacc8182c 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanIT.java @@ -26,13 +26,13 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.mistralai.MistralAiChatModel; import org.springframework.ai.mistralai.MistralAiChatOptions; import org.springframework.ai.mistralai.api.MistralAiApi; +import org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -53,7 +53,7 @@ class PaymentStatusBeanIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusPromptIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusPromptIT.java index 01a278a3d..c524dcb85 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusPromptIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusPromptIT.java @@ -25,13 +25,13 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.mistralai.MistralAiChatModel; import org.springframework.ai.mistralai.MistralAiChatOptions; import org.springframework.ai.mistralai.api.MistralAiApi; +import org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -51,7 +51,7 @@ public class PaymentStatusPromptIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class)); @Test void functionCallTest() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/WeatherServicePromptIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/WeatherServicePromptIT.java index 758ab779f..45d40d981 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/WeatherServicePromptIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/WeatherServicePromptIT.java @@ -27,9 +27,6 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration; -import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Request; -import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Response; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; @@ -37,6 +34,9 @@ import org.springframework.ai.mistralai.MistralAiChatModel; import org.springframework.ai.mistralai.MistralAiChatOptions; import org.springframework.ai.mistralai.api.MistralAiApi; import org.springframework.ai.mistralai.api.MistralAiApi.ChatCompletionRequest.ToolChoice; +import org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration; +import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Request; +import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Response; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.tool.function.FunctionToolCallback; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -56,7 +56,7 @@ public class WeatherServicePromptIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.mistralai.api-key=" + System.getenv("MISTRAL_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class)); @Test void promptFunctionCall() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/pom.xml index 10b41e644..08a9e3d0b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/pom.xml @@ -39,21 +39,18 @@ org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotChatAutoConfiguration.java similarity index 93% rename from auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotChatAutoConfiguration.java index d1d836c61..ec7b66317 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotChatAutoConfiguration.java @@ -48,19 +48,17 @@ import org.springframework.web.client.RestClient; * {@link AutoConfiguration Auto-configuration} for Moonshot Chat Model. * * @author Geng Rong + * @author Ilayaperumal Gopinathan */ @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) @EnableConfigurationProperties({ MoonshotCommonProperties.class, MoonshotChatProperties.class }) @ConditionalOnClass(MoonshotApi.class) -public class MoonshotAutoConfiguration { +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MOONSHOT, + matchIfMissing = true) +public class MoonshotChatAutoConfiguration { @Bean @ConditionalOnMissingBean - // @ConditionalOnProperty(prefix = MoonshotChatProperties.CONFIG_PREFIX, name = - // "enabled", havingValue = "true", - // matchIfMissing = true) - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MOONSHOT, - matchIfMissing = true) public MoonshotChatModel moonshotChatModel(MoonshotCommonProperties commonProperties, MoonshotChatProperties chatProperties, ObjectProvider restClientBuilderProvider, List toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver, diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index aa04b8eda..9fa8089d9 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration +org.springframework.ai.model.moonshot.autoconfigure.MoonshotChatAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfigurationIT.java deleted file mode 100644 index 24f2fced3..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfigurationIT.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.moonshot.autoconfigure; - -import java.util.Objects; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.moonshot.MoonshotChatModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariable(named = "MOONSHOT_API_KEY", matches = ".*") -public class MoonshotAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(MoonshotAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)); - - @Test - void generate() { - this.contextRunner.run(context -> { - MoonshotChatModel client = context.getBean(MoonshotChatModel.class); - String response = client.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void generateStreaming() { - this.contextRunner.run(context -> { - MoonshotChatModel client = context.getBean(MoonshotChatModel.class); - Flux responseFlux = client.stream(new Prompt(new UserMessage("Hello"))); - String response = Objects.requireNonNull(responseFlux.collectList().block()) - .stream() - .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) - .collect(Collectors.joining()); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotChatAutoConfigurationIT.java similarity index 92% rename from spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfigurationIT.java rename to auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotChatAutoConfigurationIT.java index 24f2fced3..33f29afba 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotChatAutoConfigurationIT.java @@ -40,14 +40,14 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Geng Rong */ @EnabledIfEnvironmentVariable(named = "MOONSHOT_API_KEY", matches = ".*") -public class MoonshotAutoConfigurationIT { +public class MoonshotChatAutoConfigurationIT { - private static final Log logger = LogFactory.getLog(MoonshotAutoConfigurationIT.class); + private static final Log logger = LogFactory.getLog(MoonshotChatAutoConfigurationIT.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)); + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)); @Test void generate() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java index 119b0fbfe..bec989217 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java @@ -42,7 +42,7 @@ public class MoonshotPropertiesTests { "spring.ai.moonshot.chat.options.temperature=0.55") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(MoonshotChatProperties.class); var connectionProperties = context.getBean(MoonshotCommonProperties.class); @@ -71,7 +71,7 @@ public class MoonshotPropertiesTests { "spring.ai.moonshot.chat.options.temperature=0.55") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(MoonshotChatProperties.class); var connectionProperties = context.getBean(MoonshotCommonProperties.class); @@ -110,7 +110,7 @@ public class MoonshotPropertiesTests { ) // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(MoonshotChatProperties.class); var connectionProperties = context.getBean(MoonshotCommonProperties.class); @@ -137,16 +137,16 @@ public class MoonshotPropertiesTests { .withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL", "spring.ai.model.chat=none") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(MoonshotChatModel.class)).isEmpty(); }); new ApplicationContextRunner() .withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(MoonshotChatModel.class)).isNotEmpty(); @@ -156,7 +156,7 @@ public class MoonshotPropertiesTests { .withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL", "spring.ai.model.chat=moonshot") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(MoonshotChatModel.class)).isNotEmpty(); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackInPromptIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackInPromptIT.java index 0dc2271f3..44960f9c5 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackInPromptIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackInPromptIT.java @@ -25,7 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration; +import org.springframework.ai.model.moonshot.autoconfigure.MoonshotChatAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; @@ -53,7 +53,7 @@ public class FunctionCallbackInPromptIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)); + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)); @Test void functionCallTest() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java index 7ab0093de..a5f0e1e30 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java @@ -26,7 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration; +import org.springframework.ai.model.moonshot.autoconfigure.MoonshotChatAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; @@ -57,7 +57,7 @@ class FunctionCallbackWithPlainFunctionBeanIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MoonshotFunctionCallbackIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MoonshotFunctionCallbackIT.java index 86a85a9e1..c769d5155 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MoonshotFunctionCallbackIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MoonshotFunctionCallbackIT.java @@ -26,7 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration; +import org.springframework.ai.model.moonshot.autoconfigure.MoonshotChatAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; @@ -56,7 +56,7 @@ public class MoonshotFunctionCallbackIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) + RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml index 4bd179f43..559b83920 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml @@ -46,14 +46,12 @@ org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java deleted file mode 100644 index 777c89718..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.oci.genai.autoconfigure; - -import java.io.IOException; - -import com.oracle.bmc.ClientConfiguration; -import com.oracle.bmc.Region; -import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider; -import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider; -import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider; -import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider; -import com.oracle.bmc.auth.SimplePrivateKeySupplier; -import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider; -import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient; -import com.oracle.bmc.retrier.RetryConfiguration; -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.model.SpringAIModelProperties; -import org.springframework.ai.model.SpringAIModels; -import org.springframework.ai.oci.OCIEmbeddingModel; -import org.springframework.ai.oci.cohere.OCICohereChatModel; -import org.springframework.beans.factory.ObjectProvider; -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.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.util.StringUtils; - -/** - * {@link AutoConfiguration Auto-configuration} for Oracle Cloud Infrastructure Generative - * AI. - * - * @author Anders Swanson - */ -@AutoConfiguration -@ConditionalOnClass({ GenerativeAiInferenceClient.class, OCIEmbeddingModel.class }) -@EnableConfigurationProperties({ OCIConnectionProperties.class, OCIEmbeddingModelProperties.class, - OCICohereChatModelProperties.class }) -public class OCIGenAiAutoConfiguration { - - private static BasicAuthenticationDetailsProvider authenticationProvider(OCIConnectionProperties properties) - throws IOException { - return switch (properties.getAuthenticationType()) { - case FILE -> new ConfigFileAuthenticationDetailsProvider(properties.getFile(), properties.getProfile()); - case INSTANCE_PRINCIPAL -> InstancePrincipalsAuthenticationDetailsProvider.builder().build(); - case WORKLOAD_IDENTITY -> OkeWorkloadIdentityAuthenticationDetailsProvider.builder().build(); - case SIMPLE -> SimpleAuthenticationDetailsProvider.builder() - .userId(properties.getUserId()) - .tenantId(properties.getTenantId()) - .fingerprint(properties.getFingerprint()) - .privateKeySupplier(new SimplePrivateKeySupplier(properties.getPrivateKey())) - .passPhrase(properties.getPassPhrase()) - .region(Region.valueOf(properties.getRegion())) - .build(); - }; - } - - @ConditionalOnMissingBean - @Bean - public GenerativeAiInferenceClient generativeAiInferenceClient(OCIConnectionProperties properties) - throws IOException { - ClientConfiguration clientConfiguration = ClientConfiguration.builder() - .retryConfiguration(RetryConfiguration.SDK_DEFAULT_RETRY_CONFIGURATION) - .build(); - GenerativeAiInferenceClient.Builder builder = GenerativeAiInferenceClient.builder() - .configuration(clientConfiguration); - if (StringUtils.hasText(properties.getRegion())) { - builder.region(Region.valueOf(properties.getRegion())); - } - if (StringUtils.hasText(properties.getEndpoint())) { - builder.endpoint(properties.getEndpoint()); - } - return builder.build(authenticationProvider(properties)); - } - - @Bean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OCI_GENAI, - matchIfMissing = true) - public OCIEmbeddingModel ociEmbeddingModel(GenerativeAiInferenceClient generativeAiClient, - OCIEmbeddingModelProperties properties) { - return new OCIEmbeddingModel(generativeAiClient, properties.getEmbeddingOptions()); - } - - @Bean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OCI_GENAI, - matchIfMissing = true) - public OCICohereChatModel ociChatModel(GenerativeAiInferenceClient generativeAiClient, - OCICohereChatModelProperties properties, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - var chatModel = new OCICohereChatModel(generativeAiClient, properties.getOptions(), - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - -} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiChatAutoConfiguration.java new file mode 100644 index 000000000..fe2024ce3 --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiChatAutoConfiguration.java @@ -0,0 +1,62 @@ +/* + * 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.springframework.ai.model.oci.genai.autoconfigure; + +import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient; +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.ai.chat.observation.ChatModelObservationConvention; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.ai.oci.cohere.OCICohereChatModel; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +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.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Chat {@link AutoConfiguration Auto-configuration} for Oracle Cloud Infrastructure + * Generative AI. + * + * @author Anders Swanson + * @author Ilayaperumal Gopinathan + */ +@AutoConfiguration +@ConditionalOnClass(OCICohereChatModel.class) +@EnableConfigurationProperties(OCICohereChatModelProperties.class) +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OCI_GENAI, + matchIfMissing = true) +@ImportAutoConfiguration(OCIGenAiInferenceClientAutoConfiguration.class) +public class OCIGenAiChatAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public OCICohereChatModel ociChatModel(GenerativeAiInferenceClient generativeAiClient, + OCICohereChatModelProperties properties, ObjectProvider observationRegistry, + ObjectProvider observationConvention) { + var chatModel = new OCICohereChatModel(generativeAiClient, properties.getOptions(), + observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); + observationConvention.ifAvailable(chatModel::setObservationConvention); + + return chatModel; + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiEmbeddingAutoConfiguration.java new file mode 100644 index 000000000..7c9ded2c1 --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiEmbeddingAutoConfiguration.java @@ -0,0 +1,54 @@ +/* + * 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.springframework.ai.model.oci.genai.autoconfigure; + +import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient; + +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.ai.oci.OCIEmbeddingModel; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +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.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Embedding {@link AutoConfiguration Auto-configuration} for Oracle Cloud Infrastructure + * Generative AI. + * + * @author Anders Swanson + * @author Ilayaperumal Gopinathan + */ +@AutoConfiguration +@ConditionalOnClass(OCIEmbeddingModel.class) +@EnableConfigurationProperties(OCIEmbeddingModelProperties.class) +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OCI_GENAI, + matchIfMissing = true) +@ImportAutoConfiguration(OCIGenAiInferenceClientAutoConfiguration.class) +public class OCIGenAiEmbeddingAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public OCIEmbeddingModel ociEmbeddingModel(GenerativeAiInferenceClient generativeAiClient, + OCIEmbeddingModelProperties properties) { + return new OCIEmbeddingModel(generativeAiClient, properties.getEmbeddingOptions()); + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiInferenceClientAutoConfiguration.java similarity index 66% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiInferenceClientAutoConfiguration.java index 84d872137..9188756f5 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiInferenceClientAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * 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. @@ -28,31 +28,43 @@ import com.oracle.bmc.auth.SimplePrivateKeySupplier; import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider; import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient; import com.oracle.bmc.retrier.RetryConfiguration; -import io.micrometer.observation.ObservationRegistry; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.oci.OCIEmbeddingModel; -import org.springframework.ai.oci.cohere.OCICohereChatModel; -import org.springframework.beans.factory.ObjectProvider; 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.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.util.StringUtils; /** * {@link AutoConfiguration Auto-configuration} for Oracle Cloud Infrastructure Generative - * AI. + * AI Inference Client. * * @author Anders Swanson + * @author Ilayaperumal Gopinathan */ @AutoConfiguration -@ConditionalOnClass({ GenerativeAiInferenceClient.class, OCIEmbeddingModel.class }) -@EnableConfigurationProperties({ OCIConnectionProperties.class, OCIEmbeddingModelProperties.class, - OCICohereChatModelProperties.class }) -public class OCIGenAiAutoConfiguration { +@ConditionalOnClass(GenerativeAiInferenceClient.class) +@EnableConfigurationProperties(OCIConnectionProperties.class) +public class OCIGenAiInferenceClientAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public GenerativeAiInferenceClient generativeAiInferenceClient(OCIConnectionProperties properties) + throws IOException { + ClientConfiguration clientConfiguration = ClientConfiguration.builder() + .retryConfiguration(RetryConfiguration.SDK_DEFAULT_RETRY_CONFIGURATION) + .build(); + GenerativeAiInferenceClient.Builder builder = GenerativeAiInferenceClient.builder() + .configuration(clientConfiguration); + if (StringUtils.hasText(properties.getRegion())) { + builder.region(Region.valueOf(properties.getRegion())); + } + if (StringUtils.hasText(properties.getEndpoint())) { + builder.endpoint(properties.getEndpoint()); + } + return builder.build(authenticationProvider(properties)); + } private static BasicAuthenticationDetailsProvider authenticationProvider(OCIConnectionProperties properties) throws IOException { @@ -71,43 +83,4 @@ public class OCIGenAiAutoConfiguration { }; } - @ConditionalOnMissingBean - @Bean - public GenerativeAiInferenceClient generativeAiInferenceClient(OCIConnectionProperties properties) - throws IOException { - ClientConfiguration clientConfiguration = ClientConfiguration.builder() - .retryConfiguration(RetryConfiguration.SDK_DEFAULT_RETRY_CONFIGURATION) - .build(); - GenerativeAiInferenceClient.Builder builder = GenerativeAiInferenceClient.builder() - .configuration(clientConfiguration); - if (StringUtils.hasText(properties.getRegion())) { - builder.region(Region.valueOf(properties.getRegion())); - } - if (StringUtils.hasText(properties.getEndpoint())) { - builder.endpoint(properties.getEndpoint()); - } - return builder.build(authenticationProvider(properties)); - } - - @Bean - @ConditionalOnProperty(prefix = OCIEmbeddingModelProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public OCIEmbeddingModel ociEmbeddingModel(GenerativeAiInferenceClient generativeAiClient, - OCIEmbeddingModelProperties properties) { - return new OCIEmbeddingModel(generativeAiClient, properties.getEmbeddingOptions()); - } - - @Bean - @ConditionalOnProperty(prefix = OCICohereChatModelProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public OCICohereChatModel ociChatModel(GenerativeAiInferenceClient generativeAiClient, - OCICohereChatModelProperties properties, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - var chatModel = new OCICohereChatModel(generativeAiClient, properties.getOptions(), - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index cbd6babd2..6f5a674d6 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.oci.genai.autoconfigure.OCIGenAiAutoConfiguration +org.springframework.ai.model.oci.genai.autoconfigure.OCIGenAiChatAutoConfiguration +org.springframework.ai.model.oci.genai.autoconfigure.OCIGenAiEmbeddingAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAIAutoConfigurationTest.java b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAIAutoConfigurationTest.java index b8935152e..28c18307e 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAIAutoConfigurationTest.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAIAutoConfigurationTest.java @@ -56,7 +56,7 @@ class OCIGenAIAutoConfigurationTest { "spring.ai.oci.genai.cohere.chat.options.frequencyPenalty=0.1", "spring.ai.oci.genai.cohere.chat.options.presencePenalty=0.2" // @formatter:on - ).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class)); + ).withConfiguration(AutoConfigurations.of(OCIGenAiChatAutoConfiguration.class)); contextRunner.run(context -> { OCICohereChatModel chatModel = context.getBean(OCICohereChatModel.class); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfigurationIT.java index 610ef9c97..53db7bfb9 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfigurationIT.java @@ -52,7 +52,7 @@ public class OCIGenAiAutoConfigurationIT { "spring.ai.oci.genai.embedding.servingMode=on-demand", "spring.ai.oci.genai.embedding.model=cohere.embed-english-light-v2.0" // @formatter:on - ).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class)); + ).withConfiguration(AutoConfigurations.of(OCIGenAiEmbeddingAutoConfiguration.class)); private final ApplicationContextRunner cohereChatContextRunner = new ApplicationContextRunner().withPropertyValues( // @formatter:off @@ -62,7 +62,7 @@ public class OCIGenAiAutoConfigurationIT { "spring.ai.oci.genai.cohere.chat.options.servingMode=on-demand", "spring.ai.oci.genai.cohere.chat.options.model=" + this.CHAT_MODEL_ID // @formatter:on - ).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class)); + ).withConfiguration(AutoConfigurations.of(OCIGenAiChatAutoConfiguration.class)); @Test void embeddings() { 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 cfece9d07..522c75d8b 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml @@ -38,28 +38,18 @@ org.springframework.ai spring-ai-autoconfigure-model-tool ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-autoconfigure-model-image-observation - ${project.parent.version} - true 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 new file mode 100644 index 000000000..cfbd820e8 --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaApiAutoConfiguration.java @@ -0,0 +1,74 @@ +/* + * 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.springframework.ai.model.ollama.autoconfigure; + +import org.springframework.ai.ollama.api.OllamaApi; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * {@link AutoConfiguration Auto-configuration} for Ollama API. + * + * @author Christian Tzolov + * @author Eddú Meléndez + * @author Thomas Vitale + * @author Ilayaperumal Gopinathan + * @since 0.8.0 + */ +@AutoConfiguration +@ConditionalOnClass(OllamaApi.class) +@EnableConfigurationProperties(OllamaConnectionProperties.class) +public class OllamaApiAutoConfiguration { + + @Bean + @ConditionalOnMissingBean(OllamaConnectionDetails.class) + public PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectionProperties properties) { + return new PropertiesOllamaConnectionDetails(properties); + } + + @Bean + @ConditionalOnMissingBean + public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails, + ObjectProvider restClientBuilderProvider, + ObjectProvider webClientBuilderProvider) { + return new OllamaApi(connectionDetails.getBaseUrl(), + restClientBuilderProvider.getIfAvailable(RestClient::builder), + webClientBuilderProvider.getIfAvailable(WebClient::builder)); + } + + static class PropertiesOllamaConnectionDetails implements OllamaConnectionDetails { + + private final OllamaConnectionProperties properties; + + PropertiesOllamaConnectionDetails(OllamaConnectionProperties properties) { + this.properties = properties; + } + + @Override + public String getBaseUrl() { + return this.properties.getBaseUrl(); + } + + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java deleted file mode 100644 index 0a0af3125..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * 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.springframework.ai.model.ollama.autoconfigure; - -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.model.SpringAIModelProperties; -import org.springframework.ai.model.SpringAIModels; -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.ollama.OllamaChatModel; -import org.springframework.ai.ollama.OllamaEmbeddingModel; -import org.springframework.ai.ollama.api.OllamaApi; -import org.springframework.ai.ollama.management.ModelManagementOptions; -import org.springframework.ai.ollama.management.PullModelStrategy; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -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.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestClient; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * {@link AutoConfiguration Auto-configuration} for Ollama Chat Client. - * - * @author Christian Tzolov - * @author Eddú Meléndez - * @author Thomas Vitale - * @since 0.8.0 - */ -@AutoConfiguration(after = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -@ConditionalOnClass(OllamaApi.class) -@EnableConfigurationProperties({ OllamaChatProperties.class, OllamaEmbeddingProperties.class, - OllamaConnectionProperties.class, OllamaInitializationProperties.class }) -@ImportAutoConfiguration(classes = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class, - WebClientAutoConfiguration.class }) -public class OllamaAutoConfiguration { - - @Bean - @ConditionalOnMissingBean(OllamaConnectionDetails.class) - public PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectionProperties properties) { - return new PropertiesOllamaConnectionDetails(properties); - } - - @Bean - @ConditionalOnMissingBean - public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails, - ObjectProvider restClientBuilderProvider, - ObjectProvider webClientBuilderProvider) { - return new OllamaApi(connectionDetails.getBaseUrl(), - restClientBuilderProvider.getIfAvailable(RestClient::builder), - webClientBuilderProvider.getIfAvailable(WebClient::builder)); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OLLAMA, - matchIfMissing = true) - public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties properties, - OllamaInitializationProperties initProperties, ToolCallingManager toolCallingManager, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - var chatModelPullStrategy = initProperties.getChat().isInclude() ? initProperties.getPullModelStrategy() - : PullModelStrategy.NEVER; - - var chatModel = OllamaChatModel.builder() - .ollamaApi(ollamaApi) - .defaultOptions(properties.getOptions()) - .toolCallingManager(toolCallingManager) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .modelManagementOptions( - new ModelManagementOptions(chatModelPullStrategy, initProperties.getChat().getAdditionalModels(), - initProperties.getTimeout(), initProperties.getMaxRetries())) - .build(); - - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OLLAMA, - matchIfMissing = true) - public OllamaEmbeddingModel ollamaEmbeddingModel(OllamaApi ollamaApi, OllamaEmbeddingProperties properties, - OllamaInitializationProperties initProperties, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - var embeddingModelPullStrategy = initProperties.getEmbedding().isInclude() - ? initProperties.getPullModelStrategy() : PullModelStrategy.NEVER; - - var embeddingModel = OllamaEmbeddingModel.builder() - .ollamaApi(ollamaApi) - .defaultOptions(properties.getOptions()) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .modelManagementOptions(new ModelManagementOptions(embeddingModelPullStrategy, - initProperties.getEmbedding().getAdditionalModels(), initProperties.getTimeout(), - initProperties.getMaxRetries())) - .build(); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - } - - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - - static class PropertiesOllamaConnectionDetails implements OllamaConnectionDetails { - - private final OllamaConnectionProperties properties; - - PropertiesOllamaConnectionDetails(OllamaConnectionProperties properties) { - this.properties = properties; - } - - @Override - public String getBaseUrl() { - return this.properties.getBaseUrl(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java similarity index 57% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java index 4c422e37a..6df931d11 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfiguration.java @@ -14,18 +14,21 @@ * limitations under the License. */ -package org.springframework.ai.model.anthropic.autoconfigure; +package org.springframework.ai.model.ollama.autoconfigure; import io.micrometer.observation.ObservationRegistry; -import org.springframework.ai.anthropic.AnthropicChatModel; -import org.springframework.ai.anthropic.api.AnthropicApi; -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.observation.ChatModelObservationConvention; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.ai.model.tool.ToolCallingManager; +import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; +import org.springframework.ai.ollama.OllamaChatModel; +import org.springframework.ai.ollama.api.OllamaApi; +import org.springframework.ai.ollama.management.ModelManagementOptions; +import org.springframework.ai.ollama.management.PullModelStrategy; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; @@ -37,53 +40,42 @@ import org.springframework.boot.autoconfigure.web.reactive.function.client.WebCl 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.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestClient; -import org.springframework.web.reactive.function.client.WebClient; /** - * {@link AutoConfiguration Auto-configuration} for Anthropic Chat Model. + * {@link AutoConfiguration Auto-configuration} for Ollama Chat model. * * @author Christian Tzolov + * @author Eddú Meléndez * @author Thomas Vitale - * @since 1.0.0 + * @author Ilayaperumal Gopinathan + * @since 0.8.0 */ -@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, - ToolCallingAutoConfiguration.class }) -@EnableConfigurationProperties({ AnthropicChatProperties.class, AnthropicConnectionProperties.class }) -@ConditionalOnClass(AnthropicApi.class) -@ConditionalOnProperty(prefix = AnthropicChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", +@AutoConfiguration(after = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class }) +@ConditionalOnClass(OllamaChatModel.class) +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OLLAMA, matchIfMissing = true) -@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, +@EnableConfigurationProperties({ OllamaChatProperties.class, OllamaInitializationProperties.class }) +@ImportAutoConfiguration(classes = { OllamaApiAutoConfiguration.class, RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class, WebClientAutoConfiguration.class }) -public class AnthropicAutoConfiguration { +public class OllamaChatAutoConfiguration { @Bean @ConditionalOnMissingBean - public AnthropicApi anthropicApi(AnthropicConnectionProperties connectionProperties, - ObjectProvider restClientBuilderProvider, - ObjectProvider webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) { - - return new AnthropicApi(connectionProperties.getBaseUrl(), connectionProperties.getApiKey(), - connectionProperties.getVersion(), restClientBuilderProvider.getIfAvailable(RestClient::builder), - webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, - connectionProperties.getBetaVersion()); - } - - @Bean - @ConditionalOnMissingBean - public AnthropicChatModel anthropicChatModel(AnthropicApi anthropicApi, AnthropicChatProperties chatProperties, - RetryTemplate retryTemplate, ToolCallingManager toolCallingManager, + public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties properties, + OllamaInitializationProperties initProperties, ToolCallingManager toolCallingManager, ObjectProvider observationRegistry, ObjectProvider observationConvention) { + var chatModelPullStrategy = initProperties.getChat().isInclude() ? initProperties.getPullModelStrategy() + : PullModelStrategy.NEVER; - var chatModel = AnthropicChatModel.builder() - .anthropicApi(anthropicApi) - .defaultOptions(chatProperties.getOptions()) + var chatModel = OllamaChatModel.builder() + .ollamaApi(ollamaApi) + .defaultOptions(properties.getOptions()) .toolCallingManager(toolCallingManager) - .retryTemplate(retryTemplate) .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) + .modelManagementOptions( + new ModelManagementOptions(chatModelPullStrategy, initProperties.getChat().getAdditionalModels(), + initProperties.getTimeout(), initProperties.getMaxRetries())) .build(); observationConvention.ifAvailable(chatModel::setObservationConvention); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfiguration.java new file mode 100644 index 000000000..0d0bb0964 --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfiguration.java @@ -0,0 +1,79 @@ +/* + * 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.springframework.ai.model.ollama.autoconfigure; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.ai.ollama.OllamaEmbeddingModel; +import org.springframework.ai.ollama.api.OllamaApi; +import org.springframework.ai.ollama.management.ModelManagementOptions; +import org.springframework.ai.ollama.management.PullModelStrategy; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +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.context.annotation.Bean; + +/** + * {@link AutoConfiguration Auto-configuration} for Ollama Chat Client. + * + * @author Christian Tzolov + * @author Eddú Meléndez + * @author Thomas Vitale + * @author Ilayaperumal Gopinathan + * @since 0.8.0 + */ +@AutoConfiguration(after = RestClientAutoConfiguration.class) +@ConditionalOnClass(OllamaEmbeddingModel.class) +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OLLAMA, + matchIfMissing = true) +@EnableConfigurationProperties({ OllamaEmbeddingProperties.class, OllamaInitializationProperties.class }) +@ImportAutoConfiguration(classes = { OllamaApiAutoConfiguration.class, RestClientAutoConfiguration.class, + WebClientAutoConfiguration.class }) +public class OllamaEmbeddingAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public OllamaEmbeddingModel ollamaEmbeddingModel(OllamaApi ollamaApi, OllamaEmbeddingProperties properties, + OllamaInitializationProperties initProperties, ObjectProvider observationRegistry, + ObjectProvider observationConvention) { + var embeddingModelPullStrategy = initProperties.getEmbedding().isInclude() + ? initProperties.getPullModelStrategy() : PullModelStrategy.NEVER; + + var embeddingModel = OllamaEmbeddingModel.builder() + .ollamaApi(ollamaApi) + .defaultOptions(properties.getOptions()) + .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) + .modelManagementOptions(new ModelManagementOptions(embeddingModelPullStrategy, + initProperties.getEmbedding().getAdditionalModels(), initProperties.getTimeout(), + initProperties.getMaxRetries())) + .build(); + + observationConvention.ifAvailable(embeddingModel::setObservationConvention); + + return embeddingModel; + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 82d659ccf..39ea13b57 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration +org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration +org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java index 1623d3ed3..65e388a59 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java @@ -55,7 +55,7 @@ public class OllamaChatAutoConfigurationIT extends BaseOllamaIT { "spring.ai.ollama.chat.options.temperature=0.5", "spring.ai.ollama.chat.options.topK=10") // @formatter:on - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class)); private final UserMessage userMessage = new UserMessage("What's the capital of Denmark?"); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java index 077073ef9..de27c35c2 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java @@ -41,7 +41,8 @@ public class OllamaChatAutoConfigurationTests { "spring.ai.ollama.chat.options.topP=0.56", "spring.ai.ollama.chat.options.topK=123") // @formatter:on - .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(OllamaChatProperties.class); var connectionProperties = context.getBean(OllamaConnectionProperties.class); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationIT.java index 1a38c32ae..7bf54314f 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationIT.java @@ -45,7 +45,8 @@ public class OllamaEmbeddingAutoConfigurationIT extends BaseOllamaIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.ollama.embedding.options.model=" + MODEL_NAME, "spring.ai.ollama.base-url=" + getBaseUrl()) - .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaEmbeddingAutoConfiguration.class)); @BeforeAll public static void beforeAll() throws IOException, InterruptedException { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java index b29d88f8b..6f2443278 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java @@ -41,7 +41,8 @@ public class OllamaEmbeddingAutoConfigurationTests { "spring.ai.ollama.embedding.options.topK=13" // @formatter:on ) - .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaAutoConfiguration.class)) + .withConfiguration( + AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaEmbeddingAutoConfiguration.class)) .run(context -> { var embeddingProperties = context.getBean(OllamaEmbeddingProperties.class); var connectionProperties = context.getBean(OllamaConnectionProperties.class); 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 a02d8a02d..6555038b9 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 @@ -26,56 +26,59 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; /** - * Unit Tests for {@link OllamaAutoConfiguration}'s conditional enabling of models. + * Unit Tests for Ollama auto-configurations conditional enabling of models. * * @author Ilayaperumal Gopinathan */ public class OllamaModelConfigurationTests { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); @Test void chatModelActivation() { - this.contextRunner.run(context -> { + this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class)).run(context -> { assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(OllamaChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty(); }); - this.contextRunner.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none") + this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=none") .run(context -> { - assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OllamaChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(OllamaChatModel.class)).isEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isEmpty(); }); - this.contextRunner.withPropertyValues("spring.ai.model.chat=ollama", "spring.ai.model.embedding=none") + this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=ollama") .run(context -> { assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(OllamaChatModel.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isEmpty(); }); } @Test void embeddingModelActivation() { - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaEmbeddingAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> { - assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=none") + .run(context -> { + assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.model.embedding=ollama").run(context -> { - assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.embedding=ollama") + .run(context -> { + assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty(); + }); } } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java index 875a90742..76fcaf77f 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java @@ -25,13 +25,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT; -import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT; +import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.ai.ollama.api.OllamaOptions; import org.springframework.ai.tool.function.FunctionToolCallback; @@ -53,7 +53,7 @@ public class FunctionCallbackInPromptIT extends BaseOllamaIT { "spring.ai.ollama.chat.options.temperature=0.5", "spring.ai.ollama.chat.options.topK=10") // @formatter:on - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class)); @BeforeAll public static void beforeAll() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionCallbackIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionCallbackIT.java index 48d32380d..56f1db056 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionCallbackIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionCallbackIT.java @@ -25,13 +25,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT; -import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT; +import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.ai.ollama.api.OllamaOptions; @@ -57,7 +57,7 @@ public class OllamaFunctionCallbackIT extends BaseOllamaIT { "spring.ai.ollama.chat.options.temperature=0.5", "spring.ai.ollama.chat.options.topK=10") // @formatter:on - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @BeforeAll diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java index 947192863..faaf0ea5f 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java @@ -26,13 +26,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT; -import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT; +import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration; import org.springframework.ai.model.tool.ToolCallingChatOptions; import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.ai.ollama.api.OllamaOptions; @@ -64,7 +64,7 @@ public class OllamaFunctionToolBeanIT extends BaseOllamaIT { "spring.ai.ollama.chat.options.temperature=0.5", "spring.ai.ollama.chat.options.topK=10") // @formatter:on - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @BeforeAll diff --git a/spring-ai-spring-boot-autoconfigure/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackContextKotlinIT.kt b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackContextKotlinIT.kt similarity index 95% rename from spring-ai-spring-boot-autoconfigure/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackContextKotlinIT.kt rename to auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackContextKotlinIT.kt index 1a5564813..27182cbd9 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackContextKotlinIT.kt +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackContextKotlinIT.kt @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test import org.slf4j.LoggerFactory import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT -import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration +import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration import org.springframework.ai.chat.messages.UserMessage import org.springframework.ai.chat.prompt.Prompt import org.springframework.ai.model.function.FunctionCallingOptions @@ -56,7 +56,7 @@ class FunctionCallbackResolverKotlinIT : BaseOllamaIT() { "spring.ai.ollama.chat.options.temperature=0.5", "spring.ai.ollama.chat.options.topK=10" ) - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration::class.java)) + .withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration::class.java)) .withUserConfiguration(Config::class.java) @Test diff --git a/spring-ai-spring-boot-autoconfigure/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackKotlinIT.kt b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackKotlinIT.kt similarity index 95% rename from spring-ai-spring-boot-autoconfigure/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackKotlinIT.kt rename to auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackKotlinIT.kt index 0c46d0292..6976c18ab 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackKotlinIT.kt +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackKotlinIT.kt @@ -21,7 +21,7 @@ import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test import org.slf4j.LoggerFactory import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT -import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration +import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration import org.springframework.ai.chat.messages.UserMessage import org.springframework.ai.chat.prompt.Prompt import org.springframework.ai.model.tool.ToolCallingChatOptions @@ -55,7 +55,7 @@ class FunctionCallbackKotlinIT : BaseOllamaIT() { "spring.ai.ollama.chat.options.temperature=0.5", "spring.ai.ollama.chat.options.topK=10" ) - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration::class.java)) + .withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration::class.java)) .withUserConfiguration(Config::class.java) @Test diff --git a/spring-ai-spring-boot-autoconfigure/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/MockKotlinWeatherService.kt b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/MockKotlinWeatherService.kt similarity index 97% rename from spring-ai-spring-boot-autoconfigure/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/MockKotlinWeatherService.kt rename to auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/MockKotlinWeatherService.kt index 3bcdf606e..9a73e320f 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/MockKotlinWeatherService.kt +++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/kotlin/org/springframework/ai/model/ollama/autoconfigure/tool/MockKotlinWeatherService.kt @@ -19,7 +19,6 @@ package org.springframework.ai.model.ollama.autoconfigure.tool import com.fasterxml.jackson.annotation.JsonClassDescription import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonInclude.Include -import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonPropertyDescription class MockKotlinWeatherService : Function1 { 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 0dddf7793..e5346f439 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/pom.xml @@ -30,51 +30,38 @@ org.springframework.ai spring-ai-openai ${project.parent.version} - true - - org.springframework.ai - spring-ai-autoconfigure-model-chat-client - ${project.parent.version} - true - - org.springframework.ai spring-ai-autoconfigure-model-tool ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-observation-chat ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-observation-embedding ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-observation-image ${project.parent.version} - true @@ -106,6 +93,13 @@ test + + org.springframework.ai + spring-ai-autoconfigure-model-chat-client + ${project.parent.version} + test + + org.springframework.boot spring-boot-starter-test 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 c01dc0856..2b2f47975 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 @@ -19,7 +19,6 @@ package org.springframework.ai.model.openai.autoconfigure; import org.springframework.ai.model.SimpleApiKey; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.openai.OpenAiAudioTranscriptionModel; import org.springframework.ai.openai.api.OpenAiApi; import org.springframework.ai.openai.api.OpenAiAudioApi; 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 96e297267..ba5db2579 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 @@ -59,7 +59,7 @@ import org.springframework.web.reactive.function.client.WebClient; import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties; /** - * {@link AutoConfiguration Auto-configuration} for OpenAI. + * Chat {@link AutoConfiguration Auto-configuration} for OpenAI. * * @author Christian Tzolov * @author Stefan Vassilev 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 3367d0d38..75f092fbe 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 @@ -43,7 +43,7 @@ import org.springframework.web.reactive.function.client.WebClient; import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties; /** - * {@link AutoConfiguration Auto-configuration} for OpenAI. + * Embedding {@link AutoConfiguration Auto-configuration} for OpenAI. * * @author Christian Tzolov * @author Stefan Vassilev 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 4a9966072..a7eee06fd 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 @@ -43,7 +43,7 @@ import org.springframework.web.client.RestClient; import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties; /** - * {@link AutoConfiguration Auto-configuration} for OpenAI. + * Image {@link AutoConfiguration Auto-configuration} for OpenAI. * * @author Christian Tzolov * @author Stefan Vassilev 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 86b72a103..7e9f9adb2 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 @@ -16,27 +16,11 @@ package org.springframework.ai.model.openai.autoconfigure; -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.image.observation.ImageModelObservationConvention; import org.springframework.ai.model.SimpleApiKey; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.openai.OpenAiAudioSpeechModel; -import org.springframework.ai.openai.OpenAiAudioTranscriptionModel; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.OpenAiImageModel; import org.springframework.ai.openai.OpenAiModerationModel; import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.openai.api.OpenAiAudioApi; -import org.springframework.ai.openai.api.OpenAiImageApi; import org.springframework.ai.openai.api.OpenAiModerationApi; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.beans.factory.ObjectProvider; @@ -48,17 +32,15 @@ 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.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; -import org.springframework.web.reactive.function.client.WebClient; import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties; /** - * {@link AutoConfiguration Auto-configuration} for OpenAI. + * Moderation {@link AutoConfiguration Auto-configuration} for OpenAI. * * @author Christian Tzolov * @author Stefan Vassilev 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 7be055456..b6a992947 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 @@ -35,25 +35,16 @@ - - org.springframework.ai - spring-ai-autoconfigure-model-chat-client - ${project.parent.version} - true - - org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfiguration.java similarity index 89% rename from auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfiguration.java index d35ed9c23..6fcbb4f29 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * 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. @@ -36,13 +36,13 @@ import org.springframework.jdbc.core.JdbcTemplate; */ @AutoConfiguration(after = JdbcTemplateAutoConfiguration.class) @ConditionalOnClass(PostgresMlEmbeddingModel.class) +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.POSTGRESML, + matchIfMissing = true) @EnableConfigurationProperties(PostgresMlEmbeddingProperties.class) -public class PostgresMlAutoConfiguration { +public class PostgresMlEmbeddingAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.POSTGRESML, - matchIfMissing = true) public PostgresMlEmbeddingModel postgresMlEmbeddingModel(JdbcTemplate jdbcTemplate, PostgresMlEmbeddingProperties embeddingProperties) { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index b677fa1cd..e7e0cfd8f 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.postgresml.autoconfigure.PostgresMlAutoConfiguration +org.springframework.ai.model.postgresml.autoconfigure.PostgresMlEmbeddingAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfigurationIT.java deleted file mode 100644 index 678c9f75f..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfigurationIT.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.postgresml.autoconfigure; - -import java.util.List; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; - -import org.springframework.ai.embedding.EmbeddingResponse; -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.test.context.runner.ApplicationContextRunner; -import org.springframework.boot.testcontainers.service.connection.ServiceConnection; -import org.springframework.jdbc.core.JdbcTemplate; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Utkarsh Srivastava - */ -@JdbcTest(properties = "logging.level.sql=TRACE") -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Testcontainers -@Disabled("Disabled from automatic execution, as it requires an excessive amount of memory (over 9GB)!") -public class PostgresMlAutoConfigurationIT { - - @Container - @ServiceConnection - static PostgreSQLContainer postgres = new PostgreSQLContainer<>( - DockerImageName.parse("ghcr.io/postgresml/postgresml:2.8.1").asCompatibleSubstituteFor("postgres")) - .withCommand("sleep", "infinity") - .withUsername("postgresml") - .withPassword("postgresml") - .withDatabaseName("postgresml") - .waitingFor(Wait.forLogMessage(".*Starting dashboard.*\\s", 1)); - - @Autowired - JdbcTemplate jdbcTemplate; - - @Test - void embedding() { - ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withBean(JdbcTemplate.class, () -> this.jdbcTemplate) - .withConfiguration(AutoConfigurations.of(PostgresMlAutoConfiguration.class)); - contextRunner.run(context -> { - PostgresMlEmbeddingModel embeddingModel = context.getBean(PostgresMlEmbeddingModel.class); - - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isZero(); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - - assertThat(embeddingModel.dimensions()).isEqualTo(768); - }); - } - - @Test - void embeddingActivation() { - new ApplicationContextRunner().withBean(JdbcTemplate.class, () -> this.jdbcTemplate) - .withConfiguration(AutoConfigurations.of(PostgresMlAutoConfiguration.class)) - .withPropertyValues("spring.ai.postgresml.embedding.enabled=false") - .run(context -> { - assertThat(context.getBeansOfType(PostgresMlEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(PostgresMlEmbeddingModel.class)).isEmpty(); - }); - - new ApplicationContextRunner().withBean(JdbcTemplate.class, () -> this.jdbcTemplate) - .withConfiguration(AutoConfigurations.of(PostgresMlAutoConfiguration.class)) - .withPropertyValues("spring.ai.postgresml.embedding.enabled=true") - .run(context -> { - assertThat(context.getBeansOfType(PostgresMlEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(PostgresMlEmbeddingModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner().withBean(JdbcTemplate.class, () -> this.jdbcTemplate) - .withConfiguration(AutoConfigurations.of(PostgresMlAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(PostgresMlEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(PostgresMlEmbeddingModel.class)).isNotEmpty(); - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfigurationIT.java similarity index 91% rename from spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfigurationIT.java rename to auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfigurationIT.java index 678c9f75f..531d52a60 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingAutoConfigurationIT.java @@ -45,7 +45,7 @@ import static org.assertj.core.api.Assertions.assertThat; @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Testcontainers @Disabled("Disabled from automatic execution, as it requires an excessive amount of memory (over 9GB)!") -public class PostgresMlAutoConfigurationIT { +public class PostgresMlEmbeddingAutoConfigurationIT { @Container @ServiceConnection @@ -64,7 +64,7 @@ public class PostgresMlAutoConfigurationIT { void embedding() { ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withBean(JdbcTemplate.class, () -> this.jdbcTemplate) - .withConfiguration(AutoConfigurations.of(PostgresMlAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(PostgresMlEmbeddingAutoConfiguration.class)); contextRunner.run(context -> { PostgresMlEmbeddingModel embeddingModel = context.getBean(PostgresMlEmbeddingModel.class); @@ -83,7 +83,7 @@ public class PostgresMlAutoConfigurationIT { @Test void embeddingActivation() { new ApplicationContextRunner().withBean(JdbcTemplate.class, () -> this.jdbcTemplate) - .withConfiguration(AutoConfigurations.of(PostgresMlAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(PostgresMlEmbeddingAutoConfiguration.class)) .withPropertyValues("spring.ai.postgresml.embedding.enabled=false") .run(context -> { assertThat(context.getBeansOfType(PostgresMlEmbeddingProperties.class)).isNotEmpty(); @@ -91,7 +91,7 @@ public class PostgresMlAutoConfigurationIT { }); new ApplicationContextRunner().withBean(JdbcTemplate.class, () -> this.jdbcTemplate) - .withConfiguration(AutoConfigurations.of(PostgresMlAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(PostgresMlEmbeddingAutoConfiguration.class)) .withPropertyValues("spring.ai.postgresml.embedding.enabled=true") .run(context -> { assertThat(context.getBeansOfType(PostgresMlEmbeddingProperties.class)).isNotEmpty(); @@ -99,7 +99,7 @@ public class PostgresMlAutoConfigurationIT { }); new ApplicationContextRunner().withBean(JdbcTemplate.class, () -> this.jdbcTemplate) - .withConfiguration(AutoConfigurations.of(PostgresMlAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(PostgresMlEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(PostgresMlEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(PostgresMlEmbeddingModel.class)).isNotEmpty(); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/pom.xml index abb2d58da..9fb92ef3e 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/pom.xml @@ -39,21 +39,18 @@ org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanChatAutoConfiguration.java similarity index 54% rename from auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanChatAutoConfiguration.java index 98d456ef9..173828d46 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanChatAutoConfiguration.java @@ -18,19 +18,14 @@ package org.springframework.ai.model.qianfan.autoconfigure; import io.micrometer.observation.ObservationRegistry; +import org.springframework.ai.chat.observation.ChatModelObservationConvention; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.image.observation.ImageModelObservationConvention; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallbackResolver; import org.springframework.ai.qianfan.QianFanChatModel; -import org.springframework.ai.qianfan.QianFanEmbeddingModel; -import org.springframework.ai.qianfan.QianFanImageModel; import org.springframework.ai.qianfan.api.QianFanApi; -import org.springframework.ai.qianfan.api.QianFanImageApi; +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.ConditionalOnClass; @@ -47,21 +42,20 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; /** - * {@link AutoConfiguration Auto-configuration} for QianFan Chat, Embedding, and Image - * Models. + * Chat {@link AutoConfiguration Auto-configuration} for QianFan Chat Model. * * @author Geng Rong + * @author Ilayaperumal Gopinathan */ @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) @ConditionalOnClass(QianFanApi.class) -@EnableConfigurationProperties({ QianFanConnectionProperties.class, QianFanChatProperties.class, - QianFanEmbeddingProperties.class, QianFanImageProperties.class }) -public class QianFanAutoConfiguration { +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.QIANFAN, + matchIfMissing = true) +@EnableConfigurationProperties({ QianFanConnectionProperties.class, QianFanChatProperties.class }) +public class QianFanChatAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.QIANFAN, - matchIfMissing = true) public QianFanChatModel qianFanChatModel(QianFanConnectionProperties commonProperties, QianFanChatProperties chatProperties, ObjectProvider restClientBuilderProvider, RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, @@ -81,64 +75,6 @@ public class QianFanAutoConfiguration { return chatModel; } - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.QIANFAN, - matchIfMissing = true) - public QianFanEmbeddingModel qianFanEmbeddingModel(QianFanConnectionProperties commonProperties, - QianFanEmbeddingProperties embeddingProperties, - ObjectProvider restClientBuilderProvider, RetryTemplate retryTemplate, - ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var qianFanApi = qianFanApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(), - embeddingProperties.getApiKey(), commonProperties.getApiKey(), embeddingProperties.getSecretKey(), - commonProperties.getSecretKey(), restClientBuilderProvider.getIfAvailable(RestClient::builder), - responseErrorHandler); - - var embeddingModel = new QianFanEmbeddingModel(qianFanApi, embeddingProperties.getMetadataMode(), - embeddingProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.QIANFAN, - matchIfMissing = true) - public QianFanImageModel qianFanImageModel(QianFanConnectionProperties commonProperties, - QianFanImageProperties imageProperties, ObjectProvider restClientBuilderProvider, - RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey() - : commonProperties.getApiKey(); - - String secretKey = StringUtils.hasText(imageProperties.getSecretKey()) ? imageProperties.getSecretKey() - : commonProperties.getSecretKey(); - - String baseUrl = StringUtils.hasText(imageProperties.getBaseUrl()) ? imageProperties.getBaseUrl() - : commonProperties.getBaseUrl(); - - Assert.hasText(apiKey, "QianFan API key must be set. Use the property: spring.ai.qianfan.api-key"); - Assert.hasText(secretKey, "QianFan secret key must be set. Use the property: spring.ai.qianfan.secret-key"); - Assert.hasText(baseUrl, "QianFan base URL must be set. Use the property: spring.ai.qianfan.base-url"); - - var qianFanImageApi = new QianFanImageApi(baseUrl, apiKey, secretKey, - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - - var imageModel = new QianFanImageModel(qianFanImageApi, imageProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(imageModel::setObservationConvention); - - return imageModel; - } - private QianFanApi qianFanApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey, String secretKey, String commonSecretKey, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanEmbeddingAutoConfiguration.java new file mode 100644 index 000000000..39bf6db3e --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanEmbeddingAutoConfiguration.java @@ -0,0 +1,93 @@ +/* + * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.ai.qianfan.QianFanEmbeddingModel; +import org.springframework.ai.qianfan.api.QianFanApi; +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.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.context.annotation.Bean; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestClient; + +/** + * Embedding {@link AutoConfiguration Auto-configuration} for QianFan Embedding Model. + * + * @author Geng Rong + * @author Ilayaperumal Gopinathan + */ +@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) +@ConditionalOnClass(QianFanApi.class) +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.QIANFAN, + matchIfMissing = true) +@EnableConfigurationProperties({ QianFanConnectionProperties.class, QianFanEmbeddingProperties.class }) +public class QianFanEmbeddingAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public QianFanEmbeddingModel qianFanEmbeddingModel(QianFanConnectionProperties commonProperties, + QianFanEmbeddingProperties embeddingProperties, + ObjectProvider restClientBuilderProvider, RetryTemplate retryTemplate, + ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, + ObjectProvider observationConvention) { + + var qianFanApi = qianFanApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(), + embeddingProperties.getApiKey(), commonProperties.getApiKey(), embeddingProperties.getSecretKey(), + commonProperties.getSecretKey(), restClientBuilderProvider.getIfAvailable(RestClient::builder), + responseErrorHandler); + + var embeddingModel = new QianFanEmbeddingModel(qianFanApi, embeddingProperties.getMetadataMode(), + embeddingProperties.getOptions(), retryTemplate, + observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); + + observationConvention.ifAvailable(embeddingModel::setObservationConvention); + + return embeddingModel; + } + + private QianFanApi qianFanApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey, + String secretKey, String commonSecretKey, RestClient.Builder restClientBuilder, + ResponseErrorHandler responseErrorHandler) { + + String resolvedBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonBaseUrl; + Assert.hasText(resolvedBaseUrl, "QianFan base URL must be set"); + + String resolvedApiKey = StringUtils.hasText(apiKey) ? apiKey : commonApiKey; + Assert.hasText(resolvedApiKey, "QianFan API key must be set"); + + String resolvedSecretKey = StringUtils.hasText(secretKey) ? secretKey : commonSecretKey; + Assert.hasText(resolvedSecretKey, "QianFan Secret key must be set"); + + return new QianFanApi(resolvedBaseUrl, resolvedApiKey, resolvedSecretKey, restClientBuilder, + responseErrorHandler); + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanImageAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanImageAutoConfiguration.java new file mode 100644 index 000000000..d3cd534fc --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanImageAutoConfiguration.java @@ -0,0 +1,87 @@ +/* + * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; + +import io.micrometer.observation.ObservationRegistry; + +import org.springframework.ai.image.observation.ImageModelObservationConvention; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.ai.qianfan.QianFanImageModel; +import org.springframework.ai.qianfan.api.QianFanApi; +import org.springframework.ai.qianfan.api.QianFanImageApi; +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.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.context.annotation.Bean; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestClient; + +/** + * Image {@link AutoConfiguration Auto-configuration} for QianFan Image Model. + * + * @author Geng Rong + * @author Ilayaperumal Gopinathan + */ +@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) +@ConditionalOnClass(QianFanApi.class) +@ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.QIANFAN, + matchIfMissing = true) +@EnableConfigurationProperties({ QianFanConnectionProperties.class, QianFanImageProperties.class }) +public class QianFanImageAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public QianFanImageModel qianFanImageModel(QianFanConnectionProperties commonProperties, + QianFanImageProperties imageProperties, ObjectProvider restClientBuilderProvider, + RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, + ObjectProvider observationRegistry, + ObjectProvider observationConvention) { + + String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey() + : commonProperties.getApiKey(); + + String secretKey = StringUtils.hasText(imageProperties.getSecretKey()) ? imageProperties.getSecretKey() + : commonProperties.getSecretKey(); + + String baseUrl = StringUtils.hasText(imageProperties.getBaseUrl()) ? imageProperties.getBaseUrl() + : commonProperties.getBaseUrl(); + + Assert.hasText(apiKey, "QianFan API key must be set. Use the property: spring.ai.qianfan.api-key"); + Assert.hasText(secretKey, "QianFan secret key must be set. Use the property: spring.ai.qianfan.secret-key"); + Assert.hasText(baseUrl, "QianFan base URL must be set. Use the property: spring.ai.qianfan.base-url"); + + var qianFanImageApi = new QianFanImageApi(baseUrl, apiKey, secretKey, + restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); + + var imageModel = new QianFanImageModel(qianFanImageApi, imageProperties.getOptions(), retryTemplate, + observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); + + observationConvention.ifAvailable(imageModel::setObservationConvention); + + return imageModel; + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 74529bcf0..b8ae44638 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.qianfan.autoconfigure.QianFanAutoConfiguration +org.springframework.ai.model.qianfan.autoconfigure.QianFanChatAutoConfiguration +org.springframework.ai.model.qianfan.autoconfigure.QianFanEmbeddingAutoConfiguration +org.springframework.ai.model.qianfan.autoconfigure.QianFanImageAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfigurationIT.java index bc2334e20..570759420 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfigurationIT.java @@ -27,7 +27,6 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables; import reactor.core.publisher.Flux; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; @@ -37,6 +36,7 @@ import org.springframework.ai.image.ImageResponse; import org.springframework.ai.qianfan.QianFanChatModel; import org.springframework.ai.qianfan.QianFanEmbeddingModel; import org.springframework.ai.qianfan.QianFanImageModel; +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.test.context.runner.ApplicationContextRunner; @@ -55,12 +55,12 @@ public class QianFanAutoConfigurationIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.qianfan.apiKey=" + System.getenv("QIANFAN_API_KEY"), "spring.ai.qianfan.secretKey=" + System.getenv("QIANFAN_SECRET_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class)); @Test void generate() { - this.contextRunner.run(context -> { + this.contextRunner.withConfiguration(AutoConfigurations.of(QianFanChatAutoConfiguration.class)).run(context -> { QianFanChatModel client = context.getBean(QianFanChatModel.class); String response = client.call("Hello"); assertThat(response).isNotEmpty(); @@ -70,7 +70,7 @@ public class QianFanAutoConfigurationIT { @Test void generateStreaming() { - this.contextRunner.run(context -> { + this.contextRunner.withConfiguration(AutoConfigurations.of(QianFanChatAutoConfiguration.class)).run(context -> { QianFanChatModel client = context.getBean(QianFanChatModel.class); Flux responseFlux = client.stream(new Prompt(new UserMessage("Hello"))); String response = Objects.requireNonNull(responseFlux.collectList().block()) @@ -84,31 +84,34 @@ public class QianFanAutoConfigurationIT { @Test void embedding() { - this.contextRunner.run(context -> { - QianFanEmbeddingModel embeddingClient = context.getBean(QianFanEmbeddingModel.class); + this.contextRunner.withConfiguration(AutoConfigurations.of(QianFanEmbeddingAutoConfiguration.class)) + .run(context -> { + QianFanEmbeddingModel embeddingClient = context.getBean(QianFanEmbeddingModel.class); - EmbeddingResponse embeddingResponse = embeddingClient - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); + EmbeddingResponse embeddingResponse = embeddingClient + .embedForResponse(List.of("Hello World", "World is big and salvation is near")); + assertThat(embeddingResponse.getResults()).hasSize(2); + assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); + assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - assertThat(embeddingClient.dimensions()).isEqualTo(1024); - }); + assertThat(embeddingClient.dimensions()).isEqualTo(1024); + }); } @Test void generateImage() { - this.contextRunner.withPropertyValues("spring.ai.qianfan.image.options.size=1024x1024").run(context -> { - QianFanImageModel imageModel = context.getBean(QianFanImageModel.class); - ImageResponse imageResponse = imageModel.call(new ImagePrompt("forest")); - assertThat(imageResponse.getResults()).hasSize(1); - assertThat(imageResponse.getResult().getOutput().getUrl()).isNull(); - assertThat(imageResponse.getResult().getOutput().getB64Json()).isNotEmpty(); - logger.info("Generated image: " + imageResponse.getResult().getOutput().getB64Json()); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(QianFanImageAutoConfiguration.class)) + .withPropertyValues("spring.ai.qianfan.image.options.size=1024x1024") + .run(context -> { + QianFanImageModel imageModel = context.getBean(QianFanImageModel.class); + ImageResponse imageResponse = imageModel.call(new ImagePrompt("forest")); + assertThat(imageResponse.getResults()).hasSize(1); + assertThat(imageResponse.getResult().getOutput().getUrl()).isNull(); + assertThat(imageResponse.getResult().getOutput().getB64Json()).isNotEmpty(); + logger.info("Generated image: " + imageResponse.getResult().getOutput().getB64Json()); + }); } } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java index d10e3b704..208e09ef6 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * 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. @@ -18,11 +18,11 @@ package org.springframework.ai.model.qianfan.autoconfigure; import org.junit.jupiter.api.Test; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.qianfan.QianFanChatModel; import org.springframework.ai.qianfan.QianFanEmbeddingModel; import org.springframework.ai.qianfan.QianFanImageModel; import org.springframework.ai.qianfan.api.QianFanApi; +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; @@ -35,6 +35,7 @@ import static org.assertj.core.api.Assertions.assertThat; * {@link QianFanEmbeddingProperties}. * * @author Geng Rong + * @author Ilayaperumal Gopinathan */ public class QianFanPropertiesTests { @@ -50,7 +51,7 @@ public class QianFanPropertiesTests { "spring.ai.qianfan.chat.options.temperature=0.55") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(QianFanChatProperties.class); var connectionProperties = context.getBean(QianFanConnectionProperties.class); @@ -82,7 +83,7 @@ public class QianFanPropertiesTests { "spring.ai.qianfan.chat.options.temperature=0.55") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(QianFanChatProperties.class); var connectionProperties = context.getBean(QianFanConnectionProperties.class); @@ -111,7 +112,7 @@ public class QianFanPropertiesTests { "spring.ai.qianfan.embedding.options.model=MODEL_XYZ") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanEmbeddingAutoConfiguration.class)) .run(context -> { var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class); var connectionProperties = context.getBean(QianFanConnectionProperties.class); @@ -141,7 +142,7 @@ public class QianFanPropertiesTests { "spring.ai.qianfan.embedding.options.model=MODEL_XYZ") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanEmbeddingAutoConfiguration.class)) .run(context -> { var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class); var connectionProperties = context.getBean(QianFanConnectionProperties.class); @@ -179,18 +180,15 @@ public class QianFanPropertiesTests { ) // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(QianFanChatProperties.class); var connectionProperties = context.getBean(QianFanConnectionProperties.class); - var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class); assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); assertThat(connectionProperties.getSecretKey()).isEqualTo("SECRET_KEY"); - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("bge_large_zh"); - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5); assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); @@ -217,7 +215,7 @@ public class QianFanPropertiesTests { ) // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanEmbeddingAutoConfiguration.class)) .run(context -> { var connectionProperties = context.getBean(QianFanConnectionProperties.class); var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class); @@ -237,9 +235,9 @@ public class QianFanPropertiesTests { .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.embedding=none") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanEmbeddingAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(QianFanEmbeddingModel.class)).isEmpty(); }); @@ -247,7 +245,7 @@ public class QianFanPropertiesTests { .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", "spring.ai.qianfan.base-url=TEST_BASE_URL") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(QianFanEmbeddingModel.class)).isNotEmpty(); @@ -257,7 +255,7 @@ public class QianFanPropertiesTests { .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.chat=qianfan") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(QianFanEmbeddingModel.class)).isNotEmpty(); @@ -270,9 +268,9 @@ public class QianFanPropertiesTests { .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.chat=none") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanChatAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(QianFanChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(QianFanChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(QianFanChatModel.class)).isEmpty(); }); @@ -280,7 +278,7 @@ public class QianFanPropertiesTests { .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", "spring.ai.qianfan.base-url=TEST_BASE_URL") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(QianFanChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(QianFanChatModel.class)).isNotEmpty(); @@ -290,7 +288,7 @@ public class QianFanPropertiesTests { .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.chat.enabled=true") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + RestClientAutoConfiguration.class, QianFanChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(QianFanChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(QianFanChatModel.class)).isNotEmpty(); @@ -310,7 +308,7 @@ public class QianFanPropertiesTests { // @formatter:on .withConfiguration( AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + WebClientAutoConfiguration.class, QianFanImageAutoConfiguration.class)) .run(context -> { var imageProperties = context.getBean(QianFanImageProperties.class); var connectionProperties = context.getBean(QianFanConnectionProperties.class); @@ -342,7 +340,7 @@ public class QianFanPropertiesTests { // @formatter:on .withConfiguration( AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + WebClientAutoConfiguration.class, QianFanImageAutoConfiguration.class)) .run(context -> { var imageProperties = context.getBean(QianFanImageProperties.class); var connectionProperties = context.getBean(QianFanConnectionProperties.class); @@ -379,7 +377,7 @@ public class QianFanPropertiesTests { // @formatter:on .withConfiguration( AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + WebClientAutoConfiguration.class, QianFanImageAutoConfiguration.class)) .run(context -> { var imageProperties = context.getBean(QianFanImageProperties.class); var connectionProperties = context.getBean(QianFanConnectionProperties.class); @@ -405,9 +403,9 @@ public class QianFanPropertiesTests { "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.image=none") .withConfiguration( AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + WebClientAutoConfiguration.class, QianFanImageAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(QianFanImageProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(QianFanImageProperties.class)).isEmpty(); assertThat(context.getBeansOfType(QianFanImageModel.class)).isEmpty(); }); @@ -416,7 +414,7 @@ public class QianFanPropertiesTests { "spring.ai.qianfan.base-url=TEST_BASE_URL") .withConfiguration( AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + WebClientAutoConfiguration.class, QianFanImageAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(QianFanImageProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(QianFanImageModel.class)).isNotEmpty(); @@ -427,7 +425,7 @@ public class QianFanPropertiesTests { "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.chat=qianfan") .withConfiguration( AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) + WebClientAutoConfiguration.class, QianFanImageAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(QianFanImageProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(QianFanImageModel.class)).isNotEmpty(); 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 f73cc84ad..0bb37d261 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 @@ -39,14 +39,12 @@ org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-image-observation ${project.parent.version} - true 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 c6d83e9cd..b38df3132 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 @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * 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. @@ -37,10 +37,13 @@ import org.springframework.web.client.RestClient; * * @author Mark Pollack * @author Christian Tzolov + * @author Ilayaperumal Gopinathan * @since 0.8.0 */ @AutoConfiguration(after = { RestClientAutoConfiguration.class }) @ConditionalOnClass(StabilityAiApi.class) +@ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.STABILITY_AI, + matchIfMissing = true) @EnableConfigurationProperties({ StabilityAiConnectionProperties.class, StabilityAiImageProperties.class }) public class StabilityAiImageAutoConfiguration { @@ -64,8 +67,6 @@ public class StabilityAiImageAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.STABILITY, - matchIfMissing = true) public StabilityAiImageModel stabilityAiImageModel(StabilityAiApi stabilityAiApi, StabilityAiImageProperties stabilityAiImageProperties) { return new StabilityAiImageModel(stabilityAiApi, stabilityAiImageProperties.getOptions()); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java index 9cbc4e9f1..cfd9e7132 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * 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. @@ -80,7 +80,7 @@ public class StabilityAiImagePropertiesTests { "spring.ai.stabilityai.image.base-url=ENDPOINT", "spring.ai.model.image=none") .withConfiguration(AutoConfigurations.of(StabilityAiImageAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(StabilityAiImageProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(StabilityAiImageProperties.class)).isEmpty(); assertThat(context.getBeansOfType(StabilityAiImageModel.class)).isEmpty(); }); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml index 248c9968a..bb6601bc6 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml @@ -39,7 +39,6 @@ org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java index d4896a80d..e2c25760c 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java @@ -39,13 +39,13 @@ import org.springframework.context.annotation.Bean; */ @AutoConfiguration @EnableConfigurationProperties({ TransformersEmbeddingModelProperties.class }) +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.TRANSFORMERS, + matchIfMissing = true) @ConditionalOnClass({ OrtSession.class, HuggingFaceTokenizer.class, TransformersEmbeddingModel.class }) public class TransformersEmbeddingModelAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.TRANSFORMERS, - matchIfMissing = true) public TransformersEmbeddingModel embeddingModel(TransformersEmbeddingModelProperties properties, ObjectProvider observationRegistry, ObjectProvider observationConvention) { 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 c4b091a78..18a0515b4 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 @@ -48,35 +48,24 @@ org.springframework.ai spring-ai-autoconfigure-model-tool ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-autoconfigure-model-image-observation - ${project.parent.version} - true diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingConnectionAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingConnectionAutoConfiguration.java new file mode 100644 index 000000000..4dd06fc0e --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingConnectionAutoConfiguration.java @@ -0,0 +1,63 @@ +/* + * 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.springframework.ai.model.vertexai.autoconfigure.embedding; + +import com.google.cloud.vertexai.VertexAI; + +import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Auto-configuration for Vertex AI Embedding Connection. + * + * @author Christian Tzolov + * @author Mark Pollack + * @author Ilayaperumal Gopinathan + * @since 1.0.0 + */ +@AutoConfiguration +@ConditionalOnClass(VertexAI.class) +@EnableConfigurationProperties(VertexAiEmbeddingConnectionProperties.class) +public class VertexAiEmbeddingConnectionAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public VertexAiEmbeddingConnectionDetails connectionDetails( + VertexAiEmbeddingConnectionProperties connectionProperties) { + + Assert.hasText(connectionProperties.getProjectId(), "Vertex AI project-id must be set!"); + Assert.hasText(connectionProperties.getLocation(), "Vertex AI location must be set!"); + + var connectionBuilder = VertexAiEmbeddingConnectionDetails.builder() + .projectId(connectionProperties.getProjectId()) + .location(connectionProperties.getLocation()); + + if (StringUtils.hasText(connectionProperties.getApiEndpoint())) { + connectionBuilder.apiEndpoint(connectionProperties.getApiEndpoint()); + } + + return connectionBuilder.build(); + + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiMultiModalEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiMultiModalEmbeddingAutoConfiguration.java new file mode 100644 index 000000000..5015bec2f --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiMultiModalEmbeddingAutoConfiguration.java @@ -0,0 +1,61 @@ +/* + * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.embedding; + +import java.io.IOException; + +import com.google.cloud.vertexai.VertexAI; + +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; +import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails; +import org.springframework.ai.vertexai.embedding.multimodal.VertexAiMultimodalEmbeddingModel; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +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.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; + +/** + * Auto-configuration for Vertex AI Gemini Chat. + * + * @author Christian Tzolov + * @author Mark Pollack + * @author Ilayaperumal Gopinathan + * @since 1.0.0 + */ +@AutoConfiguration(after = { SpringAiRetryAutoConfiguration.class }) +@ConditionalOnClass({ VertexAI.class, VertexAiMultimodalEmbeddingModel.class }) +@ConditionalOnProperty(name = SpringAIModelProperties.MULTI_MODAL_EMBEDDING_MODEL, + havingValue = SpringAIModels.VERTEX_AI, matchIfMissing = true) +@EnableConfigurationProperties(VertexAiMultimodalEmbeddingProperties.class) +@ImportAutoConfiguration( + classes = { SpringAiRetryAutoConfiguration.class, VertexAiEmbeddingConnectionAutoConfiguration.class }) +public class VertexAiMultiModalEmbeddingAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public VertexAiMultimodalEmbeddingModel multimodalEmbedding(VertexAiEmbeddingConnectionDetails connectionDetails, + VertexAiMultimodalEmbeddingProperties multimodalEmbeddingProperties) throws IOException { + + return new VertexAiMultimodalEmbeddingModel(connectionDetails, multimodalEmbeddingProperties.getOptions()); + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingAutoConfiguration.java similarity index 57% rename from auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingAutoConfiguration.java index cd67ccf95..54fc82e48 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingAutoConfiguration.java @@ -16,17 +16,13 @@ package org.springframework.ai.model.vertexai.autoconfigure.embedding; -import java.io.IOException; - -import com.google.cloud.vertexai.VertexAI; import io.micrometer.observation.ObservationRegistry; +import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails; -import org.springframework.ai.vertexai.embedding.multimodal.VertexAiMultimodalEmbeddingModel; import org.springframework.ai.vertexai.embedding.text.VertexAiTextEmbeddingModel; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -37,47 +33,26 @@ 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.util.Assert; -import org.springframework.util.StringUtils; /** * Auto-configuration for Vertex AI Gemini Chat. * * @author Christian Tzolov * @author Mark Pollack + * @author Ilayaperumal Gopinathan * @since 1.0.0 */ @AutoConfiguration(after = { SpringAiRetryAutoConfiguration.class }) -@ConditionalOnClass({ VertexAI.class, VertexAiTextEmbeddingModel.class }) -@EnableConfigurationProperties({ VertexAiEmbeddingConnectionProperties.class, VertexAiTextEmbeddingProperties.class, - VertexAiMultimodalEmbeddingProperties.class }) -@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class }) -public class VertexAiEmbeddingAutoConfiguration { +@ConditionalOnClass(VertexAiTextEmbeddingModel.class) +@ConditionalOnProperty(name = SpringAIModelProperties.TEXT_EMBEDDING_MODEL, havingValue = SpringAIModels.VERTEX_AI, + matchIfMissing = true) +@EnableConfigurationProperties(VertexAiTextEmbeddingProperties.class) +@ImportAutoConfiguration( + classes = { SpringAiRetryAutoConfiguration.class, VertexAiEmbeddingConnectionAutoConfiguration.class }) +public class VertexAiTextEmbeddingAutoConfiguration { @Bean @ConditionalOnMissingBean - public VertexAiEmbeddingConnectionDetails connectionDetails( - VertexAiEmbeddingConnectionProperties connectionProperties) { - - Assert.hasText(connectionProperties.getProjectId(), "Vertex AI project-id must be set!"); - Assert.hasText(connectionProperties.getLocation(), "Vertex AI location must be set!"); - - var connectionBuilder = VertexAiEmbeddingConnectionDetails.builder() - .projectId(connectionProperties.getProjectId()) - .location(connectionProperties.getLocation()); - - if (StringUtils.hasText(connectionProperties.getApiEndpoint())) { - connectionBuilder.apiEndpoint(connectionProperties.getApiEndpoint()); - } - - return connectionBuilder.build(); - - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.TEXT_EMBEDDING_MODEL, havingValue = SpringAIModels.VERTEX_AI, - matchIfMissing = true) public VertexAiTextEmbeddingModel textEmbedding(VertexAiEmbeddingConnectionDetails connectionDetails, VertexAiTextEmbeddingProperties textEmbeddingProperties, RetryTemplate retryTemplate, ObjectProvider observationRegistry, @@ -91,14 +66,4 @@ public class VertexAiEmbeddingAutoConfiguration { return embeddingModel; } - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.MULTI_MODAL_EMBEDDING_MODEL, - havingValue = SpringAIModels.VERTEX_AI, matchIfMissing = true) - public VertexAiMultimodalEmbeddingModel multimodalEmbedding(VertexAiEmbeddingConnectionDetails connectionDetails, - VertexAiMultimodalEmbeddingProperties multimodalEmbeddingProperties) throws IOException { - - return new VertexAiMultimodalEmbeddingModel(connectionDetails, multimodalEmbeddingProperties.getOptions()); - } - } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfiguration.java similarity index 96% rename from auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfiguration.java index 7a4ceec79..a7e2e9590 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfiguration.java @@ -22,12 +22,12 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.vertexai.VertexAI; import io.micrometer.observation.ObservationRegistry; +import org.springframework.ai.chat.observation.ChatModelObservationConvention; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; +import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -49,13 +49,16 @@ import org.springframework.util.StringUtils; * @author Christian Tzolov * @author Soby Chacko * @author Mark Pollack + * @author Ilayaperumal Gopinathan * @since 1.0.0 */ @AutoConfiguration(after = { SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class }) @ConditionalOnClass({ VertexAI.class, VertexAiGeminiChatModel.class }) +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.VERTEX_AI, + matchIfMissing = true) @EnableConfigurationProperties({ VertexAiGeminiChatProperties.class, VertexAiGeminiConnectionProperties.class }) @ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -public class VertexAiGeminiAutoConfiguration { +public class VertexAiGeminiChatAutoConfiguration { @Bean @ConditionalOnMissingBean @@ -87,8 +90,6 @@ public class VertexAiGeminiAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.VERTEX_AI, - matchIfMissing = true) public VertexAiGeminiChatModel vertexAiGeminiChat(VertexAI vertexAi, VertexAiGeminiChatProperties chatProperties, ToolCallingManager toolCallingManager, ApplicationContext context, RetryTemplate retryTemplate, ObjectProvider observationRegistry, diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 1f2a06e16..0cf7cd5b9 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,5 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiEmbeddingAutoConfiguration -org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiAutoConfiguration +org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiTextEmbeddingAutoConfiguration +org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiMultiModalEmbeddingAutoConfiguration +org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiChatAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingModelAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingModelAutoConfigurationIT.java index c1ed7eaf3..7955ce2ac 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingModelAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingModelAutoConfigurationIT.java @@ -37,107 +37,119 @@ import static org.assertj.core.api.Assertions.assertThat; /** * @author Christian Tzolov + * @author Ilayaperumal Gopinathan */ @EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*") @EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_LOCATION", matches = ".*") public class VertexAiTextEmbeddingModelAutoConfigurationIT { - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.vertex.ai.embedding.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), - "spring.ai.vertex.ai.embedding.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - .withConfiguration(AutoConfigurations.of(VertexAiEmbeddingAutoConfiguration.class)); + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( + "spring.ai.vertex.ai.embedding.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), + "spring.ai.vertex.ai.embedding.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")); @TempDir File tempDir; @Test public void textEmbedding() { - this.contextRunner.run(context -> { - var conntectionProperties = context.getBean(VertexAiEmbeddingConnectionProperties.class); - var textEmbeddingProperties = context.getBean(VertexAiTextEmbeddingProperties.class); + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiTextEmbeddingAutoConfiguration.class)) + .run(context -> { + var conntectionProperties = context.getBean(VertexAiEmbeddingConnectionProperties.class); + var textEmbeddingProperties = context.getBean(VertexAiTextEmbeddingProperties.class); - assertThat(conntectionProperties).isNotNull(); - assertThat(textEmbeddingProperties.isEnabled()).isTrue(); + assertThat(conntectionProperties).isNotNull(); + assertThat(textEmbeddingProperties.isEnabled()).isTrue(); - VertexAiTextEmbeddingModel embeddingModel = context.getBean(VertexAiTextEmbeddingModel.class); - assertThat(embeddingModel).isInstanceOf(VertexAiTextEmbeddingModel.class); + VertexAiTextEmbeddingModel embeddingModel = context.getBean(VertexAiTextEmbeddingModel.class); + assertThat(embeddingModel).isInstanceOf(VertexAiTextEmbeddingModel.class); - List embeddings = embeddingModel.embed(List.of("Spring Framework", "Spring AI")); + List embeddings = embeddingModel.embed(List.of("Spring Framework", "Spring AI")); - assertThat(embeddings.size()).isEqualTo(2); // batch size - assertThat(embeddings.get(0).length).isEqualTo(embeddingModel.dimensions()); - }); + assertThat(embeddings.size()).isEqualTo(2); // batch size + assertThat(embeddings.get(0).length).isEqualTo(embeddingModel.dimensions()); + }); } @Test void textEmbeddingActivation() { - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.embedding.text.enabled=false").run(context -> { - assertThat(context.getBeansOfType(VertexAiTextEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiTextEmbeddingModel.class)).isEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiTextEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.text.embedding=none") + .run(context -> { + assertThat(context.getBeansOfType(VertexAiTextEmbeddingProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(VertexAiTextEmbeddingModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.embedding.text.enabled=true").run(context -> { - assertThat(context.getBeansOfType(VertexAiTextEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiTextEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiTextEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.text.embedding=vertexai") + .run(context -> { + assertThat(context.getBeansOfType(VertexAiTextEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(VertexAiTextEmbeddingModel.class)).isNotEmpty(); + }); - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(VertexAiTextEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiTextEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiTextEmbeddingAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(VertexAiTextEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(VertexAiTextEmbeddingModel.class)).isNotEmpty(); + }); } @Test public void multimodalEmbedding() { - this.contextRunner.run(context -> { - var conntectionProperties = context.getBean(VertexAiEmbeddingConnectionProperties.class); - var multimodalEmbeddingProperties = context.getBean(VertexAiMultimodalEmbeddingProperties.class); + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiMultiModalEmbeddingAutoConfiguration.class)) + .run(context -> { + var conntectionProperties = context.getBean(VertexAiEmbeddingConnectionProperties.class); + var multimodalEmbeddingProperties = context.getBean(VertexAiMultimodalEmbeddingProperties.class); - assertThat(conntectionProperties).isNotNull(); - assertThat(multimodalEmbeddingProperties.isEnabled()).isTrue(); + assertThat(conntectionProperties).isNotNull(); + assertThat(multimodalEmbeddingProperties.isEnabled()).isTrue(); - VertexAiMultimodalEmbeddingModel multiModelEmbeddingModel = context - .getBean(VertexAiMultimodalEmbeddingModel.class); + VertexAiMultimodalEmbeddingModel multiModelEmbeddingModel = context + .getBean(VertexAiMultimodalEmbeddingModel.class); - assertThat(multiModelEmbeddingModel).isNotNull(); + assertThat(multiModelEmbeddingModel).isNotNull(); - var document = new Document("Hello World"); + var document = new Document("Hello World"); - DocumentEmbeddingRequest embeddingRequest = new DocumentEmbeddingRequest(List.of(document), - EmbeddingOptionsBuilder.builder().build()); + DocumentEmbeddingRequest embeddingRequest = new DocumentEmbeddingRequest(List.of(document), + EmbeddingOptionsBuilder.builder().build()); - EmbeddingResponse embeddingResponse = multiModelEmbeddingModel.call(embeddingRequest); - assertThat(embeddingResponse.getResults()).hasSize(1); - assertThat(embeddingResponse.getResults().get(0)).isNotNull(); - assertThat(embeddingResponse.getResults().get(0).getMetadata().getModalityType()) - .isEqualTo(EmbeddingResultMetadata.ModalityType.TEXT); - assertThat(embeddingResponse.getResults().get(0).getOutput()).hasSize(1408); + EmbeddingResponse embeddingResponse = multiModelEmbeddingModel.call(embeddingRequest); + assertThat(embeddingResponse.getResults()).hasSize(1); + assertThat(embeddingResponse.getResults().get(0)).isNotNull(); + assertThat(embeddingResponse.getResults().get(0).getMetadata().getModalityType()) + .isEqualTo(EmbeddingResultMetadata.ModalityType.TEXT); + assertThat(embeddingResponse.getResults().get(0).getOutput()).hasSize(1408); - assertThat(embeddingResponse.getMetadata().getModel()).isEqualTo("multimodalembedding@001"); - assertThat(embeddingResponse.getMetadata().getUsage().getPromptTokens()).isEqualTo(0); + assertThat(embeddingResponse.getMetadata().getModel()).isEqualTo("multimodalembedding@001"); + assertThat(embeddingResponse.getMetadata().getUsage().getPromptTokens()).isEqualTo(0); - assertThat(multiModelEmbeddingModel.dimensions()).isEqualTo(1408); + assertThat(multiModelEmbeddingModel.dimensions()).isEqualTo(1408); - }); + }); } @Test void multimodalEmbeddingActivation() { - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.embedding.multimodal.enabled=false").run(context -> { - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingModel.class)).isEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiMultiModalEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.multi-modal.embedding=none") + .run(context -> { + assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingModel.class)).isEmpty(); + }); - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.embedding.multimodal.enabled=true").run(context -> { - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiMultiModalEmbeddingAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.multi-modal.embedding=vertexai") + .run(context -> { + assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingModel.class)).isNotEmpty(); + }); - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingModel.class)).isNotEmpty(); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiMultiModalEmbeddingAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingModel.class)).isNotEmpty(); + }); } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfigurationIT.java deleted file mode 100644 index a6cdfb3dd..000000000 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfigurationIT.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.gemini; - -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; - -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_LOCATION", matches = ".*") -public class VertexAiGeminiAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(VertexAiGeminiAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.vertex.ai.gemini.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), - "spring.ai.vertex.ai.gemini.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - .withConfiguration(AutoConfigurations.of(VertexAiGeminiAutoConfiguration.class)); - - @Test - void generate() { - this.contextRunner.run(context -> { - VertexAiGeminiChatModel chatModel = context.getBean(VertexAiGeminiChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void generateStreaming() { - this.contextRunner.run(context -> { - VertexAiGeminiChatModel chatModel = context.getBean(VertexAiGeminiChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - String response = responseFlux.collectList() - .block() - .stream() - .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) - .collect(Collectors.joining()); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfigurationIT.java similarity index 93% rename from spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfigurationIT.java rename to auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfigurationIT.java index a6cdfb3dd..a623599d6 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatAutoConfigurationIT.java @@ -35,14 +35,14 @@ import static org.assertj.core.api.Assertions.assertThat; @EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*") @EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_LOCATION", matches = ".*") -public class VertexAiGeminiAutoConfigurationIT { +public class VertexAiGeminiChatAutoConfigurationIT { - private static final Log logger = LogFactory.getLog(VertexAiGeminiAutoConfigurationIT.class); + private static final Log logger = LogFactory.getLog(VertexAiGeminiChatAutoConfigurationIT.class); private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.vertex.ai.gemini.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), "spring.ai.vertex.ai.gemini.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - .withConfiguration(AutoConfigurations.of(VertexAiGeminiAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(VertexAiGeminiChatAutoConfiguration.class)); @Test void generate() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiModelConfigurationTests.java new file mode 100644 index 000000000..f3314dd0d --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiModelConfigurationTests.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025-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.springframework.ai.model.vertexai.autoconfigure.gemini; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit Tests for OpenAI auto configurations' conditional enabling of models. + * + * @author Ilayaperumal Gopinathan + */ +@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_LOCATION", matches = ".*") +public class VertexAiModelConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( + "spring.ai.vertex.ai.gemini.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), + "spring.ai.vertex.ai.gemini.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")); + + @Test + void chatModelActivation() { + + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiGeminiChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=none") + .run(context -> { + assertThat(context.getBeansOfType(VertexAiGeminiChatProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(VertexAiGeminiChatModel.class)).isEmpty(); + }); + + this.contextRunner.withConfiguration(AutoConfigurations.of(VertexAiGeminiChatAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=vertexai") + .run(context -> { + assertThat(context.getBeansOfType(VertexAiGeminiChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(VertexAiGeminiChatModel.class)).isNotEmpty(); + }); + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionBeanIT.java index 2cbb71c49..99e6df1ba 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionBeanIT.java @@ -24,7 +24,7 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiAutoConfiguration; +import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiChatAutoConfiguration; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; @@ -49,7 +49,7 @@ class FunctionCallWithFunctionBeanIT { .withPropertyValues("spring.ai.vertex.ai.gemini.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), "spring.ai.vertex.ai.gemini.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - .withConfiguration(AutoConfigurations.of(VertexAiGeminiAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(VertexAiGeminiChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionWrapperIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionWrapperIT.java index 32cc29327..a0c38b417 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionWrapperIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionWrapperIT.java @@ -23,7 +23,7 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiAutoConfiguration; +import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiChatAutoConfiguration; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; @@ -47,7 +47,7 @@ public class FunctionCallWithFunctionWrapperIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.vertex.ai.gemini.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), "spring.ai.vertex.ai.gemini.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - .withConfiguration(AutoConfigurations.of(VertexAiGeminiAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(VertexAiGeminiChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithPromptFunctionIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithPromptFunctionIT.java index 3d9d04a86..62870fdc4 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithPromptFunctionIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithPromptFunctionIT.java @@ -23,7 +23,7 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiAutoConfiguration; +import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiChatAutoConfiguration; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; @@ -44,7 +44,7 @@ public class FunctionCallWithPromptFunctionIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.vertex.ai.gemini.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), "spring.ai.vertex.ai.gemini.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - .withConfiguration(AutoConfigurations.of(VertexAiGeminiAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(VertexAiGeminiChatAutoConfiguration.class)); @Test void functionCallTest() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/pom.xml index 00e937e6a..22ceb2fa7 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/pom.xml @@ -39,14 +39,12 @@ org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiChatAutoConfiguration.java similarity index 72% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiChatAutoConfiguration.java index fbb20c697..506447d42 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiChatAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * 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. @@ -16,8 +16,9 @@ package org.springframework.ai.model.watsonxai.autoconfigure; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; import org.springframework.ai.watsonx.WatsonxAiChatModel; -import org.springframework.ai.watsonx.WatsonxAiEmbeddingModel; import org.springframework.ai.watsonx.api.WatsonxAiApi; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -30,20 +31,20 @@ import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestClient; /** - * WatsonX.ai autoconfiguration class. + * WatsonX.ai chat autoconfiguration class. * * @author Pablo Sanchidrian Herrera * @author John Jario Moreno Rojas * @author Christian Tzolov + * @author Ilayaperumal Gopinathan * @since 1.0.0 */ @AutoConfiguration(after = RestClientAutoConfiguration.class) @ConditionalOnClass(WatsonxAiApi.class) -@EnableConfigurationProperties({ WatsonxAiConnectionProperties.class, WatsonxAiChatProperties.class, - WatsonxAiEmbeddingProperties.class }) -@ConditionalOnProperty(prefix = WatsonxAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.WATSONX_AI, matchIfMissing = true) -public class WatsonxAiAutoConfiguration { +@EnableConfigurationProperties({ WatsonxAiConnectionProperties.class, WatsonxAiChatProperties.class }) +public class WatsonxAiChatAutoConfiguration { @Bean @ConditionalOnMissingBean @@ -56,19 +57,8 @@ public class WatsonxAiAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = WatsonxAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) public WatsonxAiChatModel watsonxChatModel(WatsonxAiApi watsonxApi, WatsonxAiChatProperties chatProperties) { return new WatsonxAiChatModel(watsonxApi, chatProperties.getOptions()); } - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = WatsonxAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public WatsonxAiEmbeddingModel watsonxAiEmbeddingModel(WatsonxAiApi watsonxApi, - WatsonxAiEmbeddingProperties properties) { - return new WatsonxAiEmbeddingModel(watsonxApi, properties.getOptions()); - } - } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiEmbeddingAutoConfiguration.java similarity index 78% rename from auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiEmbeddingAutoConfiguration.java index 2477069c6..1210cb3aa 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiEmbeddingAutoConfiguration.java @@ -18,7 +18,6 @@ package org.springframework.ai.model.watsonxai.autoconfigure; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; -import org.springframework.ai.watsonx.WatsonxAiChatModel; import org.springframework.ai.watsonx.WatsonxAiEmbeddingModel; import org.springframework.ai.watsonx.api.WatsonxAiApi; import org.springframework.beans.factory.ObjectProvider; @@ -32,18 +31,20 @@ import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestClient; /** - * WatsonX.ai autoconfiguration class. + * WatsonX.ai embedding autoconfiguration class. * * @author Pablo Sanchidrian Herrera * @author John Jario Moreno Rojas * @author Christian Tzolov + * @author Ilayaperumal Gopinathan * @since 1.0.0 */ @AutoConfiguration(after = RestClientAutoConfiguration.class) @ConditionalOnClass(WatsonxAiApi.class) -@EnableConfigurationProperties({ WatsonxAiConnectionProperties.class, WatsonxAiChatProperties.class, - WatsonxAiEmbeddingProperties.class }) -public class WatsonxAiAutoConfiguration { +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.WATSONX_AI, + matchIfMissing = true) +@EnableConfigurationProperties({ WatsonxAiConnectionProperties.class, WatsonxAiEmbeddingProperties.class }) +public class WatsonxAiEmbeddingAutoConfiguration { @Bean @ConditionalOnMissingBean @@ -56,16 +57,6 @@ public class WatsonxAiAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.WATSONX_AI, - matchIfMissing = true) - public WatsonxAiChatModel watsonxChatModel(WatsonxAiApi watsonxApi, WatsonxAiChatProperties chatProperties) { - return new WatsonxAiChatModel(watsonxApi, chatProperties.getOptions()); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.WATSONX_AI, - matchIfMissing = true) public WatsonxAiEmbeddingModel watsonxAiEmbeddingModel(WatsonxAiApi watsonxApi, WatsonxAiEmbeddingProperties properties) { return new WatsonxAiEmbeddingModel(watsonxApi, properties.getOptions()); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 6c9942c1c..1ca223068 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.watsonxai.autoconfigure.WatsonxAiAutoConfiguration +org.springframework.ai.model.watsonxai.autoconfigure.WatsonxAiChatAutoConfiguration +org.springframework.ai.model.watsonxai.autoconfigure.WatsonxAiEmbeddingAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/test/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/test/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfigurationTests.java index 86f6fca45..f9fe70ff0 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/test/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfigurationTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/test/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfigurationTests.java @@ -37,8 +37,8 @@ public class WatsonxAiAutoConfigurationTests { "spring.ai.watsonx.ai.projectId=1", "spring.ai.watsonx.ai.IAMToken=123456") // @formatter:on - .withConfiguration( - AutoConfigurations.of(RestClientAutoConfiguration.class, WatsonxAiAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, + WatsonxAiChatAutoConfiguration.class, WatsonxAiEmbeddingAutoConfiguration.class)) .run(context -> { var connectionProperties = context.getBean(WatsonxAiConnectionProperties.class); assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); 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 61719f3a0..05189fe1a 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml @@ -39,28 +39,24 @@ org.springframework.ai spring-ai-autoconfigure-retry ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-chat-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-embedding-observation ${project.parent.version} - true org.springframework.ai spring-ai-autoconfigure-model-image-observation ${project.parent.version} - true diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatAutoConfiguration.java similarity index 65% rename from spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatAutoConfiguration.java index 51674fc76..20df42b7c 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * 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. @@ -14,19 +14,21 @@ * limitations under the License. */ -package org.springframework.ai.model.moonshot.autoconfigure; +package org.springframework.ai.model.zhipuai.autoconfigure; import java.util.List; import io.micrometer.observation.ObservationRegistry; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.observation.ChatModelObservationConvention; +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; import org.springframework.ai.model.function.FunctionCallback; import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.moonshot.MoonshotChatModel; -import org.springframework.ai.moonshot.api.MoonshotApi; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; +import org.springframework.ai.zhipuai.ZhiPuAiChatModel; +import org.springframework.ai.zhipuai.api.ZhiPuAiApi; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -43,37 +45,51 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; /** - * {@link AutoConfiguration Auto-configuration} for Moonshot Chat Model. + * Chat {@link AutoConfiguration Auto-configuration} for ZhiPuAI. * * @author Geng Rong + * @author Ilayaperumal Gopinathan */ @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) -@EnableConfigurationProperties({ MoonshotCommonProperties.class, MoonshotChatProperties.class }) -@ConditionalOnClass(MoonshotApi.class) -public class MoonshotAutoConfiguration { +@ConditionalOnClass(ZhiPuAiApi.class) +@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.ZHIPUAI, + matchIfMissing = true) +@EnableConfigurationProperties({ ZhiPuAiConnectionProperties.class, ZhiPuAiChatProperties.class }) +public class ZhiPuAiChatAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = MoonshotChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public MoonshotChatModel moonshotChatModel(MoonshotCommonProperties commonProperties, - MoonshotChatProperties chatProperties, ObjectProvider restClientBuilderProvider, + public ZhiPuAiChatModel zhiPuAiChatModel(ZhiPuAiConnectionProperties commonProperties, + ZhiPuAiChatProperties chatProperties, ObjectProvider restClientBuilderProvider, List toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver, RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, ObjectProvider observationConvention) { - var moonshotApi = moonshotApi(chatProperties.getApiKey(), commonProperties.getApiKey(), - chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), + var zhiPuAiApi = zhiPuAiApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), + chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - var chatModel = new MoonshotChatModel(moonshotApi, chatProperties.getOptions(), functionCallbackResolver, + var chatModel = new ZhiPuAiChatModel(zhiPuAiApi, chatProperties.getOptions(), functionCallbackResolver, toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); observationConvention.ifAvailable(chatModel::setObservationConvention); + return chatModel; } + private ZhiPuAiApi zhiPuAiApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey, + RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { + + String resolvedBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonBaseUrl; + Assert.hasText(resolvedBaseUrl, "ZhiPuAI base URL must be set"); + + String resolvedApiKey = StringUtils.hasText(apiKey) ? apiKey : commonApiKey; + Assert.hasText(resolvedApiKey, "ZhiPuAI API key must be set"); + + return new ZhiPuAiApi(resolvedBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler); + } + @Bean @ConditionalOnMissingBean public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { @@ -82,16 +98,4 @@ public class MoonshotAutoConfiguration { return manager; } - private MoonshotApi moonshotApi(String apiKey, String commonApiKey, String baseUrl, String commonBaseUrl, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { - - var resolvedApiKey = StringUtils.hasText(apiKey) ? apiKey : commonApiKey; - var resoledBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonBaseUrl; - - Assert.hasText(resolvedApiKey, "Moonshot API key must be set"); - Assert.hasText(resoledBaseUrl, "Moonshot base URL must be set"); - - return new MoonshotApi(resoledBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler); - } - } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingAutoConfiguration.java similarity index 52% rename from auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingAutoConfiguration.java index 4202ce2ef..52fd055e4 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2023-2024 the original author or authors. + * 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. @@ -16,23 +16,14 @@ package org.springframework.ai.model.zhipuai.autoconfigure; -import java.util.List; - import io.micrometer.observation.ObservationRegistry; +import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.zhipuai.ZhiPuAiChatModel; import org.springframework.ai.zhipuai.ZhiPuAiEmbeddingModel; -import org.springframework.ai.zhipuai.ZhiPuAiImageModel; import org.springframework.ai.zhipuai.api.ZhiPuAiApi; -import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -40,7 +31,6 @@ 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.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; @@ -49,43 +39,20 @@ import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; /** - * {@link AutoConfiguration Auto-configuration} for ZhiPuAI. + * Embedding {@link AutoConfiguration Auto-configuration} for ZhiPuAI. * * @author Geng Rong + * @author Ilayaperumal Gopinathan */ @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) @ConditionalOnClass(ZhiPuAiApi.class) -@EnableConfigurationProperties({ ZhiPuAiConnectionProperties.class, ZhiPuAiChatProperties.class, - ZhiPuAiEmbeddingProperties.class, ZhiPuAiImageProperties.class }) -public class ZhiPuAiAutoConfiguration { +@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.ZHIPUAI, + matchIfMissing = true) +@EnableConfigurationProperties({ ZhiPuAiConnectionProperties.class, ZhiPuAiEmbeddingProperties.class }) +public class ZhiPuAiEmbeddingAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.ZHIPUAI, - matchIfMissing = true) - public ZhiPuAiChatModel zhiPuAiChatModel(ZhiPuAiConnectionProperties commonProperties, - ZhiPuAiChatProperties chatProperties, ObjectProvider restClientBuilderProvider, - List toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver, - RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var zhiPuAiApi = zhiPuAiApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), - chatProperties.getApiKey(), commonProperties.getApiKey(), - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - - var chatModel = new ZhiPuAiChatModel(zhiPuAiApi, chatProperties.getOptions(), functionCallbackResolver, - toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.ZHIPUAI, - matchIfMissing = true) public ZhiPuAiEmbeddingModel zhiPuAiEmbeddingModel(ZhiPuAiConnectionProperties commonProperties, ZhiPuAiEmbeddingProperties embeddingProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, @@ -116,34 +83,4 @@ public class ZhiPuAiAutoConfiguration { return new ZhiPuAiApi(resolvedBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler); } - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.ZHIPUAI, - matchIfMissing = true) - public ZhiPuAiImageModel zhiPuAiImageModel(ZhiPuAiConnectionProperties commonProperties, - ZhiPuAiImageProperties imageProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate, - ResponseErrorHandler responseErrorHandler) { - - String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey() - : commonProperties.getApiKey(); - - String baseUrl = StringUtils.hasText(imageProperties.getBaseUrl()) ? imageProperties.getBaseUrl() - : commonProperties.getBaseUrl(); - - Assert.hasText(apiKey, "ZhiPuAI API key must be set"); - Assert.hasText(baseUrl, "ZhiPuAI base URL must be set"); - - var zhiPuAiImageApi = new ZhiPuAiImageApi(baseUrl, apiKey, restClientBuilder, responseErrorHandler); - - return new ZhiPuAiImageModel(zhiPuAiImageApi, imageProperties.getOptions(), retryTemplate); - } - - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - } 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 new file mode 100644 index 000000000..4bf5a17e2 --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiImageAutoConfiguration.java @@ -0,0 +1,71 @@ +/* + * 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.springframework.ai.model.zhipuai.autoconfigure; + +import org.springframework.ai.model.SpringAIModelProperties; +import org.springframework.ai.model.SpringAIModels; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; +import org.springframework.ai.zhipuai.ZhiPuAiImageModel; +import org.springframework.ai.zhipuai.api.ZhiPuAiApi; +import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; +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.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestClient; + +/** + * Image {@link AutoConfiguration Auto-configuration} for ZhiPuAI. + * + * @author Geng Rong + * @author Ilayaperumal Gopinathan + */ +@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) +@ConditionalOnClass(ZhiPuAiApi.class) +@ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.ZHIPUAI, + matchIfMissing = true) +@EnableConfigurationProperties({ ZhiPuAiConnectionProperties.class, ZhiPuAiImageProperties.class }) +public class ZhiPuAiImageAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public ZhiPuAiImageModel zhiPuAiImageModel(ZhiPuAiConnectionProperties commonProperties, + ZhiPuAiImageProperties imageProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate, + ResponseErrorHandler responseErrorHandler) { + + String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey() + : commonProperties.getApiKey(); + + String baseUrl = StringUtils.hasText(imageProperties.getBaseUrl()) ? imageProperties.getBaseUrl() + : commonProperties.getBaseUrl(); + + Assert.hasText(apiKey, "ZhiPuAI API key must be set"); + Assert.hasText(baseUrl, "ZhiPuAI base URL must be set"); + + var zhiPuAiImageApi = new ZhiPuAiImageApi(baseUrl, apiKey, restClientBuilder, responseErrorHandler); + + return new ZhiPuAiImageModel(zhiPuAiImageApi, imageProperties.getOptions(), retryTemplate); + } + +} diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 1cf07eded..59e61da56 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -13,4 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiAutoConfiguration +org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration +org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiEmbeddingAutoConfiguration +org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiImageAutoConfiguration diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfigurationIT.java index d36e0a691..51d3e1998 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfigurationIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfigurationIT.java @@ -25,13 +25,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import reactor.core.publisher.Flux; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.embedding.EmbeddingResponse; import org.springframework.ai.image.ImagePrompt; import org.springframework.ai.image.ImageResponse; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.zhipuai.ZhiPuAiChatModel; import org.springframework.ai.zhipuai.ZhiPuAiEmbeddingModel; import org.springframework.ai.zhipuai.ZhiPuAiImageModel; @@ -51,12 +51,12 @@ public class ZhiPuAiAutoConfigurationIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.zhipuai.apiKey=" + System.getenv("ZHIPU_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)); + .withConfiguration( + AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class)); @Test void generate() { - this.contextRunner.run(context -> { + this.contextRunner.withConfiguration(AutoConfigurations.of(ZhiPuAiChatAutoConfiguration.class)).run(context -> { ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); String response = chatModel.call("Hello"); assertThat(response).isNotEmpty(); @@ -66,7 +66,7 @@ public class ZhiPuAiAutoConfigurationIT { @Test void generateStreaming() { - this.contextRunner.run(context -> { + this.contextRunner.withConfiguration(AutoConfigurations.of(ZhiPuAiChatAutoConfiguration.class)).run(context -> { ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); String response = responseFlux.collectList() @@ -82,30 +82,33 @@ public class ZhiPuAiAutoConfigurationIT { @Test void embedding() { - this.contextRunner.run(context -> { - ZhiPuAiEmbeddingModel embeddingModel = context.getBean(ZhiPuAiEmbeddingModel.class); + this.contextRunner.withConfiguration(AutoConfigurations.of(ZhiPuAiEmbeddingAutoConfiguration.class)) + .run(context -> { + ZhiPuAiEmbeddingModel embeddingModel = context.getBean(ZhiPuAiEmbeddingModel.class); - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); + EmbeddingResponse embeddingResponse = embeddingModel + .embedForResponse(List.of("Hello World", "World is big and salvation is near")); + assertThat(embeddingResponse.getResults()).hasSize(2); + assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); + assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); + assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - assertThat(embeddingModel.dimensions()).isEqualTo(1024); - }); + assertThat(embeddingModel.dimensions()).isEqualTo(1024); + }); } @Test void generateImage() { - this.contextRunner.withPropertyValues("spring.ai.zhipuai.image.options.size=1024x1024").run(context -> { - ZhiPuAiImageModel ImageModel = context.getBean(ZhiPuAiImageModel.class); - ImageResponse imageResponse = ImageModel.call(new ImagePrompt("forest")); - assertThat(imageResponse.getResults()).hasSize(1); - assertThat(imageResponse.getResult().getOutput().getUrl()).isNotEmpty(); - logger.info("Generated image: " + imageResponse.getResult().getOutput().getUrl()); - }); + this.contextRunner.withConfiguration(AutoConfigurations.of(ZhiPuAiImageAutoConfiguration.class)) + .withPropertyValues("spring.ai.zhipuai.image.options.size=1024x1024") + .run(context -> { + ZhiPuAiImageModel ImageModel = context.getBean(ZhiPuAiImageModel.class); + ImageResponse imageResponse = ImageModel.call(new ImagePrompt("forest")); + assertThat(imageResponse.getResults()).hasSize(1); + assertThat(imageResponse.getResult().getOutput().getUrl()).isNotEmpty(); + logger.info("Generated image: " + imageResponse.getResult().getOutput().getUrl()); + }); } } diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java index 79b98dfed..e18351d80 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java @@ -20,8 +20,8 @@ import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONCompareMode; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.zhipuai.ZhiPuAiChatModel; import org.springframework.ai.zhipuai.ZhiPuAiEmbeddingModel; import org.springframework.ai.zhipuai.ZhiPuAiImageModel; @@ -51,7 +51,7 @@ public class ZhiPuAiPropertiesTests { "spring.ai.zhipuai.chat.options.temperature=0.55") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(ZhiPuAiChatProperties.class); var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); @@ -80,7 +80,7 @@ public class ZhiPuAiPropertiesTests { "spring.ai.zhipuai.chat.options.temperature=0.55") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(ZhiPuAiChatProperties.class); var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); @@ -106,7 +106,7 @@ public class ZhiPuAiPropertiesTests { "spring.ai.zhipuai.embedding.options.model=MODEL_XYZ") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiEmbeddingAutoConfiguration.class)) .run(context -> { var embeddingProperties = context.getBean(ZhiPuAiEmbeddingProperties.class); var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); @@ -133,7 +133,7 @@ public class ZhiPuAiPropertiesTests { "spring.ai.zhipuai.embedding.options.model=MODEL_XYZ") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiEmbeddingAutoConfiguration.class)) .run(context -> { var embeddingProperties = context.getBean(ZhiPuAiEmbeddingProperties.class); var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); @@ -157,7 +157,7 @@ public class ZhiPuAiPropertiesTests { "spring.ai.zhipuai.image.options.model=MODEL_XYZ") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiImageAutoConfiguration.class)) .run(context -> { var imageProperties = context.getBean(ZhiPuAiImageProperties.class); var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); @@ -183,7 +183,7 @@ public class ZhiPuAiPropertiesTests { "spring.ai.zhipuai.image.options.model=MODEL_XYZ") // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiImageAutoConfiguration.class)) .run(context -> { var imageProperties = context.getBean(ZhiPuAiImageProperties.class); var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); @@ -247,17 +247,14 @@ public class ZhiPuAiPropertiesTests { ) // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(ZhiPuAiChatProperties.class); var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - var embeddingProperties = context.getBean(ZhiPuAiEmbeddingProperties.class); assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("Embedding-2"); - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); assertThat(chatProperties.getOptions().getStop()).contains("boza", "koza"); @@ -295,7 +292,7 @@ public class ZhiPuAiPropertiesTests { ) // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiEmbeddingAutoConfiguration.class)) .run(context -> { var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); var embeddingProperties = context.getBean(ZhiPuAiEmbeddingProperties.class); @@ -318,7 +315,7 @@ public class ZhiPuAiPropertiesTests { ) // @formatter:on .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiImageAutoConfiguration.class)) .run(context -> { var imageProperties = context.getBean(ZhiPuAiImageProperties.class); var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); @@ -337,16 +334,16 @@ public class ZhiPuAiPropertiesTests { .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", "spring.ai.model.embedding=none") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiEmbeddingAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiEmbeddingProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(ZhiPuAiEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(ZhiPuAiEmbeddingModel.class)).isEmpty(); }); new ApplicationContextRunner() .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(ZhiPuAiEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(ZhiPuAiEmbeddingModel.class)).isNotEmpty(); @@ -356,7 +353,7 @@ public class ZhiPuAiPropertiesTests { .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", "spring.ai.model.embedding=zhipuai") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(ZhiPuAiEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(ZhiPuAiEmbeddingModel.class)).isNotEmpty(); @@ -369,16 +366,16 @@ public class ZhiPuAiPropertiesTests { .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", "spring.ai.model.chat=none") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiChatAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(ZhiPuAiChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(ZhiPuAiChatModel.class)).isEmpty(); }); new ApplicationContextRunner() .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(ZhiPuAiChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(ZhiPuAiChatModel.class)).isNotEmpty(); @@ -388,7 +385,7 @@ public class ZhiPuAiPropertiesTests { .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", "spring.ai.model.chat=zhipuai") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(ZhiPuAiChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(ZhiPuAiChatModel.class)).isNotEmpty(); @@ -402,16 +399,16 @@ public class ZhiPuAiPropertiesTests { .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", "spring.ai.model.image=none") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiImageAutoConfiguration.class)) .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiImageProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(ZhiPuAiImageProperties.class)).isEmpty(); assertThat(context.getBeansOfType(ZhiPuAiImageModel.class)).isEmpty(); }); new ApplicationContextRunner() .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiImageAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(ZhiPuAiImageProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(ZhiPuAiImageModel.class)).isNotEmpty(); @@ -421,7 +418,7 @@ public class ZhiPuAiPropertiesTests { .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", "spring.ai.model.image=zhipuai") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiImageAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(ZhiPuAiImageProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(ZhiPuAiImageModel.class)).isNotEmpty(); diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackInPromptIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackInPromptIT.java index 12dc83d6d..6b640953c 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackInPromptIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackInPromptIT.java @@ -25,14 +25,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.zhipuai.ZhiPuAiChatModel; import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -52,7 +52,7 @@ public class FunctionCallbackInPromptIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.zhipuai.apiKey=" + System.getenv("ZHIPU_AI_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)); + RestClientAutoConfiguration.class, ZhiPuAiChatAutoConfiguration.class)); @Test void functionCallTest() { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java index c384ca4f6..0ab9a8758 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java @@ -26,14 +26,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.zhipuai.ZhiPuAiChatModel; import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -56,7 +56,7 @@ class FunctionCallbackWithPlainFunctionBeanIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.zhipuai.apiKey=" + System.getenv("ZHIPU_AI_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/ZhipuAiFunctionCallbackIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/ZhipuAiFunctionCallbackIT.java index b7e636592..edee1c1bd 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/ZhipuAiFunctionCallbackIT.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/ZhipuAiFunctionCallbackIT.java @@ -25,14 +25,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiAutoConfiguration; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiChatAutoConfiguration; +import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; import org.springframework.ai.zhipuai.ZhiPuAiChatModel; import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; import org.springframework.boot.autoconfigure.AutoConfigurations; @@ -54,7 +54,7 @@ public class ZhipuAiFunctionCallbackIT { private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() .withPropertyValues("spring.ai.zhipuai.apiKey=" + System.getenv("ZHIPU_AI_API_KEY")) .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) + RestClientAutoConfiguration.class, ZhiPuAiChatAutoConfiguration.class)) .withUserConfiguration(Config.class); @Test diff --git a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml index 6eebc504d..c632a6553 100644 --- a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml +++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml @@ -28,6 +28,7 @@ org.springframework.ai spring-ai-core ${project.parent.version} + true diff --git a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/pom.xml b/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/pom.xml deleted file mode 100644 index 3a77ea3ed..000000000 --- a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - 4.0.0 - - org.springframework.ai - spring-ai - 1.0.0-SNAPSHOT - ../../../../pom.xml - - spring-ai-autoconfigure-observation-chat - jar - Spring AI Chat Observation Auto Configuration - Spring AI Chat Observation Auto Configuration - https://github.com/spring-projects/spring-ai - - - https://github.com/spring-projects/spring-ai - git://github.com/spring-projects/spring-ai.git - git@github.com:spring-projects/spring-ai.git - - - - - - - org.springframework.ai - spring-ai-core - ${parent.version} - - - - io.micrometer - micrometer-tracing-bridge-otel - true - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - org.springframework.ai - spring-ai-test - ${project.parent.version} - test - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.mockito - mockito-core - test - - - - diff --git a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfiguration.java b/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfiguration.java deleted file mode 100644 index f439263fd..000000000 --- a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfiguration.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.chat.observation; - -import java.util.List; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.otel.bridge.OtelTracer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationContext; -import org.springframework.ai.chat.client.observation.ChatClientObservationContext; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.observation.ChatModelCompletionObservationFilter; -import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler; -import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler; -import org.springframework.ai.chat.observation.ChatModelObservationContext; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationFilter; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext; -import org.springframework.ai.image.observation.ImageModelObservationContext; -import org.springframework.ai.model.observation.ErrorLoggingObservationHandler; -import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Auto-configuration for Spring AI chat model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration( - afterName = { "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration" }) -@ConditionalOnClass(ChatModel.class) -@EnableConfigurationProperties({ ChatObservationProperties.class }) -public class ChatObservationAutoConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(ChatObservationAutoConfiguration.class); - - private static void logPromptContentWarning() { - logger.warn( - "You have enabled the inclusion of the prompt content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - } - - private static void logCompletionWarning() { - logger.warn( - "You have enabled the inclusion of the completion content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(MeterRegistry.class) - ChatModelMeterObservationHandler chatModelMeterObservationHandler(ObjectProvider meterRegistry) { - return new ChatModelMeterObservationHandler(meterRegistry.getObject()); - } - - /** - * The chat content is typically too big to be included in an observation as span - * attributes. That's why the preferred way to store it is as span events, which are - * supported by OpenTelemetry but not yet surfaced through the Micrometer APIs. This - * primary/fallback configuration is a temporary solution until - * https://github.com/micrometer-metrics/micrometer/issues/5238 is delivered. - */ - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(OtelTracer.class) - @ConditionalOnBean(OtelTracer.class) - static class PrimaryChatContentObservationConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-prompt", - havingValue = "true") - ChatModelPromptContentObservationHandler chatModelPromptContentObservationHandler() { - logPromptContentWarning(); - return new ChatModelPromptContentObservationHandler(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-completion", - havingValue = "true") - ChatModelCompletionObservationHandler chatModelCompletionObservationHandler() { - logCompletionWarning(); - return new ChatModelCompletionObservationHandler(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingClass("io.micrometer.tracing.otel.bridge.OtelTracer") - static class FallbackChatContentObservationConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-prompt", - havingValue = "true") - ChatModelPromptContentObservationFilter chatModelPromptObservationFilter() { - logPromptContentWarning(); - return new ChatModelPromptContentObservationFilter(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-completion", - havingValue = "true") - ChatModelCompletionObservationFilter chatModelCompletionObservationFilter() { - logCompletionWarning(); - return new ChatModelCompletionObservationFilter(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Tracer.class) - @ConditionalOnBean(Tracer.class) - static class TracingChatContentObservationConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-error-logging", - havingValue = "true") - public ErrorLoggingObservationHandler errorLoggingObservationHandler(Tracer tracer) { - return new ErrorLoggingObservationHandler(tracer, - List.of(EmbeddingModelObservationContext.class, ImageModelObservationContext.class, - ChatModelObservationContext.class, ChatClientObservationContext.class, - AdvisorObservationContext.class, VectorStoreObservationContext.class)); - } - - } - -} diff --git a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationProperties.java b/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationProperties.java deleted file mode 100644 index cd353ac0b..000000000 --- a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationProperties.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.chat.observation; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for chat model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@ConfigurationProperties(ChatObservationProperties.CONFIG_PREFIX) -public class ChatObservationProperties { - - public static final String CONFIG_PREFIX = "spring.ai.chat.observations"; - - /** - * Whether to include the completion content in the observations. - */ - private boolean includeCompletion = false; - - /** - * Whether to include the prompt content in the observations. - */ - private boolean includePrompt = false; - - /** - * Whether to include error logging in the observations. - */ - private boolean includeErrorLogging = false; - - public boolean isIncludeCompletion() { - return this.includeCompletion; - } - - public void setIncludeCompletion(boolean includeCompletion) { - this.includeCompletion = includeCompletion; - } - - public boolean isIncludePrompt() { - return this.includePrompt; - } - - public void setIncludePrompt(boolean includePrompt) { - this.includePrompt = includePrompt; - } - - public boolean isIncludeErrorLogging() { - return this.includeErrorLogging; - } - - public void setIncludeErrorLogging(boolean includeErrorLogging) { - this.includeErrorLogging = includeErrorLogging; - } - -} diff --git a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/java/org/springframework/ai/autoconfigure/chat/observation/package-info.java b/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/java/org/springframework/ai/autoconfigure/chat/observation/package-info.java deleted file mode 100644 index 6032da63a..000000000 --- a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/java/org/springframework/ai/autoconfigure/chat/observation/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023-2024 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. - */ - -/** - * Auto-configuration for chat observation. - */ -@NonNullApi -@NonNullFields -package org.springframework.ai.autoconfigure.chat.observation; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 9493b7587..000000000 --- a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright 2025-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. -# -org.springframework.ai.autoconfigure.chat.observation.ChatObservationAutoConfiguration diff --git a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/test/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfigurationTests.java b/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/test/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfigurationTests.java deleted file mode 100644 index d16010981..000000000 --- a/auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat/src/test/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfigurationTests.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.chat.observation; - -import io.micrometer.core.instrument.composite.CompositeMeterRegistry; -import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; -import io.micrometer.tracing.otel.bridge.OtelTracer; -import io.opentelemetry.api.OpenTelemetry; -import org.junit.jupiter.api.Test; - -import org.springframework.ai.chat.observation.ChatModelCompletionObservationFilter; -import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler; -import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationFilter; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ChatObservationAutoConfiguration}. - * - * @author Thomas Vitale - */ -class ChatObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ChatObservationAutoConfiguration.class)); - - @Test - void meterObservationHandlerEnabled() { - this.contextRunner.withBean(CompositeMeterRegistry.class) - .run(context -> assertThat(context).hasSingleBean(ChatModelMeterObservationHandler.class)); - } - - @Test - void meterObservationHandlerDisabled() { - this.contextRunner.run(context -> assertThat(context).doesNotHaveBean(ChatModelMeterObservationHandler.class)); - } - - @Test - void promptFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationFilter.class)); - } - - @Test - void promptHandlerDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationHandler.class)); - } - - @Test - void promptHandlerEnabled() { - this.contextRunner - .withBean(OtelTracer.class, OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null) - .withPropertyValues("spring.ai.chat.observations.include-prompt=true") - .run(context -> assertThat(context).hasSingleBean(ChatModelPromptContentObservationHandler.class)); - } - - @Test - void promptHandlerDisabled() { - this.contextRunner.withPropertyValues("spring.ai.chat.observations.include-prompt=true") - .run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationHandler.class)); - } - - @Test - void completionFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationFilter.class)); - } - - @Test - void completionHandlerDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationHandler.class)); - } - - @Test - void completionHandlerEnabled() { - this.contextRunner - .withBean(OtelTracer.class, OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null) - .withPropertyValues("spring.ai.chat.observations.include-completion=true") - .run(context -> assertThat(context).hasSingleBean(ChatModelCompletionObservationHandler.class)); - } - - @Test - void completionHandlerDisabled() { - this.contextRunner.withPropertyValues("spring.ai.chat.observations.include-completion=true") - .run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationHandler.class)); - } - -} diff --git a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/pom.xml b/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/pom.xml deleted file mode 100644 index 93f3fc00b..000000000 --- a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - 4.0.0 - - org.springframework.ai - spring-ai - 1.0.0-SNAPSHOT - ../../../../pom.xml - - spring-ai-autoconfigure-observation-embedding - jar - Spring AI Embedding Observation Auto Configuration - Spring AI Embedding Observation Auto Configuration - https://github.com/spring-projects/spring-ai - - - https://github.com/spring-projects/spring-ai - git://github.com/spring-projects/spring-ai.git - git@github.com:spring-projects/spring-ai.git - - - - - - - org.springframework.ai - spring-ai-core - ${parent.version} - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - org.springframework.ai - spring-ai-test - ${project.parent.version} - test - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.mockito - mockito-core - test - - - - diff --git a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfiguration.java b/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfiguration.java deleted file mode 100644 index ba1d61886..000000000 --- a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfiguration.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.embedding.observation; - -import io.micrometer.core.instrument.MeterRegistry; - -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.embedding.observation.EmbeddingModelMeterObservationHandler; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; - -/** - * Auto-configuration for Spring AI embedding model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration( - afterName = "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration") -@ConditionalOnClass(EmbeddingModel.class) -public class EmbeddingObservationAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(MeterRegistry.class) - EmbeddingModelMeterObservationHandler embeddingModelMeterObservationHandler( - ObjectProvider meterRegistry) { - return new EmbeddingModelMeterObservationHandler(meterRegistry.getObject()); - } - -} diff --git a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/package-info.java b/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/package-info.java deleted file mode 100644 index 2275a771e..000000000 --- a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023-2024 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. - */ - -/** - * Auto-configuration for embedding observation. - */ -@NonNullApi -@NonNullFields -package org.springframework.ai.autoconfigure.embedding.observation; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 162692d8a..000000000 --- a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright 2025-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. -# -org.springframework.ai.autoconfigure.embedding.observation.EmbeddingObservationAutoConfiguration diff --git a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/test/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfigurationTests.java b/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/test/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfigurationTests.java deleted file mode 100644 index 42037c9e0..000000000 --- a/auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding/src/test/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfigurationTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.embedding.observation; - -import io.micrometer.core.instrument.composite.CompositeMeterRegistry; -import org.junit.jupiter.api.Test; - -import org.springframework.ai.embedding.observation.EmbeddingModelMeterObservationHandler; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link EmbeddingObservationAutoConfiguration}. - * - * @author Thomas Vitale - */ -class EmbeddingObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(EmbeddingObservationAutoConfiguration.class)); - - @Test - void meterObservationHandlerEnabled() { - this.contextRunner.withBean(CompositeMeterRegistry.class) - .run(context -> assertThat(context).hasSingleBean(EmbeddingModelMeterObservationHandler.class)); - } - - @Test - void meterObservationHandlerDisabled() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(EmbeddingModelMeterObservationHandler.class)); - } - -} diff --git a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/pom.xml b/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/pom.xml deleted file mode 100644 index 2172c6c32..000000000 --- a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/pom.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - 4.0.0 - - org.springframework.ai - spring-ai - 1.0.0-SNAPSHOT - ../../../../pom.xml - - spring-ai-autoconfigure-observation-image - jar - Spring AI Image Observation Auto Configuration - Spring AI Image Observation Auto Configuration - https://github.com/spring-projects/spring-ai - - - https://github.com/spring-projects/spring-ai - git://github.com/spring-projects/spring-ai.git - git@github.com:spring-projects/spring-ai.git - - - - - - - org.springframework.ai - spring-ai-core - ${parent.version} - - - - - org.springframework.boot - spring-boot-starter - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - org.springframework.ai - spring-ai-test - ${project.parent.version} - test - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.mockito - mockito-core - test - - - - diff --git a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfiguration.java b/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfiguration.java deleted file mode 100644 index a89a42102..000000000 --- a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.image.observation; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.image.ImageModel; -import org.springframework.ai.image.observation.ImageModelPromptContentObservationFilter; -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.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -/** - * Auto-configuration for Spring AI image model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration( - afterName = "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration.class") -@ConditionalOnClass(ImageModel.class) -@EnableConfigurationProperties({ ImageObservationProperties.class }) -public class ImageObservationAutoConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(ImageObservationAutoConfiguration.class); - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ImageObservationProperties.CONFIG_PREFIX, name = "include-prompt", - havingValue = "true") - ImageModelPromptContentObservationFilter imageModelPromptObservationFilter() { - logger.warn( - "You have enabled the inclusion of the image prompt content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - return new ImageModelPromptContentObservationFilter(); - } - -} diff --git a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationProperties.java b/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationProperties.java deleted file mode 100644 index 3e454ee8d..000000000 --- a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationProperties.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.image.observation; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for image model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@ConfigurationProperties(ImageObservationProperties.CONFIG_PREFIX) -public class ImageObservationProperties { - - public static final String CONFIG_PREFIX = "spring.ai.image.observations"; - - /** - * Whether to include the prompt content in the observations. - */ - private boolean includePrompt = false; - - public boolean isIncludePrompt() { - return this.includePrompt; - } - - public void setIncludePrompt(boolean includePrompt) { - this.includePrompt = includePrompt; - } - -} diff --git a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/java/org/springframework/ai/autoconfigure/image/observation/package-info.java b/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/java/org/springframework/ai/autoconfigure/image/observation/package-info.java deleted file mode 100644 index 559f61df9..000000000 --- a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/java/org/springframework/ai/autoconfigure/image/observation/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023-2024 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. - */ - -/** - * Auto-configuration for image observation. - */ -@NonNullApi -@NonNullFields -package org.springframework.ai.autoconfigure.image.observation; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 78af4bcff..000000000 --- a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright 2025-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. -# -org.springframework.ai.autoconfigure.image.observation.ImageObservationAutoConfiguration diff --git a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/test/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfigurationTests.java b/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/test/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfigurationTests.java deleted file mode 100644 index deb0a22ad..000000000 --- a/auto-configurations/observation/image/spring-ai-autoconfigure-observation-image/src/test/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfigurationTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.image.observation; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.image.observation.ImageModelPromptContentObservationFilter; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ImageObservationAutoConfiguration}. - * - * @author Thomas Vitale - */ -class ImageObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ImageObservationAutoConfiguration.class)); - - @Test - void promptFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ImageModelPromptContentObservationFilter.class)); - } - - @Test - void promptFilterEnabled() { - this.contextRunner.withPropertyValues("spring.ai.image.observations.include-prompt=true") - .run(context -> assertThat(context).hasSingleBean(ImageModelPromptContentObservationFilter.class)); - } - -} diff --git a/pom.xml b/pom.xml index 6643a452d..a869c5626 100644 --- a/pom.xml +++ b/pom.xml @@ -91,8 +91,6 @@ auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-weaviate auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-pgvector - spring-ai-spring-boot-autoconfigure - spring-ai-retry spring-ai-spring-boot-docker-compose spring-ai-spring-boot-testcontainers diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModels.java b/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModels.java index f937d7671..b85d41e56 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModels.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModels.java @@ -46,11 +46,11 @@ public class SpringAIModels { public static final String QIANFAN = "qianfan"; - public static final String STABILITY = "stabilityai"; + public static final String STABILITY_AI = "stabilityai"; public static final String TRANSFORMERS = "transformers"; - public static final String VERTEX_AI = "vertex"; + public static final String VERTEX_AI = "vertexai"; public static final String WATSONX_AI = "watsonx"; diff --git a/spring-ai-integration-tests/pom.xml b/spring-ai-integration-tests/pom.xml index 948d8b624..303e31d7b 100644 --- a/spring-ai-integration-tests/pom.xml +++ b/spring-ai-integration-tests/pom.xml @@ -56,14 +56,14 @@ org.springframework.ai - spring-ai-openai-spring-boot-starter + spring-ai-starter-model-openai ${project.parent.version} test org.springframework.ai - spring-ai-pgvector-store-spring-boot-starter + spring-ai-starter-vector-store-pgvector ${project.parent.version} test diff --git a/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/TestcontainersConfiguration.java b/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/TestcontainersConfiguration.java index 2db1e5bc4..524dbf9f3 100644 --- a/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/TestcontainersConfiguration.java +++ b/spring-ai-integration-tests/src/test/java/org/springframework/ai/integration/tests/TestcontainersConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.ai.integration.tests; +import java.time.Duration; + import org.testcontainers.containers.PostgreSQLContainer; import org.springframework.boot.test.context.TestConfiguration; @@ -33,7 +35,7 @@ class TestcontainersConfiguration { @Bean @ServiceConnection PostgreSQLContainer pgvectorContainer() { - return new PostgreSQLContainer<>("pgvector/pgvector:pg17"); + return new PostgreSQLContainer<>("pgvector/pgvector:pg17").withStartupTimeout(Duration.ofMinutes(6)); } } diff --git a/spring-ai-spring-boot-autoconfigure/pom.xml b/spring-ai-spring-boot-autoconfigure/pom.xml deleted file mode 100644 index 3cb3683fe..000000000 --- a/spring-ai-spring-boot-autoconfigure/pom.xml +++ /dev/null @@ -1,365 +0,0 @@ - - - 4.0.0 - - org.springframework.ai - spring-ai - 1.0.0-SNAPSHOT - - spring-ai-spring-boot-autoconfigure - jar - Spring AI Auto Configuration - Spring AI Auto Configuration - https://github.com/spring-projects/spring-ai - - - https://github.com/spring-projects/spring-ai - git://github.com/spring-projects/spring-ai.git - git@github.com:spring-projects/spring-ai.git - - - - - - com.google.protobuf - protobuf-java - ${protobuf-java.version} - true - - - - - - org.springframework.boot - spring-boot-starter - - - - io.micrometer - micrometer-tracing-bridge-otel - true - - - - - org.springframework.ai - spring-ai-mcp - ${project.parent.version} - true - - - - io.modelcontextprotocol.sdk - mcp-spring-webflux - ${mcp.sdk.version} - true - - - - io.modelcontextprotocol.sdk - mcp-spring-webmvc - ${mcp.sdk.version} - true - - - - - org.springframework.ai - spring-ai-openai - ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-postgresml - ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-azure-openai - ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-oci-genai - ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-huggingface - ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-ollama - ${project.parent.version} - true - - - - - org.springframework.ai - spring-ai-transformers - ${project.parent.version} - true - - - - - io.netty - netty-codec-http2 - 4.1.100.Final - true - - - - - org.springframework.ai - spring-ai-cassandra-store - ${project.parent.version} - true - - - - - org.springframework.ai - spring-ai-vertex-ai-embedding - ${project.parent.version} - true - - - - - org.springframework.ai - spring-ai-vertex-ai-gemini - ${project.parent.version} - true - - - - - org.springframework.ai - spring-ai-stability-ai - ${project.parent.version} - true - - - - - org.springframework.ai - spring-ai-watsonx-ai - ${project.parent.version} - true - - - - - org.springframework.ai - spring-ai-bedrock - ${project.parent.version} - true - - - - - org.springframework.ai - spring-ai-bedrock-converse - ${project.parent.version} - true - - - - - org.springframework.ai - spring-ai-mistral-ai - ${project.parent.version} - true - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.ai - spring-ai-anthropic - ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-minimax - ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-zhipuai - ${project.parent.version} - true - - - - - org.springframework.ai - spring-ai-moonshot - ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-qianfan - ${project.parent.version} - true - - - - software.amazon.awssdk - apache-client - ${awssdk.version} - true - - - - - org.springframework.ai - spring-ai-azure-cosmos-db-store - ${project.parent.version} - true - - - - org.springframework.ai - spring-ai-couchbase-store - ${project.parent.version} - true - - - - - - org.springframework.ai - spring-ai-test - ${project.parent.version} - test - - - - com.oracle.oci.sdk - oci-java-sdk-common - ${oci-sdk-version} - test - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.boot - spring-boot-starter-jdbc - test - - - - org.springframework.boot - spring-boot-testcontainers - test - - - - org.testcontainers - testcontainers - test - - - - org.testcontainers - ollama - test - - - - org.testcontainers - postgresql - test - - - - org.testcontainers - junit-jupiter - test - - - - com.redis - testcontainers-redis - 2.2.0 - test - - - - org.awaitility - awaitility - test - - - - org.testcontainers - localstack - test - - - - org.skyscreamer - jsonassert - 1.5.0 - test - - - - io.micrometer - micrometer-observation-test - test - - - - com.fasterxml.jackson.module - jackson-module-kotlin - test - - - - org.testcontainers - couchbase - test - - - - org.testcontainers - cassandra - test - - - - - diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfiguration.java deleted file mode 100644 index f439263fd..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfiguration.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.chat.observation; - -import java.util.List; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.otel.bridge.OtelTracer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationContext; -import org.springframework.ai.chat.client.observation.ChatClientObservationContext; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.observation.ChatModelCompletionObservationFilter; -import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler; -import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler; -import org.springframework.ai.chat.observation.ChatModelObservationContext; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationFilter; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext; -import org.springframework.ai.image.observation.ImageModelObservationContext; -import org.springframework.ai.model.observation.ErrorLoggingObservationHandler; -import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Auto-configuration for Spring AI chat model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration( - afterName = { "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration" }) -@ConditionalOnClass(ChatModel.class) -@EnableConfigurationProperties({ ChatObservationProperties.class }) -public class ChatObservationAutoConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(ChatObservationAutoConfiguration.class); - - private static void logPromptContentWarning() { - logger.warn( - "You have enabled the inclusion of the prompt content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - } - - private static void logCompletionWarning() { - logger.warn( - "You have enabled the inclusion of the completion content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(MeterRegistry.class) - ChatModelMeterObservationHandler chatModelMeterObservationHandler(ObjectProvider meterRegistry) { - return new ChatModelMeterObservationHandler(meterRegistry.getObject()); - } - - /** - * The chat content is typically too big to be included in an observation as span - * attributes. That's why the preferred way to store it is as span events, which are - * supported by OpenTelemetry but not yet surfaced through the Micrometer APIs. This - * primary/fallback configuration is a temporary solution until - * https://github.com/micrometer-metrics/micrometer/issues/5238 is delivered. - */ - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(OtelTracer.class) - @ConditionalOnBean(OtelTracer.class) - static class PrimaryChatContentObservationConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-prompt", - havingValue = "true") - ChatModelPromptContentObservationHandler chatModelPromptContentObservationHandler() { - logPromptContentWarning(); - return new ChatModelPromptContentObservationHandler(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-completion", - havingValue = "true") - ChatModelCompletionObservationHandler chatModelCompletionObservationHandler() { - logCompletionWarning(); - return new ChatModelCompletionObservationHandler(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingClass("io.micrometer.tracing.otel.bridge.OtelTracer") - static class FallbackChatContentObservationConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-prompt", - havingValue = "true") - ChatModelPromptContentObservationFilter chatModelPromptObservationFilter() { - logPromptContentWarning(); - return new ChatModelPromptContentObservationFilter(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-completion", - havingValue = "true") - ChatModelCompletionObservationFilter chatModelCompletionObservationFilter() { - logCompletionWarning(); - return new ChatModelCompletionObservationFilter(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Tracer.class) - @ConditionalOnBean(Tracer.class) - static class TracingChatContentObservationConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-error-logging", - havingValue = "true") - public ErrorLoggingObservationHandler errorLoggingObservationHandler(Tracer tracer) { - return new ErrorLoggingObservationHandler(tracer, - List.of(EmbeddingModelObservationContext.class, ImageModelObservationContext.class, - ChatModelObservationContext.class, ChatClientObservationContext.class, - AdvisorObservationContext.class, VectorStoreObservationContext.class)); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationProperties.java deleted file mode 100644 index cd353ac0b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationProperties.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.chat.observation; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for chat model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@ConfigurationProperties(ChatObservationProperties.CONFIG_PREFIX) -public class ChatObservationProperties { - - public static final String CONFIG_PREFIX = "spring.ai.chat.observations"; - - /** - * Whether to include the completion content in the observations. - */ - private boolean includeCompletion = false; - - /** - * Whether to include the prompt content in the observations. - */ - private boolean includePrompt = false; - - /** - * Whether to include error logging in the observations. - */ - private boolean includeErrorLogging = false; - - public boolean isIncludeCompletion() { - return this.includeCompletion; - } - - public void setIncludeCompletion(boolean includeCompletion) { - this.includeCompletion = includeCompletion; - } - - public boolean isIncludePrompt() { - return this.includePrompt; - } - - public void setIncludePrompt(boolean includePrompt) { - this.includePrompt = includePrompt; - } - - public boolean isIncludeErrorLogging() { - return this.includeErrorLogging; - } - - public void setIncludeErrorLogging(boolean includeErrorLogging) { - this.includeErrorLogging = includeErrorLogging; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/observation/package-info.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/observation/package-info.java deleted file mode 100644 index 6032da63a..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/chat/observation/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023-2024 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. - */ - -/** - * Auto-configuration for chat observation. - */ -@NonNullApi -@NonNullFields -package org.springframework.ai.autoconfigure.chat.observation; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfiguration.java deleted file mode 100644 index ba1d61886..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfiguration.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.embedding.observation; - -import io.micrometer.core.instrument.MeterRegistry; - -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.embedding.observation.EmbeddingModelMeterObservationHandler; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; - -/** - * Auto-configuration for Spring AI embedding model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration( - afterName = "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration") -@ConditionalOnClass(EmbeddingModel.class) -public class EmbeddingObservationAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(MeterRegistry.class) - EmbeddingModelMeterObservationHandler embeddingModelMeterObservationHandler( - ObjectProvider meterRegistry) { - return new EmbeddingModelMeterObservationHandler(meterRegistry.getObject()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/package-info.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/package-info.java deleted file mode 100644 index 2275a771e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/embedding/observation/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023-2024 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. - */ - -/** - * Auto-configuration for embedding observation. - */ -@NonNullApi -@NonNullFields -package org.springframework.ai.autoconfigure.embedding.observation; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfiguration.java deleted file mode 100644 index a89a42102..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.image.observation; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.image.ImageModel; -import org.springframework.ai.image.observation.ImageModelPromptContentObservationFilter; -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.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -/** - * Auto-configuration for Spring AI image model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration( - afterName = "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration.class") -@ConditionalOnClass(ImageModel.class) -@EnableConfigurationProperties({ ImageObservationProperties.class }) -public class ImageObservationAutoConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(ImageObservationAutoConfiguration.class); - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ImageObservationProperties.CONFIG_PREFIX, name = "include-prompt", - havingValue = "true") - ImageModelPromptContentObservationFilter imageModelPromptObservationFilter() { - logger.warn( - "You have enabled the inclusion of the image prompt content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - return new ImageModelPromptContentObservationFilter(); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationProperties.java deleted file mode 100644 index 3e454ee8d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationProperties.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.image.observation; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for image model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@ConfigurationProperties(ImageObservationProperties.CONFIG_PREFIX) -public class ImageObservationProperties { - - public static final String CONFIG_PREFIX = "spring.ai.image.observations"; - - /** - * Whether to include the prompt content in the observations. - */ - private boolean includePrompt = false; - - public boolean isIncludePrompt() { - return this.includePrompt; - } - - public void setIncludePrompt(boolean includePrompt) { - this.includePrompt = includePrompt; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/image/observation/package-info.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/image/observation/package-info.java deleted file mode 100644 index 559f61df9..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/image/observation/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023-2024 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. - */ - -/** - * Auto-configuration for image observation. - */ -@NonNullApi -@NonNullFields -package org.springframework.ai.autoconfigure.image.observation; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatProperties.java deleted file mode 100644 index 95ddedd7e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicChatProperties.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.anthropic.autoconfigure; - -import org.springframework.ai.anthropic.AnthropicChatModel; -import org.springframework.ai.anthropic.AnthropicChatOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Anthropic Chat autoconfiguration properties. - * - * @author Christian Tzolov - * @author Alexandros Pappas - * @since 1.0.0 - */ -@ConfigurationProperties(AnthropicChatProperties.CONFIG_PREFIX) -public class AnthropicChatProperties { - - public static final String CONFIG_PREFIX = "spring.ai.anthropic.chat"; - - /** - * Enable Anthropic chat model. - */ - private boolean enabled = true; - - /** - * Client lever Ollama options. Use this property to configure generative temperature, - * topK and topP and alike parameters. The null values are ignored defaulting to the - * generative's defaults. - */ - @NestedConfigurationProperty - private AnthropicChatOptions options = AnthropicChatOptions.builder() - .model(AnthropicChatModel.DEFAULT_MODEL_NAME) - .maxTokens(AnthropicChatModel.DEFAULT_MAX_TOKENS) - .temperature(AnthropicChatModel.DEFAULT_TEMPERATURE) - .build(); - - public AnthropicChatOptions getOptions() { - return this.options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicConnectionProperties.java deleted file mode 100644 index 7f56f2d05..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicConnectionProperties.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.anthropic.autoconfigure; - -import org.springframework.ai.anthropic.api.AnthropicApi; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Anthropic API connection properties. - * - * @author Christian Tzolov - * @since 1.0.0 - */ -@ConfigurationProperties(AnthropicConnectionProperties.CONFIG_PREFIX) -public class AnthropicConnectionProperties { - - public static final String CONFIG_PREFIX = "spring.ai.anthropic"; - - /** - * Anthropic API access key. - */ - private String apiKey; - - /** - * Anthropic API base URL. - */ - private String baseUrl = AnthropicApi.DEFAULT_BASE_URL; - - /** - * Anthropic API version. - */ - private String version = AnthropicApi.DEFAULT_ANTHROPIC_VERSION; - - /** - * Beta features version. Such as tools-2024-04-04 or - * max-tokens-3-5-sonnet-2024-07-15. - */ - private String betaVersion = AnthropicApi.DEFAULT_ANTHROPIC_BETA_VERSION; - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - - public String getVersion() { - return this.version; - } - - public void setVersion(String version) { - this.version = version; - } - - public String getBetaVersion() { - return this.betaVersion; - } - - public void setBetaVersion(String betaVersion) { - this.betaVersion = betaVersion; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAIClientBuilderCustomizer.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAIClientBuilderCustomizer.java deleted file mode 100644 index 0d4e3061b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAIClientBuilderCustomizer.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.springframework.ai.model.azure.openai.autoconfigure; - -import com.azure.ai.openai.OpenAIClientBuilder; - -/** - * Callback interface that can be implemented by beans wishing to customize the - * {@link OpenAIClientBuilder} whilst retaining the default auto-configuration. - * - * @author Manuel Andreo Garcia - * @since 1.0.0-M6 - */ -@FunctionalInterface -public interface AzureOpenAIClientBuilderCustomizer { - - /** - * Customize the {@link OpenAIClientBuilder}. - * @param clientBuilder the {@link OpenAIClientBuilder} to customize - */ - void customize(OpenAIClientBuilder clientBuilder); - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAudioTranscriptionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAudioTranscriptionProperties.java deleted file mode 100644 index 8a1fe78db..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAudioTranscriptionProperties.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; - -import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for Azure OpenAI audio transcription. - * - * @author Piotr Olaszewski - */ -@ConfigurationProperties(AzureOpenAiAudioTranscriptionProperties.CONFIG_PREFIX) -public class AzureOpenAiAudioTranscriptionProperties { - - public static final String CONFIG_PREFIX = "spring.ai.azure.openai.audio.transcription"; - - /** - * Enable AzureOpenAI audio transcription model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private AzureOpenAiAudioTranscriptionOptions options = AzureOpenAiAudioTranscriptionOptions.builder().build(); - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public AzureOpenAiAudioTranscriptionOptions getOptions() { - return this.options; - } - - public void setOptions(AzureOpenAiAudioTranscriptionOptions options) { - this.options = options; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatProperties.java deleted file mode 100644 index 42c3a2c2a..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatProperties.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; - -import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -@ConfigurationProperties(AzureOpenAiChatProperties.CONFIG_PREFIX) -public class AzureOpenAiChatProperties { - - public static final String CONFIG_PREFIX = "spring.ai.azure.openai.chat"; - - public static final String DEFAULT_DEPLOYMENT_NAME = "gpt-4o"; - - private static final Double DEFAULT_TEMPERATURE = 0.7; - - /** - * Enable Azure OpenAI chat model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private AzureOpenAiChatOptions options = AzureOpenAiChatOptions.builder() - .deploymentName(DEFAULT_DEPLOYMENT_NAME) - .temperature(DEFAULT_TEMPERATURE) - .build(); - - public AzureOpenAiChatOptions getOptions() { - return this.options; - } - - public void setOptions(AzureOpenAiChatOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiConnectionProperties.java deleted file mode 100644 index a3c2191cc..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiConnectionProperties.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; - -import java.util.HashMap; -import java.util.Map; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(AzureOpenAiConnectionProperties.CONFIG_PREFIX) -public class AzureOpenAiConnectionProperties { - - public static final String CONFIG_PREFIX = "spring.ai.azure.openai"; - - /** - * Azure OpenAI API key. From the Azure AI OpenAI `Keys and Endpoint` section under - * `Resource Management`. - */ - private String apiKey; - - /** - * (non Azure) OpenAI API key. Used to authenticate with the OpenAI service, instead - * of Azure OpenAI. This automatically sets the endpoint to https://api.openai.com/v1. - */ - private String openAiApiKey; - - /** - * Azure OpenAI API endpoint. From the Azure AI OpenAI `Keys and Endpoint` section - * under `Resource Management`. - */ - private String endpoint; - - private Map customHeaders = new HashMap<>(); - - public String getEndpoint() { - return this.endpoint; - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - } - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getOpenAiApiKey() { - return this.openAiApiKey; - } - - public void setOpenAiApiKey(String openAiApiKey) { - this.openAiApiKey = openAiApiKey; - } - - public Map getCustomHeaders() { - return this.customHeaders; - } - - public void setCustomHeaders(Map customHeaders) { - this.customHeaders = customHeaders; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingProperties.java deleted file mode 100644 index a0d4697fa..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingProperties.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; - -import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingOptions; -import org.springframework.ai.document.MetadataMode; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; -import org.springframework.util.Assert; - -@ConfigurationProperties(AzureOpenAiEmbeddingProperties.CONFIG_PREFIX) -public class AzureOpenAiEmbeddingProperties { - - public static final String CONFIG_PREFIX = "spring.ai.azure.openai.embedding"; - - /** - * Enable Azure OpenAI embedding model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private AzureOpenAiEmbeddingOptions options = AzureOpenAiEmbeddingOptions.builder() - .deploymentName("text-embedding-ada-002") - .build(); - - private MetadataMode metadataMode = MetadataMode.EMBED; - - public AzureOpenAiEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(AzureOpenAiEmbeddingOptions options) { - Assert.notNull(options, "Options must not be null"); - this.options = options; - } - - public MetadataMode getMetadataMode() { - return this.metadataMode; - } - - public void setMetadataMode(MetadataMode metadataMode) { - Assert.notNull(metadataMode, "Metadata mode must not be null"); - this.metadataMode = metadataMode; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiImageOptionsProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiImageOptionsProperties.java deleted file mode 100644 index 51465008b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiImageOptionsProperties.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; - -import org.springframework.ai.azure.openai.AzureOpenAiImageOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for Azure OpenAI image generation options. - * - * @author Benoit Moussaud - * @since 1.0.0 M1 - */ -@ConfigurationProperties(AzureOpenAiImageOptionsProperties.CONFIG_PREFIX) -public class AzureOpenAiImageOptionsProperties { - - public static final String CONFIG_PREFIX = "spring.ai.azure.openai.image"; - - /** - * Enable Azure OpenAI chat client. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private AzureOpenAiImageOptions options = AzureOpenAiImageOptions.builder().build(); - - public AzureOpenAiImageOptions getOptions() { - return this.options; - } - - public void setOptions(AzureOpenAiImageOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionConfiguration.java deleted file mode 100644 index f6abd70db..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionConfiguration.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure; - -import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; -import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; -import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; -import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.StringUtils; - -/** - * {@link Configuration} for AWS connection. - * - * @author Christian Tzolov - * @author Wei Jiang - */ -@Configuration -@EnableConfigurationProperties({ BedrockAwsConnectionProperties.class }) -public class BedrockAwsConnectionConfiguration { - - @Bean - @ConditionalOnMissingBean - public AwsCredentialsProvider credentialsProvider(BedrockAwsConnectionProperties properties) { - - if (StringUtils.hasText(properties.getAccessKey()) && StringUtils.hasText(properties.getSecretKey())) { - - if (StringUtils.hasText(properties.getSessionToken())) { - return StaticCredentialsProvider.create(AwsSessionCredentials.create(properties.getAccessKey(), - properties.getSecretKey(), properties.getSessionToken())); - } - - return StaticCredentialsProvider - .create(AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey())); - } - - return DefaultCredentialsProvider.create(); - } - - @Bean - @ConditionalOnMissingBean - public AwsRegionProvider regionProvider(BedrockAwsConnectionProperties properties) { - - if (StringUtils.hasText(properties.getRegion())) { - return new StaticRegionProvider(properties.getRegion()); - } - - return DefaultAwsRegionProviderChain.builder().build(); - } - - static class StaticRegionProvider implements AwsRegionProvider { - - private final Region region; - - StaticRegionProvider(String region) { - try { - this.region = Region.of(region); - } - catch (IllegalArgumentException e) { - throw new IllegalArgumentException("The region '" + region + "' is not a valid region!", e); - } - } - - @Override - public Region getRegion() { - return this.region; - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionProperties.java deleted file mode 100644 index cc47b675b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionProperties.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure; - -import java.time.Duration; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for Bedrock AWS connection. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -@ConfigurationProperties(BedrockAwsConnectionProperties.CONFIG_PREFIX) -public class BedrockAwsConnectionProperties { - - public static final String CONFIG_PREFIX = "spring.ai.bedrock.aws"; - - /** - * AWS region to use. Defaults to us-east-1. - */ - private String region = "us-east-1"; - - /** - * AWS access key. - */ - private String accessKey; - - /** - * AWS secret key. - */ - private String secretKey; - - /** - * AWS session token. (optional) When provided the AwsSessionCredentials are used. - * Otherwise the AwsBasicCredentials are used. - */ - private String sessionToken; - - /** - * Set model timeout, Defaults 5 min. - */ - private Duration timeout = Duration.ofMinutes(5L); - - public String getRegion() { - return this.region; - } - - public void setRegion(String awsRegion) { - this.region = awsRegion; - } - - public String getAccessKey() { - return this.accessKey; - } - - public void setAccessKey(String accessKey) { - this.accessKey = accessKey; - } - - public String getSecretKey() { - return this.secretKey; - } - - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - - public Duration getTimeout() { - return this.timeout; - } - - public void setTimeout(Duration timeout) { - this.timeout = timeout; - } - - public String getSessionToken() { - return this.sessionToken; - } - - public void setSessionToken(String sessionToken) { - this.sessionToken = sessionToken; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java deleted file mode 100644 index ce60731c0..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.cohere; - -import com.fasterxml.jackson.databind.ObjectMapper; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; - -import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionConfiguration; -import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionProperties; -import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingModel; -import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -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.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -/** - * {@link AutoConfiguration Auto-configuration} for Bedrock Cohere Embedding Model. - * - * @author Christian Tzolov - * @author Wei Jiang - * @since 0.8.0 - */ -@AutoConfiguration -@ConditionalOnClass(CohereEmbeddingBedrockApi.class) -@EnableConfigurationProperties({ BedrockCohereEmbeddingProperties.class, BedrockAwsConnectionProperties.class }) -@ConditionalOnProperty(prefix = BedrockCohereEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") -@Import(BedrockAwsConnectionConfiguration.class) -public class BedrockCohereEmbeddingAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public CohereEmbeddingBedrockApi cohereEmbeddingApi(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockCohereEmbeddingProperties properties, - BedrockAwsConnectionProperties awsProperties, ObjectMapper objectMapper) { - return new CohereEmbeddingBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - objectMapper, awsProperties.getTimeout()); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(CohereEmbeddingBedrockApi.class) - public BedrockCohereEmbeddingModel cohereEmbeddingModel(CohereEmbeddingBedrockApi cohereEmbeddingApi, - BedrockCohereEmbeddingProperties properties) { - - return new BedrockCohereEmbeddingModel(cohereEmbeddingApi, properties.getOptions()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingProperties.java deleted file mode 100644 index 131a9832a..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingProperties.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.cohere; - -import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingOptions; -import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingModel; -import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingRequest; -import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingRequest.InputType; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Bedrock Cohere Embedding autoconfiguration properties. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -@ConfigurationProperties(BedrockCohereEmbeddingProperties.CONFIG_PREFIX) -public class BedrockCohereEmbeddingProperties { - - public static final String CONFIG_PREFIX = "spring.ai.bedrock.cohere.embedding"; - - /** - * Enable Bedrock Cohere Embedding Model. False by default. - */ - private boolean enabled = false; - - /** - * Bedrock Cohere Embedding generative name. Defaults to - * 'cohere.embed-multilingual-v3'. - */ - private String model = CohereEmbeddingModel.COHERE_EMBED_MULTILINGUAL_V3.id(); - - @NestedConfigurationProperty - private BedrockCohereEmbeddingOptions options = BedrockCohereEmbeddingOptions.builder() - .inputType(InputType.SEARCH_DOCUMENT) - .truncate(CohereEmbeddingRequest.Truncate.NONE) - .build(); - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getModel() { - return this.model; - } - - public void setModel(String model) { - this.model = model; - } - - public BedrockCohereEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(BedrockCohereEmbeddingOptions options) { - this.options = options; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java deleted file mode 100644 index 1cfea4c07..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.titan; - -import com.fasterxml.jackson.databind.ObjectMapper; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; - -import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionConfiguration; -import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionProperties; -import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel; -import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -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.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -/** - * {@link AutoConfiguration Auto-configuration} for Bedrock Titan Embedding Model. - * - * @author Christian Tzolov - * @author Wei Jiang - * @since 0.8.0 - */ -@AutoConfiguration -@ConditionalOnClass(TitanEmbeddingBedrockApi.class) -@EnableConfigurationProperties({ BedrockTitanEmbeddingProperties.class, BedrockAwsConnectionProperties.class }) -@ConditionalOnProperty(prefix = BedrockTitanEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") -@Import(BedrockAwsConnectionConfiguration.class) -public class BedrockTitanEmbeddingAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public TitanEmbeddingBedrockApi titanEmbeddingBedrockApi(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockTitanEmbeddingProperties properties, - BedrockAwsConnectionProperties awsProperties, ObjectMapper objectMapper) { - return new TitanEmbeddingBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - objectMapper, awsProperties.getTimeout()); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(TitanEmbeddingBedrockApi.class) - public BedrockTitanEmbeddingModel titanEmbeddingModel(TitanEmbeddingBedrockApi titanEmbeddingApi, - BedrockTitanEmbeddingProperties properties) { - - return new BedrockTitanEmbeddingModel(titanEmbeddingApi).withInputType(properties.getInputType()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfiguration.java deleted file mode 100644 index 89d790a9f..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfiguration.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.client.autoconfigure; - -import io.micrometer.observation.ObservationRegistry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.chat.client.ChatClientCustomizer; -import org.springframework.ai.chat.client.observation.ChatClientInputContentObservationFilter; -import org.springframework.ai.chat.client.observation.ChatClientObservationConvention; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -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.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Scope; - -/** - * {@link EnableAutoConfiguration Auto-configuration} for {@link ChatClient}. - *

- * This will produce a {@link ChatClient.Builder ChatClient.Builder} bean with the - * {@code prototype} scope, meaning each injection point will receive a newly cloned - * instance of the builder. - * - * @author Christian Tzolov - * @author Mark Pollack - * @author Josh Long - * @author Arjen Poutsma - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration -@ConditionalOnClass(ChatClient.class) -@EnableConfigurationProperties(ChatClientBuilderProperties.class) -@ConditionalOnProperty(prefix = ChatClientBuilderProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) -public class ChatClientAutoConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(ChatClientAutoConfiguration.class); - - @Bean - @ConditionalOnMissingBean - ChatClientBuilderConfigurer chatClientBuilderConfigurer(ObjectProvider customizerProvider) { - ChatClientBuilderConfigurer configurer = new ChatClientBuilderConfigurer(); - configurer.setChatClientCustomizers(customizerProvider.orderedStream().toList()); - return configurer; - } - - @Bean - @Scope("prototype") - @ConditionalOnMissingBean - ChatClient.Builder chatClientBuilder(ChatClientBuilderConfigurer chatClientBuilderConfigurer, ChatModel chatModel, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - ChatClient.Builder builder = ChatClient.builder(chatModel, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP), - observationConvention.getIfUnique(() -> null)); - return chatClientBuilderConfigurer.configure(builder); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatClientBuilderProperties.CONFIG_PREFIX + ".observations", name = "include-input", - havingValue = "true") - ChatClientInputContentObservationFilter chatClientInputContentObservationFilter() { - logger.warn( - "You have enabled the inclusion of the input content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - return new ChatClientInputContentObservationFilter(); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderConfigurer.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderConfigurer.java deleted file mode 100644 index f0448058c..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderConfigurer.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.client.autoconfigure; - -import java.util.List; - -import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.chat.client.ChatClientCustomizer; - -/** - * Builder for configuring a {@link ChatClient.Builder}. - * - * @author Christian Tzolov - * @author Mark Pollack - * @author Josh Long - * @author Arjen Poutsma - * @since 1.0.0 M1 - */ -public class ChatClientBuilderConfigurer { - - private List customizers; - - void setChatClientCustomizers(List customizers) { - this.customizers = customizers; - } - - /** - * Configure the specified {@link ChatClient.Builder}. The builder can be further - * tuned and default settings can be overridden. - * @param builder the {@link ChatClient.Builder} instance to configure - * @return the configured builder - */ - public ChatClient.Builder configure(ChatClient.Builder builder) { - applyCustomizers(builder); - return builder; - } - - private void applyCustomizers(ChatClient.Builder builder) { - if (this.customizers != null) { - for (ChatClientCustomizer customizer : this.customizers) { - customizer.customize(builder); - } - } - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderProperties.java deleted file mode 100644 index 0ca9023d7..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderProperties.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.client.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for the chat client builder. - * - * @author Christian Tzolov - * @author Mark Pollack - * @author Josh Long - * @author Arjen Poutsma - * @since 1.0.0 - */ -@ConfigurationProperties(ChatClientBuilderProperties.CONFIG_PREFIX) -public class ChatClientBuilderProperties { - - public static final String CONFIG_PREFIX = "spring.ai.chat.client"; - - /** - * Enable chat client builder. - */ - private boolean enabled = true; - - private Observations observations = new Observations(); - - public Observations getObservations() { - return this.observations; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public static class Observations { - - /** - * Whether to include the input content in the observations. - */ - private boolean includeInput = false; - - public boolean isIncludeInput() { - return this.includeInput; - } - - public void setIncludeInput(boolean includeCompletion) { - this.includeInput = includeCompletion; - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/CommonChatMemoryProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/CommonChatMemoryProperties.java deleted file mode 100644 index 00ea424c4..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/CommonChatMemoryProperties.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.memory.autoconfigure; - -/** - * Configuration properties for the common chat memory. - * - * @author Mick Semb Wever - * @since 1.0.0 - */ -public class CommonChatMemoryProperties { - - private boolean initializeSchema = true; - - public boolean isInitializeSchema() { - return this.initializeSchema; - } - - public void setInitializeSchema(boolean initializeSchema) { - this.initializeSchema = initializeSchema; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryAutoConfiguration.java deleted file mode 100644 index 937a49beb..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryAutoConfiguration.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.memory.autoconfigure.cassandra; - -import com.datastax.oss.driver.api.core.CqlSession; - -import org.springframework.ai.chat.memory.cassandra.CassandraChatMemory; -import org.springframework.ai.chat.memory.cassandra.CassandraChatMemoryConfig; -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.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -/** - * {@link AutoConfiguration Auto-configuration} for {@link CassandraChatMemory}. - * - * @author Mick Semb Wever - * @author Jihoon Kim - * @since 1.0.0 - */ -@AutoConfiguration(after = CassandraAutoConfiguration.class) -@ConditionalOnClass({ CassandraChatMemory.class, CqlSession.class }) -@EnableConfigurationProperties(CassandraChatMemoryProperties.class) -public class CassandraChatMemoryAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public CassandraChatMemory chatMemory(CassandraChatMemoryProperties properties, CqlSession cqlSession) { - - var builder = CassandraChatMemoryConfig.builder().withCqlSession(cqlSession); - - builder = builder.withKeyspaceName(properties.getKeyspace()) - .withTableName(properties.getTable()) - .withAssistantColumnName(properties.getAssistantColumn()) - .withUserColumnName(properties.getUserColumn()); - - if (!properties.isInitializeSchema()) { - builder = builder.disallowSchemaChanges(); - } - if (null != properties.getTimeToLive()) { - builder = builder.withTimeToLive(properties.getTimeToLive()); - } - - return CassandraChatMemory.create(builder.build()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryProperties.java deleted file mode 100644 index 14ae105ab..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryProperties.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.memory.autoconfigure.cassandra; - -import java.time.Duration; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.chat.memory.autoconfigure.CommonChatMemoryProperties; -import org.springframework.ai.chat.memory.cassandra.CassandraChatMemoryConfig; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.lang.Nullable; - -/** - * Configuration properties for Cassandra chat memory. - * - * @author Mick Semb Wever - * @author Jihoon Kim - * @since 1.0.0 - */ -@ConfigurationProperties(CassandraChatMemoryProperties.CONFIG_PREFIX) -public class CassandraChatMemoryProperties extends CommonChatMemoryProperties { - - public static final String CONFIG_PREFIX = "spring.ai.chat.memory.cassandra"; - - private static final Logger logger = LoggerFactory.getLogger(CassandraChatMemoryProperties.class); - - private String keyspace = CassandraChatMemoryConfig.DEFAULT_KEYSPACE_NAME; - - private String table = CassandraChatMemoryConfig.DEFAULT_TABLE_NAME; - - private String assistantColumn = CassandraChatMemoryConfig.DEFAULT_ASSISTANT_COLUMN_NAME; - - private String userColumn = CassandraChatMemoryConfig.DEFAULT_USER_COLUMN_NAME; - - private Duration timeToLive = null; - - public String getKeyspace() { - return this.keyspace; - } - - public void setKeyspace(String keyspace) { - this.keyspace = keyspace; - } - - public String getTable() { - return this.table; - } - - public void setTable(String table) { - this.table = table; - } - - public String getAssistantColumn() { - return this.assistantColumn; - } - - public void setAssistantColumn(String assistantColumn) { - this.assistantColumn = assistantColumn; - } - - public String getUserColumn() { - return this.userColumn; - } - - public void setUserColumn(String userColumn) { - this.userColumn = userColumn; - } - - @Nullable - public Duration getTimeToLive() { - return this.timeToLive; - } - - public void setTimeToLive(Duration timeToLive) { - this.timeToLive = timeToLive; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfiguration.java deleted file mode 100644 index 81aee1992..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfiguration.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.observation.autoconfigure; - -import java.util.List; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.otel.bridge.OtelTracer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationContext; -import org.springframework.ai.chat.client.observation.ChatClientObservationContext; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.observation.ChatModelCompletionObservationFilter; -import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler; -import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler; -import org.springframework.ai.chat.observation.ChatModelObservationContext; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationFilter; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext; -import org.springframework.ai.image.observation.ImageModelObservationContext; -import org.springframework.ai.model.observation.ErrorLoggingObservationHandler; -import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Auto-configuration for Spring AI chat model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration( - afterName = { "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration" }) -@ConditionalOnClass(ChatModel.class) -@EnableConfigurationProperties({ ChatObservationProperties.class }) -public class ChatObservationAutoConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(ChatObservationAutoConfiguration.class); - - private static void logPromptContentWarning() { - logger.warn( - "You have enabled the inclusion of the prompt content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - } - - private static void logCompletionWarning() { - logger.warn( - "You have enabled the inclusion of the completion content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(MeterRegistry.class) - ChatModelMeterObservationHandler chatModelMeterObservationHandler(ObjectProvider meterRegistry) { - return new ChatModelMeterObservationHandler(meterRegistry.getObject()); - } - - /** - * The chat content is typically too big to be included in an observation as span - * attributes. That's why the preferred way to store it is as span events, which are - * supported by OpenTelemetry but not yet surfaced through the Micrometer APIs. This - * primary/fallback configuration is a temporary solution until - * https://github.com/micrometer-metrics/micrometer/issues/5238 is delivered. - */ - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(OtelTracer.class) - @ConditionalOnBean(OtelTracer.class) - static class PrimaryChatContentObservationConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-prompt", - havingValue = "true") - ChatModelPromptContentObservationHandler chatModelPromptContentObservationHandler() { - logPromptContentWarning(); - return new ChatModelPromptContentObservationHandler(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-completion", - havingValue = "true") - ChatModelCompletionObservationHandler chatModelCompletionObservationHandler() { - logCompletionWarning(); - return new ChatModelCompletionObservationHandler(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingClass("io.micrometer.tracing.otel.bridge.OtelTracer") - static class FallbackChatContentObservationConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-prompt", - havingValue = "true") - ChatModelPromptContentObservationFilter chatModelPromptObservationFilter() { - logPromptContentWarning(); - return new ChatModelPromptContentObservationFilter(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-completion", - havingValue = "true") - ChatModelCompletionObservationFilter chatModelCompletionObservationFilter() { - logCompletionWarning(); - return new ChatModelCompletionObservationFilter(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Tracer.class) - @ConditionalOnBean(Tracer.class) - static class TracingChatContentObservationConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-error-logging", - havingValue = "true") - public ErrorLoggingObservationHandler errorLoggingObservationHandler(Tracer tracer) { - return new ErrorLoggingObservationHandler(tracer, - List.of(EmbeddingModelObservationContext.class, ImageModelObservationContext.class, - ChatModelObservationContext.class, ChatClientObservationContext.class, - AdvisorObservationContext.class, VectorStoreObservationContext.class)); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java deleted file mode 100644 index be37d1b26..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.observation.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for chat model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@ConfigurationProperties(ChatObservationProperties.CONFIG_PREFIX) -public class ChatObservationProperties { - - public static final String CONFIG_PREFIX = "spring.ai.chat.observations"; - - /** - * Whether to include the completion content in the observations. - */ - private boolean includeCompletion = false; - - /** - * Whether to include the prompt content in the observations. - */ - private boolean includePrompt = false; - - /** - * Whether to include error logging in the observations. - */ - private boolean includeErrorLogging = false; - - public boolean isIncludeCompletion() { - return this.includeCompletion; - } - - public void setIncludeCompletion(boolean includeCompletion) { - this.includeCompletion = includeCompletion; - } - - public boolean isIncludePrompt() { - return this.includePrompt; - } - - public void setIncludePrompt(boolean includePrompt) { - this.includePrompt = includePrompt; - } - - public boolean isIncludeErrorLogging() { - return this.includeErrorLogging; - } - - public void setIncludeErrorLogging(boolean includeErrorLogging) { - this.includeErrorLogging = includeErrorLogging; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/package-info.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/package-info.java deleted file mode 100644 index 66bef736b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023-2024 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. - */ - -/** - * Auto-configuration for chat observation. - */ -@NonNullApi -@NonNullFields -package org.springframework.ai.model.chat.observation.autoconfigure; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfiguration.java deleted file mode 100644 index ee0902343..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfiguration.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.embedding.observation.autoconfigure; - -import io.micrometer.core.instrument.MeterRegistry; - -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.embedding.observation.EmbeddingModelMeterObservationHandler; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; - -/** - * Auto-configuration for Spring AI embedding model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration( - afterName = "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration") -@ConditionalOnClass(EmbeddingModel.class) -public class EmbeddingObservationAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(MeterRegistry.class) - EmbeddingModelMeterObservationHandler embeddingModelMeterObservationHandler( - ObjectProvider meterRegistry) { - return new EmbeddingModelMeterObservationHandler(meterRegistry.getObject()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/package-info.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/package-info.java deleted file mode 100644 index 654b59b6f..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023-2024 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. - */ - -/** - * Auto-configuration for embedding observation. - */ -@NonNullApi -@NonNullFields -package org.springframework.ai.model.embedding.observation.autoconfigure; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java deleted file mode 100644 index a79a6517d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.huggingface.autoconfigure; - -import org.springframework.ai.huggingface.HuggingfaceChatModel; -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.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -@AutoConfiguration -@ConditionalOnClass(HuggingfaceChatModel.class) -@EnableConfigurationProperties(HuggingfaceChatProperties.class) -public class HuggingfaceChatAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = HuggingfaceChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public HuggingfaceChatModel huggingfaceChatModel(HuggingfaceChatProperties huggingfaceChatProperties) { - return new HuggingfaceChatModel(huggingfaceChatProperties.getApiKey(), huggingfaceChatProperties.getUrl()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatProperties.java deleted file mode 100644 index 371df322f..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatProperties.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.huggingface.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for Hugging Face chat model. - * - * @author Christian Tzolov - * @author Josh Long - * @author Mark Pollack - * @author Thomas Vitale - */ -@ConfigurationProperties(HuggingfaceChatProperties.CONFIG_PREFIX) -public class HuggingfaceChatProperties { - - public static final String CONFIG_PREFIX = "spring.ai.huggingface.chat"; - - /** - * API Key to authenticate with the Inference Endpoint. - */ - private String apiKey; - - /** - * URL of the Inference Endpoint. - */ - private String url; - - /** - * Enable Hugging Face chat model. - */ - private boolean enabled = true; - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getUrl() { - return this.url; - } - - public void setUrl(String url) { - this.url = url; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfiguration.java deleted file mode 100644 index f97e12f03..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfiguration.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.image.observation.autoconfigure; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.image.ImageModel; -import org.springframework.ai.image.observation.ImageModelPromptContentObservationFilter; -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.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; - -/** - * Auto-configuration for Spring AI image model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@AutoConfiguration( - afterName = "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration.class") -@ConditionalOnClass(ImageModel.class) -@EnableConfigurationProperties({ ImageObservationProperties.class }) -public class ImageObservationAutoConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(ImageObservationAutoConfiguration.class); - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ImageObservationProperties.CONFIG_PREFIX, name = "include-prompt", - havingValue = "true") - ImageModelPromptContentObservationFilter imageModelPromptObservationFilter() { - logger.warn( - "You have enabled the inclusion of the image prompt content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - return new ImageModelPromptContentObservationFilter(); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationProperties.java deleted file mode 100644 index 7d372d986..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationProperties.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.image.observation.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for image model observations. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@ConfigurationProperties(ImageObservationProperties.CONFIG_PREFIX) -public class ImageObservationProperties { - - public static final String CONFIG_PREFIX = "spring.ai.image.observations"; - - /** - * Whether to include the prompt content in the observations. - */ - private boolean includePrompt = false; - - public boolean isIncludePrompt() { - return this.includePrompt; - } - - public void setIncludePrompt(boolean includePrompt) { - this.includePrompt = includePrompt; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/package-info.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/package-info.java deleted file mode 100644 index 983cc5a16..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/package-info.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023-2024 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. - */ - -/** - * Auto-configuration for image observation. - */ -@NonNullApi -@NonNullFields -package org.springframework.ai.model.image.observation.autoconfigure; - -import org.springframework.lang.NonNullApi; -import org.springframework.lang.NonNullFields; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatProperties.java deleted file mode 100644 index a04b1655c..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxChatProperties.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -import org.springframework.ai.minimax.MiniMaxChatOptions; -import org.springframework.ai.minimax.api.MiniMaxApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for MiniMax chat model. - * - * @author Geng Rong - */ -@ConfigurationProperties(MiniMaxChatProperties.CONFIG_PREFIX) -public class MiniMaxChatProperties extends MiniMaxParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.minimax.chat"; - - public static final String DEFAULT_CHAT_MODEL = MiniMaxApi.ChatModel.ABAB_5_5_Chat.value; - - private static final Double DEFAULT_TEMPERATURE = 0.7; - - /** - * Enable MiniMax chat model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private MiniMaxChatOptions options = MiniMaxChatOptions.builder() - .model(DEFAULT_CHAT_MODEL) - .temperature(DEFAULT_TEMPERATURE) - .build(); - - public MiniMaxChatOptions getOptions() { - return this.options; - } - - public void setOptions(MiniMaxChatOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxConnectionProperties.java deleted file mode 100644 index e41162a39..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxConnectionProperties.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(MiniMaxConnectionProperties.CONFIG_PREFIX) -public class MiniMaxConnectionProperties extends MiniMaxParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.minimax"; - - public static final String DEFAULT_BASE_URL = "https://api.minimax.chat"; - - public MiniMaxConnectionProperties() { - super.setBaseUrl(DEFAULT_BASE_URL); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingProperties.java deleted file mode 100644 index 465a573f9..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxEmbeddingProperties.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -import org.springframework.ai.document.MetadataMode; -import org.springframework.ai.minimax.MiniMaxEmbeddingOptions; -import org.springframework.ai.minimax.api.MiniMaxApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for MiniMax embedding model. - * - * @author Geng Rong - */ -@ConfigurationProperties(MiniMaxEmbeddingProperties.CONFIG_PREFIX) -public class MiniMaxEmbeddingProperties extends MiniMaxParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.minimax.embedding"; - - public static final String DEFAULT_EMBEDDING_MODEL = MiniMaxApi.EmbeddingModel.Embo_01.value; - - /** - * Enable MiniMax embedding model. - */ - private boolean enabled = true; - - private MetadataMode metadataMode = MetadataMode.EMBED; - - @NestedConfigurationProperty - private MiniMaxEmbeddingOptions options = MiniMaxEmbeddingOptions.builder().model(DEFAULT_EMBEDDING_MODEL).build(); - - public MiniMaxEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(MiniMaxEmbeddingOptions options) { - this.options = options; - } - - public MetadataMode getMetadataMode() { - return this.metadataMode; - } - - public void setMetadataMode(MetadataMode metadataMode) { - this.metadataMode = metadataMode; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxParentProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxParentProperties.java deleted file mode 100644 index 2927c6ea3..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxParentProperties.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -/** - * @author Geng Rong - */ -class MiniMaxParentProperties { - - private String apiKey; - - private String baseUrl; - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatProperties.java deleted file mode 100644 index 19a782a8e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatProperties.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure; - -import org.springframework.ai.mistralai.MistralAiChatOptions; -import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for Mistral AI chat. - * - * @author Ricken Bazolo - * @author Christian Tzolov - * @author Thomas Vitale - * @author Alexandros Pappas - * @since 0.8.1 - */ -@ConfigurationProperties(MistralAiChatProperties.CONFIG_PREFIX) -public class MistralAiChatProperties extends MistralAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.mistralai.chat"; - - public static final String DEFAULT_CHAT_MODEL = MistralAiApi.ChatModel.SMALL.getValue(); - - private static final Double DEFAULT_TEMPERATURE = 0.7; - - private static final Double DEFAULT_TOP_P = 1.0; - - private static final Boolean IS_ENABLED = false; - - /** - * Enable OpenAI chat model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private MistralAiChatOptions options = MistralAiChatOptions.builder() - .model(DEFAULT_CHAT_MODEL) - .temperature(DEFAULT_TEMPERATURE) - .safePrompt(!IS_ENABLED) - .topP(DEFAULT_TOP_P) - .build(); - - public MistralAiChatProperties() { - super.setBaseUrl(MistralAiCommonProperties.DEFAULT_BASE_URL); - } - - public MistralAiChatOptions getOptions() { - return this.options; - } - - public void setOptions(MistralAiChatOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiCommonProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiCommonProperties.java deleted file mode 100644 index 3d1a90bfc..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiCommonProperties.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Common properties for Mistral AI. - * - * @author Ricken Bazolo - * @author Christian Tzolov - * @since 0.8.1 - */ -@ConfigurationProperties(MistralAiCommonProperties.CONFIG_PREFIX) -public class MistralAiCommonProperties extends MistralAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.mistralai"; - - public static final String DEFAULT_BASE_URL = "https://api.mistral.ai"; - - public MistralAiCommonProperties() { - super.setBaseUrl(DEFAULT_BASE_URL); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingProperties.java deleted file mode 100644 index f8c3498be..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiEmbeddingProperties.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure; - -import org.springframework.ai.document.MetadataMode; -import org.springframework.ai.mistralai.MistralAiEmbeddingOptions; -import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for MistralAI embedding model. - * - * @author Ricken Bazolo - * @since 0.8.1 - */ -@ConfigurationProperties(MistralAiEmbeddingProperties.CONFIG_PREFIX) -public class MistralAiEmbeddingProperties extends MistralAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.mistralai.embedding"; - - public static final String DEFAULT_EMBEDDING_MODEL = MistralAiApi.EmbeddingModel.EMBED.getValue(); - - public static final String DEFAULT_ENCODING_FORMAT = "float"; - - public MetadataMode metadataMode = MetadataMode.EMBED; - - /** - * Enable MistralAI embedding model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private MistralAiEmbeddingOptions options = MistralAiEmbeddingOptions.builder() - .withModel(DEFAULT_EMBEDDING_MODEL) - .withEncodingFormat(DEFAULT_ENCODING_FORMAT) - .build(); - - public MistralAiEmbeddingProperties() { - super.setBaseUrl(MistralAiCommonProperties.DEFAULT_BASE_URL); - } - - public MistralAiEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(MistralAiEmbeddingOptions options) { - this.options = options; - } - - public MetadataMode getMetadataMode() { - return this.metadataMode; - } - - public void setMetadataMode(MetadataMode metadataMode) { - this.metadataMode = metadataMode; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiParentProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiParentProperties.java deleted file mode 100644 index 0da844cc2..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiParentProperties.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure; - -/** - * Parent properties for Mistral AI. - * - * @author Ricken Bazolo - * @since 0.8.1 - */ -public class MistralAiParentProperties { - - private String apiKey; - - private String baseUrl; - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotChatProperties.java deleted file mode 100644 index 3933eba3e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotChatProperties.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.moonshot.autoconfigure; - -import org.springframework.ai.moonshot.MoonshotChatOptions; -import org.springframework.ai.moonshot.api.MoonshotApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for Moonshot chat client. - * - * @author Geng Rong - * @author Alexandros Pappas - */ -@ConfigurationProperties(MoonshotChatProperties.CONFIG_PREFIX) -public class MoonshotChatProperties extends MoonshotParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.moonshot.chat"; - - public static final String DEFAULT_CHAT_MODEL = MoonshotApi.ChatModel.MOONSHOT_V1_8K.getValue(); - - private static final Double DEFAULT_TEMPERATURE = 0.7; - - /** - * Enable Moonshot chat client. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private MoonshotChatOptions options = MoonshotChatOptions.builder() - .model(DEFAULT_CHAT_MODEL) - .temperature(DEFAULT_TEMPERATURE) - .build(); - - public MoonshotChatOptions getOptions() { - return this.options; - } - - public void setOptions(MoonshotChatOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotCommonProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotCommonProperties.java deleted file mode 100644 index 7d9461f91..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotCommonProperties.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.moonshot.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Parent properties for Moonshot. - * - * @author Geng Rong - */ -@ConfigurationProperties(MoonshotCommonProperties.CONFIG_PREFIX) -public class MoonshotCommonProperties extends MoonshotParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.moonshot"; - - public static final String DEFAULT_BASE_URL = "https://api.moonshot.cn"; - - public MoonshotCommonProperties() { - super.setBaseUrl(DEFAULT_BASE_URL); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotParentProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotParentProperties.java deleted file mode 100644 index 23a07ced3..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotParentProperties.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.moonshot.autoconfigure; - -/** - * Parent properties for Moonshot. - * - * @author Geng Rong - */ -public class MoonshotParentProperties { - - private String apiKey; - - private String baseUrl; - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCICohereChatModelProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCICohereChatModelProperties.java deleted file mode 100644 index ebed22636..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCICohereChatModelProperties.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.oci.genai.autoconfigure; - -import org.springframework.ai.oci.cohere.OCICohereChatOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for OCI Cohere chat model. - * - * @author Anders Swanson - */ -@ConfigurationProperties(OCICohereChatModelProperties.CONFIG_PREFIX) -public class OCICohereChatModelProperties { - - public static final String CONFIG_PREFIX = "spring.ai.oci.genai.cohere.chat"; - - private static final String DEFAULT_SERVING_MODE = ServingMode.ON_DEMAND.getMode(); - - private static final Double DEFAULT_TEMPERATURE = 0.7; - - private boolean enabled; - - @NestedConfigurationProperty - private OCICohereChatOptions options = OCICohereChatOptions.builder() - .servingMode(DEFAULT_SERVING_MODE) - .temperature(DEFAULT_TEMPERATURE) - .build(); - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public OCICohereChatOptions getOptions() { - return this.options; - } - - public void setOptions(OCICohereChatOptions options) { - this.options = options; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIConnectionProperties.java deleted file mode 100644 index 69f33d910..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIConnectionProperties.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.oci.genai.autoconfigure; - -import java.nio.file.Paths; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.util.StringUtils; - -/** - * Configuration properties for OCI connection. - * - * @author Anders Swanson - */ -@ConfigurationProperties(OCIConnectionProperties.CONFIG_PREFIX) -public class OCIConnectionProperties { - - public static final String CONFIG_PREFIX = "spring.ai.oci.genai"; - - private static final String DEFAULT_PROFILE = "DEFAULT"; - - private AuthenticationType authenticationType = AuthenticationType.FILE; - - private String profile; - - private String file = Paths.get(System.getProperty("user.home"), ".oci", "config").toString(); - - private String tenantId; - - private String userId; - - private String fingerprint; - - private String privateKey; - - private String passPhrase; - - private String region = "us-chicago-1"; - - private String endpoint; - - public String getRegion() { - return this.region; - } - - public void setRegion(String region) { - this.region = region; - } - - public String getPassPhrase() { - return this.passPhrase; - } - - public void setPassPhrase(String passPhrase) { - this.passPhrase = passPhrase; - } - - public String getPrivateKey() { - return this.privateKey; - } - - public void setPrivateKey(String privateKey) { - this.privateKey = privateKey; - } - - public String getFingerprint() { - return this.fingerprint; - } - - public void setFingerprint(String fingerprint) { - this.fingerprint = fingerprint; - } - - public String getUserId() { - return this.userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - - public String getTenantId() { - return this.tenantId; - } - - public void setTenantId(String tenantId) { - this.tenantId = tenantId; - } - - public String getFile() { - return this.file; - } - - public void setFile(String file) { - this.file = file; - } - - public String getProfile() { - return StringUtils.hasText(this.profile) ? this.profile : DEFAULT_PROFILE; - } - - public void setProfile(String profile) { - this.profile = profile; - } - - public AuthenticationType getAuthenticationType() { - return this.authenticationType; - } - - public void setAuthenticationType(AuthenticationType authenticationType) { - this.authenticationType = authenticationType; - } - - public String getEndpoint() { - return this.endpoint; - } - - public void setEndpoint(String endpoint) { - this.endpoint = endpoint; - } - - public enum AuthenticationType { - - FILE("file"), INSTANCE_PRINCIPAL("instance-principal"), WORKLOAD_IDENTITY("workload-identity"), - SIMPLE("simple"); - - private final String authType; - - AuthenticationType(String authType) { - this.authType = authType; - } - - public String getAuthType() { - return this.authType; - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIEmbeddingModelProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIEmbeddingModelProperties.java deleted file mode 100644 index 3ba1ea993..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIEmbeddingModelProperties.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.oci.genai.autoconfigure; - -import com.oracle.bmc.generativeaiinference.model.EmbedTextDetails; - -import org.springframework.ai.oci.OCIEmbeddingOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for OCI embedding model. - * - * @author Anders Swanson - */ -@ConfigurationProperties(OCIEmbeddingModelProperties.CONFIG_PREFIX) -public class OCIEmbeddingModelProperties { - - public static final String CONFIG_PREFIX = "spring.ai.oci.genai.embedding"; - - private ServingMode servingMode = ServingMode.ON_DEMAND; - - private EmbedTextDetails.Truncate truncate = EmbedTextDetails.Truncate.End; - - private String compartment; - - private String model; - - private boolean enabled; - - public OCIEmbeddingOptions getEmbeddingOptions() { - return OCIEmbeddingOptions.builder() - .compartment(this.compartment) - .model(this.model) - .servingMode(this.servingMode.getMode()) - .truncate(this.truncate) - .build(); - } - - public ServingMode getServingMode() { - return this.servingMode; - } - - public void setServingMode(ServingMode servingMode) { - this.servingMode = servingMode; - } - - public String getCompartment() { - return this.compartment; - } - - public void setCompartment(String compartment) { - this.compartment = compartment; - } - - public String getModel() { - return this.model; - } - - public void setModel(String model) { - this.model = model; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public EmbedTextDetails.Truncate getTruncate() { - return this.truncate; - } - - public void setTruncate(EmbedTextDetails.Truncate truncate) { - this.truncate = truncate; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/ServingMode.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/ServingMode.java deleted file mode 100644 index 49b09ae13..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/ServingMode.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.oci.genai.autoconfigure; - -/** - * OCI serving mode. - * - * @author Anders Swanson - */ -public enum ServingMode { - - ON_DEMAND("on-demand"), DEDICATED("dedicated"); - - private final String mode; - - ServingMode(String mode) { - this.mode = mode; - } - - public String getMode() { - return this.mode; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java deleted file mode 100644 index 7d7948f8e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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.springframework.ai.model.ollama.autoconfigure; - -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.ollama.OllamaChatModel; -import org.springframework.ai.ollama.OllamaEmbeddingModel; -import org.springframework.ai.ollama.api.OllamaApi; -import org.springframework.ai.ollama.management.ModelManagementOptions; -import org.springframework.ai.ollama.management.PullModelStrategy; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -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.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.web.client.RestClient; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * {@link AutoConfiguration Auto-configuration} for Ollama Chat Client. - * - * @author Christian Tzolov - * @author Eddú Meléndez - * @author Thomas Vitale - * @since 0.8.0 - */ -@AutoConfiguration(after = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -@ConditionalOnClass(OllamaApi.class) -@EnableConfigurationProperties({ OllamaChatProperties.class, OllamaEmbeddingProperties.class, - OllamaConnectionProperties.class, OllamaInitializationProperties.class }) -@ImportAutoConfiguration(classes = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class, - WebClientAutoConfiguration.class }) -public class OllamaAutoConfiguration { - - @Bean - @ConditionalOnMissingBean(OllamaConnectionDetails.class) - public PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectionProperties properties) { - return new PropertiesOllamaConnectionDetails(properties); - } - - @Bean - @ConditionalOnMissingBean - public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails, - ObjectProvider restClientBuilderProvider, - ObjectProvider webClientBuilderProvider) { - return new OllamaApi(connectionDetails.getBaseUrl(), - restClientBuilderProvider.getIfAvailable(RestClient::builder), - webClientBuilderProvider.getIfAvailable(WebClient::builder)); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = OllamaChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties properties, - OllamaInitializationProperties initProperties, ToolCallingManager toolCallingManager, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - var chatModelPullStrategy = initProperties.getChat().isInclude() ? initProperties.getPullModelStrategy() - : PullModelStrategy.NEVER; - - var chatModel = OllamaChatModel.builder() - .ollamaApi(ollamaApi) - .defaultOptions(properties.getOptions()) - .toolCallingManager(toolCallingManager) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .modelManagementOptions( - new ModelManagementOptions(chatModelPullStrategy, initProperties.getChat().getAdditionalModels(), - initProperties.getTimeout(), initProperties.getMaxRetries())) - .build(); - - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = OllamaEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public OllamaEmbeddingModel ollamaEmbeddingModel(OllamaApi ollamaApi, OllamaEmbeddingProperties properties, - OllamaInitializationProperties initProperties, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - var embeddingModelPullStrategy = initProperties.getEmbedding().isInclude() - ? initProperties.getPullModelStrategy() : PullModelStrategy.NEVER; - - var embeddingModel = OllamaEmbeddingModel.builder() - .ollamaApi(ollamaApi) - .defaultOptions(properties.getOptions()) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .modelManagementOptions(new ModelManagementOptions(embeddingModelPullStrategy, - initProperties.getEmbedding().getAdditionalModels(), initProperties.getTimeout(), - initProperties.getMaxRetries())) - .build(); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - } - - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - - static class PropertiesOllamaConnectionDetails implements OllamaConnectionDetails { - - private final OllamaConnectionProperties properties; - - PropertiesOllamaConnectionDetails(OllamaConnectionProperties properties) { - this.properties = properties; - } - - @Override - public String getBaseUrl() { - return this.properties.getBaseUrl(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatProperties.java deleted file mode 100644 index 22569554a..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatProperties.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import org.springframework.ai.ollama.api.OllamaModel; -import org.springframework.ai.ollama.api.OllamaOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Ollama Chat autoconfiguration properties. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -@ConfigurationProperties(OllamaChatProperties.CONFIG_PREFIX) -public class OllamaChatProperties { - - public static final String CONFIG_PREFIX = "spring.ai.ollama.chat"; - - /** - * Enable Ollama chat model. - */ - private boolean enabled = true; - - /** - * Client lever Ollama options. Use this property to configure generative temperature, - * topK and topP and alike parameters. The null values are ignored defaulting to the - * generative's defaults. - */ - @NestedConfigurationProperty - private OllamaOptions options = OllamaOptions.builder().model(OllamaModel.MISTRAL.id()).build(); - - public String getModel() { - return this.options.getModel(); - } - - public void setModel(String model) { - this.options.setModel(model); - } - - public OllamaOptions getOptions() { - return this.options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaConnectionDetails.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaConnectionDetails.java deleted file mode 100644 index eef0f2af3..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaConnectionDetails.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; - -/** - * Connection details for an Ollama service. - * - * @author Eddú Meléndez - */ -public interface OllamaConnectionDetails extends ConnectionDetails { - - String getBaseUrl(); - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaConnectionProperties.java deleted file mode 100644 index 58e7e1c62..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaConnectionProperties.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Ollama connection autoconfiguration properties. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -@ConfigurationProperties(OllamaConnectionProperties.CONFIG_PREFIX) -public class OllamaConnectionProperties { - - public static final String CONFIG_PREFIX = "spring.ai.ollama"; - - /** - * Base URL where Ollama API server is running. - */ - private String baseUrl = "http://localhost:11434"; - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingProperties.java deleted file mode 100644 index 287c52999..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingProperties.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import org.springframework.ai.ollama.api.OllamaModel; -import org.springframework.ai.ollama.api.OllamaOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Ollama Embedding autoconfiguration properties. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -@ConfigurationProperties(OllamaEmbeddingProperties.CONFIG_PREFIX) -public class OllamaEmbeddingProperties { - - public static final String CONFIG_PREFIX = "spring.ai.ollama.embedding"; - - /** - * Enable Ollama embedding model. - */ - private boolean enabled = true; - - /** - * Client lever Ollama options. Use this property to configure generative temperature, - * topK and topP and alike parameters. The null values are ignored defaulting to the - * generative's defaults. - */ - @NestedConfigurationProperty - private OllamaOptions options = OllamaOptions.builder().model(OllamaModel.MXBAI_EMBED_LARGE.id()).build(); - - public String getModel() { - return this.options.getModel(); - } - - public void setModel(String model) { - this.options.setModel(model); - } - - public OllamaOptions getOptions() { - return this.options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaInitializationProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaInitializationProperties.java deleted file mode 100644 index 31f92fc3d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaInitializationProperties.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import java.time.Duration; -import java.util.List; - -import org.springframework.ai.ollama.management.PullModelStrategy; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Ollama initialization configuration properties. - * - * @author Thomas Vitale - * @since 1.0.0 - */ -@ConfigurationProperties(OllamaInitializationProperties.CONFIG_PREFIX) -public class OllamaInitializationProperties { - - public static final String CONFIG_PREFIX = "spring.ai.ollama.init"; - - /** - * Chat models initialization settings. - */ - private final ModelTypeInit chat = new ModelTypeInit(); - - /** - * Embedding models initialization settings. - */ - private final ModelTypeInit embedding = new ModelTypeInit(); - - /** - * Whether to pull models at startup-time and how. - */ - private PullModelStrategy pullModelStrategy = PullModelStrategy.NEVER; - - /** - * How long to wait for a model to be pulled. - */ - private Duration timeout = Duration.ofMinutes(5); - - /** - * Maximum number of retries for the model pull operation. - */ - private int maxRetries = 0; - - public PullModelStrategy getPullModelStrategy() { - return this.pullModelStrategy; - } - - public void setPullModelStrategy(PullModelStrategy pullModelStrategy) { - this.pullModelStrategy = pullModelStrategy; - } - - public ModelTypeInit getChat() { - return this.chat; - } - - public ModelTypeInit getEmbedding() { - return this.embedding; - } - - public Duration getTimeout() { - return this.timeout; - } - - public void setTimeout(Duration timeout) { - this.timeout = timeout; - } - - public int getMaxRetries() { - return this.maxRetries; - } - - public void setMaxRetries(int maxRetries) { - this.maxRetries = maxRetries; - } - - public static class ModelTypeInit { - - /** - * Include this type of models in the initialization task. - */ - private boolean include = true; - - /** - * Additional models to initialize besides the ones configured via default - * properties. - */ - private List additionalModels = List.of(); - - public boolean isInclude() { - return this.include; - } - - public void setInclude(boolean include) { - this.include = include; - } - - public List getAdditionalModels() { - return this.additionalModels; - } - - public void setAdditionalModels(List additionalModels) { - this.additionalModels = additionalModels; - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechProperties.java deleted file mode 100644 index 8508e5d8e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechProperties.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import org.springframework.ai.openai.OpenAiAudioSpeechOptions; -import org.springframework.ai.openai.api.OpenAiAudioApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for OpenAI audio speech. - * - * Default values for required options are model = tts_1, response format = mp3, voice = - * alloy, and speed = 1. - * - * @author Ahmed Yousri - * @author Stefan Vassilev - */ -@ConfigurationProperties(OpenAiAudioSpeechProperties.CONFIG_PREFIX) -public class OpenAiAudioSpeechProperties extends OpenAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.openai.audio.speech"; - - public static final String DEFAULT_SPEECH_MODEL = OpenAiAudioApi.TtsModel.TTS_1.getValue(); - - private static final Float SPEED = 1.0f; - - private static final OpenAiAudioApi.SpeechRequest.Voice VOICE = OpenAiAudioApi.SpeechRequest.Voice.ALLOY; - - private static final OpenAiAudioApi.SpeechRequest.AudioResponseFormat DEFAULT_RESPONSE_FORMAT = OpenAiAudioApi.SpeechRequest.AudioResponseFormat.MP3; - - /** - * Enable OpenAI audio speech model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private OpenAiAudioSpeechOptions options = OpenAiAudioSpeechOptions.builder() - .model(DEFAULT_SPEECH_MODEL) - .responseFormat(DEFAULT_RESPONSE_FORMAT) - .voice(VOICE) - .speed(SPEED) - .build(); - - public OpenAiAudioSpeechOptions getOptions() { - return this.options; - } - - public void setOptions(OpenAiAudioSpeechOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionProperties.java deleted file mode 100644 index eb4a967f0..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionProperties.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import org.springframework.ai.openai.OpenAiAudioTranscriptionOptions; -import org.springframework.ai.openai.api.OpenAiAudioApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -@ConfigurationProperties(OpenAiAudioTranscriptionProperties.CONFIG_PREFIX) -public class OpenAiAudioTranscriptionProperties extends OpenAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.openai.audio.transcription"; - - public static final String DEFAULT_TRANSCRIPTION_MODEL = OpenAiAudioApi.WhisperModel.WHISPER_1.getValue(); - - private static final Double DEFAULT_TEMPERATURE = 0.7; - - private static final OpenAiAudioApi.TranscriptResponseFormat DEFAULT_RESPONSE_FORMAT = OpenAiAudioApi.TranscriptResponseFormat.TEXT; - - /** - * Enable OpenAI audio transcription model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private OpenAiAudioTranscriptionOptions options = OpenAiAudioTranscriptionOptions.builder() - .model(DEFAULT_TRANSCRIPTION_MODEL) - .temperature(DEFAULT_TEMPERATURE.floatValue()) - .responseFormat(DEFAULT_RESPONSE_FORMAT) - .build(); - - public OpenAiAudioTranscriptionOptions getOptions() { - return this.options; - } - - public void setOptions(OpenAiAudioTranscriptionOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfiguration.java deleted file mode 100644 index 1d6ddc51a..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfiguration.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * 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.springframework.ai.model.openai.autoconfigure; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import io.micrometer.observation.ObservationRegistry; -import org.jetbrains.annotations.NotNull; - -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.image.observation.ImageModelObservationConvention; -import org.springframework.ai.model.SimpleApiKey; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.openai.OpenAiAudioSpeechModel; -import org.springframework.ai.openai.OpenAiAudioTranscriptionModel; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.OpenAiImageModel; -import org.springframework.ai.openai.OpenAiModerationModel; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.openai.api.OpenAiAudioApi; -import org.springframework.ai.openai.api.OpenAiImageApi; -import org.springframework.ai.openai.api.OpenAiModerationApi; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -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.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -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; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestClient; -import org.springframework.web.reactive.function.client.WebClient; - -/** - * {@link AutoConfiguration Auto-configuration} for OpenAI. - * - * @author Christian Tzolov - * @author Stefan Vassilev - * @author Thomas Vitale - * @author Ilayaperumal Gopinathan - */ -@AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, - SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -@ConditionalOnClass(OpenAiApi.class) -@EnableConfigurationProperties({ OpenAiConnectionProperties.class, OpenAiChatProperties.class, - OpenAiEmbeddingProperties.class, OpenAiImageProperties.class, OpenAiAudioTranscriptionProperties.class, - OpenAiAudioSpeechProperties.class, OpenAiModerationProperties.class }) -@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -public class OpenAiAutoConfiguration { - - private static @NotNull ResolvedConnectionProperties resolveConnectionProperties( - OpenAiParentProperties commonProperties, OpenAiParentProperties modelProperties, String modelType) { - - String baseUrl = StringUtils.hasText(modelProperties.getBaseUrl()) ? modelProperties.getBaseUrl() - : commonProperties.getBaseUrl(); - String apiKey = StringUtils.hasText(modelProperties.getApiKey()) ? modelProperties.getApiKey() - : commonProperties.getApiKey(); - String projectId = StringUtils.hasText(modelProperties.getProjectId()) ? modelProperties.getProjectId() - : commonProperties.getProjectId(); - String organizationId = StringUtils.hasText(modelProperties.getOrganizationId()) - ? modelProperties.getOrganizationId() : commonProperties.getOrganizationId(); - - Map> connectionHeaders = new HashMap<>(); - if (StringUtils.hasText(projectId)) { - connectionHeaders.put("OpenAI-Project", List.of(projectId)); - } - if (StringUtils.hasText(organizationId)) { - connectionHeaders.put("OpenAI-Organization", List.of(organizationId)); - } - - Assert.hasText(baseUrl, - "OpenAI base URL must be set. Use the connection property: spring.ai.openai.base-url or spring.ai.openai." - + modelType + ".base-url property."); - Assert.hasText(apiKey, - "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)); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = OpenAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public OpenAiChatModel openAiChatModel(OpenAiConnectionProperties commonProperties, - OpenAiChatProperties chatProperties, ObjectProvider restClientBuilderProvider, - ObjectProvider webClientBuilderProvider, ToolCallingManager toolCallingManager, - RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var openAiApi = openAiApi(chatProperties, commonProperties, - restClientBuilderProvider.getIfAvailable(RestClient::builder), - webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "chat"); - - var chatModel = OpenAiChatModel.builder() - .openAiApi(openAiApi) - .defaultOptions(chatProperties.getOptions()) - .toolCallingManager(toolCallingManager) - .retryTemplate(retryTemplate) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .build(); - - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = OpenAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public OpenAiEmbeddingModel openAiEmbeddingModel(OpenAiConnectionProperties commonProperties, - OpenAiEmbeddingProperties embeddingProperties, ObjectProvider restClientBuilderProvider, - ObjectProvider webClientBuilderProvider, RetryTemplate retryTemplate, - ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var openAiApi = openAiApi(embeddingProperties, commonProperties, - restClientBuilderProvider.getIfAvailable(RestClient::builder), - webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "embedding"); - - var embeddingModel = new OpenAiEmbeddingModel(openAiApi, embeddingProperties.getMetadataMode(), - embeddingProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - } - - private OpenAiApi openAiApi(OpenAiChatProperties chatProperties, OpenAiConnectionProperties commonProperties, - RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, - ResponseErrorHandler responseErrorHandler, String modelType) { - - ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, chatProperties, - modelType); - - return OpenAiApi.builder() - .baseUrl(resolved.baseUrl()) - .apiKey(new SimpleApiKey(resolved.apiKey())) - .headers(resolved.headers()) - .completionsPath(chatProperties.getCompletionsPath()) - .embeddingsPath(OpenAiEmbeddingProperties.DEFAULT_EMBEDDINGS_PATH) - .restClientBuilder(restClientBuilder) - .webClientBuilder(webClientBuilder) - .responseErrorHandler(responseErrorHandler) - .build(); - } - - private OpenAiApi openAiApi(OpenAiEmbeddingProperties embeddingProperties, - OpenAiConnectionProperties commonProperties, RestClient.Builder restClientBuilder, - WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler, String modelType) { - - ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, embeddingProperties, - modelType); - - return OpenAiApi.builder() - .baseUrl(resolved.baseUrl()) - .apiKey(new SimpleApiKey(resolved.apiKey())) - .headers(resolved.headers()) - .completionsPath(OpenAiChatProperties.DEFAULT_COMPLETIONS_PATH) - .embeddingsPath(embeddingProperties.getEmbeddingsPath()) - .restClientBuilder(restClientBuilder) - .webClientBuilder(webClientBuilder) - .responseErrorHandler(responseErrorHandler) - .build(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = OpenAiImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public OpenAiImageModel openAiImageModel(OpenAiConnectionProperties commonProperties, - OpenAiImageProperties imageProperties, ObjectProvider restClientBuilderProvider, - RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, imageProperties, "image"); - - var openAiImageApi = OpenAiImageApi.builder() - .baseUrl(resolved.baseUrl()) - .apiKey(new SimpleApiKey(resolved.apiKey())) - .headers(resolved.headers()) - .restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder)) - .responseErrorHandler(responseErrorHandler) - .build(); - var imageModel = new OpenAiImageModel(openAiImageApi, imageProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(imageModel::setObservationConvention); - - return imageModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = OpenAiAudioTranscriptionProperties.CONFIG_PREFIX, name = "enabled", - havingValue = "true", matchIfMissing = true) - public OpenAiAudioTranscriptionModel openAiAudioTranscriptionModel(OpenAiConnectionProperties commonProperties, - OpenAiAudioTranscriptionProperties transcriptionProperties, RetryTemplate retryTemplate, - ObjectProvider restClientBuilderProvider, - ObjectProvider webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) { - - ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, transcriptionProperties, - "transcription"); - - var openAiAudioApi = OpenAiAudioApi.builder() - .baseUrl(resolved.baseUrl()) - .apiKey(new SimpleApiKey(resolved.apiKey())) - .headers(resolved.headers()) - .restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder)) - .webClientBuilder(webClientBuilderProvider.getIfAvailable(WebClient::builder)) - .responseErrorHandler(responseErrorHandler) - .build(); - - return new OpenAiAudioTranscriptionModel(openAiAudioApi, transcriptionProperties.getOptions(), retryTemplate); - - } - - @Bean - @ConditionalOnMissingBean - public OpenAiModerationModel openAiModerationClient(OpenAiConnectionProperties commonProperties, - OpenAiModerationProperties moderationProperties, RetryTemplate retryTemplate, - ObjectProvider restClientBuilderProvider, ResponseErrorHandler responseErrorHandler) { - - ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, moderationProperties, - "moderation"); - - var openAiModerationApi = OpenAiModerationApi.builder() - .baseUrl(resolved.baseUrl) - .apiKey(new SimpleApiKey(resolved.apiKey())) - .headers(resolved.headers()) - .restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder)) - .responseErrorHandler(responseErrorHandler) - .build(); - return new OpenAiModerationModel(openAiModerationApi, retryTemplate) - .withDefaultOptions(moderationProperties.getOptions()); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = OpenAiAudioSpeechProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public OpenAiAudioSpeechModel openAiAudioSpeechClient(OpenAiConnectionProperties commonProperties, - OpenAiAudioSpeechProperties speechProperties, RetryTemplate retryTemplate, - ObjectProvider restClientBuilderProvider, - ObjectProvider webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) { - - ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, speechProperties, - "speach"); - - var openAiAudioApi = OpenAiAudioApi.builder() - .baseUrl(resolved.baseUrl()) - .apiKey(new SimpleApiKey(resolved.apiKey())) - .headers(resolved.headers()) - .restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder)) - .webClientBuilder(webClientBuilderProvider.getIfAvailable(WebClient::builder)) - .responseErrorHandler(responseErrorHandler) - .build(); - - return new OpenAiAudioSpeechModel(openAiAudioApi, speechProperties.getOptions()); - } - - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - - private record ResolvedConnectionProperties(String baseUrl, String apiKey, MultiValueMap headers) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatProperties.java deleted file mode 100644 index 3cef1db88..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatProperties.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -@ConfigurationProperties(OpenAiChatProperties.CONFIG_PREFIX) -public class OpenAiChatProperties extends OpenAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.openai.chat"; - - public static final String DEFAULT_CHAT_MODEL = "gpt-4o-mini"; - - public static final String DEFAULT_COMPLETIONS_PATH = "/v1/chat/completions"; - - private static final Double DEFAULT_TEMPERATURE = 0.7; - - /** - * Enable OpenAI chat model. - */ - private boolean enabled = true; - - private String completionsPath = DEFAULT_COMPLETIONS_PATH; - - @NestedConfigurationProperty - private OpenAiChatOptions options = OpenAiChatOptions.builder() - .model(DEFAULT_CHAT_MODEL) - .temperature(DEFAULT_TEMPERATURE) - .build(); - - public OpenAiChatOptions getOptions() { - return this.options; - } - - public void setOptions(OpenAiChatOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getCompletionsPath() { - return this.completionsPath; - } - - public void setCompletionsPath(String completionsPath) { - this.completionsPath = completionsPath; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiConnectionProperties.java deleted file mode 100644 index 41f00e303..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiConnectionProperties.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(OpenAiConnectionProperties.CONFIG_PREFIX) -public class OpenAiConnectionProperties extends OpenAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.openai"; - - public static final String DEFAULT_BASE_URL = "https://api.openai.com"; - - public OpenAiConnectionProperties() { - super.setBaseUrl(DEFAULT_BASE_URL); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingProperties.java deleted file mode 100644 index c70e82c66..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingProperties.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import org.springframework.ai.document.MetadataMode; -import org.springframework.ai.openai.OpenAiEmbeddingOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -@ConfigurationProperties(OpenAiEmbeddingProperties.CONFIG_PREFIX) -public class OpenAiEmbeddingProperties extends OpenAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.openai.embedding"; - - public static final String DEFAULT_EMBEDDING_MODEL = "text-embedding-ada-002"; - - public static final String DEFAULT_EMBEDDINGS_PATH = "/v1/embeddings"; - - /** - * Enable OpenAI embedding model. - */ - private boolean enabled = true; - - private MetadataMode metadataMode = MetadataMode.EMBED; - - private String embeddingsPath = DEFAULT_EMBEDDINGS_PATH; - - @NestedConfigurationProperty - private OpenAiEmbeddingOptions options = OpenAiEmbeddingOptions.builder().model(DEFAULT_EMBEDDING_MODEL).build(); - - public OpenAiEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(OpenAiEmbeddingOptions options) { - this.options = options; - } - - public MetadataMode getMetadataMode() { - return this.metadataMode; - } - - public void setMetadataMode(MetadataMode metadataMode) { - this.metadataMode = metadataMode; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getEmbeddingsPath() { - return this.embeddingsPath; - } - - public void setEmbeddingsPath(String embeddingsPath) { - this.embeddingsPath = embeddingsPath; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageProperties.java deleted file mode 100644 index ac7b11c80..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageProperties.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import org.springframework.ai.openai.OpenAiImageOptions; -import org.springframework.ai.openai.api.OpenAiImageApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * OpenAI Image autoconfiguration properties. - * - * @author Thomas Vitale - * @since 0.8.0 - */ -@ConfigurationProperties(OpenAiImageProperties.CONFIG_PREFIX) -public class OpenAiImageProperties extends OpenAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.openai.image"; - - public static final String DEFAULT_IMAGE_MODEL = OpenAiImageApi.ImageModel.DALL_E_3.getValue(); - - /** - * Enable OpenAI image model. - */ - private boolean enabled = true; - - /** - * Options for OpenAI Image API. - */ - @NestedConfigurationProperty - private OpenAiImageOptions options = OpenAiImageOptions.builder().model(DEFAULT_IMAGE_MODEL).build(); - - public OpenAiImageOptions getOptions() { - return this.options; - } - - public void setOptions(OpenAiImageOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationProperties.java deleted file mode 100644 index a268f93bd..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationProperties.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import org.springframework.ai.openai.OpenAiModerationOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * OpenAI Moderation autoconfiguration properties. - * - * @author Ahmed Yousri - * @since 0.9.0 - */ -@ConfigurationProperties(OpenAiModerationProperties.CONFIG_PREFIX) -public class OpenAiModerationProperties extends OpenAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.openai.moderation"; - - /** - * Options for OpenAI Moderation API. - */ - @NestedConfigurationProperty - private OpenAiModerationOptions options = OpenAiModerationOptions.builder().build(); - - public OpenAiModerationOptions getOptions() { - return this.options; - } - - public void setOptions(OpenAiModerationOptions options) { - this.options = options; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiParentProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiParentProperties.java deleted file mode 100644 index 9b3f29a0e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiParentProperties.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -/** - * Internal parent properties for the OpenAI properties. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -class OpenAiParentProperties { - - private String apiKey; - - private String baseUrl; - - private String projectId; - - private String organizationId; - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - - public String getProjectId() { - return this.projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - public String getOrganizationId() { - return this.organizationId; - } - - public void setOrganizationId(String organizationId) { - this.organizationId = organizationId; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java deleted file mode 100644 index ae0092ee8..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.postgresml.autoconfigure; - -import org.springframework.ai.postgresml.PostgresMlEmbeddingModel; -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.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.jdbc.core.JdbcTemplate; - -/** - * Auto-configuration class for PostgresMlEmbeddingModel. - * - * @author Utkarsh Srivastava - * @author Christian Tzolov - */ -@AutoConfiguration(after = JdbcTemplateAutoConfiguration.class) -@ConditionalOnClass(PostgresMlEmbeddingModel.class) -@EnableConfigurationProperties(PostgresMlEmbeddingProperties.class) -public class PostgresMlAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = PostgresMlEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public PostgresMlEmbeddingModel postgresMlEmbeddingModel(JdbcTemplate jdbcTemplate, - PostgresMlEmbeddingProperties embeddingProperties) { - - return new PostgresMlEmbeddingModel(jdbcTemplate, embeddingProperties.getOptions(), - embeddingProperties.isCreateExtension()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingProperties.java deleted file mode 100644 index 5cd3ca0df..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingProperties.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.postgresml.autoconfigure; - -import java.util.Map; - -import org.springframework.ai.document.MetadataMode; -import org.springframework.ai.postgresml.PostgresMlEmbeddingModel; -import org.springframework.ai.postgresml.PostgresMlEmbeddingOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; -import org.springframework.util.Assert; - -/** - * Configuration properties for Postgres ML. - * - * @author Utkarsh Srivastava - * @author Christian Tzolov - */ -@ConfigurationProperties(PostgresMlEmbeddingProperties.CONFIG_PREFIX) -public class PostgresMlEmbeddingProperties { - - public static final String CONFIG_PREFIX = "spring.ai.postgresml.embedding"; - - /** - * Enable Postgres ML embedding model. - */ - private boolean enabled = true; - - /** - * Create the extensions required for embedding - */ - private boolean createExtension; - - @NestedConfigurationProperty - private PostgresMlEmbeddingOptions options = PostgresMlEmbeddingOptions.builder() - .transformer(PostgresMlEmbeddingModel.DEFAULT_TRANSFORMER_MODEL) - .vectorType(PostgresMlEmbeddingModel.VectorType.PG_ARRAY) - .kwargs(Map.of()) - .metadataMode(MetadataMode.EMBED) - .build(); - - public PostgresMlEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(PostgresMlEmbeddingOptions options) { - Assert.notNull(options, "options must not be null."); - Assert.notNull(options.getTransformer(), "transformer must not be null."); - Assert.notNull(options.getVectorType(), "vectorType must not be null."); - Assert.notNull(options.getKwargs(), "kwargs must not be null."); - Assert.notNull(options.getMetadataMode(), "metadataMode must not be null."); - - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public boolean isCreateExtension() { - return this.createExtension; - } - - public void setCreateExtension(boolean createExtension) { - this.createExtension = createExtension; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java deleted file mode 100644 index a48870d9d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; - -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.image.observation.ImageModelObservationConvention; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.qianfan.QianFanChatModel; -import org.springframework.ai.qianfan.QianFanEmbeddingModel; -import org.springframework.ai.qianfan.QianFanImageModel; -import org.springframework.ai.qianfan.api.QianFanApi; -import org.springframework.ai.qianfan.api.QianFanImageApi; -import org.springframework.beans.factory.ObjectProvider; -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.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -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.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestClient; - -/** - * {@link AutoConfiguration Auto-configuration} for QianFan Chat, Embedding, and Image - * Models. - * - * @author Geng Rong - */ -@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) -@ConditionalOnClass(QianFanApi.class) -@EnableConfigurationProperties({ QianFanConnectionProperties.class, QianFanChatProperties.class, - QianFanEmbeddingProperties.class, QianFanImageProperties.class }) -public class QianFanAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = QianFanChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public QianFanChatModel qianFanChatModel(QianFanConnectionProperties commonProperties, - QianFanChatProperties chatProperties, ObjectProvider restClientBuilderProvider, - RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var qianFanApi = qianFanApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), - chatProperties.getApiKey(), commonProperties.getApiKey(), chatProperties.getSecretKey(), - commonProperties.getSecretKey(), restClientBuilderProvider.getIfAvailable(RestClient::builder), - responseErrorHandler); - - var chatModel = new QianFanChatModel(qianFanApi, chatProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = QianFanEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public QianFanEmbeddingModel qianFanEmbeddingModel(QianFanConnectionProperties commonProperties, - QianFanEmbeddingProperties embeddingProperties, - ObjectProvider restClientBuilderProvider, RetryTemplate retryTemplate, - ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var qianFanApi = qianFanApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(), - embeddingProperties.getApiKey(), commonProperties.getApiKey(), embeddingProperties.getSecretKey(), - commonProperties.getSecretKey(), restClientBuilderProvider.getIfAvailable(RestClient::builder), - responseErrorHandler); - - var embeddingModel = new QianFanEmbeddingModel(qianFanApi, embeddingProperties.getMetadataMode(), - embeddingProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = QianFanImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public QianFanImageModel qianFanImageModel(QianFanConnectionProperties commonProperties, - QianFanImageProperties imageProperties, ObjectProvider restClientBuilderProvider, - RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey() - : commonProperties.getApiKey(); - - String secretKey = StringUtils.hasText(imageProperties.getSecretKey()) ? imageProperties.getSecretKey() - : commonProperties.getSecretKey(); - - String baseUrl = StringUtils.hasText(imageProperties.getBaseUrl()) ? imageProperties.getBaseUrl() - : commonProperties.getBaseUrl(); - - Assert.hasText(apiKey, "QianFan API key must be set. Use the property: spring.ai.qianfan.api-key"); - Assert.hasText(secretKey, "QianFan secret key must be set. Use the property: spring.ai.qianfan.secret-key"); - Assert.hasText(baseUrl, "QianFan base URL must be set. Use the property: spring.ai.qianfan.base-url"); - - var qianFanImageApi = new QianFanImageApi(baseUrl, apiKey, secretKey, - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - - var imageModel = new QianFanImageModel(qianFanImageApi, imageProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(imageModel::setObservationConvention); - - return imageModel; - } - - private QianFanApi qianFanApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey, - String secretKey, String commonSecretKey, RestClient.Builder restClientBuilder, - ResponseErrorHandler responseErrorHandler) { - - String resolvedBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonBaseUrl; - Assert.hasText(resolvedBaseUrl, "QianFan base URL must be set"); - - String resolvedApiKey = StringUtils.hasText(apiKey) ? apiKey : commonApiKey; - Assert.hasText(resolvedApiKey, "QianFan API key must be set"); - - String resolvedSecretKey = StringUtils.hasText(secretKey) ? secretKey : commonSecretKey; - Assert.hasText(resolvedSecretKey, "QianFan Secret key must be set"); - - return new QianFanApi(resolvedBaseUrl, resolvedApiKey, resolvedSecretKey, restClientBuilder, - responseErrorHandler); - } - - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanChatProperties.java deleted file mode 100644 index 9ca6b16cb..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanChatProperties.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; - -import org.springframework.ai.qianfan.QianFanChatOptions; -import org.springframework.ai.qianfan.api.QianFanApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for QianFan chat model. - * - * @author Geng Rong - */ -@ConfigurationProperties(QianFanChatProperties.CONFIG_PREFIX) -public class QianFanChatProperties extends QianFanParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.qianfan.chat"; - - public static final String DEFAULT_CHAT_MODEL = QianFanApi.ChatModel.ERNIE_Speed_8K.value; - - private static final Double DEFAULT_TEMPERATURE = 0.7; - - /** - * Enable QianFan chat client. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private QianFanChatOptions options = QianFanChatOptions.builder() - .model(DEFAULT_CHAT_MODEL) - .temperature(DEFAULT_TEMPERATURE) - .build(); - - public QianFanChatOptions getOptions() { - return this.options; - } - - public void setOptions(QianFanChatOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanConnectionProperties.java deleted file mode 100644 index e26662ef6..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanConnectionProperties.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; - -import org.springframework.ai.qianfan.api.QianFanConstants; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(QianFanConnectionProperties.CONFIG_PREFIX) -public class QianFanConnectionProperties extends QianFanParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.qianfan"; - - public static final String DEFAULT_BASE_URL = QianFanConstants.DEFAULT_BASE_URL; - - public QianFanConnectionProperties() { - super.setBaseUrl(DEFAULT_BASE_URL); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanEmbeddingProperties.java deleted file mode 100644 index d71bd12b3..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanEmbeddingProperties.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; - -import org.springframework.ai.document.MetadataMode; -import org.springframework.ai.qianfan.QianFanEmbeddingOptions; -import org.springframework.ai.qianfan.api.QianFanApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for QianFan embedding model. - * - * @author Geng Rong - */ -@ConfigurationProperties(QianFanEmbeddingProperties.CONFIG_PREFIX) -public class QianFanEmbeddingProperties extends QianFanParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.qianfan.embedding"; - - /** - * Enable QianFan embedding client. - */ - private boolean enabled = true; - - private MetadataMode metadataMode = MetadataMode.EMBED; - - @NestedConfigurationProperty - private QianFanEmbeddingOptions options = QianFanEmbeddingOptions.builder() - .model(QianFanApi.DEFAULT_EMBEDDING_MODEL) - .build(); - - public QianFanEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(QianFanEmbeddingOptions options) { - this.options = options; - } - - public MetadataMode getMetadataMode() { - return this.metadataMode; - } - - public void setMetadataMode(MetadataMode metadataMode) { - this.metadataMode = metadataMode; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanImageProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanImageProperties.java deleted file mode 100644 index 3341b760b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanImageProperties.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; - -import org.springframework.ai.qianfan.QianFanImageOptions; -import org.springframework.ai.qianfan.api.QianFanImageApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * QianFan Image autoconfiguration properties. - * - * @author Geng Rong - */ -@ConfigurationProperties(QianFanImageProperties.CONFIG_PREFIX) -public class QianFanImageProperties extends QianFanParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.qianfan.image"; - - public static final String DEFAULT_IMAGE_MODEL = QianFanImageApi.ImageModel.Stable_Diffusion_XL.getValue(); - - /** - * Enable QianFan image model. - */ - private boolean enabled = true; - - /** - * Options for QianFan Image API. - */ - @NestedConfigurationProperty - private QianFanImageOptions options = QianFanImageOptions.builder().model(DEFAULT_IMAGE_MODEL).build(); - - public QianFanImageOptions getOptions() { - return this.options; - } - - public void setOptions(QianFanImageOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanParentProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanParentProperties.java deleted file mode 100644 index 424cbadc1..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanParentProperties.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; - -/** - * @author Geng Rong - */ -class QianFanParentProperties { - - private String apiKey; - - private String secretKey; - - private String baseUrl; - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getSecretKey() { - return this.secretKey; - } - - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiConnectionProperties.java deleted file mode 100644 index d1ec0261d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiConnectionProperties.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.stabilityai.autoconfigure; - -import org.springframework.ai.stabilityai.api.StabilityAiApi; -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(StabilityAiConnectionProperties.CONFIG_PREFIX) -public class StabilityAiConnectionProperties extends StabilityAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.stabilityai"; - - public static final String DEFAULT_BASE_URL = StabilityAiApi.DEFAULT_BASE_URL; - - public StabilityAiConnectionProperties() { - super.setBaseUrl(DEFAULT_BASE_URL); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java deleted file mode 100644 index 7c143a941..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.stabilityai.autoconfigure; - -import org.springframework.ai.stabilityai.StabilityAiImageModel; -import org.springframework.ai.stabilityai.api.StabilityAiApi; -import org.springframework.beans.factory.ObjectProvider; -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.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestClient; - -/** - * {@link AutoConfiguration Auto-configuration} for StabilityAI Image Model. - * - * @author Mark Pollack - * @author Christian Tzolov - * @since 0.8.0 - */ -@AutoConfiguration(after = { RestClientAutoConfiguration.class }) -@ConditionalOnClass(StabilityAiApi.class) -@EnableConfigurationProperties({ StabilityAiConnectionProperties.class, StabilityAiImageProperties.class }) -public class StabilityAiImageAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public StabilityAiApi stabilityAiApi(StabilityAiConnectionProperties commonProperties, - StabilityAiImageProperties imageProperties, ObjectProvider restClientBuilderProvider) { - - String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey() - : commonProperties.getApiKey(); - - String baseUrl = StringUtils.hasText(imageProperties.getBaseUrl()) ? imageProperties.getBaseUrl() - : commonProperties.getBaseUrl(); - - Assert.hasText(apiKey, "StabilityAI API key must be set"); - Assert.hasText(baseUrl, "StabilityAI base URL must be set"); - - return new StabilityAiApi(apiKey, imageProperties.getOptions().getModel(), baseUrl, - restClientBuilderProvider.getIfAvailable(RestClient::builder)); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = StabilityAiImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public StabilityAiImageModel stabilityAiImageModel(StabilityAiApi stabilityAiApi, - StabilityAiImageProperties stabilityAiImageProperties) { - return new StabilityAiImageModel(stabilityAiApi, stabilityAiImageProperties.getOptions()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageProperties.java deleted file mode 100644 index dc78af948..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageProperties.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.stabilityai.autoconfigure; - -import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for Stability AI image model. - * - * @author Mark Pollack - * @author Christian Tzolov - * @since 0.8.0 - */ -@ConfigurationProperties(StabilityAiImageProperties.CONFIG_PREFIX) -public class StabilityAiImageProperties extends StabilityAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.stabilityai.image"; - - /** - * Enable Stability image model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private StabilityAiImageOptions options = StabilityAiImageOptions.builder().build(); // stable-diffusion-v1-6 - - // is - // default - // model - - public StabilityAiImageOptions getOptions() { - return this.options; - } - - public void setOptions(StabilityAiImageOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiParentProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiParentProperties.java deleted file mode 100644 index f75cc2b09..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiParentProperties.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.stabilityai.autoconfigure; - -/** - * Internal parent properties for the StabilityAI properties. - * - * @author Mark Pollack - * @since 0.8.0 - */ -class StabilityAiParentProperties { - - private String apiKey; - - private String baseUrl; - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java deleted file mode 100644 index b653b67d7..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.springframework.ai.model.tool.autoconfigure; - -import java.util.ArrayList; -import java.util.List; - -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.tool.ToolCallbackProvider; -import org.springframework.ai.tool.execution.DefaultToolExecutionExceptionProcessor; -import org.springframework.ai.tool.execution.ToolExecutionExceptionProcessor; -import org.springframework.ai.tool.resolution.DelegatingToolCallbackResolver; -import org.springframework.ai.tool.resolution.SpringBeanToolCallbackResolver; -import org.springframework.ai.tool.resolution.StaticToolCallbackResolver; -import org.springframework.ai.tool.resolution.ToolCallbackResolver; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.support.GenericApplicationContext; - -/** - * Auto-configuration for common tool calling features of {@link ChatModel}. - * - * @author Thomas Vitale - * @author Christian Tzolov - * @since 1.0.0 - */ -@AutoConfiguration -@ConditionalOnClass(ChatModel.class) -public class ToolCallingAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - ToolCallbackResolver toolCallbackResolver(GenericApplicationContext applicationContext, - List functionCallbacks, List tcbProviders) { - - List allFunctionAndToolCallbacks = new ArrayList<>(functionCallbacks); - tcbProviders.stream().map(pr -> List.of(pr.getToolCallbacks())).forEach(allFunctionAndToolCallbacks::addAll); - - var staticToolCallbackResolver = new StaticToolCallbackResolver(allFunctionAndToolCallbacks); - - var springBeanToolCallbackResolver = SpringBeanToolCallbackResolver.builder() - .applicationContext(applicationContext) - .build(); - - return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver, springBeanToolCallbackResolver)); - } - - @Bean - @ConditionalOnMissingBean - ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() { - return new DefaultToolExecutionExceptionProcessor(false); - } - - @Bean - @ConditionalOnMissingBean - ToolCallingManager toolCallingManager(ToolCallbackResolver toolCallbackResolver, - ToolExecutionExceptionProcessor toolExecutionExceptionProcessor, - ObjectProvider observationRegistry) { - return ToolCallingManager.builder() - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .toolCallbackResolver(toolCallbackResolver) - .toolExecutionExceptionProcessor(toolExecutionExceptionProcessor) - .build(); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelProperties.java deleted file mode 100644 index a4535f8ac..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelProperties.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.transformers.autoconfigure; - -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer; - -import org.springframework.ai.document.Document; -import org.springframework.ai.document.MetadataMode; -import org.springframework.ai.transformers.TransformersEmbeddingModel; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for the Transformer Embedding model. - * - * @author Christian Tzolov - */ -@ConfigurationProperties(TransformersEmbeddingModelProperties.CONFIG_PREFIX) -public class TransformersEmbeddingModelProperties { - - public static final String CONFIG_PREFIX = "spring.ai.embedding.transformer"; - - public static final String DEFAULT_CACHE_DIRECTORY = new File(System.getProperty("java.io.tmpdir"), - "spring-ai-onnx-generative") - .getAbsolutePath(); - - @NestedConfigurationProperty - private final Tokenizer tokenizer = new Tokenizer(); - - /** - * Controls caching of remote, large resources to local file system. - */ - @NestedConfigurationProperty - private final Cache cache = new Cache(); - - @NestedConfigurationProperty - private final Onnx onnx = new Onnx(); - - /** - * Enable the Transformer Embedding model. - */ - private boolean enabled = true; - - /** - * Specifies what parts of the {@link Document}'s content and metadata will be used - * for computing the embeddings. Applicable for the - * {@link TransformersEmbeddingModel#embed(Document)} method only. Has no effect on - * the {@link TransformersEmbeddingModel#embed(String)} or - * {@link TransformersEmbeddingModel#embed(List)}. Defaults to - * {@link MetadataMode#NONE}. - */ - private MetadataMode metadataMode = MetadataMode.NONE; - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public Cache getCache() { - return this.cache; - } - - public Onnx getOnnx() { - return this.onnx; - } - - public Tokenizer getTokenizer() { - return this.tokenizer; - } - - public MetadataMode getMetadataMode() { - return this.metadataMode; - } - - public void setMetadataMode(MetadataMode metadataMode) { - this.metadataMode = metadataMode; - } - - /** - * Configurations for the {@link HuggingFaceTokenizer} used to convert sentences into - * tokens. - */ - public static class Tokenizer { - - /** - * URI of a pre-trained HuggingFaceTokenizer created by the ONNX engine (e.g. - * tokenizer.json). - */ - private String uri = TransformersEmbeddingModel.DEFAULT_ONNX_TOKENIZER_URI; - - /** - * HuggingFaceTokenizer options such as 'addSpecialTokens', 'modelMaxLength', - * 'truncation', 'padding', 'maxLength', 'stride' and 'padToMultipleOf'. Leave - * empty to fall back to the defaults. - */ - @NestedConfigurationProperty - private Map options = new HashMap<>(); - - public String getUri() { - return this.uri; - } - - public void setUri(String uri) { - this.uri = uri; - } - - public Map getOptions() { - return this.options; - } - - public void setOptions(Map options) { - this.options = options; - } - - } - - public static class Cache { - - /** - * Enable the Resource caching. - */ - private boolean enabled = true; - - /** - * Resource cache directory. Used to cache remote resources, such as the ONNX - * models, to the local file system. Applicable only for cache.enabled == true. - * Defaults to {java.io.tmpdir}/spring-ai-onnx-generative. - */ - private String directory = DEFAULT_CACHE_DIRECTORY; - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getDirectory() { - return this.directory; - } - - public void setDirectory(String directory) { - this.directory = directory; - } - - } - - public static class Onnx { - - /** - * Existing, pre-trained ONNX generative. Commonly exported from - * https://sbert.net/docs/pretrained_models.html. Defaults to - * sentence-transformers/all-MiniLM-L6-v2. - */ - private String modelUri = TransformersEmbeddingModel.DEFAULT_ONNX_MODEL_URI; - - /** - * Defaults to: 'last_hidden_state'. - */ - private String modelOutputName = TransformersEmbeddingModel.DEFAULT_MODEL_OUTPUT_NAME; - - /** - * Run on a GPU or with another provider (optional). - * https://onnxruntime.ai/docs/get-started/with-java.html#run-on-a-gpu-or-with-another-provider-optional - * - * The GPU device ID to execute on. Only applicable if >= 0. Ignored otherwise. - */ - private int gpuDeviceId = -1; - - public String getModelUri() { - return this.modelUri; - } - - public void setModelUri(String modelUri) { - this.modelUri = modelUri; - } - - public int getGpuDeviceId() { - return this.gpuDeviceId; - } - - public void setGpuDeviceId(int gpuDeviceId) { - this.gpuDeviceId = gpuDeviceId; - } - - public String getModelOutputName() { - return this.modelOutputName; - } - - public void setModelOutputName(String modelOutputName) { - this.modelOutputName = modelOutputName; - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java deleted file mode 100644 index ab57b118f..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.embedding; - -import java.io.IOException; - -import com.google.cloud.vertexai.VertexAI; -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails; -import org.springframework.ai.vertexai.embedding.multimodal.VertexAiMultimodalEmbeddingModel; -import org.springframework.ai.vertexai.embedding.text.VertexAiTextEmbeddingModel; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -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.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Auto-configuration for Vertex AI Gemini Chat. - * - * @author Christian Tzolov - * @author Mark Pollack - * @since 1.0.0 - */ -@AutoConfiguration(after = { SpringAiRetryAutoConfiguration.class }) -@ConditionalOnClass({ VertexAI.class, VertexAiTextEmbeddingModel.class }) -@EnableConfigurationProperties({ VertexAiEmbeddingConnectionProperties.class, VertexAiTextEmbeddingProperties.class, - VertexAiMultimodalEmbeddingProperties.class }) -@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class }) -public class VertexAiEmbeddingAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public VertexAiEmbeddingConnectionDetails connectionDetails( - VertexAiEmbeddingConnectionProperties connectionProperties) { - - Assert.hasText(connectionProperties.getProjectId(), "Vertex AI project-id must be set!"); - Assert.hasText(connectionProperties.getLocation(), "Vertex AI location must be set!"); - - var connectionBuilder = VertexAiEmbeddingConnectionDetails.builder() - .projectId(connectionProperties.getProjectId()) - .location(connectionProperties.getLocation()); - - if (StringUtils.hasText(connectionProperties.getApiEndpoint())) { - connectionBuilder.apiEndpoint(connectionProperties.getApiEndpoint()); - } - - return connectionBuilder.build(); - - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = VertexAiTextEmbeddingProperties.CONFIG_PREFIX, name = "enabled", - havingValue = "true", matchIfMissing = true) - public VertexAiTextEmbeddingModel textEmbedding(VertexAiEmbeddingConnectionDetails connectionDetails, - VertexAiTextEmbeddingProperties textEmbeddingProperties, RetryTemplate retryTemplate, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var embeddingModel = new VertexAiTextEmbeddingModel(connectionDetails, textEmbeddingProperties.getOptions(), - retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = VertexAiMultimodalEmbeddingProperties.CONFIG_PREFIX, name = "enabled", - havingValue = "true", matchIfMissing = true) - public VertexAiMultimodalEmbeddingModel multimodalEmbedding(VertexAiEmbeddingConnectionDetails connectionDetails, - VertexAiMultimodalEmbeddingProperties multimodalEmbeddingProperties) throws IOException { - - return new VertexAiMultimodalEmbeddingModel(connectionDetails, multimodalEmbeddingProperties.getOptions()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingConnectionProperties.java deleted file mode 100644 index a55cb3774..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingConnectionProperties.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.embedding; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.io.Resource; - -/** - * Configuration properties for Vertex AI Embedding. - * - * @author Christian Tzolov - * @since 1.0.0 - */ -@ConfigurationProperties(VertexAiEmbeddingConnectionProperties.CONFIG_PREFIX) -public class VertexAiEmbeddingConnectionProperties { - - public static final String CONFIG_PREFIX = "spring.ai.vertex.ai.embedding"; - - /** - * Vertex AI Gemini project ID. - */ - private String projectId; - - /** - * Vertex AI Gemini location. - */ - private String location; - - /** - * URI to Vertex AI Gemini credentials (optional) - */ - private Resource credentialsUri; - - /** - * Vertex AI Gemini API endpoint. - */ - private String apiEndpoint; - - public String getProjectId() { - return this.projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - public String getLocation() { - return this.location; - } - - public void setLocation(String location) { - this.location = location; - } - - public Resource getCredentialsUri() { - return this.credentialsUri; - } - - public void setCredentialsUri(Resource credentialsUri) { - this.credentialsUri = credentialsUri; - } - - public String getApiEndpoint() { - return this.apiEndpoint; - } - - public void setApiEndpoint(String apiEndpoint) { - this.apiEndpoint = apiEndpoint; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiMultimodalEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiMultimodalEmbeddingProperties.java deleted file mode 100644 index e1bb8e2c3..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiMultimodalEmbeddingProperties.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.embedding; - -import org.springframework.ai.vertexai.embedding.multimodal.VertexAiMultimodalEmbeddingOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for Vertex AI Gemini Chat. - * - * @author Christian Tzolov - * @since 1.0.0 - */ -@ConfigurationProperties(VertexAiMultimodalEmbeddingProperties.CONFIG_PREFIX) -public class VertexAiMultimodalEmbeddingProperties { - - public static final String CONFIG_PREFIX = "spring.ai.vertex.ai.embedding.multimodal"; - - private boolean enabled = true; - - /** - * Vertex AI Text Embedding API options. - */ - private VertexAiMultimodalEmbeddingOptions options = VertexAiMultimodalEmbeddingOptions.builder() - .model(VertexAiMultimodalEmbeddingOptions.DEFAULT_MODEL_NAME) - .build(); - - public VertexAiMultimodalEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(VertexAiMultimodalEmbeddingOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingProperties.java deleted file mode 100644 index 737a4a6bc..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingProperties.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.embedding; - -import org.springframework.ai.vertexai.embedding.text.VertexAiTextEmbeddingOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for Vertex AI Gemini Chat. - * - * @author Christian Tzolov - * @since 1.0.0 - */ -@ConfigurationProperties(VertexAiTextEmbeddingProperties.CONFIG_PREFIX) -public class VertexAiTextEmbeddingProperties { - - public static final String CONFIG_PREFIX = "spring.ai.vertex.ai.embedding.text"; - - private boolean enabled = true; - - /** - * Vertex AI Text Embedding API options. - */ - private VertexAiTextEmbeddingOptions options = VertexAiTextEmbeddingOptions.builder() - .taskType(VertexAiTextEmbeddingOptions.TaskType.RETRIEVAL_DOCUMENT) - .model(VertexAiTextEmbeddingOptions.DEFAULT_MODEL_NAME) - .build(); - - public VertexAiTextEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(VertexAiTextEmbeddingOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java deleted file mode 100644 index 4a059efa2..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.gemini; - -import java.io.IOException; - -import com.google.auth.oauth2.GoogleCredentials; -import com.google.cloud.vertexai.VertexAI; -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -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.context.properties.EnableConfigurationProperties; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -/** - * Auto-configuration for Vertex AI Gemini Chat. - * - * @author Christian Tzolov - * @author Soby Chacko - * @author Mark Pollack - * @since 1.0.0 - */ -@AutoConfiguration(after = { SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -@ConditionalOnClass({ VertexAI.class, VertexAiGeminiChatModel.class }) -@EnableConfigurationProperties({ VertexAiGeminiChatProperties.class, VertexAiGeminiConnectionProperties.class }) -@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -public class VertexAiGeminiAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public VertexAI vertexAi(VertexAiGeminiConnectionProperties connectionProperties) throws IOException { - - Assert.hasText(connectionProperties.getProjectId(), "Vertex AI project-id must be set!"); - Assert.hasText(connectionProperties.getLocation(), "Vertex AI location must be set!"); - Assert.notNull(connectionProperties.getTransport(), "Vertex AI transport must be set!"); - - var vertexAIBuilder = new VertexAI.Builder().setProjectId(connectionProperties.getProjectId()) - .setLocation(connectionProperties.getLocation()) - .setTransport(com.google.cloud.vertexai.Transport.valueOf(connectionProperties.getTransport().name())); - - if (StringUtils.hasText(connectionProperties.getApiEndpoint())) { - vertexAIBuilder.setApiEndpoint(connectionProperties.getApiEndpoint()); - } - if (!CollectionUtils.isEmpty(connectionProperties.getScopes())) { - vertexAIBuilder.setScopes(connectionProperties.getScopes()); - } - - if (connectionProperties.getCredentialsUri() != null) { - GoogleCredentials credentials = GoogleCredentials - .fromStream(connectionProperties.getCredentialsUri().getInputStream()); - - vertexAIBuilder.setCredentials(credentials); - } - return vertexAIBuilder.build(); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = VertexAiGeminiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public VertexAiGeminiChatModel vertexAiGeminiChat(VertexAI vertexAi, VertexAiGeminiChatProperties chatProperties, - ToolCallingManager toolCallingManager, ApplicationContext context, RetryTemplate retryTemplate, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - VertexAiGeminiChatModel chatModel = VertexAiGeminiChatModel.builder() - .vertexAI(vertexAi) - .defaultOptions(chatProperties.getOptions()) - .toolCallingManager(toolCallingManager) - .retryTemplate(retryTemplate) - .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) - .build(); - - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - - /** - * Because of the OPEN_API_SCHEMA type, the FunctionCallbackResolver instance must - * different from the other JSON schema types. - */ - // private FunctionCallbackResolver springAiFunctionManager(ApplicationContext - // context) { - // DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - // manager.setSchemaType(SchemaType.OPEN_API_SCHEMA); - // manager.setApplicationContext(context); - // return manager; - // } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatProperties.java deleted file mode 100644 index 1770e5ed1..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiChatProperties.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.gemini; - -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * Configuration properties for Vertex AI Gemini Chat. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -@ConfigurationProperties(VertexAiGeminiChatProperties.CONFIG_PREFIX) -public class VertexAiGeminiChatProperties { - - public static final String CONFIG_PREFIX = "spring.ai.vertex.ai.gemini.chat"; - - public static final String DEFAULT_MODEL = VertexAiGeminiChatModel.ChatModel.GEMINI_2_0_FLASH.getValue(); - - /** - * Vertex AI Gemini API generative options. - */ - private VertexAiGeminiChatOptions options = VertexAiGeminiChatOptions.builder() - .temperature(0.7) - .candidateCount(1) - .model(DEFAULT_MODEL) - .build(); - - public VertexAiGeminiChatOptions getOptions() { - return this.options; - } - - public void setOptions(VertexAiGeminiChatOptions options) { - this.options = options; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiConnectionProperties.java deleted file mode 100644 index 7adf07052..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiConnectionProperties.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.gemini; - -import java.util.List; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.io.Resource; - -/** - * Configuration properties for Vertex AI Gemini Chat. - * - * @author Christian Tzolov - * @since 0.8.0 - */ -@ConfigurationProperties(VertexAiGeminiConnectionProperties.CONFIG_PREFIX) -public class VertexAiGeminiConnectionProperties { - - public static final String CONFIG_PREFIX = "spring.ai.vertex.ai.gemini"; - - /** - * Vertex AI Gemini project ID. - */ - private String projectId; - - /** - * Vertex AI Gemini location. - */ - private String location; - - /** - * URI to Vertex AI Gemini credentials (optional) - */ - private Resource credentialsUri; - - /** - * Vertex AI Gemini API endpoint. - */ - private String apiEndpoint; - - /** - * - */ - private List scopes = List.of(); - - private Transport transport = Transport.GRPC; - - public String getProjectId() { - return this.projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - public String getLocation() { - return this.location; - } - - public void setLocation(String location) { - this.location = location; - } - - public Resource getCredentialsUri() { - return this.credentialsUri; - } - - public void setCredentialsUri(Resource credentialsUri) { - this.credentialsUri = credentialsUri; - } - - public String getApiEndpoint() { - return this.apiEndpoint; - } - - public void setApiEndpoint(String apiEndpoint) { - this.apiEndpoint = apiEndpoint; - } - - public List getScopes() { - return this.scopes; - } - - public void setScopes(List scopes) { - this.scopes = scopes; - } - - public Transport getTransport() { - return this.transport; - } - - public void setTransport(Transport transport) { - this.transport = transport; - } - - public enum Transport { - - /** When used, the clients will send REST requests to the backing service. */ - REST, - /** - * When used, the clients will send gRPC to the backing service. This is usually - * more efficient and is the default transport. - */ - GRPC - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiChatProperties.java deleted file mode 100644 index cff01d7fc..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiChatProperties.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.watsonxai.autoconfigure; - -import java.util.List; - -import org.springframework.ai.watsonx.WatsonxAiChatOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Chat properties for Watsonx.AI Chat. - * - * @author Christian Tzolov - * @author Alexandros Pappas - * @since 1.0.0 - */ -@ConfigurationProperties(WatsonxAiChatProperties.CONFIG_PREFIX) -public class WatsonxAiChatProperties { - - public static final String CONFIG_PREFIX = "spring.ai.watsonx.ai.chat"; - - /** - * Enable Watsonx.AI chat model. - */ - private boolean enabled = true; - - /** - * Watsonx AI generative options. - */ - @NestedConfigurationProperty - private WatsonxAiChatOptions options = WatsonxAiChatOptions.builder() - .model("google/flan-ul2") - .temperature(0.7) - .topP(1.0) - .topK(50) - .decodingMethod("greedy") - .maxNewTokens(20) - .minNewTokens(0) - .repetitionPenalty(1.0) - .stopSequences(List.of()) - .build(); - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public WatsonxAiChatOptions getOptions() { - return this.options; - } - - public void setOptions(WatsonxAiChatOptions options) { - this.options = options; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiConnectionProperties.java deleted file mode 100644 index a49ab048c..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiConnectionProperties.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.watsonxai.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * WatsonX.ai connection autoconfiguration properties. - * - * @author Pablo Sanchidrian Herrera - * @author John Jario Moreno Rojas - * @since 1.0.0 - */ -@ConfigurationProperties(WatsonxAiConnectionProperties.CONFIG_PREFIX) -public class WatsonxAiConnectionProperties { - - public static final String CONFIG_PREFIX = "spring.ai.watsonx.ai"; - - private String baseUrl = "https://us-south.ml.cloud.ibm.com/"; - - private String streamEndpoint = "ml/v1/text/generation_stream?version=2023-05-29"; - - private String textEndpoint = "ml/v1/text/generation?version=2023-05-29"; - - private String embeddingEndpoint = "ml/v1/text/embeddings?version=2023-05-29"; - - private String projectId; - - private String IAMToken; - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - - public String getStreamEndpoint() { - return this.streamEndpoint; - } - - public void setStreamEndpoint(String streamEndpoint) { - this.streamEndpoint = streamEndpoint; - } - - public String getTextEndpoint() { - return this.textEndpoint; - } - - public void setTextEndpoint(String textEndpoint) { - this.textEndpoint = textEndpoint; - } - - public String getEmbeddingEndpoint() { - return this.embeddingEndpoint; - } - - public void setEmbeddingEndpoint(String embeddingEndpoint) { - this.embeddingEndpoint = embeddingEndpoint; - } - - public String getProjectId() { - return this.projectId; - } - - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - public String getIAMToken() { - return this.IAMToken; - } - - public void setIAMToken(String IAMToken) { - this.IAMToken = IAMToken; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiEmbeddingProperties.java deleted file mode 100644 index 1691951f0..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiEmbeddingProperties.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.watsonxai.autoconfigure; - -import org.springframework.ai.watsonx.WatsonxAiEmbeddingOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Watsonx.ai Embedding autoconfiguration properties. - * - * @author Pablo Sanchidrian Herrera - * @since 1.0.0 - */ -@ConfigurationProperties(WatsonxAiEmbeddingProperties.CONFIG_PREFIX) -public class WatsonxAiEmbeddingProperties { - - public static final String CONFIG_PREFIX = "spring.ai.watsonx.ai.embedding"; - - /** - * Enable Watsonx.ai embedding model. - */ - private boolean enabled = true; - - /** - * Client lever Watsonx.ai embedding options. Use this property to configure the - * model. The null values are ignored defaulting to the defaults. - */ - @NestedConfigurationProperty - private WatsonxAiEmbeddingOptions options = WatsonxAiEmbeddingOptions.create() - .withModel(WatsonxAiEmbeddingOptions.DEFAULT_MODEL); - - public String getModel() { - return this.options.getModel(); - } - - public void setModel(String model) { - this.options.setModel(model); - } - - public WatsonxAiEmbeddingOptions getOptions() { - return this.options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java deleted file mode 100644 index db27df995..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure; - -import java.util.List; - -import io.micrometer.observation.ObservationRegistry; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.observation.ChatModelObservationConvention; -import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention; -import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.zhipuai.ZhiPuAiChatModel; -import org.springframework.ai.zhipuai.ZhiPuAiEmbeddingModel; -import org.springframework.ai.zhipuai.ZhiPuAiImageModel; -import org.springframework.ai.zhipuai.api.ZhiPuAiApi; -import org.springframework.ai.zhipuai.api.ZhiPuAiImageApi; -import org.springframework.beans.factory.ObjectProvider; -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.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -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.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.client.ResponseErrorHandler; -import org.springframework.web.client.RestClient; - -/** - * {@link AutoConfiguration Auto-configuration} for ZhiPuAI. - * - * @author Geng Rong - */ -@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class }) -@ConditionalOnClass(ZhiPuAiApi.class) -@EnableConfigurationProperties({ ZhiPuAiConnectionProperties.class, ZhiPuAiChatProperties.class, - ZhiPuAiEmbeddingProperties.class, ZhiPuAiImageProperties.class }) -public class ZhiPuAiAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ZhiPuAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public ZhiPuAiChatModel zhiPuAiChatModel(ZhiPuAiConnectionProperties commonProperties, - ZhiPuAiChatProperties chatProperties, ObjectProvider restClientBuilderProvider, - List toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver, - RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var zhiPuAiApi = zhiPuAiApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), - chatProperties.getApiKey(), commonProperties.getApiKey(), - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - - var chatModel = new ZhiPuAiChatModel(zhiPuAiApi, chatProperties.getOptions(), functionCallbackResolver, - toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(chatModel::setObservationConvention); - - return chatModel; - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ZhiPuAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public ZhiPuAiEmbeddingModel zhiPuAiEmbeddingModel(ZhiPuAiConnectionProperties commonProperties, - ZhiPuAiEmbeddingProperties embeddingProperties, RestClient.Builder restClientBuilder, - RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, - ObjectProvider observationRegistry, - ObjectProvider observationConvention) { - - var zhiPuAiApi = zhiPuAiApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(), - embeddingProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, responseErrorHandler); - - var embeddingModel = new ZhiPuAiEmbeddingModel(zhiPuAiApi, embeddingProperties.getMetadataMode(), - embeddingProperties.getOptions(), retryTemplate, - observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); - - observationConvention.ifAvailable(embeddingModel::setObservationConvention); - - return embeddingModel; - } - - private ZhiPuAiApi zhiPuAiApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { - - String resolvedBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonBaseUrl; - Assert.hasText(resolvedBaseUrl, "ZhiPuAI base URL must be set"); - - String resolvedApiKey = StringUtils.hasText(apiKey) ? apiKey : commonApiKey; - Assert.hasText(resolvedApiKey, "ZhiPuAI API key must be set"); - - return new ZhiPuAiApi(resolvedBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = ZhiPuAiImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true", - matchIfMissing = true) - public ZhiPuAiImageModel zhiPuAiImageModel(ZhiPuAiConnectionProperties commonProperties, - ZhiPuAiImageProperties imageProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate, - ResponseErrorHandler responseErrorHandler) { - - String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey() - : commonProperties.getApiKey(); - - String baseUrl = StringUtils.hasText(imageProperties.getBaseUrl()) ? imageProperties.getBaseUrl() - : commonProperties.getBaseUrl(); - - Assert.hasText(apiKey, "ZhiPuAI API key must be set"); - Assert.hasText(baseUrl, "ZhiPuAI base URL must be set"); - - var zhiPuAiImageApi = new ZhiPuAiImageApi(baseUrl, apiKey, restClientBuilder, responseErrorHandler); - - return new ZhiPuAiImageModel(zhiPuAiImageApi, imageProperties.getOptions(), retryTemplate); - } - - @Bean - @ConditionalOnMissingBean - public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) { - DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver(); - manager.setApplicationContext(context); - return manager; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatProperties.java deleted file mode 100644 index e5f44f475..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiChatProperties.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure; - -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; -import org.springframework.ai.zhipuai.api.ZhiPuAiApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for ZhiPuAI chat model. - * - * @author Geng Rong - */ -@ConfigurationProperties(ZhiPuAiChatProperties.CONFIG_PREFIX) -public class ZhiPuAiChatProperties extends ZhiPuAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.zhipuai.chat"; - - public static final String DEFAULT_CHAT_MODEL = ZhiPuAiApi.ChatModel.GLM_4_Air.value; - - private static final Double DEFAULT_TEMPERATURE = 0.7; - - /** - * Enable ZhiPuAI chat model. - */ - private boolean enabled = true; - - @NestedConfigurationProperty - private ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder() - .model(DEFAULT_CHAT_MODEL) - .temperature(DEFAULT_TEMPERATURE) - .build(); - - public ZhiPuAiChatOptions getOptions() { - return this.options; - } - - public void setOptions(ZhiPuAiChatOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiConnectionProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiConnectionProperties.java deleted file mode 100644 index 3e3aee144..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiConnectionProperties.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties(ZhiPuAiConnectionProperties.CONFIG_PREFIX) -public class ZhiPuAiConnectionProperties extends ZhiPuAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.zhipuai"; - - public static final String DEFAULT_BASE_URL = "https://open.bigmodel.cn/api/paas"; - - public ZhiPuAiConnectionProperties() { - super.setBaseUrl(DEFAULT_BASE_URL); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingProperties.java deleted file mode 100644 index 300b0a23d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiEmbeddingProperties.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure; - -import org.springframework.ai.document.MetadataMode; -import org.springframework.ai.zhipuai.ZhiPuAiEmbeddingOptions; -import org.springframework.ai.zhipuai.api.ZhiPuAiApi; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for ZhiPuAI embedding model. - * - * @author Geng Rong - */ -@ConfigurationProperties(ZhiPuAiEmbeddingProperties.CONFIG_PREFIX) -public class ZhiPuAiEmbeddingProperties extends ZhiPuAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.zhipuai.embedding"; - - public static final String DEFAULT_EMBEDDING_MODEL = ZhiPuAiApi.EmbeddingModel.Embedding_2.value; - - /** - * Enable ZhiPuAI embedding model. - */ - private boolean enabled = true; - - private MetadataMode metadataMode = MetadataMode.EMBED; - - @NestedConfigurationProperty - private ZhiPuAiEmbeddingOptions options = ZhiPuAiEmbeddingOptions.builder().model(DEFAULT_EMBEDDING_MODEL).build(); - - public ZhiPuAiEmbeddingOptions getOptions() { - return this.options; - } - - public void setOptions(ZhiPuAiEmbeddingOptions options) { - this.options = options; - } - - public MetadataMode getMetadataMode() { - return this.metadataMode; - } - - public void setMetadataMode(MetadataMode metadataMode) { - this.metadataMode = metadataMode; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiImageProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiImageProperties.java deleted file mode 100644 index bfcceee5e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiImageProperties.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure; - -import org.springframework.ai.zhipuai.ZhiPuAiImageOptions; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Configuration properties for ZhiPuAI chat model. - * - * @author Geng Rong - */ -@ConfigurationProperties(ZhiPuAiImageProperties.CONFIG_PREFIX) -public class ZhiPuAiImageProperties extends ZhiPuAiParentProperties { - - public static final String CONFIG_PREFIX = "spring.ai.zhipuai.image"; - - /** - * Enable ZhiPuAI image model. - */ - private boolean enabled = true; - - /** - * Options for ZhiPuAI Image API. - */ - @NestedConfigurationProperty - private ZhiPuAiImageOptions options = ZhiPuAiImageOptions.builder().build(); - - public ZhiPuAiImageOptions getOptions() { - return this.options; - } - - public void setOptions(ZhiPuAiImageOptions options) { - this.options = options; - } - - public boolean isEnabled() { - return this.enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiParentProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiParentProperties.java deleted file mode 100644 index 5a8e3f193..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiParentProperties.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure; - -/** - * @author Geng Rong - */ -class ZhiPuAiParentProperties { - - private String apiKey; - - private String baseUrl; - - public String getApiKey() { - return this.apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - public String getBaseUrl() { - return this.baseUrl; - } - - public void setBaseUrl(String baseUrl) { - this.baseUrl = baseUrl; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfiguration.java deleted file mode 100644 index f44ec9613..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfiguration.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.retry.autoconfigure; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.retry.NonTransientAiException; -import org.springframework.ai.retry.RetryUtils; -import org.springframework.ai.retry.TransientAiException; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.http.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; - -/** - * {@link AutoConfiguration Auto-configuration} for AI Retry. - * - * @author Christian Tzolov - */ -@AutoConfiguration -@ConditionalOnClass(RetryUtils.class) -@EnableConfigurationProperties({ SpringAiRetryProperties.class }) -public class SpringAiRetryAutoConfiguration { - - private static final Logger logger = LoggerFactory.getLogger(SpringAiRetryAutoConfiguration.class); - - @Bean - @ConditionalOnMissingBean - public RetryTemplate retryTemplate(SpringAiRetryProperties properties) { - return RetryTemplate.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:" + context.getRetryCount(), throwable); - } - }) - .build(); - } - - @Bean - @ConditionalOnMissingBean - public ResponseErrorHandler responseErrorHandler(SpringAiRetryProperties properties) { - - return new ResponseErrorHandler() { - - @Override - public boolean hasError(@NonNull ClientHttpResponse response) throws IOException { - return response.getStatusCode().isError(); - } - - @Override - public void handleError(@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); - - // Explicitly configured transient codes - if (properties.getOnHttpCodes().contains(response.getStatusCode().value())) { - throw new TransientAiException(message); - } - - // onClientErrors - If true, do not throw a NonTransientAiException, - // and do not attempt retry for 4xx client error codes, false by - // default. - if (!properties.isOnClientErrors() && response.getStatusCode().is4xxClientError()) { - throw new NonTransientAiException(message); - } - - // Explicitly configured non-transient codes - if (!CollectionUtils.isEmpty(properties.getExcludeOnHttpCodes()) - && properties.getExcludeOnHttpCodes().contains(response.getStatusCode().value())) { - throw new NonTransientAiException(message); - } - throw new TransientAiException(message); - } - } - }; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryProperties.java deleted file mode 100644 index 385103a94..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryProperties.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.retry.autoconfigure; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.NestedConfigurationProperty; - -/** - * Properties for AI Retry. - * - * @author Christian Tzolov - */ -@ConfigurationProperties(SpringAiRetryProperties.CONFIG_PREFIX) -public class SpringAiRetryProperties { - - public static final String CONFIG_PREFIX = "spring.ai.retry"; - - /** - * Maximum number of retry attempts. - */ - private int maxAttempts = 10; - - /** - * Exponential Backoff properties. - */ - @NestedConfigurationProperty - private Backoff backoff = new Backoff(); - - /** - * If false, throw a NonTransientAiException, and do not attempt retry for 4xx client - * error codes. False by default. If true, throw a TransientAiException, and attempt - * retry for 4xx client. - */ - private boolean onClientErrors = false; - - /** - * List of HTTP status codes that should not trigger a retry (e.g. throw - * NonTransientAiException). - */ - private List excludeOnHttpCodes = new ArrayList<>(); - - /** - * List of HTTP status codes that should trigger a retry. - */ - private List onHttpCodes = new ArrayList<>(); - - public int getMaxAttempts() { - return this.maxAttempts; - } - - public void setMaxAttempts(int maxAttempts) { - this.maxAttempts = maxAttempts; - } - - public Backoff getBackoff() { - return this.backoff; - } - - public List getExcludeOnHttpCodes() { - return this.excludeOnHttpCodes; - } - - public void setExcludeOnHttpCodes(List onHttpCodes) { - this.excludeOnHttpCodes = onHttpCodes; - } - - public boolean isOnClientErrors() { - return this.onClientErrors; - } - - public void setOnClientErrors(boolean onClientErrors) { - this.onClientErrors = onClientErrors; - } - - public List getOnHttpCodes() { - return this.onHttpCodes; - } - - public void setOnHttpCodes(List onHttpCodes) { - this.onHttpCodes = onHttpCodes; - } - - /** - * Exponential Backoff properties. - */ - public static class Backoff { - - /** - * Initial sleep duration. - */ - private Duration initialInterval = Duration.ofMillis(2000); - - /** - * Backoff interval multiplier. - */ - private int multiplier = 5; - - /** - * Maximum backoff duration. - */ - private Duration maxInterval = Duration.ofMillis(3 * 60000); - - public Duration getInitialInterval() { - return this.initialInterval; - } - - public void setInitialInterval(Duration initialInterval) { - this.initialInterval = initialInterval; - } - - public int getMultiplier() { - return this.multiplier; - } - - public void setMultiplier(int multiplier) { - this.multiplier = multiplier; - } - - public Duration getMaxInterval() { - return this.maxInterval; - } - - public void setMaxInterval(Duration maxInterval) { - this.maxInterval = maxInterval; - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json deleted file mode 100644 index 3a94062ad..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "groups": [ - { - "name": "spring.ai.azure.openai.chat.options.enhancements", - "type": "com.azure.ai.openai.models.AzureChatEnhancementConfiguration", - "sourceType": "org.springframework.ai.azure.openai.AzureOpenAiChatOptions", - "sourceMethod": "getEnhancements()" - }, - { - "name": "spring.ai.mistralai.chat.options.tool-choice", - "type": "org.springframework.ai.mistralai.api.MistralAiApi$ChatCompletionRequest$ToolChoice", - "sourceType": "org.springframework.ai.mistralai.MistralAiChatOptions" - }, - { - "name": "spring.ai.openai.chat.output-audio", - "type": "org.springframework.ai.openai.api.OpenAiApi$ChatCompletionRequest$AudioParameters", - "sourceType": "org.springframework.ai.openai.OpenAiChatOptions" - } - ], - "properties": [ - { - "name": "spring.ai.azure.openai.chat.options.enhancements.grounding", - "type": "com.azure.ai.openai.models.AzureChatGroundingEnhancementConfiguration", - "sourceType": "com.azure.ai.openai.models.AzureChatEnhancementConfiguration" - }, - { - "name": "spring.ai.azure.openai.chat.options.enhancements.ocr", - "type": "com.azure.ai.openai.models.AzureChatOCREnhancementConfiguration", - "sourceType": "com.azure.ai.openai.models.AzureChatEnhancementConfiguration" - }, - { - "name": "spring.ai.openai.chat.output-audio.voice", - "type": "org.springframework.ai.openai.api.OpenAiApi$ChatCompletionRequest$AudioParameters$Voice", - "sourceType": "org.springframework.ai.openai.api.OpenAiApi$ChatCompletionRequest$AudioParameters" - }, - { - "name": "spring.ai.openai.chat.output-audio.format", - "type": "org.springframework.ai.openai.api.OpenAiApi$ChatCompletionRequest$AudioParameters$AudioResponseFormat", - "sourceType": "org.springframework.ai.openai.api.OpenAiApi$ChatCompletionRequest$AudioParameters" - } - ], - "hints": [] -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 7cb623cc6..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright 2023-2024 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. -# -org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration -org.springframework.ai.model.oci.genai.autoconfigure.OCIGenAiAutoConfiguration -org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration -org.springframework.ai.model.transformers.autoconfigure.TransformersEmbeddingModelAutoConfiguration -org.springframework.ai.model.huggingface.autoconfigure.HuggingfaceChatAutoConfiguration -gemini.org.springframework.ai.model.vertexai.autoconfigure.VertexAiGeminiAutoConfiguration -cohere.org.springframework.ai.model.bedrock.autoconfigure.BedrockCohereEmbeddingAutoConfiguration -titan.org.springframework.ai.model.bedrock.autoconfigure.BedrockTitanEmbeddingAutoConfiguration -converse.org.springframework.ai.model.bedrock.autoconfigure.BedrockConverseProxyChatAutoConfiguration -org.springframework.ai.model.chat.observation.autoconfigure.ChatObservationAutoConfiguration -org.springframework.ai.model.embedding.observation.autoconfigure.EmbeddingObservationAutoConfiguration -org.springframework.ai.model.image.observation.autoconfigure.ImageObservationAutoConfiguration -org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration -org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration -org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration -org.springframework.ai.model.postgresml.autoconfigure.PostgresMlAutoConfiguration -org.springframework.ai.model.anthropic.autoconfigure.AnthropicAutoConfiguration -org.springframework.ai.model.watsonxai.autoconfigure.WatsonxAiAutoConfiguration -org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiAutoConfiguration -org.springframework.ai.model.chat.client.autoconfigure.ChatClientAutoConfiguration -org.springframework.ai.autoconfigure.zhipuai.ZhiPuAiAutoConfiguration -org.springframework.ai.autoconfigure.chat.client.ChatClientAutoConfiguration -org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration -org.springframework.ai.model.qianfan.autoconfigure.QianFanAutoConfiguration -org.springframework.ai.model.minimax.autoconfigure.MiniMaxAutoConfiguration -embedding.org.springframework.ai.model.vertexai.autoconfigure.VertexAiEmbeddingAutoConfiguration -cassandra.org.springframework.ai.model.chat.memory.autoconfigure.CassandraChatMemoryAutoConfiguration -org.springframework.ai.autoconfigure.vectorstore.observation.VectorStoreObservationAutoConfiguration -org.springframework.ai.autoconfigure.vectorstore.couchbase.CouchbaseSearchVectorStoreAutoConfiguration diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfigurationTests.java deleted file mode 100644 index d16010981..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/chat/observation/ChatObservationAutoConfigurationTests.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.chat.observation; - -import io.micrometer.core.instrument.composite.CompositeMeterRegistry; -import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; -import io.micrometer.tracing.otel.bridge.OtelTracer; -import io.opentelemetry.api.OpenTelemetry; -import org.junit.jupiter.api.Test; - -import org.springframework.ai.chat.observation.ChatModelCompletionObservationFilter; -import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler; -import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationFilter; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ChatObservationAutoConfiguration}. - * - * @author Thomas Vitale - */ -class ChatObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ChatObservationAutoConfiguration.class)); - - @Test - void meterObservationHandlerEnabled() { - this.contextRunner.withBean(CompositeMeterRegistry.class) - .run(context -> assertThat(context).hasSingleBean(ChatModelMeterObservationHandler.class)); - } - - @Test - void meterObservationHandlerDisabled() { - this.contextRunner.run(context -> assertThat(context).doesNotHaveBean(ChatModelMeterObservationHandler.class)); - } - - @Test - void promptFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationFilter.class)); - } - - @Test - void promptHandlerDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationHandler.class)); - } - - @Test - void promptHandlerEnabled() { - this.contextRunner - .withBean(OtelTracer.class, OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null) - .withPropertyValues("spring.ai.chat.observations.include-prompt=true") - .run(context -> assertThat(context).hasSingleBean(ChatModelPromptContentObservationHandler.class)); - } - - @Test - void promptHandlerDisabled() { - this.contextRunner.withPropertyValues("spring.ai.chat.observations.include-prompt=true") - .run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationHandler.class)); - } - - @Test - void completionFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationFilter.class)); - } - - @Test - void completionHandlerDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationHandler.class)); - } - - @Test - void completionHandlerEnabled() { - this.contextRunner - .withBean(OtelTracer.class, OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null) - .withPropertyValues("spring.ai.chat.observations.include-completion=true") - .run(context -> assertThat(context).hasSingleBean(ChatModelCompletionObservationHandler.class)); - } - - @Test - void completionHandlerDisabled() { - this.contextRunner.withPropertyValues("spring.ai.chat.observations.include-completion=true") - .run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationHandler.class)); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfigurationTests.java deleted file mode 100644 index 42037c9e0..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/embedding/observation/EmbeddingObservationAutoConfigurationTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.embedding.observation; - -import io.micrometer.core.instrument.composite.CompositeMeterRegistry; -import org.junit.jupiter.api.Test; - -import org.springframework.ai.embedding.observation.EmbeddingModelMeterObservationHandler; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link EmbeddingObservationAutoConfiguration}. - * - * @author Thomas Vitale - */ -class EmbeddingObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(EmbeddingObservationAutoConfiguration.class)); - - @Test - void meterObservationHandlerEnabled() { - this.contextRunner.withBean(CompositeMeterRegistry.class) - .run(context -> assertThat(context).hasSingleBean(EmbeddingModelMeterObservationHandler.class)); - } - - @Test - void meterObservationHandlerDisabled() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(EmbeddingModelMeterObservationHandler.class)); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfigurationTests.java deleted file mode 100644 index deb0a22ad..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/image/observation/ImageObservationAutoConfigurationTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.autoconfigure.image.observation; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.image.observation.ImageModelPromptContentObservationFilter; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ImageObservationAutoConfiguration}. - * - * @author Thomas Vitale - */ -class ImageObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ImageObservationAutoConfiguration.class)); - - @Test - void promptFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ImageModelPromptContentObservationFilter.class)); - } - - @Test - void promptFilterEnabled() { - this.contextRunner.withPropertyValues("spring.ai.image.observations.include-prompt=true") - .run(context -> assertThat(context).hasSingleBean(ImageModelPromptContentObservationFilter.class)); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfigurationIT.java deleted file mode 100644 index a46ff4022..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfigurationIT.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.anthropic.autoconfigure; - -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; - -import org.springframework.ai.anthropic.AnthropicChatModel; -import org.springframework.ai.anthropic.AnthropicChatOptions; -import org.springframework.ai.anthropic.api.AnthropicApi; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".*") -public class AnthropicAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(AnthropicAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY")) - .withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class)); - - @Test - void call() { - this.contextRunner.run(context -> { - AnthropicChatModel chatModel = context.getBean(AnthropicChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void callWith8KResponseContext() { - this.contextRunner - .withPropertyValues("spring.ai.anthropic.beta-version=" + AnthropicApi.BETA_MAX_TOKENS, - "spring.ai.anthropic.chat.options.model=" + AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getValue()) - .run(context -> { - AnthropicChatModel chatModel = context.getBean(AnthropicChatModel.class); - var optoins = AnthropicChatOptions.builder().maxTokens(8192).build(); - var response = chatModel.call(new Prompt("Tell me a joke", optoins)); - assertThat(response.getResult().getOutput().getText()).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void stream() { - this.contextRunner.run(context -> { - AnthropicChatModel chatModel = context.getBean(AnthropicChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - - String response = responseFlux.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java deleted file mode 100644 index ceb714227..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.anthropic.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.anthropic.AnthropicChatModel; -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.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit Tests for {@link AnthropicChatProperties}, {@link AnthropicConnectionProperties}. - */ -public class AnthropicPropertiesTests { - - @Test - public void connectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.anthropic.base-url=TEST_BASE_URL", - "spring.ai.anthropic.api-key=abc123", - "spring.ai.anthropic.version=6666", - "spring.ai.anthropic.beta-version=7777", - "spring.ai.anthropic.chat.options.model=MODEL_XYZ", - "spring.ai.anthropic.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(AnthropicChatProperties.class); - var connectionProperties = context.getBean(AnthropicConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getVersion()).isEqualTo("6666"); - assertThat(connectionProperties.getBetaVersion()).isEqualTo("7777"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - // enabled is true by default - assertThat(chatProperties.isEnabled()).isTrue(); - }); - } - - @Test - public void chatOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.anthropic.api-key=API_KEY", - "spring.ai.anthropic.base-url=TEST_BASE_URL", - - "spring.ai.anthropic.chat.options.model=MODEL_XYZ", - "spring.ai.anthropic.chat.options.max-tokens=123", - "spring.ai.anthropic.chat.options.metadata.user-id=MyUserId", - "spring.ai.anthropic.chat.options.stop_sequences=boza,koza", - - "spring.ai.anthropic.chat.options.temperature=0.55", - "spring.ai.anthropic.chat.options.top-p=0.56", - "spring.ai.anthropic.chat.options.top-k=100" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(AnthropicChatProperties.class); - var connectionProperties = context.getBean(AnthropicConnectionProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); - assertThat(chatProperties.getOptions().getStopSequences()).contains("boza", "koza"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56); - assertThat(chatProperties.getOptions().getTopK()).isEqualTo(100); - - assertThat(chatProperties.getOptions().getMetadata().userId()).isEqualTo("MyUserId"); - }); - } - - @Test - public void chatCompletionDisabled() { - - // It is enabled by default - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AnthropicChatModel.class)).isNotEmpty(); - }); - - // Explicitly enable the chat auto-configuration. - new ApplicationContextRunner().withPropertyValues("spring.ai.anthropic.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(AnthropicChatModel.class)).isNotEmpty(); - }); - - // Explicitly disable the chat auto-configuration. - new ApplicationContextRunner().withPropertyValues("spring.ai.anthropic.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isEmpty(); - assertThat(context.getBeansOfType(AnthropicChatModel.class)).isEmpty(); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java deleted file mode 100644 index fb4e53e86..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.anthropic.autoconfigure.tool; - -import java.util.List; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.anthropic.AnthropicChatModel; -import org.springframework.ai.anthropic.AnthropicChatOptions; -import org.springframework.ai.anthropic.api.AnthropicApi; -import org.springframework.ai.model.anthropic.autoconfigure.AnthropicAutoConfiguration; -import org.springframework.ai.model.anthropic.autoconfigure.tool.MockWeatherService.Request; -import org.springframework.ai.model.anthropic.autoconfigure.tool.MockWeatherService.Response; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".*") -class FunctionCallWithFunctionBeanIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionBeanIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY")) - .withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - - this.contextRunner - .withPropertyValues( - "spring.ai.anthropic.chat.options.model=" + AnthropicApi.ChatModel.CLAUDE_3_5_HAIKU.getValue()) - .run(context -> { - - AnthropicChatModel chatModel = context.getBean(AnthropicChatModel.class); - - var userMessage = new UserMessage( - "What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius."); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - AnthropicChatOptions.builder().toolNames("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - response = chatModel.call(new Prompt(List.of(userMessage), - AnthropicChatOptions.builder().toolNames("weatherFunction3").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - - this.contextRunner - .withPropertyValues( - "spring.ai.anthropic.chat.options.model=" + AnthropicApi.ChatModel.CLAUDE_3_5_HAIKU.getValue()) - .run(context -> { - - AnthropicChatModel chatModel = context.getBean(AnthropicChatModel.class); - - var userMessage = new UserMessage( - "What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius."); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - ToolCallingChatOptions.builder().toolNames("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Configuration - static class Config { - - @Bean - @Description("Get the weather in location. Return temperature in 36°F or 36°C format.") - public Function weatherFunction() { - return new MockWeatherService(); - } - - // Relies on the Request's JsonClassDescription annotation to provide the - // function description. - @Bean - public Function weatherFunction3() { - MockWeatherService weatherService = new MockWeatherService(); - return (weatherService::apply); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java deleted file mode 100644 index 27b8d2dab..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.anthropic.autoconfigure.tool; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.anthropic.AnthropicChatModel; -import org.springframework.ai.anthropic.AnthropicChatOptions; -import org.springframework.ai.anthropic.api.AnthropicApi; -import org.springframework.ai.model.anthropic.autoconfigure.AnthropicAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".*") -public class FunctionCallWithPromptFunctionIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithPromptFunctionIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY")) - .withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner - .withPropertyValues( - "spring.ai.anthropic.chat.options.model=" + AnthropicApi.ChatModel.CLAUDE_3_5_HAIKU.getValue()) - .run(context -> { - - AnthropicChatModel chatModel = context.getBean(AnthropicChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, in Paris and in Tokyo? Return the temperature in Celsius."); - - var promptOptions = AnthropicChatOptions.builder() - .toolCallbacks( - List.of(FunctionToolCallback.builder("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location. Return temperature in 36°F or 36°C format.") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationIT.java deleted file mode 100644 index 4cd87697c..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationIT.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; - -import java.lang.reflect.Field; -import java.net.URI; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Collectors; - -import com.azure.ai.openai.OpenAIClient; -import com.azure.ai.openai.OpenAIClientBuilder; -import com.azure.ai.openai.implementation.OpenAIClientImpl; -import com.azure.core.http.HttpHeader; -import com.azure.core.http.HttpHeaderName; -import com.azure.core.http.HttpMethod; -import com.azure.core.http.HttpPipeline; -import com.azure.core.http.HttpRequest; -import com.azure.core.http.HttpResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; - -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAIClientBuilderCustomizer; - -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; -import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel; -import org.springframework.ai.azure.openai.AzureOpenAiChatModel; -import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.SystemPromptTemplate; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @author Piotr Olaszewski - * @author Soby Chacko - * @author Manuel Andreo Garcia - * @since 0.8.0 - */ -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_API_KEY", matches = ".+") -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_ENDPOINT", matches = ".+") -class AzureOpenAiAutoConfigurationIT { - - private static String CHAT_MODEL_NAME = "gpt-4o"; - - private static String EMBEDDING_MODEL_NAME = "text-embedding-ada-002"; - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"), - "spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT"), - - "spring.ai.azure.openai.chat.options.deployment-name=" + CHAT_MODEL_NAME, - "spring.ai.azure.openai.chat.options.temperature=0.8", - "spring.ai.azure.openai.chat.options.maxTokens=123", - - "spring.ai.azure.openai.embedding.options.deployment-name=" + EMBEDDING_MODEL_NAME, - "spring.ai.azure.openai.audio.transcription.options.deployment-name=" + System.getenv("AZURE_OPENAI_TRANSCRIPTION_DEPLOYMENT_NAME") - // @formatter:on - ).withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)); - - private final Message systemMessage = new SystemPromptTemplate(""" - You are a helpful AI assistant. Your name is {name}. - You are an AI assistant that helps people find information. - Your name is {name} - You should reply to the user's request with your name and also in the style of a {voice}. - """).createMessage(Map.of("name", "Bob", "voice", "pirate")); - - private final UserMessage userMessage = new UserMessage( - "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."); - - @Test - void chatCompletion() { - this.contextRunner.run(context -> { - AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - ChatResponse response = chatModel.call(new Prompt(List.of(this.userMessage, this.systemMessage))); - assertThat(response.getResult().getOutput().getText()).contains("Blackbeard"); - }); - } - - @Test - void httpRequestContainsUserAgentAndCustomHeaders() { - this.contextRunner - .withPropertyValues("spring.ai.azure.openai.custom-headers.foo=bar", - "spring.ai.azure.openai.custom-headers.fizz=buzz") - .run(context -> { - OpenAIClientBuilder openAIClientBuilder = context.getBean(OpenAIClientBuilder.class); - OpenAIClient openAIClient = openAIClientBuilder.buildClient(); - Field serviceClientField = ReflectionUtils.findField(OpenAIClient.class, "serviceClient"); - assertThat(serviceClientField).isNotNull(); - ReflectionUtils.makeAccessible(serviceClientField); - OpenAIClientImpl oaci = (OpenAIClientImpl) ReflectionUtils.getField(serviceClientField, openAIClient); - assertThat(oaci).isNotNull(); - HttpPipeline httpPipeline = oaci.getHttpPipeline(); - HttpResponse httpResponse = httpPipeline - .send(new HttpRequest(HttpMethod.POST, new URI(System.getenv("AZURE_OPENAI_ENDPOINT")).toURL())) - .block(); - assertThat(httpResponse).isNotNull(); - HttpHeader httpHeader = httpResponse.getRequest().getHeaders().get(HttpHeaderName.USER_AGENT); - assertThat(httpHeader.getValue().startsWith("spring-ai azsdk-java-azure-ai-openai/")).isTrue(); - HttpHeader customHeader1 = httpResponse.getRequest().getHeaders().get("foo"); - assertThat(customHeader1.getValue()).isEqualTo("bar"); - HttpHeader customHeader2 = httpResponse.getRequest().getHeaders().get("fizz"); - assertThat(customHeader2.getValue()).isEqualTo("buzz"); - }); - } - - @Test - void chatCompletionStreaming() { - this.contextRunner.run(context -> { - - AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - - Flux response = chatModel.stream(new Prompt(List.of(this.userMessage, this.systemMessage))); - - List responses = response.collectList().block(); - assertThat(responses.size()).isGreaterThan(10); - - String stitchedResponseContent = responses.stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - - assertThat(stitchedResponseContent).contains("Blackbeard"); - }); - } - - @Test - void embedding() { - this.contextRunner.run(context -> { - AzureOpenAiEmbeddingModel embeddingModel = context.getBean(AzureOpenAiEmbeddingModel.class); - - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - - assertThat(embeddingModel.dimensions()).isEqualTo(1536); - }); - } - - @Test - @EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_TRANSCRIPTION_DEPLOYMENT_NAME", matches = ".+") - void transcribe() { - this.contextRunner.run(context -> { - AzureOpenAiAudioTranscriptionModel transcriptionModel = context - .getBean(AzureOpenAiAudioTranscriptionModel.class); - Resource audioFile = new ClassPathResource("/speech/jfk.flac"); - String response = transcriptionModel.call(audioFile); - assertThat(response).isEqualTo( - "And so my fellow Americans, ask not what your country can do for you, ask what you can do for your country."); - }); - } - - @Test - void chatActivation() { - - // Disable the chat auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.chat.enabled=false") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty()); - - // The chat auto-configuration is enabled by default. - this.contextRunner.run(context -> assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty()); - - // Explicitly enable the chat auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.chat.enabled=true") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty()); - } - - @Test - void embeddingActivation() { - - // Disable the embedding auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.embedding.enabled=false") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty()); - - // The embedding auto-configuration is enabled by default. - this.contextRunner - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty()); - - // Explicitly enable the embedding auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.embedding.enabled=true") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty()); - } - - @Test - void audioTranscriptionActivation() { - - // Disable the transcription auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.audio.transcription.enabled=false") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty()); - - // The transcription auto-configuration is enabled by default. - this.contextRunner - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty()); - - // Explicitly enable the transcription auto-configuration. - this.contextRunner.withPropertyValues("spring.ai.azure.openai.audio.transcription.enabled=true") - .run(context -> assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty()); - } - - @Test - void openAIClientBuilderCustomizer() { - AtomicBoolean firstCustomizationApplied = new AtomicBoolean(false); - AtomicBoolean secondCustomizationApplied = new AtomicBoolean(false); - this.contextRunner - .withBean("first", AzureOpenAIClientBuilderCustomizer.class, - () -> clientBuilder -> firstCustomizationApplied.set(true)) - .withBean("second", AzureOpenAIClientBuilderCustomizer.class, - () -> clientBuilder -> secondCustomizationApplied.set(true)) - .run(context -> { - context.getBean(OpenAIClientBuilder.class); - assertThat(firstCustomizationApplied.get()).isTrue(); - assertThat(secondCustomizationApplied.get()).isTrue(); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationPropertyTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationPropertyTests.java deleted file mode 100644 index df965f34e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfigurationPropertyTests.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatProperties; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiConnectionProperties; -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @since 0.8.0 - */ -public class AzureOpenAiAutoConfigurationPropertyTests { - - @Test - public void embeddingPropertiesTest() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.azure.openai.api-key=TEST_API_KEY", - "spring.ai.azure.openai.endpoint=TEST_ENDPOINT", - "spring.ai.azure.openai.embedding.options.deployment-name=MODEL_XYZ") - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(AzureOpenAiEmbeddingProperties.class); - var connectionProperties = context.getBean(AzureOpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("TEST_API_KEY"); - assertThat(connectionProperties.getEndpoint()).isEqualTo("TEST_ENDPOINT"); - - assertThat(chatProperties.getOptions().getDeploymentName()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void chatPropertiesTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.azure.openai.api-key=API_KEY", - "spring.ai.azure.openai.endpoint=ENDPOINT", - - "spring.ai.azure.openai.chat.options.deployment-name=MODEL_XYZ", - "spring.ai.azure.openai.chat.options.frequencyPenalty=-1.5", - "spring.ai.azure.openai.chat.options.logitBias.myTokenId=-5", - "spring.ai.azure.openai.chat.options.maxTokens=123", - "spring.ai.azure.openai.chat.options.n=10", - "spring.ai.azure.openai.chat.options.presencePenalty=0", - "spring.ai.azure.openai.chat.options.stop=boza,koza", - "spring.ai.azure.openai.chat.options.temperature=0.55", - "spring.ai.azure.openai.chat.options.topP=0.56", - "spring.ai.azure.openai.chat.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(AzureOpenAiChatProperties.class); - var connectionProperties = context.getBean(AzureOpenAiConnectionProperties.class); - var embeddingProperties = context.getBean(AzureOpenAiEmbeddingProperties.class); - - assertThat(connectionProperties.getEndpoint()).isEqualTo("ENDPOINT"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getDeploymentName()).isEqualTo("text-embedding-ada-002"); - - assertThat(chatProperties.getOptions().getDeploymentName()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5); - assertThat(chatProperties.getOptions().getLogitBias().get("myTokenId")).isEqualTo(-5); - assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); - assertThat(chatProperties.getOptions().getN()).isEqualTo(10); - assertThat(chatProperties.getOptions().getPresencePenalty()).isEqualTo(0); - assertThat(chatProperties.getOptions().getStop()).contains("boza", "koza"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56); - - assertThat(chatProperties.getOptions().getUser()).isEqualTo("userXYZ"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiDirectOpenAiAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiDirectOpenAiAutoConfigurationIT.java deleted file mode 100644 index 99c4ed05c..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiDirectOpenAiAutoConfigurationIT.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; -import org.springframework.ai.azure.openai.AzureOpenAiChatModel; -import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.SystemPromptTemplate; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @since 1.0.0 - */ -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") -public class AzureOpenAiDirectOpenAiAutoConfigurationIT { - - private static String CHAT_MODEL_NAME = "gpt-4o"; - - private static String EMBEDDING_MODEL_NAME = "text-embedding-ada-002"; - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.azure.openai.openai-api-key=" + System.getenv("OPENAI_API_KEY"), - - "spring.ai.azure.openai.chat.options.deployment-name=" + CHAT_MODEL_NAME, - "spring.ai.azure.openai.chat.options.temperature=0.8", - "spring.ai.azure.openai.chat.options.maxTokens=123", - "spring.ai.azure.openai.embedding.options.deployment-name=" + EMBEDDING_MODEL_NAME - // @formatter:on - ).withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)); - - private final Message systemMessage = new SystemPromptTemplate(""" - You are a helpful AI assistant. Your name is {name}. - You are an AI assistant that helps people find information. - Your name is {name} - You should reply to the user's request with your name and also in the style of a {voice}. - """).createMessage(Map.of("name", "Bob", "voice", "pirate")); - - private final UserMessage userMessage = new UserMessage( - "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."); - - @Test - public void chatCompletion() { - this.contextRunner.run(context -> { - AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - ChatResponse response = chatModel.call(new Prompt(List.of(this.userMessage, this.systemMessage))); - assertThat(response.getResult().getOutput().getText()).contains("Blackbeard"); - }); - } - - @Test - public void chatCompletionStreaming() { - this.contextRunner.run(context -> { - - AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - - Flux response = chatModel.stream(new Prompt(List.of(this.userMessage, this.systemMessage))); - - List responses = response.collectList().block(); - assertThat(responses.size()).isGreaterThan(10); - - String stitchedResponseContent = responses.stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - - assertThat(stitchedResponseContent).contains("Blackbeard"); - }); - } - - @Test - void embedding() { - this.contextRunner.run(context -> { - AzureOpenAiEmbeddingModel embeddingModel = context.getBean(AzureOpenAiEmbeddingModel.class); - - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - - assertThat(embeddingModel.dimensions()).isEqualTo(1536); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/DeploymentNameUtil.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/DeploymentNameUtil.java deleted file mode 100644 index 96655413b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/DeploymentNameUtil.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure.tool; - -import org.springframework.util.StringUtils; - -public final class DeploymentNameUtil { - - private DeploymentNameUtil() { - - } - - public static String getDeploymentName() { - String deploymentName = System.getenv("AZURE_OPENAI_DEPLOYMENT_NAME"); - if (StringUtils.hasText(deploymentName)) { - return deploymentName; - } - else { - return "gpt-4o"; - } - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java deleted file mode 100644 index 5f560d174..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.springframework.ai.model.azure.openai.autoconfigure.tool; - -import java.util.List; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; -import org.springframework.ai.azure.openai.AzureOpenAiChatModel; -import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_API_KEY", matches = ".+") -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_ENDPOINT", matches = ".+") -class FunctionCallWithFunctionBeanIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionBeanIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"), - "spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT")) - // @formatter:onn - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner - .withPropertyValues( - "spring.ai.azure.openai.chat.options..deployment-name=" + DeploymentNameUtil.getDeploymentName()) - .run(context -> { - - ChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Paris and in Tokyo? Use Multi-turn function calling."); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - AzureOpenAiChatOptions.builder().function("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - response = chatModel.call(new Prompt(List.of(userMessage), - AzureOpenAiChatOptions.builder().function("weatherFunction3").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - this.contextRunner - .withPropertyValues( - "spring.ai.azure.openai.chat.options..deployment-name=" + DeploymentNameUtil.getDeploymentName()) - .run(context -> { - - ChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Paris and in Tokyo? Use Multi-turn function calling."); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - ToolCallingChatOptions.builder().toolNames("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Configuration - static class Config { - - @Bean - @Description("Get the weather in location") - public Function weatherFunction() { - return new MockWeatherService(); - } - - // Relies on the Request's JsonClassDescription annotation to provide the - // function description. - @Bean - public Function weatherFunction3() { - MockWeatherService weatherService = new MockWeatherService(); - return (weatherService::apply); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionWrapperIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionWrapperIT.java deleted file mode 100644 index b809da5dd..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithFunctionWrapperIT.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure.tool; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; -import org.springframework.ai.azure.openai.AzureOpenAiChatModel; -import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.tool.ToolCallback; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_API_KEY", matches = ".+") -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_ENDPOINT", matches = ".+") -public class FunctionCallWithFunctionWrapperIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionWrapperIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"), - "spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT")) - // @formatter:onn - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner - .withPropertyValues( - "spring.ai.azure.openai.chat.options.deployment-name=" + DeploymentNameUtil.getDeploymentName()) - .run(context -> { - - AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Paris and in Tokyo?"); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - AzureOpenAiChatOptions.builder().function("WeatherInfo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).containsAnyOf("30", "10", "15"); - - }); - } - - @Configuration - static class Config { - - @Bean - public ToolCallback weatherFunctionInfo() { - - return FunctionToolCallback.builder("WeatherInfo", new MockWeatherService()) - .description("Get the current weather in a given location") - .inputType(MockWeatherService.Request.class) - .build(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java deleted file mode 100644 index 90e5d7a9b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/FunctionCallWithPromptFunctionIT.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure.tool; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration; -import org.springframework.ai.azure.openai.AzureOpenAiChatModel; -import org.springframework.ai.azure.openai.AzureOpenAiChatOptions; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_API_KEY", matches = ".+") -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_ENDPOINT", matches = ".+") -public class FunctionCallWithPromptFunctionIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithPromptFunctionIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"), - "spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT")) - // @formatter:onn - .withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner - .withPropertyValues( - "spring.ai.azure.openai.chat.options.deployment-name=" + DeploymentNameUtil.getDeploymentName()) - .run(context -> { - - AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, in Paris and in Tokyo? Use Multi-turn function calling."); - - var promptOptions = AzureOpenAiChatOptions.builder() - .functionCallbacks( - List.of(FunctionToolCallback.builder("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/MockWeatherService.java deleted file mode 100644 index 20c1899ef..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/tool/MockWeatherService.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.azure.openai.autoconfigure.tool; - -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - -/** - * Mock 3rd party weather service. - * - * @author Christian Tzolov - */ -public class MockWeatherService implements Function { - - @Override - public Response apply(Request request) { - - double temperature = 0; - if (request.location().contains("Paris")) { - temperature = 15; - } - else if (request.location().contains("Tokyo")) { - temperature = 10; - } - else if (request.location().contains("San Francisco")) { - temperature = 30; - } - - return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); - } - - /** - * Temperature units. - */ - public enum Unit { - - /** - * Celsius. - */ - C("metric"), - /** - * Fahrenheit. - */ - F("imperial"); - - /** - * Human readable unit name. - */ - public final String unitName; - - Unit(String text) { - this.unitName = text; - } - - } - - /** - * Weather Function request. - */ - @JsonInclude(Include.NON_NULL) - @JsonClassDescription("Weather API request") - public record Request(@JsonProperty(required = true, - value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, - - @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { - - } - - /** - * Weather Function response. - */ - public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity, - Unit unit) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionConfigurationIT.java deleted file mode 100644 index af51a1e93..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockAwsConnectionConfigurationIT.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure; - -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.auth.credentials.AwsCredentials; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.Region; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; - -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Wei Jiang - * @author Mark Pollack - * @since 1.0.0 - */ -@RequiresAwsCredentials -public class BedrockAwsConnectionConfigurationIT { - - @Test - public void autoConfigureAWSCredentialAndRegionProvider() { - BedrockTestUtils.getContextRunner() - .withConfiguration(AutoConfigurations.of(TestAutoConfiguration.class)) - .run(context -> { - var awsCredentialsProvider = context.getBean(AwsCredentialsProvider.class); - var awsRegionProvider = context.getBean(AwsRegionProvider.class); - - assertThat(awsCredentialsProvider).isNotNull(); - assertThat(awsRegionProvider).isNotNull(); - - var credentials = awsCredentialsProvider.resolveCredentials(); - assertThat(credentials).isNotNull(); - assertThat(credentials.accessKeyId()).isEqualTo(System.getenv("AWS_ACCESS_KEY_ID")); - assertThat(credentials.secretAccessKey()).isEqualTo(System.getenv("AWS_SECRET_ACCESS_KEY")); - - assertThat(awsRegionProvider.getRegion()).isEqualTo(Region.US_EAST_1); - }); - } - - @Test - public void autoConfigureWithCustomAWSCredentialAndRegionProvider() { - BedrockTestUtils.getContextRunner() - .withConfiguration(AutoConfigurations.of(TestAutoConfiguration.class, - CustomAwsCredentialsProviderAndAwsRegionProviderAutoConfiguration.class)) - .run(context -> { - var awsCredentialsProvider = context.getBean(AwsCredentialsProvider.class); - var awsRegionProvider = context.getBean(AwsRegionProvider.class); - - assertThat(awsCredentialsProvider).isNotNull(); - assertThat(awsRegionProvider).isNotNull(); - - var credentials = awsCredentialsProvider.resolveCredentials(); - assertThat(credentials).isNotNull(); - assertThat(credentials.accessKeyId()).isEqualTo("CUSTOM_ACCESS_KEY"); - assertThat(credentials.secretAccessKey()).isEqualTo("CUSTOM_SECRET_ACCESS_KEY"); - - assertThat(awsRegionProvider.getRegion()).isEqualTo(Region.AWS_GLOBAL); - }); - } - - @EnableConfigurationProperties({ BedrockAwsConnectionProperties.class }) - @Import(BedrockAwsConnectionConfiguration.class) - static class TestAutoConfiguration { - - } - - @AutoConfiguration - static class CustomAwsCredentialsProviderAndAwsRegionProviderAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public AwsCredentialsProvider credentialsProvider() { - return new AwsCredentialsProvider() { - - @Override - public AwsCredentials resolveCredentials() { - return new AwsCredentials() { - - @Override - public String accessKeyId() { - return "CUSTOM_ACCESS_KEY"; - } - - @Override - public String secretAccessKey() { - return "CUSTOM_SECRET_ACCESS_KEY"; - } - - }; - } - - }; - } - - @Bean - @ConditionalOnMissingBean - public AwsRegionProvider regionProvider() { - return new AwsRegionProvider() { - - @Override - public Region getRegion() { - return Region.AWS_GLOBAL; - } - - }; - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockTestUtils.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockTestUtils.java deleted file mode 100644 index 88b12b6c3..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/BedrockTestUtils.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure; - -import com.fasterxml.jackson.databind.ObjectMapper; -import software.amazon.awssdk.regions.Region; - -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -public final class BedrockTestUtils { - - private BedrockTestUtils() { - } // Prevent instantiation - - public static ApplicationContextRunner getContextRunner() { - return new ApplicationContextRunner() - .withPropertyValues("spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), - "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), - "spring.ai.bedrock.aws.session-token=" + System.getenv("AWS_SESSION_TOKEN"), - "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id()) - .withUserConfiguration(Config.class); - } - - public static ApplicationContextRunner getContextRunnerWithUserConfiguration() { - return new ApplicationContextRunner().withUserConfiguration(Config.class); - } - - @Configuration - static class Config { - - @Bean - public ObjectMapper objectMapper() { - return new ObjectMapper(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/RequiresAwsCredentials.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/RequiresAwsCredentials.java deleted file mode 100644 index 00f5e633b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/RequiresAwsCredentials.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; - -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") -@EnabledIfEnvironmentVariable(named = "AWS_SESSION_TOKEN", matches = ".*") -public @interface RequiresAwsCredentials { - - // You can add custom properties here if needed - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java deleted file mode 100644 index f612425cf..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.cohere; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionProperties; -import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils; -import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials; -import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingModel; -import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingModel; -import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingRequest; -import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingRequest.InputType; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @author Mark Pollack - * @since 1.0.0 - */ -@RequiresAwsCredentials -public class BedrockCohereEmbeddingAutoConfigurationIT { - - private final ApplicationContextRunner contextRunner = BedrockTestUtils.getContextRunner() - .withPropertyValues("spring.ai.bedrock.cohere.embedding.enabled=true", - "spring.ai.bedrock.cohere.embedding.model=" + CohereEmbeddingModel.COHERE_EMBED_MULTILINGUAL_V3.id(), - "spring.ai.bedrock.cohere.embedding.options.inputType=SEARCH_DOCUMENT", - "spring.ai.bedrock.cohere.embedding.options.truncate=NONE") - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)); - - @Test - public void singleEmbedding() { - this.contextRunner.run(context -> { - BedrockCohereEmbeddingModel embeddingModel = context.getBean(BedrockCohereEmbeddingModel.class); - assertThat(embeddingModel).isNotNull(); - EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(List.of("Hello World")); - assertThat(embeddingResponse.getResults()).hasSize(1); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingModel.dimensions()).isEqualTo(1024); - }); - } - - @Test - public void batchEmbedding() { - this.contextRunner.run(context -> { - - BedrockCohereEmbeddingModel embeddingModel = context.getBean(BedrockCohereEmbeddingModel.class); - - assertThat(embeddingModel).isNotNull(); - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - - assertThat(embeddingModel.dimensions()).isEqualTo(1024); - - }); - } - - @Test - public void propertiesTest() { - - BedrockTestUtils.getContextRunnerWithUserConfiguration() - .withPropertyValues("spring.ai.bedrock.cohere.embedding.enabled=true", - "spring.ai.bedrock.aws.access-key=ACCESS_KEY", "spring.ai.bedrock.aws.secret-key=SECRET_KEY", - "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), - "spring.ai.bedrock.cohere.embedding.model=MODEL_XYZ", - "spring.ai.bedrock.cohere.embedding.options.inputType=CLASSIFICATION", - "spring.ai.bedrock.cohere.embedding.options.truncate=START") - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)) - .run(context -> { - var properties = context.getBean(BedrockCohereEmbeddingProperties.class); - var awsProperties = context.getBean(BedrockAwsConnectionProperties.class); - - assertThat(properties.isEnabled()).isTrue(); - assertThat(awsProperties.getRegion()).isEqualTo(Region.US_EAST_1.id()); - assertThat(properties.getModel()).isEqualTo("MODEL_XYZ"); - - assertThat(properties.getOptions().getInputType()).isEqualTo(InputType.CLASSIFICATION); - assertThat(properties.getOptions().getTruncate()).isEqualTo(CohereEmbeddingRequest.Truncate.START); - - assertThat(awsProperties.getAccessKey()).isEqualTo("ACCESS_KEY"); - assertThat(awsProperties.getSecretKey()).isEqualTo("SECRET_KEY"); - }); - } - - @Test - public void embeddingDisabled() { - - // It is disabled by default - BedrockTestUtils.getContextRunnerWithUserConfiguration() - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isEmpty(); - assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isEmpty(); - }); - - // Explicitly enable the embedding auto-configuration. - BedrockTestUtils.getContextRunnerWithUserConfiguration() - .withPropertyValues("spring.ai.bedrock.cohere.embedding.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isNotEmpty(); - }); - - // Explicitly disable the embedding auto-configuration. - BedrockTestUtils.getContextRunnerWithUserConfiguration() - .withPropertyValues("spring.ai.bedrock.cohere.embedding.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isEmpty(); - assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isEmpty(); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfigurationIT.java deleted file mode 100644 index f89cda503..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfigurationIT.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.converse; - -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils; -import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials; -import org.springframework.ai.bedrock.converse.BedrockProxyChatModel; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@RequiresAwsCredentials -public class BedrockConverseProxyChatAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(BedrockConverseProxyChatAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = BedrockTestUtils.getContextRunner() - .withPropertyValues( - "spring.ai.bedrock.converse.chat.options.model=" + "anthropic.claude-3-5-sonnet-20240620-v1:0", - "spring.ai.bedrock.converse.chat.options.temperature=0.5") - .withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class)); - - @Test - void call() { - this.contextRunner.run(context -> { - BedrockProxyChatModel chatModel = context.getBean(BedrockProxyChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void stream() { - this.contextRunner.run(context -> { - BedrockProxyChatModel chatModel = context.getBean(BedrockProxyChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - - String response = responseFlux.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java deleted file mode 100644 index b71900197..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.converse; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * - * Unit Tests for {@link BedrockConverseProxyChatProperties}. - */ -public class BedrockConverseProxyChatPropertiesTests { - - @Test - public void chatOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.bedrock.converse.chat.options.model=MODEL_XYZ", - - "spring.ai.bedrock.converse.chat.options.max-tokens=123", - "spring.ai.bedrock.converse.chat.options.metadata.user-id=MyUserId", - "spring.ai.bedrock.converse.chat.options.stop_sequences=boza,koza", - - "spring.ai.bedrock.converse.chat.options.temperature=0.55", - "spring.ai.bedrock.converse.chat.options.top-p=0.56", - "spring.ai.bedrock.converse.chat.options.top-k=100" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(BedrockConverseProxyChatProperties.class); - - assertThat(chatProperties.isEnabled()).isTrue(); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); - assertThat(chatProperties.getOptions().getStopSequences()).contains("boza", "koza"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56); - assertThat(chatProperties.getOptions().getTopK()).isEqualTo(100); - - }); - } - - @Test - public void chatCompletionDisabled() { - - // It is enabled by default - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class)) - .run(context -> assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isNotEmpty()); - - // Explicitly enable the chat auto-configuration. - new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.converse.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class)) - .run(context -> assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isNotEmpty()); - - // Explicitly disable the chat auto-configuration. - new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.converse.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class)) - .run(context -> assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isEmpty()); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithPromptFunctionIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithPromptFunctionIT.java deleted file mode 100644 index 95c595370..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/FunctionCallWithPromptFunctionIT.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.converse.tool; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils; -import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials; -import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration; -import org.springframework.ai.bedrock.converse.BedrockProxyChatModel; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@RequiresAwsCredentials -public class FunctionCallWithPromptFunctionIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithPromptFunctionIT.class); - - private final ApplicationContextRunner contextRunner = BedrockTestUtils.getContextRunner() - .withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner - .withPropertyValues( - "spring.ai.bedrock.converse.chat.options.model=" + "anthropic.claude-3-5-sonnet-20240620-v1:0") - .run(context -> { - - BedrockProxyChatModel chatModel = context.getBean(BedrockProxyChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, in Paris and in Tokyo? Return the temperature in Celsius."); - - var promptOptions = ToolCallingChatOptions.builder() - .toolCallbacks( - List.of(FunctionToolCallback.builder("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location. Return temperature in 36°F or 36°C format.") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/MockWeatherService.java deleted file mode 100644 index 29081ec2f..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/tool/MockWeatherService.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.converse.tool; - -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - -/** - * Mock 3rd party weather service. - * - * @author Christian Tzolov - */ -public class MockWeatherService implements Function { - - @Override - public Response apply(Request request) { - - double temperature = 0; - if (request.location().contains("Paris")) { - temperature = 15; - } - else if (request.location().contains("Tokyo")) { - temperature = 10; - } - else if (request.location().contains("San Francisco")) { - temperature = 30; - } - - return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); - } - - /** - * Temperature units. - */ - public enum Unit { - - /** - * Celsius. - */ - C("metric"), - /** - * Fahrenheit. - */ - F("imperial"); - - /** - * Human readable unit name. - */ - public final String unitName; - - Unit(String text) { - this.unitName = text; - } - - } - - /** - * Weather Function request. - */ - @JsonInclude(Include.NON_NULL) - @JsonClassDescription("Weather API request") - public record Request(@JsonProperty(required = true, - value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, - @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { - - } - - /** - * Weather Function response. - */ - public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity, - Unit unit) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java deleted file mode 100644 index 107bb2a6d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.bedrock.autoconfigure.titan; - -import java.util.Base64; -import java.util.List; - -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionProperties; -import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils; -import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials; -import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel; -import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel.InputType; -import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi.TitanEmbeddingModel; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.core.io.DefaultResourceLoader; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @author Mark Pollack - * @since 1.0.0 - */ -@RequiresAwsCredentials -public class BedrockTitanEmbeddingAutoConfigurationIT { - - private final ApplicationContextRunner contextRunner = BedrockTestUtils.getContextRunner() - .withPropertyValues("spring.ai.bedrock.titan.embedding.enabled=true", - "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), - "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), - "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), - "spring.ai.bedrock.titan.embedding.model=" + TitanEmbeddingModel.TITAN_EMBED_IMAGE_V1.id()) - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)); - - @Test - public void singleTextEmbedding() { - this.contextRunner.withPropertyValues("spring.ai.bedrock.titan.embedding.inputType=TEXT").run(context -> { - BedrockTitanEmbeddingModel embeddingModel = context.getBean(BedrockTitanEmbeddingModel.class); - assertThat(embeddingModel).isNotNull(); - EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(List.of("Hello World")); - assertThat(embeddingResponse.getResults()).hasSize(1); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingModel.dimensions()).isEqualTo(1024); - }); - } - - @Test - public void singleImageEmbedding() { - this.contextRunner.withPropertyValues("spring.ai.bedrock.titan.embedding.inputType=IMAGE").run(context -> { - BedrockTitanEmbeddingModel embeddingModel = context.getBean(BedrockTitanEmbeddingModel.class); - assertThat(embeddingModel).isNotNull(); - - byte[] image = new DefaultResourceLoader().getResource("classpath:/spring_framework.png") - .getContentAsByteArray(); - - var base64Image = Base64.getEncoder().encodeToString(image); - - EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(List.of(base64Image)); - - assertThat(embeddingResponse.getResults()).hasSize(1); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingModel.dimensions()).isEqualTo(1024); - }); - } - - @Test - public void propertiesTest() { - - BedrockTestUtils.getContextRunnerWithUserConfiguration() - .withPropertyValues("spring.ai.bedrock.titan.embedding.enabled=true", - "spring.ai.bedrock.aws.access-key=ACCESS_KEY", "spring.ai.bedrock.aws.secret-key=SECRET_KEY", - "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), - "spring.ai.bedrock.titan.embedding.model=MODEL_XYZ", - "spring.ai.bedrock.titan.embedding.inputType=TEXT") - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)) - .run(context -> { - var properties = context.getBean(BedrockTitanEmbeddingProperties.class); - var awsProperties = context.getBean(BedrockAwsConnectionProperties.class); - - assertThat(properties.isEnabled()).isTrue(); - assertThat(awsProperties.getRegion()).isEqualTo(Region.US_EAST_1.id()); - assertThat(properties.getModel()).isEqualTo("MODEL_XYZ"); - - assertThat(properties.getInputType()).isEqualTo(InputType.TEXT); - - assertThat(awsProperties.getAccessKey()).isEqualTo("ACCESS_KEY"); - assertThat(awsProperties.getSecretKey()).isEqualTo("SECRET_KEY"); - }); - } - - @Test - public void embeddingDisabled() { - - // It is disabled by default - BedrockTestUtils.getContextRunnerWithUserConfiguration() - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isEmpty(); - assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isEmpty(); - }); - - // Explicitly enable the embedding auto-configuration. - BedrockTestUtils.getContextRunnerWithUserConfiguration() - .withPropertyValues("spring.ai.bedrock.titan.embedding.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isNotEmpty(); - }); - - // Explicitly disable the embedding auto-configuration. - BedrockTestUtils.getContextRunnerWithUserConfiguration() - .withPropertyValues("spring.ai.bedrock.titan.embedding.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isEmpty(); - assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isEmpty(); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfigurationIT.java deleted file mode 100644 index 5906eb3b9..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfigurationIT.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.client.autoconfigure; - -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; - -import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.chat.client.ChatClientCustomizer; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") -public class ChatClientAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(ChatClientAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"), - "spring.ai.openai.chat.options.model=gpt-4o") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class, ChatClientAutoConfiguration.class)); - - @Test - void implicitlyEnabled() { - this.contextRunner.run(context -> assertThat(context.getBeansOfType(ChatClient.Builder.class)).isNotEmpty()); - } - - @Test - void explicitlyEnabled() { - this.contextRunner.withPropertyValues("spring.ai.chat.client.enabled=true") - .run(context -> assertThat(context.getBeansOfType(ChatClient.Builder.class)).isNotEmpty()); - } - - @Test - void explicitlyDisabled() { - this.contextRunner.withPropertyValues("spring.ai.chat.client.enabled=false") - .run(context -> assertThat(context.getBeansOfType(ChatClient.Builder.class)).isEmpty()); - } - - @Test - void generate() { - this.contextRunner.run(context -> { - ChatClient.Builder builder = context.getBean(ChatClient.Builder.class); - - assertThat(builder).isNotNull(); - - ChatClient chatClient = builder.build(); - - String response = chatClient.prompt().user("Hello").call().content(); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void testChatClientCustomizers() { - this.contextRunner.withUserConfiguration(Config.class).run(context -> { - - ChatClient.Builder builder = context.getBean(ChatClient.Builder.class); - - ChatClient chatClient = builder.build(); - - assertThat(chatClient).isNotNull(); - - ActorsFilms actorsFilms = chatClient.prompt() - .user(u -> u.param("actor", "Tom Hanks")) - .call() - .entity(ActorsFilms.class); - - logger.info("" + actorsFilms); - assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks"); - assertThat(actorsFilms.movies()).hasSize(5); - }); - } - - record ActorsFilms(String actor, List movies) { - - } - - @Configuration - static class Config { - - @Bean - public ChatClientCustomizer chatClientCustomizer() { - return b -> b.defaultSystem("You are a movie expert.") - .defaultUser("Generate the filmography of 5 movies for {actor}."); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientObservationAutoConfigurationTests.java deleted file mode 100644 index 7fd0ee9f8..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientObservationAutoConfigurationTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.client.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.chat.client.observation.ChatClientInputContentObservationFilter; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ChatClientAutoConfiguration} observability support. - * - * @author Christian Tzolov - */ -class ChatClientObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ChatClientAutoConfiguration.class)); - - @Test - void inputContentFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatClientInputContentObservationFilter.class)); - } - - @Test - void inputContentFilterEnabled() { - this.contextRunner.withPropertyValues("spring.ai.chat.client.observations.include-input=true") - .run(context -> assertThat(context).hasSingleBean(ChatClientInputContentObservationFilter.class)); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryAutoConfigurationIT.java deleted file mode 100644 index a41904290..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryAutoConfigurationIT.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.memory.autoconfigure.cassandra; - -import java.time.Duration; -import java.util.List; - -import com.datastax.driver.core.utils.UUIDs; -import org.junit.jupiter.api.Test; -import org.testcontainers.containers.CassandraContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.utility.DockerImageName; - -import org.springframework.ai.chat.memory.cassandra.CassandraChatMemory; -import org.springframework.ai.chat.messages.AssistantMessage; -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.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mick Semb Wever - * @author Jihoon Kim - * @since 1.0.0 - */ -@Testcontainers -class CassandraChatMemoryAutoConfigurationIT { - - static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("cassandra"); - - @Container - static CassandraContainer cassandraContainer = new CassandraContainer(DEFAULT_IMAGE_NAME.withTag("5.0")); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration( - AutoConfigurations.of(CassandraChatMemoryAutoConfiguration.class, CassandraAutoConfiguration.class)) - .withPropertyValues("spring.ai.chat.memory.cassandra.keyspace=test_autoconfigure"); - - @Test - void addAndGet() { - this.contextRunner.withPropertyValues("spring.cassandra.contactPoints=" + getContactPointHost()) - .withPropertyValues("spring.cassandra.port=" + getContactPointPort()) - .withPropertyValues("spring.cassandra.localDatacenter=" + cassandraContainer.getLocalDatacenter()) - .withPropertyValues("spring.ai.chat.memory.cassandra.time-to-live=" + getTimeToLive()) - .run(context -> { - CassandraChatMemory memory = context.getBean(CassandraChatMemory.class); - - String sessionId = UUIDs.timeBased().toString(); - assertThat(memory.get(sessionId, Integer.MAX_VALUE)).isEmpty(); - - memory.add(sessionId, new UserMessage("test question")); - - assertThat(memory.get(sessionId, Integer.MAX_VALUE)).hasSize(1); - assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(0).getMessageType()) - .isEqualTo(MessageType.USER); - assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(0).getText()).isEqualTo("test question"); - - memory.clear(sessionId); - assertThat(memory.get(sessionId, Integer.MAX_VALUE)).isEmpty(); - - memory.add(sessionId, List.of(new UserMessage("test question"), new AssistantMessage("test answer"))); - - assertThat(memory.get(sessionId, Integer.MAX_VALUE)).hasSize(2); - assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(1).getMessageType()) - .isEqualTo(MessageType.USER); - assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(1).getText()).isEqualTo("test question"); - assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(0).getMessageType()) - .isEqualTo(MessageType.ASSISTANT); - assertThat(memory.get(sessionId, Integer.MAX_VALUE).get(0).getText()).isEqualTo("test answer"); - - CassandraChatMemoryProperties properties = context.getBean(CassandraChatMemoryProperties.class); - assertThat(properties.getTimeToLive()).isEqualTo(getTimeToLive()); - }); - } - - @Test - void compareTimeToLive_ISO8601Format() { - this.contextRunner.withPropertyValues("spring.cassandra.contactPoints=" + getContactPointHost()) - .withPropertyValues("spring.cassandra.port=" + getContactPointPort()) - .withPropertyValues("spring.cassandra.localDatacenter=" + cassandraContainer.getLocalDatacenter()) - .withPropertyValues("spring.ai.chat.memory.cassandra.time-to-live=" + getTimeToLiveString()) - .run(context -> { - CassandraChatMemoryProperties properties = context.getBean(CassandraChatMemoryProperties.class); - assertThat(properties.getTimeToLive()).isEqualTo(Duration.parse(getTimeToLiveString())); - }); - } - - private String getContactPointHost() { - return cassandraContainer.getContactPoint().getHostString(); - } - - private String getContactPointPort() { - return String.valueOf(cassandraContainer.getContactPoint().getPort()); - } - - private Duration getTimeToLive() { - return Duration.ofSeconds(12000); - } - - private String getTimeToLiveString() { - return "PT1M"; - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryPropertiesTest.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryPropertiesTest.java deleted file mode 100644 index dab9eb806..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/cassandra/CassandraChatMemoryPropertiesTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.memory.autoconfigure.cassandra; - -import java.time.Duration; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.chat.memory.cassandra.CassandraChatMemoryConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Mick Semb Wever - * @author Jihoon Kim - * @since 1.0.0 - */ -class CassandraChatMemoryPropertiesTest { - - @Test - void defaultValues() { - var props = new CassandraChatMemoryProperties(); - assertThat(props.getKeyspace()).isEqualTo(CassandraChatMemoryConfig.DEFAULT_KEYSPACE_NAME); - assertThat(props.getTable()).isEqualTo(CassandraChatMemoryConfig.DEFAULT_TABLE_NAME); - assertThat(props.getAssistantColumn()).isEqualTo(CassandraChatMemoryConfig.DEFAULT_ASSISTANT_COLUMN_NAME); - assertThat(props.getUserColumn()).isEqualTo(CassandraChatMemoryConfig.DEFAULT_USER_COLUMN_NAME); - assertThat(props.getTimeToLive()).isNull(); - assertThat(props.isInitializeSchema()).isTrue(); - } - - @Test - void customValues() { - var props = new CassandraChatMemoryProperties(); - props.setKeyspace("my_keyspace"); - props.setTable("my_table"); - props.setAssistantColumn("my_assistant_column"); - props.setUserColumn("my_user_column"); - props.setTimeToLive(Duration.ofDays(1)); - props.setInitializeSchema(false); - - assertThat(props.getKeyspace()).isEqualTo("my_keyspace"); - assertThat(props.getTable()).isEqualTo("my_table"); - assertThat(props.getAssistantColumn()).isEqualTo("my_assistant_column"); - assertThat(props.getUserColumn()).isEqualTo("my_user_column"); - assertThat(props.getTimeToLive()).isEqualTo(Duration.ofDays(1)); - assertThat(props.isInitializeSchema()).isFalse(); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfigurationTests.java deleted file mode 100644 index 3ac3f12ca..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfigurationTests.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.chat.observation.autoconfigure; - -import io.micrometer.core.instrument.composite.CompositeMeterRegistry; -import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; -import io.micrometer.tracing.otel.bridge.OtelTracer; -import io.opentelemetry.api.OpenTelemetry; -import org.junit.jupiter.api.Test; - -import org.springframework.ai.chat.observation.ChatModelCompletionObservationFilter; -import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler; -import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationFilter; -import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ChatObservationAutoConfiguration}. - * - * @author Thomas Vitale - */ -class ChatObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ChatObservationAutoConfiguration.class)); - - @Test - void meterObservationHandlerEnabled() { - this.contextRunner.withBean(CompositeMeterRegistry.class) - .run(context -> assertThat(context).hasSingleBean(ChatModelMeterObservationHandler.class)); - } - - @Test - void meterObservationHandlerDisabled() { - this.contextRunner.run(context -> assertThat(context).doesNotHaveBean(ChatModelMeterObservationHandler.class)); - } - - @Test - void promptFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationFilter.class)); - } - - @Test - void promptHandlerDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationHandler.class)); - } - - @Test - void promptHandlerEnabled() { - this.contextRunner - .withBean(OtelTracer.class, OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null) - .withPropertyValues("spring.ai.chat.observations.include-prompt=true") - .run(context -> assertThat(context).hasSingleBean(ChatModelPromptContentObservationHandler.class)); - } - - @Test - void promptHandlerDisabled() { - this.contextRunner.withPropertyValues("spring.ai.chat.observations.include-prompt=true") - .run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationHandler.class)); - } - - @Test - void completionFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationFilter.class)); - } - - @Test - void completionHandlerDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationHandler.class)); - } - - @Test - void completionHandlerEnabled() { - this.contextRunner - .withBean(OtelTracer.class, OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null) - .withPropertyValues("spring.ai.chat.observations.include-completion=true") - .run(context -> assertThat(context).hasSingleBean(ChatModelCompletionObservationHandler.class)); - } - - @Test - void completionHandlerDisabled() { - this.contextRunner.withPropertyValues("spring.ai.chat.observations.include-completion=true") - .run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationHandler.class)); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfigurationTests.java deleted file mode 100644 index 10c9feb7c..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfigurationTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.embedding.observation.autoconfigure; - -import io.micrometer.core.instrument.composite.CompositeMeterRegistry; -import org.junit.jupiter.api.Test; - -import org.springframework.ai.embedding.observation.EmbeddingModelMeterObservationHandler; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link EmbeddingObservationAutoConfiguration}. - * - * @author Thomas Vitale - */ -class EmbeddingObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(EmbeddingObservationAutoConfiguration.class)); - - @Test - void meterObservationHandlerEnabled() { - this.contextRunner.withBean(CompositeMeterRegistry.class) - .run(context -> assertThat(context).hasSingleBean(EmbeddingModelMeterObservationHandler.class)); - } - - @Test - void meterObservationHandlerDisabled() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(EmbeddingModelMeterObservationHandler.class)); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfigurationIT.java deleted file mode 100644 index db991fe08..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfigurationIT.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.huggingface.autoconfigure; - -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; - -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.huggingface.HuggingfaceChatModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "HUGGINGFACE_API_KEY", matches = ".+") -@EnabledIfEnvironmentVariable(named = "HUGGINGFACE_CHAT_URL", matches = ".+") -public class HuggingfaceChatAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(HuggingfaceChatAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.huggingface.chat.api-key=" + System.getenv("HUGGINGFACE_API_KEY"), - "spring.ai.huggingface.chat.url=" + System.getenv("HUGGINGFACE_CHAT_URL")) - // @formatter:on - .withConfiguration(AutoConfigurations.of(HuggingfaceChatAutoConfiguration.class)); - - @Test - void generate() { - this.contextRunner.run(context -> { - HuggingfaceChatModel chatModel = context.getBean(HuggingfaceChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Disabled("Until streaming support is added") - @Test - void generateStreaming() { - this.contextRunner.run(context -> { - HuggingfaceChatModel chatModel = context.getBean(HuggingfaceChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - - String response = responseFlux.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfigurationTests.java deleted file mode 100644 index 23417b67b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfigurationTests.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.image.observation.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.image.observation.ImageModelPromptContentObservationFilter; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ImageObservationAutoConfiguration}. - * - * @author Thomas Vitale - */ -class ImageObservationAutoConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ImageObservationAutoConfiguration.class)); - - @Test - void promptFilterDefault() { - this.contextRunner - .run(context -> assertThat(context).doesNotHaveBean(ImageModelPromptContentObservationFilter.class)); - } - - @Test - void promptFilterEnabled() { - this.contextRunner.withPropertyValues("spring.ai.image.observations.include-prompt=true") - .run(context -> assertThat(context).hasSingleBean(ImageModelPromptContentObservationFilter.class)); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackInPromptIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackInPromptIT.java deleted file mode 100644 index f86e76185..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackInPromptIT.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.minimax.MiniMaxChatModel; -import org.springframework.ai.minimax.MiniMaxChatOptions; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariable(named = "MINIMAX_API_KEY", matches = ".*") -public class FunctionCallbackInPromptIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallbackInPromptIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> { - - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - var promptOptions = MiniMaxChatOptions.builder() - .functionCallbacks(List.of(FunctionCallback.builder() - .function("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Test - void streamingFunctionCallTest() { - - this.contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> { - - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - var promptOptions = MiniMaxChatOptions.builder() - .functionCallbacks(List.of(FunctionCallback.builder() - .function("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - Flux response = chatModel.stream(new Prompt(List.of(userMessage), promptOptions)); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackWithPlainFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackWithPlainFunctionBeanIT.java deleted file mode 100644 index 6d511b709..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/FunctionCallbackWithPlainFunctionBeanIT.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.minimax.MiniMaxChatModel; -import org.springframework.ai.minimax.MiniMaxChatOptions; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariable(named = "MINIMAX_API_KEY", matches = ".*") -class FunctionCallbackWithPlainFunctionBeanIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallbackWithPlainFunctionBeanIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - // FIXME: multiple function calls may stop prematurely due to model performance - @Test - void functionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> { - - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - ChatResponse response = chatModel.call( - new Prompt(List.of(userMessage), MiniMaxChatOptions.builder().function("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - // Test weatherFunctionTwo - response = chatModel.call(new Prompt(List.of(userMessage), - MiniMaxChatOptions.builder().function("weatherFunctionTwo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - this.contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> { - - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - ToolCallingChatOptions toolOptions = ToolCallingChatOptions.builder().toolNames("weatherFunction").build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), toolOptions)); - - logger.info("Response: {}", response); - }); - } - - // FIXME: multiple function calls may stop prematurely due to model performance - @Test - void streamFunctionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> { - - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - Flux response = chatModel.stream( - new Prompt(List.of(userMessage), MiniMaxChatOptions.builder().function("weatherFunction").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - - // Test weatherFunctionTwo - response = chatModel.stream(new Prompt(List.of(userMessage), - MiniMaxChatOptions.builder().function("weatherFunctionTwo").build())); - - content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - }); - } - - @Configuration - static class Config { - - @Bean - @Description("Get the weather in location") - public Function weatherFunction() { - return new MockWeatherService(); - } - - // Relies on the Request's JsonClassDescription annotation to provide the - // function description. - @Bean - public Function weatherFunctionTwo() { - MockWeatherService weatherService = new MockWeatherService(); - return (weatherService::apply); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfigurationIT.java deleted file mode 100644 index 2b82ba7d4..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfigurationIT.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.ai.minimax.MiniMaxChatModel; -import org.springframework.ai.minimax.MiniMaxEmbeddingModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariable(named = "MINIMAX_API_KEY", matches = ".*") -public class MiniMaxAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(MiniMaxAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)); - - @Test - void generate() { - this.contextRunner.run(context -> { - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void generateStreaming() { - this.contextRunner.run(context -> { - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - String response = responseFlux.collectList() - .block() - .stream() - .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) - .collect(Collectors.joining()); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void embedding() { - this.contextRunner.run(context -> { - MiniMaxEmbeddingModel embeddingModel = context.getBean(MiniMaxEmbeddingModel.class); - - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - - assertThat(embeddingModel.dimensions()).isEqualTo(1536); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxFunctionCallbackIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxFunctionCallbackIT.java deleted file mode 100644 index 5bf102959..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxFunctionCallbackIT.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.minimax.MiniMaxChatModel; -import org.springframework.ai.minimax.MiniMaxChatOptions; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariable(named = "MINIMAX_API_KEY", matches = ".*") -public class MiniMaxFunctionCallbackIT { - - private final Logger logger = LoggerFactory.getLogger(MiniMaxFunctionCallbackIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> { - - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - ChatResponse response = chatModel - .call(new Prompt(List.of(userMessage), MiniMaxChatOptions.builder().function("WeatherInfo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.minimax.chat.options.model=abab6.5s-chat").run(context -> { - - MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - Flux response = chatModel - .stream(new Prompt(List.of(userMessage), MiniMaxChatOptions.builder().function("WeatherInfo").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - - }); - } - - @Configuration - static class Config { - - @Bean - public FunctionCallback weatherFunctionInfo() { - - return FunctionCallback.builder() - .function("WeatherInfo", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java deleted file mode 100644 index b0a75f743..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.minimax.MiniMaxChatModel; -import org.springframework.ai.minimax.MiniMaxEmbeddingModel; -import org.springframework.ai.minimax.api.MiniMaxApi; -import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit Tests for {@link MiniMaxConnectionProperties}, {@link MiniMaxChatProperties} and - * {@link MiniMaxEmbeddingProperties}. - * - * @author Geng Rong - */ -public class MiniMaxPropertiesTests { - - @Test - public void chatProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.minimax.base-url=TEST_BASE_URL", - "spring.ai.minimax.api-key=abc123", - "spring.ai.minimax.chat.options.model=MODEL_XYZ", - "spring.ai.minimax.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(MiniMaxChatProperties.class); - var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isNull(); - assertThat(chatProperties.getBaseUrl()).isNull(); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void chatOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.minimax.base-url=TEST_BASE_URL", - "spring.ai.minimax.api-key=abc123", - "spring.ai.minimax.chat.base-url=TEST_BASE_URL2", - "spring.ai.minimax.chat.api-key=456", - "spring.ai.minimax.chat.options.model=MODEL_XYZ", - "spring.ai.minimax.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(MiniMaxChatProperties.class); - var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isEqualTo("456"); - assertThat(chatProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void embeddingProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.minimax.base-url=TEST_BASE_URL", - "spring.ai.minimax.api-key=abc123", - "spring.ai.minimax.embedding.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class); - var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isNull(); - assertThat(embeddingProperties.getBaseUrl()).isNull(); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void embeddingOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.minimax.base-url=TEST_BASE_URL", - "spring.ai.minimax.api-key=abc123", - "spring.ai.minimax.embedding.base-url=TEST_BASE_URL2", - "spring.ai.minimax.embedding.api-key=456", - "spring.ai.minimax.embedding.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class); - var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isEqualTo("456"); - assertThat(embeddingProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void chatOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.minimax.api-key=API_KEY", - "spring.ai.minimax.base-url=TEST_BASE_URL", - - "spring.ai.minimax.chat.options.model=MODEL_XYZ", - "spring.ai.minimax.chat.options.frequencyPenalty=-1.5", - "spring.ai.minimax.chat.options.logitBias.myTokenId=-5", - "spring.ai.minimax.chat.options.maxTokens=123", - "spring.ai.minimax.chat.options.n=10", - "spring.ai.minimax.chat.options.presencePenalty=0", - "spring.ai.minimax.chat.options.responseFormat.type=json", - "spring.ai.minimax.chat.options.seed=66", - "spring.ai.minimax.chat.options.stop=boza,koza", - "spring.ai.minimax.chat.options.temperature=0.55", - "spring.ai.minimax.chat.options.topP=0.56", - - // "spring.ai.minimax.chat.options.toolChoice.functionName=toolChoiceFunctionName", - "spring.ai.minimax.chat.options.toolChoice=" + ModelOptionsUtils.toJsonString(MiniMaxApi.ChatCompletionRequest.ToolChoiceBuilder.function("toolChoiceFunctionName")), - - "spring.ai.minimax.chat.options.tools[0].function.name=myFunction1", - "spring.ai.minimax.chat.options.tools[0].function.description=function description", - "spring.ai.minimax.chat.options.tools[0].function.jsonSchema=" + """ - { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state e.g. San Francisco, CA" - }, - "lat": { - "type": "number", - "description": "The city latitude" - }, - "lon": { - "type": "number", - "description": "The city longitude" - }, - "unit": { - "type": "string", - "enum": ["c", "f"] - } - }, - "required": ["location", "lat", "lon", "unit"] - } - """ - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(MiniMaxChatProperties.class); - var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); - var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("embo-01"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5); - assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); - assertThat(chatProperties.getOptions().getN()).isEqualTo(10); - assertThat(chatProperties.getOptions().getPresencePenalty()).isEqualTo(0); - assertThat(chatProperties.getOptions().getResponseFormat()) - .isEqualTo(new MiniMaxApi.ChatCompletionRequest.ResponseFormat("json")); - assertThat(chatProperties.getOptions().getSeed()).isEqualTo(66); - assertThat(chatProperties.getOptions().getStop()).contains("boza", "koza"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56); - - JSONAssert.assertEquals("{\"type\":\"function\",\"function\":{\"name\":\"toolChoiceFunctionName\"}}", - chatProperties.getOptions().getToolChoice(), JSONCompareMode.LENIENT); - - assertThat(chatProperties.getOptions().getTools()).hasSize(1); - var tool = chatProperties.getOptions().getTools().get(0); - assertThat(tool.getType()).isEqualTo(MiniMaxApi.FunctionTool.Type.FUNCTION); - var function = tool.getFunction(); - assertThat(function.getName()).isEqualTo("myFunction1"); - assertThat(function.getDescription()).isEqualTo("function description"); - assertThat(function.getParameters()).isNotEmpty(); - }); - } - - @Test - public void embeddingOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.minimax.api-key=API_KEY", - "spring.ai.minimax.base-url=TEST_BASE_URL", - - "spring.ai.minimax.embedding.options.model=MODEL_XYZ", - "spring.ai.minimax.embedding.options.encodingFormat=MyEncodingFormat" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - var connectionProperties = context.getBean(MiniMaxConnectionProperties.class); - var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - void embeddingActivation() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL", - "spring.ai.minimax.embedding.enabled=false") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL", - "spring.ai.minimax.embedding.enabled=true") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty(); - }); - } - - @Test - void chatActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL", - "spring.ai.minimax.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL", - "spring.ai.minimax.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty(); - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MockWeatherService.java deleted file mode 100644 index 3c95cf669..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MockWeatherService.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.minimax.autoconfigure; - -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - -/** - * Mock 3rd party weather service. - * - * @author Geng Rong - */ -public class MockWeatherService implements Function { - - @Override - public Response apply(Request request) { - - double temperature = 0; - if (request.location().contains("Paris")) { - temperature = 15; - } - else if (request.location().contains("Tokyo")) { - temperature = 10; - } - else if (request.location().contains("San Francisco")) { - temperature = 30; - } - - return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); - } - - /** - * Temperature units. - */ - public enum Unit { - - /** - * Celsius. - */ - C("metric"), - /** - * Fahrenheit. - */ - F("imperial"); - - /** - * Human readable unit name. - */ - public final String unitName; - - Unit(String text) { - this.unitName = text; - } - - } - - /** - * Weather Function request. - */ - @JsonInclude(Include.NON_NULL) - @JsonClassDescription("Get the weather in location") - public record Request(@JsonProperty(required = true, - value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, - @JsonProperty(required = true, value = "lat") @JsonPropertyDescription("The city latitude") double lat, - @JsonProperty(required = true, value = "lon") @JsonPropertyDescription("The city longitude") double lon, - @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { - - } - - /** - * Weather Function response. - */ - public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity, - Unit unit) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfigurationIT.java deleted file mode 100644 index b7bb85226..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfigurationIT.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure; - -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; - -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.ai.mistralai.MistralAiChatModel; -import org.springframework.ai.mistralai.MistralAiEmbeddingModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @since 0.8.1 - */ -@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".*") -public class MistralAiAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(MistralAiAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class)); - - @Test - void generate() { - this.contextRunner.run(context -> { - MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void generateStreaming() { - this.contextRunner.run(context -> { - MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - String response = responseFlux.collectList() - .block() - .stream() - .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) - .collect(Collectors.joining()); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void embedding() { - this.contextRunner.run(context -> { - MistralAiEmbeddingModel embeddingModel = context.getBean(MistralAiEmbeddingModel.class); - - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - - assertThat(embeddingModel.dimensions()).isEqualTo(1024); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiPropertiesTests.java deleted file mode 100644 index dd4eae9ab..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiPropertiesTests.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit Tests for {@link MistralAiCommonProperties}, {@link MistralAiEmbeddingProperties}. - */ -public class MistralAiPropertiesTests { - - @Test - public void embeddingProperties() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.mistralai.base-url=TEST_BASE_URL", "spring.ai.mistralai.api-key=abc123", - "spring.ai.mistralai.embedding.options.model=MODEL_XYZ") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(MistralAiEmbeddingProperties.class); - var connectionProperties = context.getBean(MistralAiCommonProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isNull(); - assertThat(embeddingProperties.getBaseUrl()).isEqualTo(MistralAiCommonProperties.DEFAULT_BASE_URL); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void chatOptionsTest() { - - new ApplicationContextRunner().withPropertyValues("spring.ai.mistralai.base-url=TEST_BASE_URL", - "spring.ai.mistralai.chat.options.tools[0].function.name=myFunction1", - "spring.ai.mistralai.chat.options.tools[0].function.description=function description", - "spring.ai.mistralai.chat.options.tools[0].function.jsonSchema=" + """ - { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state e.g. San Francisco, CA" - }, - "lat": { - "type": "number", - "description": "The city latitude" - }, - "lon": { - "type": "number", - "description": "The city longitude" - }, - "unit": { - "type": "string", - "enum": ["c", "f"] - } - }, - "required": ["location", "lat", "lon", "unit"] - } - """, - - "spring.ai.mistralai.api-key=abc123", "spring.ai.mistralai.embedding.base-url=TEST_BASE_URL2", - "spring.ai.mistralai.embedding.api-key=456", "spring.ai.mistralai.embedding.options.model=MODEL_XYZ") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class)) - .run(context -> { - - var chatProperties = context.getBean(MistralAiChatProperties.class); - - var tool = chatProperties.getOptions().getTools().get(0); - assertThat(tool.getType()).isEqualTo(MistralAiApi.FunctionTool.Type.FUNCTION); - var function = tool.getFunction(); - assertThat(function.getName()).isEqualTo("myFunction1"); - assertThat(function.getDescription()).isEqualTo("function description"); - assertThat(function.getParameters()).isNotEmpty(); - }); - } - - @Test - public void embeddingOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues("spring.ai.mistralai.base-url=TEST_BASE_URL", - "spring.ai.mistralai.api-key=abc123", "spring.ai.mistralai.embedding.base-url=TEST_BASE_URL2", - "spring.ai.mistralai.embedding.api-key=456", "spring.ai.mistralai.embedding.options.model=MODEL_XYZ") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(MistralAiEmbeddingProperties.class); - var connectionProperties = context.getBean(MistralAiCommonProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isEqualTo("456"); - assertThat(embeddingProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void embeddingOptionsTest() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.mistralai.api-key=API_KEY", "spring.ai.mistralai.base-url=TEST_BASE_URL", - - "spring.ai.mistralai.embedding.options.model=MODEL_XYZ", - "spring.ai.mistralai.embedding.options.encodingFormat=MyEncodingFormat") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class)) - .run(context -> { - var connectionProperties = context.getBean(MistralAiCommonProperties.class); - var embeddingProperties = context.getBean(MistralAiEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(embeddingProperties.getOptions().getEncodingFormat()).isEqualTo("MyEncodingFormat"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanIT.java deleted file mode 100644 index bd7ed874a..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanIT.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure.tool; - -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.mistralai.MistralAiChatModel; -import org.springframework.ai.mistralai.MistralAiChatOptions; -import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".*") -class PaymentStatusBeanIT { - - // Assuming we have the following data - public static final Map DATA = Map.of("T1001", new StatusDate("Paid", "2021-10-05"), "T1002", - new StatusDate("Unpaid", "2021-10-06"), "T1003", new StatusDate("Paid", "2021-10-07"), "T1004", - new StatusDate("Paid", "2021-10-05"), "T1005", new StatusDate("Pending", "2021-10-08")); - - private final Logger logger = LoggerFactory.getLogger(PaymentStatusBeanIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - - this.contextRunner - .withPropertyValues("spring.ai.mistralai.chat.options.model=" + MistralAiApi.ChatModel.LARGE.getValue()) - .run(context -> { - - MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); - - ChatResponse response = chatModel - .call(new Prompt(List.of(new UserMessage("What's the status of my transaction with id T1001?")), - MistralAiChatOptions.builder() - .function("retrievePaymentStatus") - .function("retrievePaymentDate") - .build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).containsIgnoringCase("T1001"); - assertThat(response.getResult().getOutput().getText()).containsIgnoringCase("paid"); - }); - } - - record StatusDate(String status, String date) { - - } - - @Configuration - static class Config { - - @Bean - @Description("Get payment status of a transaction") - public Function retrievePaymentStatus() { - return transaction -> new Status(DATA.get(transaction.transactionId).status()); - } - - @Bean - @Description("Get payment date of a transaction") - public Function retrievePaymentDate() { - return transaction -> new Date(DATA.get(transaction.transactionId).date()); - } - - public record Transaction(@JsonProperty(required = true, value = "transaction_id") String transactionId) { - - } - - public record Status(@JsonProperty(required = true, value = "status") String status) { - - } - - public record Date(@JsonProperty(required = true, value = "date") String date) { - - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanOpenAiIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanOpenAiIT.java deleted file mode 100644 index 9c4b1c71a..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanOpenAiIT.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure.tool; - -import java.util.List; -import java.util.Map; -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Same test as {@link PaymentStatusBeanIT.java} but using {@link OpenAiChatModel} for - * Mistral AI Function Calling implementation. - * - * @author Christian Tzolov - */ -@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".*") -class PaymentStatusBeanOpenAiIT { - - // Assuming we have the following data - public static final Map DATA = Map.of("T1001", new StatusDate("Paid", "2021-10-05"), "T1002", - new StatusDate("Unpaid", "2021-10-06"), "T1003", new StatusDate("Paid", "2021-10-07"), "T1004", - new StatusDate("Paid", "2021-10-05"), "T1005", new StatusDate("Pending", "2021-10-08")); - - private final Logger logger = LoggerFactory.getLogger(PaymentStatusBeanIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY"), - "spring.ai.openai.chat.base-url=https://api.mistral.ai") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - - this.contextRunner - .withPropertyValues("spring.ai.openai.chat.options.model=" + MistralAiApi.ChatModel.SMALL.getValue()) - .run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - ChatResponse response = chatModel - .call(new Prompt(List.of(new UserMessage("What's the status of my transaction with id T1001?")), - OpenAiChatOptions.builder() - .function("retrievePaymentStatus") - .function("retrievePaymentDate") - .build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).containsIgnoringCase("T1001"); - assertThat(response.getResult().getOutput().getText()).containsIgnoringCase("paid"); - }); - } - - record StatusDate(String status, String date) { - - } - - @Configuration - static class Config { - - @Bean - @Description("Get payment status of a transaction") - public Function retrievePaymentStatus() { - return transaction -> new Status(DATA.get(transaction.transactionId).status()); - } - - @Bean - @Description("Get payment date of a transaction") - public Function retrievePaymentDate() { - return transaction -> new Date(DATA.get(transaction.transactionId).date()); - } - - public record Transaction(@JsonProperty(required = true, value = "transaction_id") String transactionId) { - - } - - public record Status(@JsonProperty(required = true, value = "status") String status) { - - } - - public record Date(@JsonProperty(required = true, value = "date") String date) { - - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusPromptIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusPromptIT.java deleted file mode 100644 index 01a278a3d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusPromptIT.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure.tool; - -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.mistralai.MistralAiChatModel; -import org.springframework.ai.mistralai.MistralAiChatOptions; -import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".*") -public class PaymentStatusPromptIT { - - // Assuming we have the following payment data. - public static final Map DATA = Map.of(new Transaction("T1001"), - new StatusDate("Paid", "2021-10-05"), new Transaction("T1002"), new StatusDate("Unpaid", "2021-10-06"), - new Transaction("T1003"), new StatusDate("Paid", "2021-10-07"), new Transaction("T1004"), - new StatusDate("Paid", "2021-10-05"), new Transaction("T1005"), new StatusDate("Pending", "2021-10-08")); - - private final Logger logger = LoggerFactory.getLogger(WeatherServicePromptIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner - .withPropertyValues("spring.ai.mistralai.chat.options.model=" + MistralAiApi.ChatModel.SMALL.getValue()) - .run(context -> { - - MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); - - UserMessage userMessage = new UserMessage("What's the status of my transaction with id T1001?"); - - var promptOptions = MistralAiChatOptions.builder() - .functionCallbacks(List.of(FunctionToolCallback - .builder("retrievePaymentStatus", - (Transaction transaction) -> new Status(DATA.get(transaction).status())) - .description("Get payment status of a transaction") - .inputType(Transaction.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).containsIgnoringCase("T1001"); - assertThat(response.getResult().getOutput().getText()).containsIgnoringCase("paid"); - }); - } - - public record Transaction(@JsonProperty(required = true, value = "transaction_id") String id) { - - } - - public record Status(@JsonProperty(required = true, value = "status") String status) { - - } - - record StatusDate(String status, String date) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/WeatherServicePromptIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/WeatherServicePromptIT.java deleted file mode 100644 index 758ab779f..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/WeatherServicePromptIT.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.mistralai.autoconfigure.tool; - -import java.util.List; -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration; -import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Request; -import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Response; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.mistralai.MistralAiChatModel; -import org.springframework.ai.mistralai.MistralAiChatOptions; -import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.ai.mistralai.api.MistralAiApi.ChatCompletionRequest.ToolChoice; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @author Alexandros Pappas - * @since 0.8.1 - */ -@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".*") -public class WeatherServicePromptIT { - - private final Logger logger = LoggerFactory.getLogger(WeatherServicePromptIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.mistralai.api-key=" + System.getenv("MISTRAL_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class)); - - @Test - void promptFunctionCall() { - this.contextRunner - .withPropertyValues("spring.ai.mistralai.chat.options.model=" + MistralAiApi.ChatModel.LARGE.getValue()) - .run(context -> { - - MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); - - UserMessage userMessage = new UserMessage("What's the weather like in Paris? Use Celsius."); - // UserMessage userMessage = new UserMessage("What's the weather like in - // San Francisco, Tokyo, and - // Paris?"); - - var promptOptions = MistralAiChatOptions.builder() - .toolChoice(ToolChoice.AUTO) - .functionCallbacks( - List.of(FunctionToolCallback.builder("CurrentWeatherService", new MyWeatherService()) - .description("Get the current weather in requested location") - .inputType(MyWeatherService.Request.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).containsAnyOf("15", "15.0"); - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - this.contextRunner - .withPropertyValues("spring.ai.mistralai.chat.options.model=" + MistralAiApi.ChatModel.LARGE.getValue()) - .run(context -> { - - MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class); - - UserMessage userMessage = new UserMessage("What's the weather like in Paris? Use Celsius."); - - ToolCallingChatOptions functionOptions = ToolCallingChatOptions.builder() - .toolCallbacks(List.of(FunctionToolCallback.builder("CurrentWeatherService", new MyWeatherService()) - .description("Get the current weather in requested location") - .inputType(MyWeatherService.Request.class) - .build())) - - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).containsAnyOf("15", "15.0"); - }); - } - - public static class MyWeatherService implements Function { - - @Override - public Response apply(Request request) { - if (request.location().contains("Paris")) { - return new Response(15, request.unit()); - } - else if (request.location().contains("Tokyo")) { - return new Response(10, request.unit()); - } - else if (request.location().contains("San Francisco")) { - return new Response(30, request.unit()); - } - throw new IllegalArgumentException("Invalid request: " + request); - } - - // @formatter:off - public enum Unit { C, F } - - @JsonInclude(Include.NON_NULL) - public record Request( - @JsonProperty(required = true, value = "location") String location, - @JsonProperty(required = true, value = "unit") Unit unit) { } - // @formatter:on - - public record Response(double temperature, Unit unit) { - - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java deleted file mode 100644 index c97df8b12..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.moonshot.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.moonshot.MoonshotChatModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -public class MoonshotPropertiesTests { - - @Test - public void chatProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.moonshot.base-url=TEST_BASE_URL", - "spring.ai.moonshot.api-key=abc123", - "spring.ai.moonshot.chat.options.model=MODEL_XYZ", - "spring.ai.moonshot.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(MoonshotChatProperties.class); - var connectionProperties = context.getBean(MoonshotCommonProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isNull(); - assertThat(chatProperties.getBaseUrl()).isNull(); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void chatOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.moonshot.base-url=TEST_BASE_URL", - "spring.ai.moonshot.api-key=abc123", - "spring.ai.moonshot.chat.base-url=TEST_BASE_URL2", - "spring.ai.moonshot.chat.api-key=456", - "spring.ai.moonshot.chat.options.model=MODEL_XYZ", - "spring.ai.moonshot.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(MoonshotChatProperties.class); - var connectionProperties = context.getBean(MoonshotCommonProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isEqualTo("456"); - assertThat(chatProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void chatOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.moonshot.api-key=API_KEY", - "spring.ai.moonshot.base-url=TEST_BASE_URL", - - "spring.ai.moonshot.chat.options.model=MODEL_XYZ", - "spring.ai.moonshot.chat.options.frequencyPenalty=-1.5", - "spring.ai.moonshot.chat.options.logitBias.myTokenId=-5", - "spring.ai.moonshot.chat.options.maxTokens=123", - "spring.ai.moonshot.chat.options.n=10", - "spring.ai.moonshot.chat.options.presencePenalty=0", - "spring.ai.moonshot.chat.options.responseFormat.type=json", - "spring.ai.moonshot.chat.options.seed=66", - "spring.ai.moonshot.chat.options.stop=boza,koza", - "spring.ai.moonshot.chat.options.temperature=0.55", - "spring.ai.moonshot.chat.options.topP=0.56", - "spring.ai.moonshot.chat.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(MoonshotChatProperties.class); - var connectionProperties = context.getBean(MoonshotCommonProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5); - assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); - assertThat(chatProperties.getOptions().getN()).isEqualTo(10); - assertThat(chatProperties.getOptions().getPresencePenalty()).isEqualTo(0); - assertThat(chatProperties.getOptions().getStop()).contains("boza", "koza"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56); - - assertThat(chatProperties.getOptions().getUser()).isEqualTo("userXYZ"); - }); - } - - @Test - void chatActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL", - "spring.ai.moonshot.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MoonshotChatModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MoonshotChatModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL", - "spring.ai.moonshot.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(MoonshotChatModel.class)).isNotEmpty(); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackInPromptIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackInPromptIT.java deleted file mode 100644 index 0dc2271f3..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackInPromptIT.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.moonshot.autoconfigure.tool; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.moonshot.MoonshotChatModel; -import org.springframework.ai.moonshot.MoonshotChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - * @author Alexandros Pappas - */ -@EnabledIfEnvironmentVariable(named = "MOONSHOT_API_KEY", matches = ".*") -public class FunctionCallbackInPromptIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallbackInPromptIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner.run(context -> { - - MoonshotChatModel chatModel = context.getBean(MoonshotChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius"); - - var promptOptions = MoonshotChatOptions.builder() - .functionCallbacks(List.of(FunctionCallback.builder() - .function("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Test - void streamingFunctionCallTest() { - - this.contextRunner.run(context -> { - - MoonshotChatModel chatModel = context.getBean(MoonshotChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius"); - - var promptOptions = MoonshotChatOptions.builder() - .functionCallbacks(List.of(FunctionCallback.builder() - .function("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - Flux response = chatModel.stream(new Prompt(List.of(userMessage), promptOptions)); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java deleted file mode 100644 index 72b3681c8..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.moonshot.autoconfigure.tool; - -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.moonshot.MoonshotChatModel; -import org.springframework.ai.moonshot.MoonshotChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - * @author Alexandros Pappas - */ -@EnabledIfEnvironmentVariable(named = "MOONSHOT_API_KEY", matches = ".*") -class FunctionCallbackWithPlainFunctionBeanIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallbackWithPlainFunctionBeanIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner.run(context -> { - - MoonshotChatModel chatModel = context.getBean(MoonshotChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius"); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - MoonshotChatOptions.builder().function("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - // Test weatherFunctionTwo - response = chatModel.call(new Prompt(List.of(userMessage), - MoonshotChatOptions.builder().function("weatherFunctionTwo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - this.contextRunner.run(context -> { - - MoonshotChatModel chatModel = context.getBean(MoonshotChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius"); - - ToolCallingChatOptions toolOptions = ToolCallingChatOptions.builder().toolNames("weatherFunction").build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), toolOptions)); - - logger.info("Response: {}", response); - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.run(context -> { - - MoonshotChatModel chatModel = context.getBean(MoonshotChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius"); - - Flux response = chatModel.stream(new Prompt(List.of(userMessage), - MoonshotChatOptions.builder().function("weatherFunction").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - - // Test weatherFunctionTwo - response = chatModel.stream(new Prompt(List.of(userMessage), - MoonshotChatOptions.builder().function("weatherFunctionTwo").build())); - - content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - }); - } - - @Configuration - static class Config { - - @Bean - @Description("Get the weather in location") - public Function weatherFunction() { - return new MockWeatherService(); - } - - // Relies on the Request's JsonClassDescription annotation to provide the - // function description. - @Bean - public Function weatherFunctionTwo() { - MockWeatherService weatherService = new MockWeatherService(); - return (weatherService::apply); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MockWeatherService.java deleted file mode 100644 index 1fbc6858b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MockWeatherService.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.moonshot.autoconfigure.tool; - -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - -/** - * Mock 3rd party weather service. - * - * @author Geng Rong - */ -public class MockWeatherService implements Function { - - @Override - public Response apply(Request request) { - - double temperature = 0; - if (request.location().contains("Paris")) { - temperature = 15; - } - else if (request.location().contains("Tokyo")) { - temperature = 10; - } - else if (request.location().contains("San Francisco")) { - temperature = 30; - } - - return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); - } - - /** - * Temperature units. - */ - public enum Unit { - - /** - * Celsius. - */ - C("metric"), - /** - * Fahrenheit. - */ - F("imperial"); - - /** - * Human readable unit name. - */ - public final String unitName; - - Unit(String text) { - this.unitName = text; - } - - } - - /** - * Weather Function request. - */ - @JsonInclude(Include.NON_NULL) - @JsonClassDescription("Weather API request") - public record Request(@JsonProperty(required = true, - value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, - @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { - - } - - /** - * Weather Function response. - */ - public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity, - Unit unit) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MoonshotFunctionCallbackIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MoonshotFunctionCallbackIT.java deleted file mode 100644 index 86a85a9e1..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/tool/MoonshotFunctionCallbackIT.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.moonshot.autoconfigure.tool; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration; -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.moonshot.MoonshotChatModel; -import org.springframework.ai.moonshot.MoonshotChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - * @author Alexandros Pappas - */ -@EnabledIfEnvironmentVariable(named = "MOONSHOT_API_KEY", matches = ".*") -public class MoonshotFunctionCallbackIT { - - private final Logger logger = LoggerFactory.getLogger(MoonshotFunctionCallbackIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner.run(context -> { - - MoonshotChatModel chatModel = context.getBean(MoonshotChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius"); - - ChatResponse response = chatModel - .call(new Prompt(List.of(userMessage), MoonshotChatOptions.builder().function("WeatherInfo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.run(context -> { - - MoonshotChatModel chatModel = context.getBean(MoonshotChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius"); - - Flux response = chatModel.stream( - new Prompt(List.of(userMessage), MoonshotChatOptions.builder().function("WeatherInfo").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .filter(Objects::nonNull) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - - }); - } - - @Configuration - static class Config { - - @Bean - public FunctionCallback weatherFunctionInfo() { - - return FunctionCallback.builder() - .function("WeatherInfo", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAIAutoConfigurationTest.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAIAutoConfigurationTest.java deleted file mode 100644 index b8935152e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAIAutoConfigurationTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.oci.genai.autoconfigure; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyPair; -import java.security.KeyPairGenerator; - -import com.oracle.bmc.http.client.pki.Pem; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.ai.oci.cohere.OCICohereChatModel; -import org.springframework.ai.oci.cohere.OCICohereChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -class OCIGenAIAutoConfigurationTest { - - @Test - void setProperties(@TempDir Path tempDir) throws Exception { - Path tmp = tempDir.resolve("my-key.pem"); - createPrivateKey(tmp); - ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.oci.genai.authenticationType=simple", - "spring.ai.oci.genai.userId=my-user", - "spring.ai.oci.genai.tenantId=my-tenant", - "spring.ai.oci.genai.fingerprint=xyz", - "spring.ai.oci.genai.privateKey=" + tmp.toAbsolutePath(), - "spring.ai.oci.genai.region=us-ashburn-1", - "spring.ai.oci.genai.cohere.chat.options.compartment=my-compartment", - "spring.ai.oci.genai.cohere.chat.options.servingMode=dedicated", - "spring.ai.oci.genai.cohere.chat.options.model=my-model", - "spring.ai.oci.genai.cohere.chat.options.maxTokens=1000", - "spring.ai.oci.genai.cohere.chat.options.temperature=0.5", - "spring.ai.oci.genai.cohere.chat.options.topP=0.8", - "spring.ai.oci.genai.cohere.chat.options.maxTokens=1000", - "spring.ai.oci.genai.cohere.chat.options.frequencyPenalty=0.1", - "spring.ai.oci.genai.cohere.chat.options.presencePenalty=0.2" - // @formatter:on - ).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class)); - - contextRunner.run(context -> { - OCICohereChatModel chatModel = context.getBean(OCICohereChatModel.class); - assertThat(chatModel).isNotNull(); - OCICohereChatOptions options = (OCICohereChatOptions) chatModel.getDefaultOptions(); - assertThat(options.getCompartment()).isEqualTo("my-compartment"); - assertThat(options.getModel()).isEqualTo("my-model"); - assertThat(options.getServingMode()).isEqualTo("dedicated"); - assertThat(options.getMaxTokens()).isEqualTo(1000); - assertThat(options.getTemperature()).isEqualTo(0.5); - assertThat(options.getTopP()).isEqualTo(0.8); - assertThat(options.getFrequencyPenalty()).isEqualTo(0.1); - assertThat(options.getPresencePenalty()).isEqualTo(0.2); - - OCIConnectionProperties props = context.getBean(OCIConnectionProperties.class); - assertThat(props.getAuthenticationType()).isEqualTo(OCIConnectionProperties.AuthenticationType.SIMPLE); - assertThat(props.getUserId()).isEqualTo("my-user"); - assertThat(props.getTenantId()).isEqualTo("my-tenant"); - assertThat(props.getFingerprint()).isEqualTo("xyz"); - assertThat(props.getPrivateKey()).isEqualTo(tmp.toAbsolutePath().toString()); - assertThat(props.getRegion()).isEqualTo("us-ashburn-1"); - - }); - } - - private void createPrivateKey(Path tmp) throws Exception { - KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); - gen.initialize(2048); - KeyPair keyPair = gen.generateKeyPair(); - byte[] encoded = Pem.encoder().encode(keyPair.getPrivate()); - Files.write(tmp, encoded); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfigurationIT.java deleted file mode 100644 index 610ef9c97..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfigurationIT.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.oci.genai.autoconfigure; - -import java.nio.file.Paths; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; - -import org.springframework.ai.embedding.EmbeddingRequest; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.ai.oci.OCIEmbeddingModel; -import org.springframework.ai.oci.cohere.OCICohereChatModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = OCIGenAiAutoConfigurationIT.COMPARTMENT_ID_KEY, matches = ".+") -public class OCIGenAiAutoConfigurationIT { - - public static final String COMPARTMENT_ID_KEY = "OCI_COMPARTMENT_ID"; - - public static final String OCI_CHAT_MODEL_ID_KEY = "OCI_CHAT_MODEL_ID"; - - private final String CONFIG_FILE = Paths.get(System.getProperty("user.home"), ".oci", "config").toString(); - - private final String COMPARTMENT_ID = System.getenv(COMPARTMENT_ID_KEY); - - private final String CHAT_MODEL_ID = System.getenv(OCI_CHAT_MODEL_ID_KEY); - - private final ApplicationContextRunner embeddingContextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.oci.genai.authenticationType=file", - "spring.ai.oci.genai.file=" + this.CONFIG_FILE, - "spring.ai.oci.genai.embedding.compartment=" + this.COMPARTMENT_ID, - "spring.ai.oci.genai.embedding.servingMode=on-demand", - "spring.ai.oci.genai.embedding.model=cohere.embed-english-light-v2.0" - // @formatter:on - ).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class)); - - private final ApplicationContextRunner cohereChatContextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.oci.genai.authenticationType=file", - "spring.ai.oci.genai.file=" + this.CONFIG_FILE, - "spring.ai.oci.genai.cohere.chat.options.compartment=" + this.COMPARTMENT_ID, - "spring.ai.oci.genai.cohere.chat.options.servingMode=on-demand", - "spring.ai.oci.genai.cohere.chat.options.model=" + this.CHAT_MODEL_ID - // @formatter:on - ).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class)); - - @Test - void embeddings() { - this.embeddingContextRunner.run(context -> { - OCIEmbeddingModel embeddingModel = context.getBean(OCIEmbeddingModel.class); - assertThat(embeddingModel).isNotNull(); - EmbeddingResponse response = embeddingModel - .call(new EmbeddingRequest(List.of("There are 50 states in the USA", "Canada has 10 provinces"), null)); - assertThat(response).isNotNull(); - assertThat(response.getResults()).hasSize(2); - }); - } - - @Test - @EnabledIfEnvironmentVariable(named = OCIGenAiAutoConfigurationIT.OCI_CHAT_MODEL_ID_KEY, matches = ".+") - void cohereChat() { - this.cohereChatContextRunner.run(context -> { - OCICohereChatModel chatModel = context.getBean(OCICohereChatModel.class); - assertThat(chatModel).isNotNull(); - String response = chatModel.call("How many states are in the United States of America?"); - assertThat(response).isNotBlank(); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/BaseOllamaIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/BaseOllamaIT.java deleted file mode 100644 index b57f2ccab..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/BaseOllamaIT.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import java.time.Duration; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.testcontainers.junit.jupiter.Testcontainers; -import org.testcontainers.ollama.OllamaContainer; - -import org.springframework.ai.ollama.api.OllamaApi; -import org.springframework.ai.ollama.management.ModelManagementOptions; -import org.springframework.ai.ollama.management.OllamaModelManager; -import org.springframework.ai.ollama.management.PullModelStrategy; -import org.springframework.util.Assert; - -@Testcontainers -@EnabledIfEnvironmentVariable(named = "OLLAMA_AUTOCONF_TESTS_ENABLED", matches = "true") -public abstract class BaseOllamaIT { - - static { - System.out.println("OLLAMA_AUTOCONF_TESTS_ENABLED=" + System.getenv("OLLAMA_AUTOCONF_TESTS_ENABLED")); - System.out.println("System property=" + System.getProperty("OLLAMA_AUTOCONF_TESTS_ENABLED")); - } - private static final String OLLAMA_LOCAL_URL = "http://localhost:11434"; - - private static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(10); - - private static final int DEFAULT_MAX_RETRIES = 2; - - // Environment variable to control whether to create a new container or use existing - // Ollama instance - private static final boolean SKIP_CONTAINER_CREATION = Boolean - .parseBoolean(System.getenv().getOrDefault("OLLAMA_WITH_REUSE", "false")); - - private static OllamaContainer ollamaContainer; - - private static final ThreadLocal ollamaApi = new ThreadLocal<>(); - - /** - * Initialize the Ollama API with the specified model. When OLLAMA_WITH_REUSE=true - * (default), uses TestContainers withReuse feature. When OLLAMA_WITH_REUSE=false, - * connects to local Ollama instance. - * @param model the Ollama model to initialize (must not be null or empty) - * @return configured OllamaApi instance - * @throws IllegalArgumentException if model is null or empty - */ - protected static OllamaApi initializeOllama(final String model) { - Assert.hasText(model, "Model name must be provided"); - - if (!SKIP_CONTAINER_CREATION) { - ollamaContainer = new OllamaContainer(OllamaImage.DEFAULT_IMAGE).withReuse(true); - ollamaContainer.start(); - } - - final OllamaApi api = buildOllamaApiWithModel(model); - ollamaApi.set(api); - return api; - } - - /** - * Get the initialized OllamaApi instance. - * @return the OllamaApi instance - * @throws IllegalStateException if called before initialization - */ - protected static OllamaApi getOllamaApi() { - OllamaApi api = ollamaApi.get(); - Assert.state(api != null, "OllamaApi not initialized. Call initializeOllama first."); - return api; - } - - @AfterAll - public static void tearDown() { - if (ollamaContainer != null) { - ollamaContainer.stop(); - } - } - - public static OllamaApi buildOllamaApiWithModel(final String model) { - final String baseUrl = SKIP_CONTAINER_CREATION ? OLLAMA_LOCAL_URL : ollamaContainer.getEndpoint(); - final OllamaApi api = new OllamaApi(baseUrl); - ensureModelIsPresent(api, model); - return api; - } - - public String getBaseUrl() { - String baseUrl = SKIP_CONTAINER_CREATION ? OLLAMA_LOCAL_URL : ollamaContainer.getEndpoint(); - return baseUrl; - } - - private static void ensureModelIsPresent(final OllamaApi ollamaApi, final String model) { - final var modelManagementOptions = ModelManagementOptions.builder() - .maxRetries(DEFAULT_MAX_RETRIES) - .timeout(DEFAULT_TIMEOUT) - .build(); - final var ollamaModelManager = new OllamaModelManager(ollamaApi, modelManagementOptions); - ollamaModelManager.pullModel(model, PullModelStrategy.WHEN_MISSING); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java deleted file mode 100644 index 1623d3ed3..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationIT.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.ollama.OllamaChatModel; -import org.springframework.ai.ollama.api.OllamaApi; -import org.springframework.ai.ollama.api.OllamaModel; -import org.springframework.ai.ollama.management.OllamaModelManager; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @author Eddú Meléndez - * @author Thomas Vitale - * @since 0.8.0 - */ -public class OllamaChatAutoConfigurationIT extends BaseOllamaIT { - - private static final String MODEL_NAME = OllamaModel.LLAMA3_2.getName(); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.ollama.baseUrl=" + getBaseUrl(), - "spring.ai.ollama.chat.options.model=" + MODEL_NAME, - "spring.ai.ollama.chat.options.temperature=0.5", - "spring.ai.ollama.chat.options.topK=10") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class)); - - private final UserMessage userMessage = new UserMessage("What's the capital of Denmark?"); - - @BeforeAll - public static void beforeAll() throws IOException, InterruptedException { - initializeOllama(MODEL_NAME); - } - - @Test - public void chatCompletion() { - this.contextRunner.run(context -> { - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - ChatResponse response = chatModel.call(new Prompt(this.userMessage)); - assertThat(response.getResult().getOutput().getText()).contains("Copenhagen"); - }); - } - - @Test - public void chatCompletionStreaming() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - Flux response = chatModel.stream(new Prompt(this.userMessage)); - - List responses = response.collectList().block(); - assertThat(responses.size()).isGreaterThan(1); - - String stitchedResponseContent = responses.stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - - assertThat(stitchedResponseContent).contains("Copenhagen"); - }); - } - - @Test - public void chatCompletionWithPull() { - this.contextRunner.withPropertyValues("spring.ai.ollama.init.pull-model-strategy=when_missing") - .withPropertyValues("spring.ai.ollama.chat.options.model=tinyllama") - .run(context -> { - var model = "tinyllama"; - OllamaApi ollamaApi = context.getBean(OllamaApi.class); - var modelManager = new OllamaModelManager(ollamaApi); - assertThat(modelManager.isModelAvailable(model)).isTrue(); - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - ChatResponse response = chatModel.call(new Prompt(this.userMessage)); - assertThat(response.getResult().getOutput().getText()).contains("Copenhagen"); - modelManager.deleteModel(model); - }); - } - - @Test - void chatActivation() { - this.contextRunner.withPropertyValues("spring.ai.ollama.chat.enabled=false").run(context -> { - assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaChatModel.class)).isEmpty(); - }); - - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaChatModel.class)).isNotEmpty(); - }); - - this.contextRunner.withPropertyValues("spring.ai.ollama.chat.enabled=true").run(context -> { - assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaChatModel.class)).isNotEmpty(); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java deleted file mode 100644 index 077073ef9..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaChatAutoConfigurationTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @since 0.8.0 - */ -public class OllamaChatAutoConfigurationTests { - - @Test - public void propertiesTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.ollama.base-url=TEST_BASE_URL", - "spring.ai.ollama.chat.options.model=MODEL_XYZ", - "spring.ai.ollama.chat.options.temperature=0.55", - "spring.ai.ollama.chat.options.topP=0.56", - "spring.ai.ollama.chat.options.topK=123") - // @formatter:on - .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(OllamaChatProperties.class); - var connectionProperties = context.getBean(OllamaConnectionProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getModel()).isEqualTo("MODEL_XYZ"); - - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56); - - assertThat(chatProperties.getOptions().getTopK()).isEqualTo(123); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationIT.java deleted file mode 100644 index 1a38c32ae..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationIT.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import java.io.IOException; -import java.util.List; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.ai.ollama.OllamaEmbeddingModel; -import org.springframework.ai.ollama.api.OllamaApi; -import org.springframework.ai.ollama.api.OllamaModel; -import org.springframework.ai.ollama.management.OllamaModelManager; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @author Thomas Vitale - * @since 1.0.0 - */ -public class OllamaEmbeddingAutoConfigurationIT extends BaseOllamaIT { - - private static final String MODEL_NAME = OllamaModel.NOMIC_EMBED_TEXT.getName(); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.ollama.embedding.options.model=" + MODEL_NAME, - "spring.ai.ollama.base-url=" + getBaseUrl()) - .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaAutoConfiguration.class)); - - @BeforeAll - public static void beforeAll() throws IOException, InterruptedException { - initializeOllama(MODEL_NAME); - } - - @Test - public void singleTextEmbedding() { - this.contextRunner.run(context -> { - OllamaEmbeddingModel embeddingModel = context.getBean(OllamaEmbeddingModel.class); - assertThat(embeddingModel).isNotNull(); - EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(List.of("Hello World")); - assertThat(embeddingResponse.getResults()).hasSize(1); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingModel.dimensions()).isEqualTo(768); - }); - } - - @Test - public void embeddingWithPull() { - this.contextRunner.withPropertyValues("spring.ai.ollama.init.pull-model-strategy=when_missing") - .withPropertyValues("spring.ai.ollama.embedding.options.model=all-minilm") - .run(context -> { - var model = "all-minilm"; - OllamaApi ollamaApi = context.getBean(OllamaApi.class); - var modelManager = new OllamaModelManager(ollamaApi); - assertThat(modelManager.isModelAvailable(model)).isTrue(); - - OllamaEmbeddingModel embeddingModel = context.getBean(OllamaEmbeddingModel.class); - EmbeddingResponse embeddingResponse = embeddingModel.embedForResponse(List.of("Hello World")); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - modelManager.deleteModel(model); - }); - } - - @Test - void embeddingActivation() { - this.contextRunner.withPropertyValues("spring.ai.ollama.embedding.enabled=false").run(context -> { - assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isEmpty(); - }); - - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty(); - }); - - this.contextRunner.withPropertyValues("spring.ai.ollama.embedding.enabled=true").run(context -> { - assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty(); - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java deleted file mode 100644 index b29d88f8b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaEmbeddingAutoConfigurationTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @since 0.8.0 - */ -public class OllamaEmbeddingAutoConfigurationTests { - - @Test - public void propertiesTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.ollama.base-url=TEST_BASE_URL", - "spring.ai.ollama.embedding.options.model=MODEL_XYZ", - "spring.ai.ollama.embedding.options.temperature=0.13", - "spring.ai.ollama.embedding.options.topK=13" - // @formatter:on - ) - .withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(OllamaEmbeddingProperties.class); - var connectionProperties = context.getBean(OllamaConnectionProperties.class); - - assertThat(embeddingProperties.getModel()).isEqualTo("MODEL_XYZ"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(embeddingProperties.getOptions().toMap()).containsKeys("temperature"); - assertThat(embeddingProperties.getOptions().toMap().get("temperature")).isEqualTo(0.13); - assertThat(embeddingProperties.getOptions().getTopK()).isEqualTo(13); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaImage.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaImage.java deleted file mode 100644 index 5bb7547d0..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaImage.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure; - -public final class OllamaImage { - - public static final String DEFAULT_IMAGE = "ollama/ollama:0.5.7"; - - private OllamaImage() { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java deleted file mode 100644 index 875a90742..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/FunctionCallbackInPromptIT.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure.tool; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT; -import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.ollama.OllamaChatModel; -import org.springframework.ai.ollama.api.OllamaOptions; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -public class FunctionCallbackInPromptIT extends BaseOllamaIT { - - private static final Logger logger = LoggerFactory.getLogger(FunctionCallbackInPromptIT.class); - - private static final String MODEL_NAME = "qwen2.5:3b"; - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.ollama.baseUrl=" + getBaseUrl(), - "spring.ai.ollama.chat.options.model=" + MODEL_NAME, - "spring.ai.ollama.chat.options.temperature=0.5", - "spring.ai.ollama.chat.options.topK=10") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class)); - - @BeforeAll - public static void beforeAll() { - initializeOllama(MODEL_NAME); - } - - @Test - void functionCallTest() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - UserMessage userMessage = new UserMessage( - "What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations."); - - var promptOptions = OllamaOptions.builder() - .functionCallbacks(List.of(FunctionToolCallback - .builder("CurrentWeatherService", new MockWeatherService()) - .description( - "Find the weather conditions, forecasts, and temperatures for a location, like a city or state.") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Test - void streamingFunctionCallTest() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - UserMessage userMessage = new UserMessage( - "What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations."); - - var promptOptions = OllamaOptions.builder() - .functionCallbacks(List.of(FunctionToolCallback - .builder("CurrentWeatherService", new MockWeatherService()) - .description( - "Find the weather conditions, forecasts, and temperatures for a location, like a city or state.") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - Flux response = chatModel.stream(new Prompt(List.of(userMessage), promptOptions)); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/MockWeatherService.java deleted file mode 100644 index 6995eeed4..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/MockWeatherService.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.ollama.autoconfigure.tool; - -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - -/** - * Mock 3rd party weather service. - * - * @author Christian Tzolov - */ -public class MockWeatherService implements Function { - - @Override - public Response apply(Request request) { - - double temperature = 10; - if (request.location().contains("Paris")) { - temperature = 15; - } - else if (request.location().contains("Tokyo")) { - temperature = 10; - } - else if (request.location().contains("San Francisco")) { - temperature = 30; - } - - return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); - } - - /** - * Temperature units. - */ - public enum Unit { - - /** - * Celsius. - */ - C("metric"), - /** - * Fahrenheit. - */ - F("imperial"); - - /** - * Human readable unit name. - */ - public final String unitName; - - Unit(String text) { - this.unitName = text; - } - - } - - /** - * Weather Function request. - */ - @JsonInclude(Include.NON_NULL) - @JsonClassDescription("Weather API request") - public record Request(@JsonProperty(required = true, - value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, - @JsonProperty(required = true, value = "lat") @JsonPropertyDescription("The city latitude") double lat, - @JsonProperty(required = true, value = "lon") @JsonPropertyDescription("The city longitude") double lon, - @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { - - } - - /** - * Weather Function response. - */ - public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity, - Unit unit) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionCallbackIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionCallbackIT.java deleted file mode 100644 index 48d32380d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionCallbackIT.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * 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.springframework.ai.model.ollama.autoconfigure.tool; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT; -import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.ollama.OllamaChatModel; -import org.springframework.ai.ollama.api.OllamaOptions; -import org.springframework.ai.tool.ToolCallback; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -public class OllamaFunctionCallbackIT extends BaseOllamaIT { - - private static final Logger logger = LoggerFactory.getLogger(OllamaFunctionCallbackIT.class); - - private static final String MODEL_NAME = "qwen2.5:3b"; - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.ollama.baseUrl=" + getBaseUrl(), - "spring.ai.ollama.chat.options.model=" + MODEL_NAME, - "spring.ai.ollama.chat.options.temperature=0.5", - "spring.ai.ollama.chat.options.topK=10") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @BeforeAll - public static void beforeAll() { - initializeOllama(MODEL_NAME); - } - - @Test - void functionCallTest() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - UserMessage userMessage = new UserMessage( - "What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations."); - - ChatResponse response = chatModel - .call(new Prompt(List.of(userMessage), OllamaOptions.builder().function("WeatherInfo").build())); - - logger.info("Response: " + response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - UserMessage userMessage = new UserMessage( - "What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations."); - - Flux response = chatModel - .stream(new Prompt(List.of(userMessage), OllamaOptions.builder().function("WeatherInfo").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: " + content); - - assertThat(content).contains("30", "10", "15"); - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations."); - - ToolCallingChatOptions functionOptions = ToolCallingChatOptions.builder().toolNames("WeatherInfo").build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions)); - - logger.info("Response: " + response.getResult().getOutput().getText()); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Configuration - static class Config { - - @Bean - public ToolCallback weatherFunctionInfo() { - - return FunctionToolCallback.builder("WeatherInfo", new MockWeatherService()) - .description( - "Find the weather conditions, forecasts, and temperatures for a location, like a city or state.") - .inputType(MockWeatherService.Request.class) - .build(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java deleted file mode 100644 index 947192863..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/ollama/autoconfigure/tool/OllamaFunctionToolBeanIT.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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.springframework.ai.model.ollama.autoconfigure.tool; - -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT; -import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.ollama.OllamaChatModel; -import org.springframework.ai.ollama.api.OllamaOptions; -import org.springframework.ai.tool.ToolCallbacks; -import org.springframework.ai.tool.annotation.Tool; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for function-based tool calling in Ollama. - * - * @author Thomas Vitale - */ -public class OllamaFunctionToolBeanIT extends BaseOllamaIT { - - private static final Logger logger = LoggerFactory.getLogger(OllamaFunctionToolBeanIT.class); - - private static final String MODEL_NAME = "qwen2.5:3b"; - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.ollama.baseUrl=" + getBaseUrl(), - "spring.ai.ollama.chat.options.model=" + MODEL_NAME, - "spring.ai.ollama.chat.options.temperature=0.5", - "spring.ai.ollama.chat.options.topK=10") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @BeforeAll - public static void beforeAll() { - initializeOllama(MODEL_NAME); - } - - @Test - void toolCallTest() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - MyTools myTools = context.getBean(MyTools.class); - - UserMessage userMessage = new UserMessage( - "What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations."); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - OllamaOptions.builder().toolCallbacks(ToolCallbacks.from(myTools)).build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - - } - - @Test - void functionCallTest() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - UserMessage userMessage = new UserMessage( - "What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations."); - - ChatResponse response = chatModel - .call(new Prompt(List.of(userMessage), OllamaOptions.builder().toolNames("weatherInfo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - UserMessage userMessage = new UserMessage( - "What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations."); - - Flux response = chatModel - .stream(new Prompt(List.of(userMessage), OllamaOptions.builder().function("weatherInfo").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).contains("30", "10", "15"); - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - this.contextRunner.run(context -> { - - OllamaChatModel chatModel = context.getBean(OllamaChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What are the weather conditions in San Francisco, Tokyo, and Paris? Find the temperature in Celsius for each of the three locations."); - - ToolCallingChatOptions functionOptions = ToolCallingChatOptions.builder().toolNames("weatherInfo").build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions)); - - logger.info("Response: {}", response.getResult().getOutput().getText()); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - static class MyTools { - - @Tool(description = "Find the weather conditions, and temperatures for a location, like a city or state.") - public String weatherByLocation(String locationName) { - int temperature = 0; - if (locationName.equals("San Francisco")) { - temperature = 30; - } - else if (locationName.equals("Tokyo")) { - temperature = 10; - } - else if (locationName.equals("Paris")) { - temperature = 15; - } - return "The temperature in " + locationName + " is " + temperature + " degrees Celsius."; - } - - } - - @Configuration - static class Config { - - @Bean - @Description("Find the weather conditions, forecasts, and temperatures for a location, like a city or state.") - public Function weatherInfo() { - return new MockWeatherService(); - } - - @Bean - public MyTools myTools() { - return new MyTools(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java deleted file mode 100644 index e384541c0..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; - -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.metadata.Usage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.ai.image.ImagePrompt; -import org.springframework.ai.image.ImageResponse; -import org.springframework.ai.openai.OpenAiAudioSpeechModel; -import org.springframework.ai.openai.OpenAiAudioTranscriptionModel; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.OpenAiImageModel; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") -public class OpenAiAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(OpenAiAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY")) - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)); - - @Test - void chatCall() { - this.contextRunner.run(context -> { - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void chatCallAudioResponse() { - this.contextRunner - .withPropertyValues( - "spring.ai.openai.chat.options.model=" + OpenAiApi.ChatModel.GPT_4_O_AUDIO_PREVIEW.getValue(), - "spring.ai.openai.chat.options.output-modalities=text,audio", - "spring.ai.openai.chat.options.output-audio.voice=ALLOY", - "spring.ai.openai.chat.options.output-audio.format=WAV") - .run(context -> { - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - ChatResponse response = chatModel - .call(new Prompt(new UserMessage("Tell me joke about Spring Framework"))); - assertThat(response).isNotNull(); - logger.info("Response: " + response); - // AudioPlayer.play(response.getResult().getOutput().getMedia().get(0).getDataAsByteArray()); - }); - } - - @Test - void transcribe() { - this.contextRunner.run(context -> { - OpenAiAudioTranscriptionModel transcriptionModel = context.getBean(OpenAiAudioTranscriptionModel.class); - Resource audioFile = new ClassPathResource("/speech/jfk.flac"); - String response = transcriptionModel.call(audioFile); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void speech() { - this.contextRunner.run(context -> { - OpenAiAudioSpeechModel speechModel = context.getBean(OpenAiAudioSpeechModel.class); - byte[] response = speechModel.call("H"); - assertThat(response).isNotNull(); - assertThat(verifyMp3FrameHeader(response)) - .withFailMessage("Expected MP3 frame header to be present in the response, but it was not found.") - .isTrue(); - assertThat(response.length).isNotEqualTo(0); - - logger.debug("Response: " + Arrays.toString(response)); - }); - } - - public boolean verifyMp3FrameHeader(byte[] audioResponse) { - // Check if the response is null or too short to contain a frame header - if (audioResponse == null || audioResponse.length < 2) { - return false; - } - // Check for the MP3 frame header - // 0xFFE0 is the sync word for an MP3 frame (11 bits set to 1 followed by 3 bits - // set to 0) - return (audioResponse[0] & 0xFF) == 0xFF && (audioResponse[1] & 0xE0) == 0xE0; - } - - @Test - void generateStreaming() { - this.contextRunner.run(context -> { - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - String response = responseFlux.collectList() - .block() - .stream() - .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) - .collect(Collectors.joining()); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void streamingWithTokenUsage() { - this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.stream-usage=true").run(context -> { - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - - Usage[] streamingTokenUsage = new Usage[1]; - String response = responseFlux.collectList().block().stream().map(chatResponse -> { - streamingTokenUsage[0] = chatResponse.getMetadata().getUsage(); - return (chatResponse.getResult() != null) ? chatResponse.getResult().getOutput().getText() : ""; - }).collect(Collectors.joining()); - - assertThat(streamingTokenUsage[0].getPromptTokens()).isGreaterThan(0); - assertThat(streamingTokenUsage[0].getCompletionTokens()).isGreaterThan(0); - assertThat(streamingTokenUsage[0].getTotalTokens()).isGreaterThan(0); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void embedding() { - this.contextRunner.run(context -> { - OpenAiEmbeddingModel embeddingModel = context.getBean(OpenAiEmbeddingModel.class); - - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - - assertThat(embeddingModel.dimensions()).isEqualTo(1536); - }); - } - - @Test - void generateImage() { - this.contextRunner.withPropertyValues("spring.ai.openai.image.options.size=1024x1024").run(context -> { - OpenAiImageModel imageModel = context.getBean(OpenAiImageModel.class); - ImageResponse imageResponse = imageModel.call(new ImagePrompt("forest")); - assertThat(imageResponse.getResults()).hasSize(1); - assertThat(imageResponse.getResult().getOutput().getUrl()).isNotEmpty(); - logger.info("Generated image: " + imageResponse.getResult().getOutput().getUrl()); - }); - } - - @Test - void generateImageWithModel() { - // The 256x256 size is supported by dall-e-2, but not by dall-e-3. - this.contextRunner - .withPropertyValues("spring.ai.openai.image.options.model=dall-e-2", - "spring.ai.openai.image.options.size=256x256") - .run(context -> { - OpenAiImageModel imageModel = context.getBean(OpenAiImageModel.class); - ImageResponse imageResponse = imageModel.call(new ImagePrompt("forest")); - assertThat(imageResponse.getResults()).hasSize(1); - assertThat(imageResponse.getResult().getOutput().getUrl()).isNotEmpty(); - logger.info("Generated image: " + imageResponse.getResult().getOutput().getUrl()); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java deleted file mode 100644 index bbff01e58..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java +++ /dev/null @@ -1,687 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.openai.OpenAiAudioSpeechModel; -import org.springframework.ai.openai.OpenAiAudioTranscriptionModel; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.OpenAiImageModel; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.ai.openai.api.OpenAiApi.ChatCompletionRequest.ToolChoiceBuilder; -import org.springframework.ai.openai.api.OpenAiAudioApi; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit Tests for {@link OpenAiConnectionProperties}, {@link OpenAiChatProperties} and - * {@link OpenAiEmbeddingProperties}. - * - * @author Christian Tzolov - * @author Thomas Vitale - * @since 0.8.0 - */ -public class OpenAiPropertiesTests { - - @Test - public void chatProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.chat.options.model=MODEL_XYZ", - "spring.ai.openai.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(OpenAiChatProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isNull(); - assertThat(chatProperties.getBaseUrl()).isNull(); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void transcriptionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.audio.transcription.options.model=MODEL_XYZ", - "spring.ai.openai.audio.transcription.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(transcriptionProperties.getApiKey()).isNull(); - assertThat(transcriptionProperties.getBaseUrl()).isNull(); - - assertThat(transcriptionProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(transcriptionProperties.getOptions().getTemperature()).isEqualTo(0.55f); - }); - } - - @Test - public void chatOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.chat.base-url=TEST_BASE_URL2", - "spring.ai.openai.chat.api-key=456", - "spring.ai.openai.chat.options.model=MODEL_XYZ", - "spring.ai.openai.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(OpenAiChatProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isEqualTo("456"); - assertThat(chatProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void transcriptionOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.audio.transcription.base-url=TEST_BASE_URL2", - "spring.ai.openai.audio.transcription.api-key=456", - "spring.ai.openai.audio.transcription.options.model=MODEL_XYZ", - "spring.ai.openai.audio.transcription.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(transcriptionProperties.getApiKey()).isEqualTo("456"); - assertThat(transcriptionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(transcriptionProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(transcriptionProperties.getOptions().getTemperature()).isEqualTo(0.55f); - }); - } - - @Test - public void speechProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.audio.speech.options.model=TTS_1", - "spring.ai.openai.audio.speech.options.voice=alloy", - "spring.ai.openai.audio.speech.options.response-format=mp3", - "spring.ai.openai.audio.speech.options.speed=0.75") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var speechProperties = context.getBean(OpenAiAudioSpeechProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(speechProperties.getApiKey()).isNull(); - assertThat(speechProperties.getBaseUrl()).isNull(); - - assertThat(speechProperties.getOptions().getModel()).isEqualTo("TTS_1"); - assertThat(speechProperties.getOptions().getVoice()) - .isEqualTo(OpenAiAudioApi.SpeechRequest.Voice.ALLOY); - assertThat(speechProperties.getOptions().getResponseFormat()) - .isEqualTo(OpenAiAudioApi.SpeechRequest.AudioResponseFormat.MP3); - assertThat(speechProperties.getOptions().getSpeed()).isEqualTo(0.75f); - }); - } - - @Test - public void speechPropertiesTest() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.audio.speech.options.model=TTS_1", - "spring.ai.openai.audio.speech.options.voice=alloy", - "spring.ai.openai.audio.speech.options.response-format=mp3", - "spring.ai.openai.audio.speech.options.speed=0.75") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var speechProperties = context.getBean(OpenAiAudioSpeechProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(speechProperties.getOptions().getModel()).isEqualTo("TTS_1"); - assertThat(speechProperties.getOptions().getVoice()) - .isEqualTo(OpenAiAudioApi.SpeechRequest.Voice.ALLOY); - assertThat(speechProperties.getOptions().getResponseFormat()) - .isEqualTo(OpenAiAudioApi.SpeechRequest.AudioResponseFormat.MP3); - assertThat(speechProperties.getOptions().getSpeed()).isEqualTo(0.75f); - }); - } - - @Test - public void speechOverrideConnectionPropertiesTest() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.audio.speech.base-url=TEST_BASE_URL2", - "spring.ai.openai.audio.speech.api-key=456", - "spring.ai.openai.audio.speech.options.model=TTS_2", - "spring.ai.openai.audio.speech.options.voice=echo", - "spring.ai.openai.audio.speech.options.response-format=opus", - "spring.ai.openai.audio.speech.options.speed=0.5") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var speechProperties = context.getBean(OpenAiAudioSpeechProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(speechProperties.getApiKey()).isEqualTo("456"); - assertThat(speechProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(speechProperties.getOptions().getModel()).isEqualTo("TTS_2"); - assertThat(speechProperties.getOptions().getVoice()).isEqualTo(OpenAiAudioApi.SpeechRequest.Voice.ECHO); - assertThat(speechProperties.getOptions().getResponseFormat()) - .isEqualTo(OpenAiAudioApi.SpeechRequest.AudioResponseFormat.OPUS); - assertThat(speechProperties.getOptions().getSpeed()).isEqualTo(0.5f); - }); - } - - @Test - public void embeddingProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.embedding.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isNull(); - assertThat(embeddingProperties.getBaseUrl()).isNull(); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void embeddingOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.embedding.base-url=TEST_BASE_URL2", - "spring.ai.openai.embedding.api-key=456", - "spring.ai.openai.embedding.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isEqualTo("456"); - assertThat(embeddingProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void imageProperties() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.image.options.model=MODEL_XYZ", - "spring.ai.openai.image.options.n=3") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(OpenAiImageProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(imageProperties.getApiKey()).isNull(); - assertThat(imageProperties.getBaseUrl()).isNull(); - - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(imageProperties.getOptions().getN()).isEqualTo(3); - }); - } - - @Test - public void imageOverrideConnectionProperties() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.api-key=abc123", - "spring.ai.openai.image.base-url=TEST_BASE_URL2", - "spring.ai.openai.image.api-key=456", - "spring.ai.openai.image.options.model=MODEL_XYZ", - "spring.ai.openai.image.options.n=3") - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(OpenAiImageProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(imageProperties.getApiKey()).isEqualTo("456"); - assertThat(imageProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(imageProperties.getOptions().getN()).isEqualTo(3); - }); - } - - @Test - public void chatOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.api-key=API_KEY", - "spring.ai.openai.base-url=TEST_BASE_URL", - - "spring.ai.openai.chat.options.model=MODEL_XYZ", - "spring.ai.openai.chat.options.frequencyPenalty=-1.5", - "spring.ai.openai.chat.options.logitBias.myTokenId=-5", - "spring.ai.openai.chat.options.maxTokens=123", - "spring.ai.openai.chat.options.n=10", - "spring.ai.openai.chat.options.presencePenalty=0", - "spring.ai.openai.chat.options.seed=66", - "spring.ai.openai.chat.options.stop=boza,koza", - "spring.ai.openai.chat.options.temperature=0.55", - "spring.ai.openai.chat.options.topP=0.56", - - // "spring.ai.openai.chat.options.toolChoice.functionName=toolChoiceFunctionName", - "spring.ai.openai.chat.options.toolChoice=" + ModelOptionsUtils.toJsonString(ToolChoiceBuilder.FUNCTION("toolChoiceFunctionName")), - - "spring.ai.openai.chat.options.tools[0].function.name=myFunction1", - "spring.ai.openai.chat.options.tools[0].function.description=function description", - "spring.ai.openai.chat.options.tools[0].function.jsonSchema=" + """ - { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state e.g. San Francisco, CA" - }, - "lat": { - "type": "number", - "description": "The city latitude" - }, - "lon": { - "type": "number", - "description": "The city longitude" - }, - "unit": { - "type": "string", - "enum": ["c", "f"] - } - }, - "required": ["location", "lat", "lon", "unit"] - } - """, - "spring.ai.openai.chat.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(OpenAiChatProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("text-embedding-ada-002"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5); - assertThat(chatProperties.getOptions().getLogitBias().get("myTokenId")).isEqualTo(-5); - assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); - assertThat(chatProperties.getOptions().getN()).isEqualTo(10); - assertThat(chatProperties.getOptions().getPresencePenalty()).isEqualTo(0); - assertThat(chatProperties.getOptions().getSeed()).isEqualTo(66); - assertThat(chatProperties.getOptions().getStop()).contains("boza", "koza"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56); - - JSONAssert.assertEquals("{\"type\":\"function\",\"function\":{\"name\":\"toolChoiceFunctionName\"}}", - "" + chatProperties.getOptions().getToolChoice(), JSONCompareMode.LENIENT); - - assertThat(chatProperties.getOptions().getUser()).isEqualTo("userXYZ"); - - assertThat(chatProperties.getOptions().getTools()).hasSize(1); - var tool = chatProperties.getOptions().getTools().get(0); - assertThat(tool.getType()).isEqualTo(OpenAiApi.FunctionTool.Type.FUNCTION); - var function = tool.getFunction(); - assertThat(function.getName()).isEqualTo("myFunction1"); - assertThat(function.getDescription()).isEqualTo("function description"); - assertThat(function.getParameters()).isNotEmpty(); - }); - } - - @Test - public void transcriptionOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.api-key=API_KEY", - "spring.ai.openai.base-url=TEST_BASE_URL", - - "spring.ai.openai.audio.transcription.options.model=MODEL_XYZ", - "spring.ai.openai.audio.transcription.options.language=en", - "spring.ai.openai.audio.transcription.options.prompt=Er, yes, I think so", - "spring.ai.openai.audio.transcription.options.responseFormat=JSON", - "spring.ai.openai.audio.transcription.options.temperature=0.55" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("text-embedding-ada-002"); - - assertThat(transcriptionProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(transcriptionProperties.getOptions().getLanguage()).isEqualTo("en"); - assertThat(transcriptionProperties.getOptions().getPrompt()).isEqualTo("Er, yes, I think so"); - assertThat(transcriptionProperties.getOptions().getResponseFormat()) - .isEqualTo(OpenAiAudioApi.TranscriptResponseFormat.JSON); - assertThat(transcriptionProperties.getOptions().getTemperature()).isEqualTo(0.55f); - }); - } - - @Test - public void embeddingOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.api-key=API_KEY", - "spring.ai.openai.base-url=TEST_BASE_URL", - - "spring.ai.openai.embedding.options.model=MODEL_XYZ", - "spring.ai.openai.embedding.options.encodingFormat=MyEncodingFormat", - "spring.ai.openai.embedding.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(embeddingProperties.getOptions().getEncodingFormat()).isEqualTo("MyEncodingFormat"); - assertThat(embeddingProperties.getOptions().getUser()).isEqualTo("userXYZ"); - }); - } - - @Test - public void imageOptionsTest() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.api-key=API_KEY", - "spring.ai.openai.base-url=TEST_BASE_URL", - - "spring.ai.openai.image.options.n=3", - "spring.ai.openai.image.options.model=MODEL_XYZ", - "spring.ai.openai.image.options.quality=hd", - "spring.ai.openai.image.options.response_format=url", - "spring.ai.openai.image.options.size=1024x1024", - "spring.ai.openai.image.options.width=1024", - "spring.ai.openai.image.options.height=1024", - "spring.ai.openai.image.options.style=vivid", - "spring.ai.openai.image.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(OpenAiImageProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(imageProperties.getOptions().getN()).isEqualTo(3); - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(imageProperties.getOptions().getQuality()).isEqualTo("hd"); - assertThat(imageProperties.getOptions().getResponseFormat()).isEqualTo("url"); - assertThat(imageProperties.getOptions().getSize()).isEqualTo("1024x1024"); - assertThat(imageProperties.getOptions().getWidth()).isEqualTo(1024); - assertThat(imageProperties.getOptions().getHeight()).isEqualTo(1024); - assertThat(imageProperties.getOptions().getStyle()).isEqualTo("vivid"); - assertThat(imageProperties.getOptions().getUser()).isEqualTo("userXYZ"); - }); - } - - @Test - void embeddingActivation() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.embedding.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.embedding.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty(); - }); - } - - @Test - void chatActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty(); - }); - - } - - @Test - void imageActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.image.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.image.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty(); - }); - - } - - @Test - void audioSpeechActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.audio.speech.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.audio.speech.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty(); - }); - - } - - @Test - void audioTranscriptionActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.audio.transcription.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.audio.transcription.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty(); - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java deleted file mode 100644 index a863c88b7..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.openai.OpenAiAudioSpeechModel; -import org.springframework.ai.openai.OpenAiAudioTranscriptionModel; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.OpenAiImageModel; -import org.springframework.ai.openai.api.OpenAiAudioApi; -import org.springframework.ai.openai.api.ResponseFormat; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit Tests for {@link OpenAiChatProperties} #options#responseFormat support. - * - * @author Christian Tzolov - */ -public class OpenAiResponseFormatPropertiesTests { - - @Test - public void responseFormatJsonSchema() { - - String responseFormatJsonSchema = """ - { - "$schema" : "https://json-schema.org/draft/2020-12/schema", - "type" : "object", - "properties" : { - "someString" : { - "type" : "string" - } - }, - "additionalProperties" : false - } - """; - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.api-key=API_KEY", - - "spring.ai.openai.chat.options.response-format.type=JSON_SCHEMA", - "spring.ai.openai.chat.options.response-format.name=MyName", - "spring.ai.openai.chat.options.response-format.schema=" + responseFormatJsonSchema, - "spring.ai.openai.chat.options.response-format.strict=true" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(OpenAiChatProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(chatProperties.getOptions().getResponseFormat()) - .isEqualTo(new ResponseFormat(ResponseFormat.Type.JSON_SCHEMA, responseFormatJsonSchema)); - }); - } - - @Test - public void responseFormatJsonObject() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", - "spring.ai.openai.chat.options.response-format.type=JSON_OBJECT") - - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(OpenAiChatProperties.class); - - assertThat(chatProperties.getOptions().getResponseFormat()) - .isEqualTo(ResponseFormat.builder().type(ResponseFormat.Type.JSON_OBJECT).build()); - }); - } - - @Test - public void emptyResponseFormat() { - - new ApplicationContextRunner().withPropertyValues("spring.ai.openai.api-key=API_KEY") - - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(OpenAiChatProperties.class); - - assertThat(chatProperties.getOptions().getResponseFormat()).isNull(); - }); - } - - @Test - public void transcriptionOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.api-key=API_KEY", - "spring.ai.openai.base-url=TEST_BASE_URL", - - "spring.ai.openai.audio.transcription.options.model=MODEL_XYZ", - "spring.ai.openai.audio.transcription.options.language=en", - "spring.ai.openai.audio.transcription.options.prompt=Er, yes, I think so", - "spring.ai.openai.audio.transcription.options.responseFormat=JSON", - "spring.ai.openai.audio.transcription.options.temperature=0.55" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("text-embedding-ada-002"); - - assertThat(transcriptionProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(transcriptionProperties.getOptions().getLanguage()).isEqualTo("en"); - assertThat(transcriptionProperties.getOptions().getPrompt()).isEqualTo("Er, yes, I think so"); - assertThat(transcriptionProperties.getOptions().getResponseFormat()) - .isEqualTo(OpenAiAudioApi.TranscriptResponseFormat.JSON); - assertThat(transcriptionProperties.getOptions().getTemperature()).isEqualTo(0.55f); - }); - } - - @Test - public void embeddingOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.api-key=API_KEY", - "spring.ai.openai.base-url=TEST_BASE_URL", - - "spring.ai.openai.embedding.options.model=MODEL_XYZ", - "spring.ai.openai.embedding.options.encodingFormat=MyEncodingFormat", - "spring.ai.openai.embedding.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(embeddingProperties.getOptions().getEncodingFormat()).isEqualTo("MyEncodingFormat"); - assertThat(embeddingProperties.getOptions().getUser()).isEqualTo("userXYZ"); - }); - } - - @Test - public void imageOptionsTest() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.openai.api-key=API_KEY", - "spring.ai.openai.base-url=TEST_BASE_URL", - - "spring.ai.openai.image.options.n=3", - "spring.ai.openai.image.options.model=MODEL_XYZ", - "spring.ai.openai.image.options.quality=hd", - "spring.ai.openai.image.options.response_format=url", - "spring.ai.openai.image.options.size=1024x1024", - "spring.ai.openai.image.options.width=1024", - "spring.ai.openai.image.options.height=1024", - "spring.ai.openai.image.options.style=vivid", - "spring.ai.openai.image.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(OpenAiImageProperties.class); - var connectionProperties = context.getBean(OpenAiConnectionProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(imageProperties.getOptions().getN()).isEqualTo(3); - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(imageProperties.getOptions().getQuality()).isEqualTo("hd"); - assertThat(imageProperties.getOptions().getResponseFormat()).isEqualTo("url"); - assertThat(imageProperties.getOptions().getSize()).isEqualTo("1024x1024"); - assertThat(imageProperties.getOptions().getWidth()).isEqualTo(1024); - assertThat(imageProperties.getOptions().getHeight()).isEqualTo(1024); - assertThat(imageProperties.getOptions().getStyle()).isEqualTo("vivid"); - assertThat(imageProperties.getOptions().getUser()).isEqualTo("userXYZ"); - }); - } - - @Test - void embeddingActivation() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.embedding.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.embedding.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty(); - }); - } - - @Test - void chatActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty(); - }); - - } - - @Test - void imageActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.image.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.image.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty(); - }); - - } - - @Test - void audioSpeechActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.audio.speech.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.audio.speech.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty(); - }); - - } - - @Test - void audioTranscriptionActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.audio.transcription.enabled=false") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL", - "spring.ai.openai.audio.transcription.enabled=true") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty(); - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java deleted file mode 100644 index 8e238be52..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure.tool; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration; -import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.api.OpenAiApi.ChatModel; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") -public class FunctionCallbackInPrompt2IT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallbackInPromptIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY")) - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName()) - .run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - ChatClient chatClient = ChatClient.builder(chatModel).build(); - - // @formatter:off - chatClient.prompt() - .user("Tell me a joke?") - .call().content(); - - String content = ChatClient.builder(chatModel).build().prompt() - .user("What's the weather like in San Francisco, Tokyo, and Paris?") - .functions(FunctionToolCallback - .builder("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build()) - .call().content(); - // @formatter:on - - logger.info("Response: {}", content); - - assertThat(content).contains("30", "10", "15"); - }); - } - - @Test - void lambdaFunctionCallTest() { - Map state = new ConcurrentHashMap<>(); - - record LightInfo(String roomName, boolean isOn) { - } - - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // @formatter:off - String content = ChatClient.builder(chatModel).build().prompt() - .user("Turn the light on in the kitchen and in the living room!") - .functions(FunctionToolCallback - .builder("turnLight", (LightInfo lightInfo) -> { - logger.info("Turning light to [" + lightInfo.isOn + "] in " + lightInfo.roomName()); - state.put(lightInfo.roomName(), lightInfo.isOn()); - }) - .description("Turn light on or off in a room") - .inputType(LightInfo.class) - .build()) - .call().content(); - // @formatter:on - logger.info("Response: {}", content); - assertThat(state).containsEntry("kitchen", Boolean.TRUE); - assertThat(state).containsEntry("living room", Boolean.TRUE); - }); - } - - @Test - void functionCallTest2() { - this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName()) - .run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // @formatter:off - String content = ChatClient.builder(chatModel).build().prompt() - .user("What's the weather like in Amsterdam?") - .functions(FunctionToolCallback - .builder("CurrentWeatherService", input -> "18 degrees Celsius") - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build()) - .call().content(); - // @formatter:on - logger.info("Response: {}", content); - - assertThat(content).contains("18"); - }); - } - - @Test - void streamingFunctionCallTest() { - - this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName()) - .run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // @formatter:off - String content = ChatClient.builder(chatModel).build().prompt() - .user("What's the weather like in San Francisco, Tokyo, and Paris?") - .functions(FunctionToolCallback - .builder("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build()) - .stream().content() - .collectList().block().stream().collect(Collectors.joining()); - // @formatter:on - - logger.info("Response: {}", content); - - assertThat(content).contains("30", "10", "15"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java deleted file mode 100644 index 45596ff5e..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure.tool; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.api.OpenAiApi.ChatModel; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") -public class FunctionCallbackInPromptIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallbackInPromptIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY")) - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner - .withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName(), - "spring.ai.openai.chat.options.temperature=0.1") - .run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris?"); - - var promptOptions = OpenAiChatOptions.builder() - .functionCallbacks( - List.of(FunctionToolCallback.builder("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Test - void streamingFunctionCallTest() { - - this.contextRunner - .withPropertyValues("spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName(), - "spring.ai.openai.chat.options.temperature=0.5") - .run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris?"); - - var promptOptions = OpenAiChatOptions.builder() - .functionCallbacks( - List.of(FunctionToolCallback.builder("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - Flux response = chatModel.stream(new Prompt(List.of(userMessage), promptOptions)); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).contains("30", "10", "15"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java deleted file mode 100644 index b1ddf745d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java +++ /dev/null @@ -1,431 +0,0 @@ -/* - * 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.springframework.ai.model.openai.autoconfigure.tool; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration; -import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.model.ToolContext; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.api.OpenAiApi.ChatModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") -class FunctionCallbackWithPlainFunctionBeanIT { - - private static final Logger logger = LoggerFactory.getLogger(FunctionCallbackWithPlainFunctionBeanIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"), - "spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName()) - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - private static Map feedback = new ConcurrentHashMap<>(); - - @BeforeEach - void setUp() { - feedback.clear(); - } - - @Test - void functionCallingVoidInput() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage("Turn the light on in the living room"); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder().function("turnLivingRoomLightOn").build())); - - logger.info("Response: {}", response); - assertThat(feedback).hasSize(1); - assertThat(feedback.get("turnLivingRoomLightOn")).isEqualTo(Boolean.valueOf(true)); - }); - } - - @Test - void functionCallingSupplier() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage("Turn the light on in the living room"); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder().function("turnLivingRoomLightOnSupplier").build())); - - logger.info("Response: {}", response); - assertThat(feedback).hasSize(1); - assertThat(feedback.get("turnLivingRoomLightOnSupplier")).isEqualTo(Boolean.valueOf(true)); - }); - } - - @Test - void functionCallingVoidOutput() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage("Turn the light on in the kitchen and in the living room"); - - ChatResponse response = chatModel - .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().function("turnLight").build())); - - logger.info("Response: {}", response); - assertThat(feedback).hasSize(2); - assertThat(feedback.get("kitchen")).isEqualTo(Boolean.valueOf(true)); - assertThat(feedback.get("living room")).isEqualTo(Boolean.valueOf(true)); - }); - } - - @Test - void functionCallingConsumer() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage("Turn the light on in the kitchen and in the living room"); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder().function("turnLightConsumer").build())); - - logger.info("Response: {}", response); - assertThat(feedback).hasSize(2); - assertThat(feedback.get("kitchen")).isEqualTo(Boolean.valueOf(true)); - assertThat(feedback.get("living room")).isEqualTo(Boolean.valueOf(true)); - - }); - } - - @Test - void trainScheduler() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "Please schedule a train from San Francisco to Los Angeles on 2023-12-25"); - - ToolCallingChatOptions functionOptions = ToolCallingChatOptions.builder() - .toolNames("trainReservation") - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions)); - - logger.info("Response: {}", response.getResult().getOutput().getText()); - }); - } - - @Test - void functionCallWithDirectBiFunction() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - ChatClient chatClient = ChatClient.builder(chatModel).build(); - - String content = chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?") - .functions("weatherFunctionWithContext") - .toolContext(Map.of("sessionId", "123")) - .call() - .content(); - logger.info(content); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? You can call the following functions 'weatherFunction'"); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder() - .function("weatherFunctionWithContext") - .toolContext(Map.of("sessionId", "123")) - .build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void functionCallWithBiFunctionClass() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - ChatClient chatClient = ChatClient.builder(chatModel).build(); - - String content = chatClient.prompt("What's the weather like in San Francisco, Tokyo, and Paris?") - .functions("weatherFunctionWithClassBiFunction") - .toolContext(Map.of("sessionId", "123")) - .call() - .content(); - logger.info(content); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? You can call the following functions 'weatherFunction'"); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder() - .function("weatherFunctionWithClassBiFunction") - .toolContext(Map.of("sessionId", "123")) - .build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void functionCallTest() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? You can call the following functions 'weatherFunction'"); - - ChatResponse response = chatModel.call( - new Prompt(List.of(userMessage), OpenAiChatOptions.builder().function("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - // Test weatherFunctionTwo - response = chatModel.call(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder().function("weatherFunctionTwo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?"); - - ToolCallingChatOptions functionOptions = ToolCallingChatOptions.builder() - .toolNames("weatherFunction") - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions)); - - logger.info("Response: {}", response.getResult().getOutput().getText()); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? You can call the following functions 'weatherFunction'"); - - Flux response = chatModel.stream( - new Prompt(List.of(userMessage), OpenAiChatOptions.builder().function("weatherFunction").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).contains("30", "10", "15"); - - // Test weatherFunctionTwo - response = chatModel.stream(new Prompt(List.of(userMessage), - OpenAiChatOptions.builder().function("weatherFunctionTwo").build())); - - content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).isNotEmpty().withFailMessage("Content returned from OpenAI model is empty"); - assertThat(content).contains("30", "10", "15"); - - }); - } - - @Configuration - static class Config { - - @Bean - @Description("Get the weather in location") - public MyBiFunction weatherFunctionWithClassBiFunction() { - return new MyBiFunction(); - } - - @Bean - @Description("Get the weather in location") - public BiFunction weatherFunctionWithContext() { - return (request, context) -> new MockWeatherService().apply(request); - } - - @Bean - @Description("Get the weather in location") - public Function weatherFunction() { - return new MockWeatherService(); - } - - // Relies on the Request's JsonClassDescription annotation to provide the - // function description. - @Bean - public Function weatherFunctionTwo() { - MockWeatherService weatherService = new MockWeatherService(); - return (weatherService::apply); - } - - @Bean - @Description("Turn light on or off in a room") - public Function turnLight() { - return (LightInfo lightInfo) -> { - logger.info("Turning light to [" + lightInfo.isOn + "] in " + lightInfo.roomName()); - feedback.put(lightInfo.roomName(), lightInfo.isOn()); - return null; - }; - } - - @Bean - @Description("Turn light on or off in a room") - public Consumer turnLightConsumer() { - return (LightInfo lightInfo) -> { - logger.info("Turning light to [" + lightInfo.isOn + "] in " + lightInfo.roomName()); - feedback.put(lightInfo.roomName(), lightInfo.isOn()); - }; - } - - @Bean - @Description("Turns light on in the living room") - public Function turnLivingRoomLightOn() { - return (Void v) -> { - logger.info("Turning light on in the living room"); - feedback.put("turnLivingRoomLightOn", Boolean.TRUE); - return "Done"; - }; - } - - @Bean - @Description("Turns light on in the living room") - public Supplier turnLivingRoomLightOnSupplier() { - return () -> { - logger.info("Turning light on in the living room"); - feedback.put("turnLivingRoomLightOnSupplier", Boolean.TRUE); - return "Done"; - }; - } - - @Bean - @Description("Schedule a train reservation") - public Function, TrainSearchResponse> trainReservation() { - return (TrainSearchRequest request) -> { - logger.info("Turning light to [" + request.data().from() + "] in " + request.data().to()); - return new TrainSearchResponse<>( - new TrainSearchScheduleResponse(request.data().from(), request.data().to(), "", "123")); - }; - } - - } - - public static class MyBiFunction - implements BiFunction { - - @Override - public MockWeatherService.Response apply(MockWeatherService.Request request, ToolContext context) { - return new MockWeatherService().apply(request); - } - - } - - record LightInfo(String roomName, boolean isOn) { - } - - record TrainSearchSchedule(String from, String to, String date) { - } - - record TrainSearchScheduleResponse(String from, String to, String date, String trainNumber) { - } - - record TrainSearchRequest(T data) { - } - - record TrainSearchResponse(T data) { - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/MockWeatherService.java deleted file mode 100644 index f3f24a06f..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/MockWeatherService.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure.tool; - -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - -/** - * Mock 3rd party weather service. - * - * @author Christian Tzolov - */ -public class MockWeatherService implements Function { - - @Override - public Response apply(Request request) { - - double temperature = 10; - if (request.location().contains("Paris")) { - temperature = 15; - } - else if (request.location().contains("Tokyo")) { - temperature = 10; - } - else if (request.location().contains("San Francisco")) { - temperature = 30; - } - - return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); - } - - /** - * Temperature units. - */ - public enum Unit { - - /** - * Celsius. - */ - C("metric"), - /** - * Fahrenheit. - */ - F("imperial"); - - /** - * Human readable unit name. - */ - public final String unitName; - - Unit(String text) { - this.unitName = text; - } - - } - - /** - * Weather Function request. - */ - @JsonInclude(Include.NON_NULL) - @JsonClassDescription("Weather API request") - public record Request(@JsonProperty(required = true, - value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, - @JsonProperty(required = true, value = "lat") @JsonPropertyDescription("The city latitude") double lat, - @JsonProperty(required = true, value = "lon") @JsonPropertyDescription("The city longitude") double lon, - @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { - - } - - /** - * Weather Function response. - */ - public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity, - Unit unit) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java deleted file mode 100644 index 682a8dacb..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure.tool; - -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration; -import org.springframework.ai.chat.client.ChatClient; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.api.OpenAiApi.ChatModel; -import org.springframework.ai.tool.ToolCallback; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") -public class OpenAiFunctionCallback2IT { - - private final Logger logger = LoggerFactory.getLogger(OpenAiFunctionCallback2IT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"), - "spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName()) - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.temperature=0.1").run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // @formatter:off - ChatClient chatClient = ChatClient.builder(chatModel) - .defaultFunctions("WeatherInfo") - .defaultUser(u -> u.text("What's the weather like in {cities}?")) - .build(); - - String content = chatClient.prompt() - .user(u -> u.param("cities", "San Francisco, Tokyo, Paris")) - .call().content(); - // @formatter:on - - logger.info("Response: {}", content); - - assertThat(content).contains("30", "10", "15"); - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.temperature=0.1").run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - // @formatter:off - String content = ChatClient.builder(chatModel).build().prompt() - .functions("WeatherInfo") - .user("What's the weather like in San Francisco, Tokyo, and Paris?") - .stream().content() - .collectList().block().stream().collect(Collectors.joining()); - // @formatter:on - - logger.info("Response: {}", content); - - assertThat(content).contains("30", "10", "15"); - }); - } - - @Configuration - static class Config { - - @Bean - public ToolCallback weatherFunctionInfo() { - - return FunctionToolCallback.builder("WeatherInfo", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java deleted file mode 100644 index 6ab701eb2..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.openai.autoconfigure.tool; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.api.OpenAiApi.ChatModel; -import org.springframework.ai.tool.ToolCallback; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*") -public class OpenAiFunctionCallbackIT { - - private final Logger logger = LoggerFactory.getLogger(OpenAiFunctionCallbackIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"), - "spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName()) - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.temperature=0.1").run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - UserMessage userMessage = new UserMessage("What's the weather like in San Francisco, Tokyo, and Paris?"); - - ChatResponse response = chatModel - .call(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().function("WeatherInfo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.temperature=0.1").run(context -> { - - OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? You can call the following functions 'WeatherInfo'"); - - Flux response = chatModel - .stream(new Prompt(List.of(userMessage), OpenAiChatOptions.builder().function("WeatherInfo").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - - }); - } - - @Configuration - static class Config { - - @Bean - public ToolCallback weatherFunctionInfo() { - - return FunctionToolCallback.builder("WeatherInfo", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingPropertiesTests.java deleted file mode 100644 index 9ac0dd861..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlEmbeddingPropertiesTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.postgresml.autoconfigure; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.document.MetadataMode; -import org.springframework.ai.postgresml.PostgresMlEmbeddingModel; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.SpringBootTest; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit Tests for {@link PostgresMlEmbeddingProperties}. - * - * @author Utkarsh Srivastava - * @author Christian Tzolov - */ -@SpringBootTest(properties = { "spring.ai.postgresml.embedding.options.metadata-mode=all", - "spring.ai.postgresml.embedding.options.kwargs.key1=value1", - "spring.ai.postgresml.embedding.options.kwargs.key2=value2", - "spring.ai.postgresml.embedding.options.transformer=abc123" }) -class PostgresMlEmbeddingPropertiesTests { - - @Autowired - private PostgresMlEmbeddingProperties postgresMlProperties; - - @Test - void postgresMlPropertiesAreCorrect() { - assertThat(this.postgresMlProperties).isNotNull(); - assertThat(this.postgresMlProperties.getOptions().getTransformer()).isEqualTo("abc123"); - assertThat(this.postgresMlProperties.getOptions().getVectorType()) - .isEqualTo(PostgresMlEmbeddingModel.VectorType.PG_ARRAY); - assertThat(this.postgresMlProperties.getOptions().getKwargs()) - .isEqualTo(Map.of("key1", "value1", "key2", "value2")); - assertThat(this.postgresMlProperties.getOptions().getMetadataMode()).isEqualTo(MetadataMode.ALL); - } - - @SpringBootConfiguration - @EnableConfigurationProperties(PostgresMlEmbeddingProperties.class) - static class TestConfiguration { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfigurationIT.java deleted file mode 100644 index bc2334e20..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfigurationIT.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; - -import java.util.List; -import java.util.Objects; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariables; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.ai.image.ImagePrompt; -import org.springframework.ai.image.ImageResponse; -import org.springframework.ai.qianfan.QianFanChatModel; -import org.springframework.ai.qianfan.QianFanEmbeddingModel; -import org.springframework.ai.qianfan.QianFanImageModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariables({ @EnabledIfEnvironmentVariable(named = "QIANFAN_API_KEY", matches = ".+"), - @EnabledIfEnvironmentVariable(named = "QIANFAN_SECRET_KEY", matches = ".+") }) -public class QianFanAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(QianFanAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.apiKey=" + System.getenv("QIANFAN_API_KEY"), - "spring.ai.qianfan.secretKey=" + System.getenv("QIANFAN_SECRET_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)); - - @Test - void generate() { - this.contextRunner.run(context -> { - QianFanChatModel client = context.getBean(QianFanChatModel.class); - String response = client.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void generateStreaming() { - this.contextRunner.run(context -> { - QianFanChatModel client = context.getBean(QianFanChatModel.class); - Flux responseFlux = client.stream(new Prompt(new UserMessage("Hello"))); - String response = Objects.requireNonNull(responseFlux.collectList().block()) - .stream() - .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) - .collect(Collectors.joining()); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void embedding() { - this.contextRunner.run(context -> { - QianFanEmbeddingModel embeddingClient = context.getBean(QianFanEmbeddingModel.class); - - EmbeddingResponse embeddingResponse = embeddingClient - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - - assertThat(embeddingClient.dimensions()).isEqualTo(1024); - }); - } - - @Test - void generateImage() { - this.contextRunner.withPropertyValues("spring.ai.qianfan.image.options.size=1024x1024").run(context -> { - QianFanImageModel imageModel = context.getBean(QianFanImageModel.class); - ImageResponse imageResponse = imageModel.call(new ImagePrompt("forest")); - assertThat(imageResponse.getResults()).hasSize(1); - assertThat(imageResponse.getResult().getOutput().getUrl()).isNull(); - assertThat(imageResponse.getResult().getOutput().getB64Json()).isNotEmpty(); - logger.info("Generated image: " + imageResponse.getResult().getOutput().getB64Json()); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java deleted file mode 100644 index 8d976f971..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java +++ /dev/null @@ -1,438 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.qianfan.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.qianfan.QianFanChatModel; -import org.springframework.ai.qianfan.QianFanEmbeddingModel; -import org.springframework.ai.qianfan.QianFanImageModel; -import org.springframework.ai.qianfan.api.QianFanApi; -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.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit Tests for {@link QianFanConnectionProperties}, {@link QianFanChatProperties} and - * {@link QianFanEmbeddingProperties}. - * - * @author Geng Rong - */ -public class QianFanPropertiesTests { - - @Test - public void chatProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.qianfan.base-url=TEST_BASE_URL", - "spring.ai.qianfan.api-key=abc123", - "spring.ai.qianfan.secret-key=def123", - "spring.ai.qianfan.chat.options.model=MODEL_XYZ", - "spring.ai.qianfan.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(QianFanChatProperties.class); - var connectionProperties = context.getBean(QianFanConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getSecretKey()).isEqualTo("def123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isNull(); - assertThat(chatProperties.getBaseUrl()).isNull(); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void chatOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.qianfan.base-url=TEST_BASE_URL", - "spring.ai.qianfan.api-key=abc123", - "spring.ai.qianfan.secret-key=def123", - "spring.ai.qianfan.chat.base-url=TEST_BASE_URL2", - "spring.ai.qianfan.chat.api-key=456", - "spring.ai.qianfan.chat.secret-key=def456", - "spring.ai.qianfan.chat.options.model=MODEL_XYZ", - "spring.ai.qianfan.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(QianFanChatProperties.class); - var connectionProperties = context.getBean(QianFanConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getSecretKey()).isEqualTo("def123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isEqualTo("456"); - assertThat(chatProperties.getSecretKey()).isEqualTo("def456"); - assertThat(chatProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void embeddingProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.qianfan.base-url=TEST_BASE_URL", - "spring.ai.qianfan.api-key=abc123", - "spring.ai.qianfan.secret-key=def123", - "spring.ai.qianfan.embedding.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class); - var connectionProperties = context.getBean(QianFanConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getSecretKey()).isEqualTo("def123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isNull(); - assertThat(embeddingProperties.getBaseUrl()).isNull(); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void embeddingOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.qianfan.base-url=TEST_BASE_URL", - "spring.ai.qianfan.api-key=abc123", - "spring.ai.qianfan.secret-key=def123", - "spring.ai.qianfan.embedding.base-url=TEST_BASE_URL2", - "spring.ai.qianfan.embedding.api-key=456", - "spring.ai.qianfan.embedding.secret-key=def456", - "spring.ai.qianfan.embedding.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class); - var connectionProperties = context.getBean(QianFanConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getSecretKey()).isEqualTo("def123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isEqualTo("456"); - assertThat(embeddingProperties.getSecretKey()).isEqualTo("def456"); - assertThat(embeddingProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void chatOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.qianfan.api-key=API_KEY", - "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL", - - "spring.ai.qianfan.chat.options.model=MODEL_XYZ", - "spring.ai.qianfan.chat.options.frequencyPenalty=-1.5", - "spring.ai.qianfan.chat.options.logitBias.myTokenId=-5", - "spring.ai.qianfan.chat.options.maxTokens=123", - "spring.ai.qianfan.chat.options.presencePenalty=0", - "spring.ai.qianfan.chat.options.responseFormat.type=json", - "spring.ai.qianfan.chat.options.stop=boza,koza", - "spring.ai.qianfan.chat.options.temperature=0.55", - "spring.ai.qianfan.chat.options.topP=0.56" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(QianFanChatProperties.class); - var connectionProperties = context.getBean(QianFanConnectionProperties.class); - var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - assertThat(connectionProperties.getSecretKey()).isEqualTo("SECRET_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("bge_large_zh"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5); - assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); - assertThat(chatProperties.getOptions().getPresencePenalty()).isEqualTo(0); - assertThat(chatProperties.getOptions().getResponseFormat()) - .isEqualTo(new QianFanApi.ChatCompletionRequest.ResponseFormat("json")); - assertThat(chatProperties.getOptions().getStop()).contains("boza", "koza"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56); - }); - } - - @Test - public void embeddingOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.qianfan.api-key=API_KEY", - "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL", - - "spring.ai.qianfan.embedding.options.model=MODEL_XYZ", - "spring.ai.qianfan.embedding.options.encodingFormat=MyEncodingFormat" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - var connectionProperties = context.getBean(QianFanConnectionProperties.class); - var embeddingProperties = context.getBean(QianFanEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - assertThat(connectionProperties.getSecretKey()).isEqualTo("SECRET_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - void embeddingActivation() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.embedding.enabled=false") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(QianFanEmbeddingModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(QianFanEmbeddingModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.embedding.enabled=true") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(QianFanEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(QianFanEmbeddingModel.class)).isNotEmpty(); - }); - } - - @Test - void chatActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(QianFanChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(QianFanChatModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(QianFanChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(QianFanChatModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(QianFanChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(QianFanChatModel.class)).isNotEmpty(); - }); - - } - - @Test - public void imageProperties() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.qianfan.base-url=TEST_BASE_URL", - "spring.ai.qianfan.api-key=abc123", - "spring.ai.qianfan.secret-key=def123", - "spring.ai.qianfan.image.options.model=MODEL_XYZ", - "spring.ai.qianfan.image.options.n=3") - // @formatter:on - .withConfiguration( - AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(QianFanImageProperties.class); - var connectionProperties = context.getBean(QianFanConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getSecretKey()).isEqualTo("def123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(imageProperties.getApiKey()).isNull(); - assertThat(imageProperties.getBaseUrl()).isNull(); - - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(imageProperties.getOptions().getN()).isEqualTo(3); - }); - } - - @Test - public void imageOverrideConnectionProperties() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.qianfan.base-url=TEST_BASE_URL", - "spring.ai.qianfan.api-key=abc123", - "spring.ai.qianfan.secret-key=def123", - "spring.ai.qianfan.image.base-url=TEST_BASE_URL2", - "spring.ai.qianfan.image.api-key=456", - "spring.ai.qianfan.image.secret-key=def456", - "spring.ai.qianfan.image.options.model=MODEL_XYZ", - "spring.ai.qianfan.image.options.n=3") - // @formatter:on - .withConfiguration( - AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(QianFanImageProperties.class); - var connectionProperties = context.getBean(QianFanConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getSecretKey()).isEqualTo("def123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(imageProperties.getApiKey()).isEqualTo("456"); - assertThat(imageProperties.getSecretKey()).isEqualTo("def456"); - assertThat(imageProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(imageProperties.getOptions().getN()).isEqualTo(3); - }); - } - - @Test - public void imageOptionsTest() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.qianfan.api-key=API_KEY", - "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL", - - "spring.ai.qianfan.image.options.n=3", - "spring.ai.qianfan.image.options.model=MODEL_XYZ", - "spring.ai.qianfan.image.options.size=1024x1024", - "spring.ai.qianfan.image.options.width=1024", - "spring.ai.qianfan.image.options.height=1024", - "spring.ai.qianfan.image.options.style=vivid", - "spring.ai.qianfan.image.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration( - AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(QianFanImageProperties.class); - var connectionProperties = context.getBean(QianFanConnectionProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - assertThat(connectionProperties.getSecretKey()).isEqualTo("SECRET_KEY"); - - assertThat(imageProperties.getOptions().getN()).isEqualTo(3); - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(imageProperties.getOptions().getSize()).isEqualTo("1024x1024"); - assertThat(imageProperties.getOptions().getWidth()).isEqualTo(1024); - assertThat(imageProperties.getOptions().getHeight()).isEqualTo(1024); - assertThat(imageProperties.getOptions().getStyle()).isEqualTo("vivid"); - assertThat(imageProperties.getOptions().getUser()).isEqualTo("userXYZ"); - }); - } - - @Test - void imageActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.image.enabled=false") - .withConfiguration( - AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(QianFanImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(QianFanImageModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL") - .withConfiguration( - AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(QianFanImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(QianFanImageModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY", - "spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.image.enabled=true") - .withConfiguration( - AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, - WebClientAutoConfiguration.class, QianFanAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(QianFanImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(QianFanImageModel.class)).isNotEmpty(); - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiAutoConfigurationIT.java deleted file mode 100644 index ad447cb4f..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiAutoConfigurationIT.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.stabilityai.autoconfigure; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; - -import org.springframework.ai.image.Image; -import org.springframework.ai.image.ImageGeneration; -import org.springframework.ai.image.ImageModel; -import org.springframework.ai.image.ImagePrompt; -import org.springframework.ai.image.ImageResponse; -import org.springframework.ai.stabilityai.StyleEnum; -import org.springframework.ai.stabilityai.api.StabilityAiImageOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "STABILITYAI_API_KEY", matches = ".*") -public class StabilityAiAutoConfigurationIT { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.stabilityai.image.api-key=" + System.getenv("STABILITYAI_API_KEY")) - .withConfiguration(AutoConfigurations.of(StabilityAiImageAutoConfiguration.class)); - - @Test - void generate() { - this.contextRunner.run(context -> { - ImageModel imageModel = context.getBean(ImageModel.class); - StabilityAiImageOptions imageOptions = StabilityAiImageOptions.builder() - .stylePreset(StyleEnum.PHOTOGRAPHIC) - .build(); - - var instructions = """ - A light cream colored mini golden doodle. - """; - - ImagePrompt imagePrompt = new ImagePrompt(instructions, imageOptions); - ImageResponse imageResponse = imageModel.call(imagePrompt); - - ImageGeneration imageGeneration = imageResponse.getResult(); - Image image = imageGeneration.getOutput(); - - assertThat(image.getB64Json()).isNotEmpty(); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java deleted file mode 100644 index 74e1624f9..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.stabilityai.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.stabilityai.StabilityAiImageModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @since 0.8.0 - */ -public class StabilityAiImagePropertiesTests { - - @Test - public void chatPropertiesTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.stabilityai.image.api-key=API_KEY", - "spring.ai.stabilityai.image.base-url=ENDPOINT", - "spring.ai.stabilityai.image.options.n=10", - "spring.ai.stabilityai.image.options.model=MODEL_XYZ", - "spring.ai.stabilityai.image.options.width=512", - "spring.ai.stabilityai.image.options.height=256", - "spring.ai.stabilityai.image.options.response-format=application/json", - "spring.ai.stabilityai.image.options.n=4", - "spring.ai.stabilityai.image.options.cfg-scale=7", - "spring.ai.stabilityai.image.options.clip-guidance-preset=SIMPLE", - "spring.ai.stabilityai.image.options.sampler=K_EULER", - "spring.ai.stabilityai.image.options.seed=0", - "spring.ai.stabilityai.image.options.steps=30", - "spring.ai.stabilityai.image.options.style-preset=neon-punk" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(StabilityAiImageAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(StabilityAiImageProperties.class); - - assertThat(chatProperties.getBaseUrl()).isEqualTo("ENDPOINT"); - assertThat(chatProperties.getApiKey()).isEqualTo("API_KEY"); - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - - assertThat(chatProperties.getOptions().getWidth()).isEqualTo(512); - assertThat(chatProperties.getOptions().getHeight()).isEqualTo(256); - assertThat(chatProperties.getOptions().getResponseFormat()).isEqualTo("application/json"); - assertThat(chatProperties.getOptions().getN()).isEqualTo(4); - assertThat(chatProperties.getOptions().getCfgScale()).isEqualTo(7); - assertThat(chatProperties.getOptions().getClipGuidancePreset()).isEqualTo("SIMPLE"); - assertThat(chatProperties.getOptions().getSampler()).isEqualTo("K_EULER"); - assertThat(chatProperties.getOptions().getSeed()).isEqualTo(0); - assertThat(chatProperties.getOptions().getSteps()).isEqualTo(30); - assertThat(chatProperties.getOptions().getStylePreset()).isEqualTo("neon-punk"); - }); - } - - @Test - void stabilityImageActivation() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.stabilityai.image.api-key=API_KEY", - "spring.ai.stabilityai.image.base-url=ENDPOINT", "spring.ai.stabilityai.image.enabled=false") - .withConfiguration(AutoConfigurations.of(StabilityAiImageAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(StabilityAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(StabilityAiImageModel.class)).isEmpty(); - - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.stabilityai.image.api-key=API_KEY", - "spring.ai.stabilityai.image.base-url=ENDPOINT") - .withConfiguration(AutoConfigurations.of(StabilityAiImageAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(StabilityAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(StabilityAiImageModel.class)).isNotEmpty(); - - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.stabilityai.image.api-key=API_KEY", - "spring.ai.stabilityai.image.base-url=ENDPOINT", "spring.ai.stabilityai.image.enabled=true") - .withConfiguration(AutoConfigurations.of(StabilityAiImageAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(StabilityAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(StabilityAiImageModel.class)).isNotEmpty(); - - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java deleted file mode 100644 index f67bad99b..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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.springframework.ai.model.tool.autoconfigure; - -import java.util.function.Function; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.model.tool.DefaultToolCallingManager; -import org.springframework.ai.model.tool.ToolCallingManager; -import org.springframework.ai.tool.StaticToolCallbackProvider; -import org.springframework.ai.tool.ToolCallback; -import org.springframework.ai.tool.ToolCallbackProvider; -import org.springframework.ai.tool.annotation.Tool; -import org.springframework.ai.tool.definition.ToolDefinition; -import org.springframework.ai.tool.execution.DefaultToolExecutionExceptionProcessor; -import org.springframework.ai.tool.execution.ToolExecutionExceptionProcessor; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.ai.tool.method.MethodToolCallback; -import org.springframework.ai.tool.method.MethodToolCallbackProvider; -import org.springframework.ai.tool.resolution.DelegatingToolCallbackResolver; -import org.springframework.ai.tool.resolution.ToolCallbackResolver; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; -import org.springframework.util.ReflectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit tests for {@link ToolCallingAutoConfiguration}. - * - * @author Thomas Vitale - * @author Christian Tzolov - */ -class ToolCallingAutoConfigurationTests { - - @Test - void beansAreCreated() { - new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(ToolCallingAutoConfiguration.class)) - .run(context -> { - var toolCallbackResolver = context.getBean(ToolCallbackResolver.class); - assertThat(toolCallbackResolver).isInstanceOf(DelegatingToolCallbackResolver.class); - - var toolExecutionExceptionProcessor = context.getBean(ToolExecutionExceptionProcessor.class); - assertThat(toolExecutionExceptionProcessor).isInstanceOf(DefaultToolExecutionExceptionProcessor.class); - - var toolCallingManager = context.getBean(ToolCallingManager.class); - assertThat(toolCallingManager).isInstanceOf(DefaultToolCallingManager.class); - }); - } - - @Test - void resolveMultipleFuncitonAndToolCallbacks() { - new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(ToolCallingAutoConfiguration.class)) - .withUserConfiguration(Config.class) - .run(context -> { - var toolCallbackResolver = context.getBean(ToolCallbackResolver.class); - assertThat(toolCallbackResolver).isInstanceOf(DelegatingToolCallbackResolver.class); - - assertThat(toolCallbackResolver.resolve("getForecast")).isNotNull(); - assertThat(toolCallbackResolver.resolve("getForecast").getName()).isEqualTo("getForecast"); - - assertThat(toolCallbackResolver.resolve("getAlert")).isNotNull(); - assertThat(toolCallbackResolver.resolve("getAlert").getName()).isEqualTo("getAlert"); - - assertThat(toolCallbackResolver.resolve("weatherFunction1")).isNotNull(); - assertThat(toolCallbackResolver.resolve("weatherFunction1").getName()).isEqualTo("weatherFunction1"); - - assertThat(toolCallbackResolver.resolve("getCurrentWeather3")).isNotNull(); - assertThat(toolCallbackResolver.resolve("getCurrentWeather3").getName()) - .isEqualTo("getCurrentWeather3"); - - assertThat(toolCallbackResolver.resolve("getCurrentWeather4")).isNotNull(); - assertThat(toolCallbackResolver.resolve("getCurrentWeather4").getName()) - .isEqualTo("getCurrentWeather4"); - - assertThat(toolCallbackResolver.resolve("getCurrentWeather5")).isNotNull(); - assertThat(toolCallbackResolver.resolve("getCurrentWeather5").getName()) - .isEqualTo("getCurrentWeather5"); - }); - } - - static class WeatherService { - - @Tool(description = "Get the weather in location. Return temperature in 36°F or 36°C format.") - public String getForecast(String location) { - return "30"; - } - - @Tool(description = "Get the weather in location. Return temperature in 36°F or 36°C format.") - public String getForecast2(String location) { - return "30"; - } - - public String getAlert(String usState) { - return "Alert"; - } - - } - - @Configuration - static class Config { - - // Note: Currently we do not have ToolCallbackResolver implementation that can - // resolve the ToolCallback from the Tool annotation. - // Therefore we need to provide the ToolCallback instances explicitly using the - // ToolCallbacks.from(...) utility method. - @Bean - public ToolCallbackProvider toolCallbacks() { - return MethodToolCallbackProvider.builder().toolObjects(new WeatherService()).build(); - } - - public record Request(String location) { - } - - public record Response(String temperature) { - } - - @Bean - @Description("Get the weather in location. Return temperature in 36°F or 36°C format.") - public Function weatherFunction1() { - return request -> new Response("30"); - } - - @Bean - public FunctionCallback functionCallbacks3() { - return FunctionCallback.builder() - .function("getCurrentWeather3", (Request request) -> "15.0°C") - .description("Gets the weather in location") - .inputType(Request.class) - .build(); - } - - @Bean - public FunctionCallback functionCallbacks4() { - return FunctionCallback.builder() - .function("getCurrentWeather4", (Request request) -> "15.0°C") - .description("Gets the weather in location") - .inputType(Request.class) - .build(); - - } - - @Bean - public ToolCallback toolCallbacks5() { - return FunctionToolCallback.builder("getCurrentWeather5", (Request request) -> "15.0°C") - .description("Gets the weather in location") - .inputType(Request.class) - .build(); - - } - - @Bean - public ToolCallbackProvider blabla() { - return new StaticToolCallbackProvider( - FunctionToolCallback.builder("getCurrentWeather5", (Request request) -> "15.0°C") - .description("Gets the weather in location") - .inputType(Request.class) - .build()); - - } - - @Bean - public ToolCallback toolCallbacks6() { - var toolMethod = ReflectionUtils.findMethod(WeatherService.class, "getAlert", String.class); - return MethodToolCallback.builder() - .toolDefinition(ToolDefinition.builder(toolMethod).build()) - .toolMethod(toolMethod) - .toolObject(new WeatherService()) - .build(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfigurationIT.java deleted file mode 100644 index 50b60c35d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfigurationIT.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.transformers.autoconfigure; - -import java.io.File; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.transformers.TransformersEmbeddingModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -public class TransformersEmbeddingModelAutoConfigurationIT { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(TransformersEmbeddingModelAutoConfiguration.class)); - - @TempDir - File tempDir; - - @Test - public void embedding() { - this.contextRunner.run(context -> { - var properties = context.getBean(TransformersEmbeddingModelProperties.class); - assertThat(properties.getCache().isEnabled()).isTrue(); - assertThat(properties.getCache().getDirectory()).isEqualTo( - new File(System.getProperty("java.io.tmpdir"), "spring-ai-onnx-generative").getAbsolutePath()); - - EmbeddingModel embeddingModel = context.getBean(EmbeddingModel.class); - assertThat(embeddingModel).isInstanceOf(TransformersEmbeddingModel.class); - - List embeddings = embeddingModel.embed(List.of("Spring Framework", "Spring AI")); - - assertThat(embeddings.size()).isEqualTo(2); // batch size - assertThat(embeddings.get(0).length).isEqualTo(embeddingModel.dimensions()); // dimensions - // size - }); - } - - @Test - public void remoteOnnxModel() { - // https://huggingface.co/intfloat/e5-small-v2 - this.contextRunner.withPropertyValues( - "spring.ai.embedding.transformer.cache.directory=" + this.tempDir.getAbsolutePath(), - "spring.ai.embedding.transformer.onnx.modelUri=https://huggingface.co/intfloat/e5-small-v2/resolve/main/model.onnx", - "spring.ai.embedding.transformer.tokenizer.uri=https://huggingface.co/intfloat/e5-small-v2/raw/main/tokenizer.json") - .run(context -> { - var properties = context.getBean(TransformersEmbeddingModelProperties.class); - assertThat(properties.getOnnx().getModelUri()) - .isEqualTo("https://huggingface.co/intfloat/e5-small-v2/resolve/main/model.onnx"); - assertThat(properties.getTokenizer().getUri()) - .isEqualTo("https://huggingface.co/intfloat/e5-small-v2/raw/main/tokenizer.json"); - - assertThat(properties.getCache().isEnabled()).isTrue(); - assertThat(properties.getCache().getDirectory()).isEqualTo(this.tempDir.getAbsolutePath()); - assertThat(this.tempDir.listFiles()).hasSize(2); - - EmbeddingModel embeddingModel = context.getBean(EmbeddingModel.class); - assertThat(embeddingModel).isInstanceOf(TransformersEmbeddingModel.class); - - assertThat(embeddingModel.dimensions()).isEqualTo(384); - - List embeddings = embeddingModel.embed(List.of("Spring Framework", "Spring AI")); - - assertThat(embeddings.size()).isEqualTo(2); // batch size - assertThat(embeddings.get(0).length).isEqualTo(embeddingModel.dimensions()); // dimensions - // size - }); - } - - @Test - void embeddingActivation() { - this.contextRunner.withPropertyValues("spring.ai.embedding.transformer.enabled=false").run(context -> { - assertThat(context.getBeansOfType(TransformersEmbeddingModelProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(TransformersEmbeddingModel.class)).isEmpty(); - }); - - this.contextRunner.withPropertyValues("spring.ai.embedding.transformer.enabled=true").run(context -> { - assertThat(context.getBeansOfType(TransformersEmbeddingModelProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(TransformersEmbeddingModel.class)).isNotEmpty(); - }); - - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(TransformersEmbeddingModelProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(TransformersEmbeddingModel.class)).isNotEmpty(); - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingModelAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingModelAutoConfigurationIT.java deleted file mode 100644 index c1ed7eaf3..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiTextEmbeddingModelAutoConfigurationIT.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.embedding; - -import java.io.File; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.ai.document.Document; -import org.springframework.ai.embedding.DocumentEmbeddingRequest; -import org.springframework.ai.embedding.EmbeddingOptionsBuilder; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.ai.embedding.EmbeddingResultMetadata; -import org.springframework.ai.vertexai.embedding.multimodal.VertexAiMultimodalEmbeddingModel; -import org.springframework.ai.vertexai.embedding.text.VertexAiTextEmbeddingModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_LOCATION", matches = ".*") -public class VertexAiTextEmbeddingModelAutoConfigurationIT { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.vertex.ai.embedding.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), - "spring.ai.vertex.ai.embedding.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - .withConfiguration(AutoConfigurations.of(VertexAiEmbeddingAutoConfiguration.class)); - - @TempDir - File tempDir; - - @Test - public void textEmbedding() { - this.contextRunner.run(context -> { - var conntectionProperties = context.getBean(VertexAiEmbeddingConnectionProperties.class); - var textEmbeddingProperties = context.getBean(VertexAiTextEmbeddingProperties.class); - - assertThat(conntectionProperties).isNotNull(); - assertThat(textEmbeddingProperties.isEnabled()).isTrue(); - - VertexAiTextEmbeddingModel embeddingModel = context.getBean(VertexAiTextEmbeddingModel.class); - assertThat(embeddingModel).isInstanceOf(VertexAiTextEmbeddingModel.class); - - List embeddings = embeddingModel.embed(List.of("Spring Framework", "Spring AI")); - - assertThat(embeddings.size()).isEqualTo(2); // batch size - assertThat(embeddings.get(0).length).isEqualTo(embeddingModel.dimensions()); - }); - } - - @Test - void textEmbeddingActivation() { - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.embedding.text.enabled=false").run(context -> { - assertThat(context.getBeansOfType(VertexAiTextEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiTextEmbeddingModel.class)).isEmpty(); - }); - - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.embedding.text.enabled=true").run(context -> { - assertThat(context.getBeansOfType(VertexAiTextEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiTextEmbeddingModel.class)).isNotEmpty(); - }); - - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(VertexAiTextEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiTextEmbeddingModel.class)).isNotEmpty(); - }); - - } - - @Test - public void multimodalEmbedding() { - this.contextRunner.run(context -> { - var conntectionProperties = context.getBean(VertexAiEmbeddingConnectionProperties.class); - var multimodalEmbeddingProperties = context.getBean(VertexAiMultimodalEmbeddingProperties.class); - - assertThat(conntectionProperties).isNotNull(); - assertThat(multimodalEmbeddingProperties.isEnabled()).isTrue(); - - VertexAiMultimodalEmbeddingModel multiModelEmbeddingModel = context - .getBean(VertexAiMultimodalEmbeddingModel.class); - - assertThat(multiModelEmbeddingModel).isNotNull(); - - var document = new Document("Hello World"); - - DocumentEmbeddingRequest embeddingRequest = new DocumentEmbeddingRequest(List.of(document), - EmbeddingOptionsBuilder.builder().build()); - - EmbeddingResponse embeddingResponse = multiModelEmbeddingModel.call(embeddingRequest); - assertThat(embeddingResponse.getResults()).hasSize(1); - assertThat(embeddingResponse.getResults().get(0)).isNotNull(); - assertThat(embeddingResponse.getResults().get(0).getMetadata().getModalityType()) - .isEqualTo(EmbeddingResultMetadata.ModalityType.TEXT); - assertThat(embeddingResponse.getResults().get(0).getOutput()).hasSize(1408); - - assertThat(embeddingResponse.getMetadata().getModel()).isEqualTo("multimodalembedding@001"); - assertThat(embeddingResponse.getMetadata().getUsage().getPromptTokens()).isEqualTo(0); - - assertThat(multiModelEmbeddingModel.dimensions()).isEqualTo(1408); - - }); - } - - @Test - void multimodalEmbeddingActivation() { - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.embedding.multimodal.enabled=false").run(context -> { - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingModel.class)).isEmpty(); - }); - - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.embedding.multimodal.enabled=true").run(context -> { - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingModel.class)).isNotEmpty(); - }); - - this.contextRunner.run(context -> { - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(VertexAiMultimodalEmbeddingModel.class)).isNotEmpty(); - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionBeanIT.java deleted file mode 100644 index 364c46635..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionBeanIT.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.gemini.tool; - -import java.util.List; -import java.util.Set; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_LOCATION", matches = ".*") -class FunctionCallWithFunctionBeanIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionBeanIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.vertex.ai.gemini.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), - "spring.ai.vertex.ai.gemini.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - - .withConfiguration(AutoConfigurations.of(VertexAiGeminiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.gemini.chat.options.model=" - // + VertexAiGeminiChatModel.ChatModel.GEMINI_PRO_1_5_PRO.getValue()) - + VertexAiGeminiChatModel.ChatModel.GEMINI_2_0_FLASH.getValue()) - .run(context -> { - - VertexAiGeminiChatModel chatModel = context.getBean(VertexAiGeminiChatModel.class); - - var userMessage = new UserMessage(""" - What's the weather like in San Francisco, Paris and in Tokyo? - Return the temperature in Celsius. - Perform multiple funciton execution if necessary. - """); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - VertexAiGeminiChatOptions.builder().function("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - response = chatModel.call(new Prompt(List.of(userMessage), - VertexAiGeminiChatOptions.builder().function("weatherFunction3").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - response = chatModel - .call(new Prompt(List.of(userMessage), VertexAiGeminiChatOptions.builder().build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).doesNotContain("30", "10", "15"); - - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - - this.contextRunner.withPropertyValues("spring.ai.vertex.ai.gemini.chat.options.model=" - // + VertexAiGeminiChatModel.ChatModel.GEMINI_PRO_1_5_PRO.getValue()) - + VertexAiGeminiChatModel.ChatModel.GEMINI_2_0_FLASH.getValue()) - .run(context -> { - - VertexAiGeminiChatModel chatModel = context.getBean(VertexAiGeminiChatModel.class); - - var userMessage = new UserMessage(""" - What's the weather like in San Francisco, Paris and in Tokyo? - Return the temperature in Celsius. - Perform multiple funciton execution if necessary. - """); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - ToolCallingChatOptions.builder().toolNames("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - response = chatModel.call(new Prompt(List.of(userMessage), - VertexAiGeminiChatOptions.builder().toolNames(Set.of("weatherFunction3")).build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Configuration - static class Config { - - @Bean - @Description("Get the weather in location") - public Function weatherFunction() { - return new MockWeatherService(); - } - - // Relies on the Request's JsonClassDescription annotation to provide the - // function description. - @Bean - public Function weatherFunction3() { - MockWeatherService weatherService = new MockWeatherService(); - return (weatherService::apply); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionWrapperIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionWrapperIT.java deleted file mode 100644 index 32cc29327..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithFunctionWrapperIT.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.gemini.tool; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.tool.ToolCallback; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_LOCATION", matches = ".*") -public class FunctionCallWithFunctionWrapperIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionWrapperIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.vertex.ai.gemini.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), - "spring.ai.vertex.ai.gemini.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - .withConfiguration(AutoConfigurations.of(VertexAiGeminiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner - .withPropertyValues("spring.ai.vertex.ai.gemini.chat.options.model=" - + VertexAiGeminiChatModel.ChatModel.GEMINI_2_0_FLASH.getValue()) - .run(context -> { - - VertexAiGeminiChatModel chatModel = context.getBean(VertexAiGeminiChatModel.class); - - var userMessage = new UserMessage(""" - What's the weather like in San Francisco, Paris and in Tokyo? - Return the temperature in Celsius. - """); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), - VertexAiGeminiChatOptions.builder().toolName("WeatherInfo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Configuration - static class Config { - - @Bean - public ToolCallback weatherFunctionInfo() { - - return FunctionToolCallback.builder("WeatherInfo", new MockWeatherService()) - .description("Get the current weather in a given location") - .inputType(MockWeatherService.Request.class) - .build(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithPromptFunctionIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithPromptFunctionIT.java deleted file mode 100644 index 3d9d04a86..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/FunctionCallWithPromptFunctionIT.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.gemini.tool; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.ai.model.vertexai.autoconfigure.gemini.VertexAiGeminiAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.tool.function.FunctionToolCallback; -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatModel; -import org.springframework.ai.vertexai.gemini.VertexAiGeminiChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_PROJECT_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "VERTEX_AI_GEMINI_LOCATION", matches = ".*") -public class FunctionCallWithPromptFunctionIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallWithPromptFunctionIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.vertex.ai.gemini.project-id=" + System.getenv("VERTEX_AI_GEMINI_PROJECT_ID"), - "spring.ai.vertex.ai.gemini.location=" + System.getenv("VERTEX_AI_GEMINI_LOCATION")) - .withConfiguration(AutoConfigurations.of(VertexAiGeminiAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner - .withPropertyValues("spring.ai.vertex.ai.gemini.chat.options.model=" - + VertexAiGeminiChatModel.ChatModel.GEMINI_2_0_FLASH_LIGHT.getValue()) - .run(context -> { - - VertexAiGeminiChatModel chatModel = context.getBean(VertexAiGeminiChatModel.class); - - // var systemMessage = new SystemMessage(""" - // Use Multi-turn function calling. - // Answer for all listed locations. - // If the information was not fetched call the function again. Repeat at - // most 3 times. - // """); - var userMessage = new UserMessage(""" - What's the weather like in San Francisco, Paris and in Tokyo? - Return the temperature in Celsius. - """); - - var promptOptions = VertexAiGeminiChatOptions.builder() - .toolCallbacks( - List.of(FunctionToolCallback.builder("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - // Verify that no function call is made. - response = chatModel - .call(new Prompt(List.of(userMessage), VertexAiGeminiChatOptions.builder().build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).doesNotContain("30", "10", "15"); - - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/MockWeatherService.java deleted file mode 100644 index af856fdd0..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/tool/MockWeatherService.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.vertexai.autoconfigure.gemini.tool; - -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - -/** - * Mock 3rd party weather service. - * - * @author Christian Tzolov - */ -@JsonClassDescription("Get the weather in location") -public class MockWeatherService implements Function { - - @Override - public Response apply(Request request) { - - double temperature = 0; - if (request.location().contains("Paris")) { - temperature = 15; - } - else if (request.location().contains("Tokyo")) { - temperature = 10; - } - else if (request.location().contains("San Francisco")) { - temperature = 30; - } - - return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); - } - - /** - * Temperature units. - */ - public enum Unit { - - /** - * Celsius. - */ - C("metric"), - /** - * Fahrenheit. - */ - F("imperial"); - - /** - * Human readable unit name. - */ - public final String unitName; - - Unit(String text) { - this.unitName = text; - } - - } - - /** - * Weather Function request. - */ - @JsonInclude(Include.NON_NULL) - @JsonClassDescription("Weather API request") - public record Request(@JsonProperty(required = true, - value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, - @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { - - } - - /** - * Weather Function response. - */ - public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity, - Unit unit) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfigurationTests.java deleted file mode 100644 index 86f6fca45..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfigurationTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.watsonxai.autoconfigure; - -import org.junit.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -public class WatsonxAiAutoConfigurationTests { - - @Test - public void propertiesTest() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.watsonx.ai.base-url=TEST_BASE_URL", - "spring.ai.watsonx.ai.stream-endpoint=ml/v1/text/generation_stream?version=2023-05-29", - "spring.ai.watsonx.ai.text-endpoint=ml/v1/text/generation?version=2023-05-29", - "spring.ai.watsonx.ai.embedding-endpoint=ml/v1/text/embeddings?version=2023-05-29", - "spring.ai.watsonx.ai.projectId=1", - "spring.ai.watsonx.ai.IAMToken=123456") - // @formatter:on - .withConfiguration( - AutoConfigurations.of(RestClientAutoConfiguration.class, WatsonxAiAutoConfiguration.class)) - .run(context -> { - var connectionProperties = context.getBean(WatsonxAiConnectionProperties.class); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getStreamEndpoint()) - .isEqualTo("ml/v1/text/generation_stream?version=2023-05-29"); - assertThat(connectionProperties.getTextEndpoint()) - .isEqualTo("ml/v1/text/generation?version=2023-05-29"); - assertThat(connectionProperties.getEmbeddingEndpoint()) - .isEqualTo("ml/v1/text/embeddings?version=2023-05-29"); - assertThat(connectionProperties.getProjectId()).isEqualTo("1"); - assertThat(connectionProperties.getIAMToken()).isEqualTo("123456"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfigurationIT.java deleted file mode 100644 index d36e0a691..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfigurationIT.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure; - -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.embedding.EmbeddingResponse; -import org.springframework.ai.image.ImagePrompt; -import org.springframework.ai.image.ImageResponse; -import org.springframework.ai.zhipuai.ZhiPuAiChatModel; -import org.springframework.ai.zhipuai.ZhiPuAiEmbeddingModel; -import org.springframework.ai.zhipuai.ZhiPuAiImageModel; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariable(named = "ZHIPU_AI_API_KEY", matches = ".*") -public class ZhiPuAiAutoConfigurationIT { - - private static final Log logger = LogFactory.getLog(ZhiPuAiAutoConfigurationIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.apiKey=" + System.getenv("ZHIPU_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)); - - @Test - void generate() { - this.contextRunner.run(context -> { - ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); - String response = chatModel.call("Hello"); - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void generateStreaming() { - this.contextRunner.run(context -> { - ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); - Flux responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello"))); - String response = responseFlux.collectList() - .block() - .stream() - .map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText()) - .collect(Collectors.joining()); - - assertThat(response).isNotEmpty(); - logger.info("Response: " + response); - }); - } - - @Test - void embedding() { - this.contextRunner.run(context -> { - ZhiPuAiEmbeddingModel embeddingModel = context.getBean(ZhiPuAiEmbeddingModel.class); - - EmbeddingResponse embeddingResponse = embeddingModel - .embedForResponse(List.of("Hello World", "World is big and salvation is near")); - assertThat(embeddingResponse.getResults()).hasSize(2); - assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0); - assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty(); - assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1); - - assertThat(embeddingModel.dimensions()).isEqualTo(1024); - }); - } - - @Test - void generateImage() { - this.contextRunner.withPropertyValues("spring.ai.zhipuai.image.options.size=1024x1024").run(context -> { - ZhiPuAiImageModel ImageModel = context.getBean(ZhiPuAiImageModel.class); - ImageResponse imageResponse = ImageModel.call(new ImagePrompt("forest")); - assertThat(imageResponse.getResults()).hasSize(1); - assertThat(imageResponse.getResult().getOutput().getUrl()).isNotEmpty(); - logger.info("Generated image: " + imageResponse.getResult().getOutput().getUrl()); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java deleted file mode 100644 index ec1b9387a..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure; - -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.ai.zhipuai.ZhiPuAiChatModel; -import org.springframework.ai.zhipuai.ZhiPuAiEmbeddingModel; -import org.springframework.ai.zhipuai.ZhiPuAiImageModel; -import org.springframework.ai.zhipuai.api.ZhiPuAiApi; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit Tests for {@link ZhiPuAiConnectionProperties}, {@link ZhiPuAiChatProperties} and - * {@link ZhiPuAiEmbeddingProperties}. - * - * @author Geng Rong - */ -public class ZhiPuAiPropertiesTests { - - @Test - public void chatProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.api-key=abc123", - "spring.ai.zhipuai.chat.options.model=MODEL_XYZ", - "spring.ai.zhipuai.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(ZhiPuAiChatProperties.class); - var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isNull(); - assertThat(chatProperties.getBaseUrl()).isNull(); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void chatOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.api-key=abc123", - "spring.ai.zhipuai.chat.base-url=TEST_BASE_URL2", - "spring.ai.zhipuai.chat.api-key=456", - "spring.ai.zhipuai.chat.options.model=MODEL_XYZ", - "spring.ai.zhipuai.chat.options.temperature=0.55") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(ZhiPuAiChatProperties.class); - var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(chatProperties.getApiKey()).isEqualTo("456"); - assertThat(chatProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - }); - } - - @Test - public void embeddingProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.api-key=abc123", - "spring.ai.zhipuai.embedding.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(ZhiPuAiEmbeddingProperties.class); - var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isNull(); - assertThat(embeddingProperties.getBaseUrl()).isNull(); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void embeddingOverrideConnectionProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.api-key=abc123", - "spring.ai.zhipuai.embedding.base-url=TEST_BASE_URL2", - "spring.ai.zhipuai.embedding.api-key=456", - "spring.ai.zhipuai.embedding.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - var embeddingProperties = context.getBean(ZhiPuAiEmbeddingProperties.class); - var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(embeddingProperties.getApiKey()).isEqualTo("456"); - assertThat(embeddingProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void imageProperties() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.api-key=abc123", - "spring.ai.zhipuai.image.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(ZhiPuAiImageProperties.class); - var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(imageProperties.getApiKey()).isNull(); - assertThat(imageProperties.getBaseUrl()).isNull(); - - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void imageOverrideConnectionProperties() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.api-key=abc123", - "spring.ai.zhipuai.image.base-url=TEST_BASE_URL2", - "spring.ai.zhipuai.image.api-key=456", - "spring.ai.zhipuai.image.options.model=MODEL_XYZ") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(ZhiPuAiImageProperties.class); - var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - - assertThat(connectionProperties.getApiKey()).isEqualTo("abc123"); - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - - assertThat(imageProperties.getApiKey()).isEqualTo("456"); - assertThat(imageProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL2"); - - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void chatOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.zhipuai.api-key=API_KEY", - "spring.ai.zhipuai.base-url=TEST_BASE_URL", - - "spring.ai.zhipuai.chat.options.model=MODEL_XYZ", - "spring.ai.zhipuai.chat.options.maxTokens=123", - "spring.ai.zhipuai.chat.options.stop=boza,koza", - "spring.ai.zhipuai.chat.options.temperature=0.55", - "spring.ai.zhipuai.chat.options.topP=0.56", - "spring.ai.zhipuai.chat.options.requestId=RequestId", - "spring.ai.zhipuai.chat.options.doSample=true", - - // "spring.ai.zhipuai.chat.options.toolChoice.functionName=toolChoiceFunctionName", - "spring.ai.zhipuai.chat.options.toolChoice=" + ModelOptionsUtils.toJsonString(ZhiPuAiApi.ChatCompletionRequest.ToolChoiceBuilder.function("toolChoiceFunctionName")), - - "spring.ai.zhipuai.chat.options.tools[0].function.name=myFunction1", - "spring.ai.zhipuai.chat.options.tools[0].function.description=function description", - "spring.ai.zhipuai.chat.options.tools[0].function.jsonSchema=" + """ - { - "type": "object", - "properties": { - "location": { - "type": "string", - "description": "The city and state e.g. San Francisco, CA" - }, - "lat": { - "type": "number", - "description": "The city latitude" - }, - "lon": { - "type": "number", - "description": "The city longitude" - }, - "unit": { - "type": "string", - "enum": ["c", "f"] - } - }, - "required": ["location", "lat", "lon", "unit"] - } - """, - "spring.ai.zhipuai.chat.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - var chatProperties = context.getBean(ZhiPuAiChatProperties.class); - var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - var embeddingProperties = context.getBean(ZhiPuAiEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("Embedding-2"); - - assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); - assertThat(chatProperties.getOptions().getStop()).contains("boza", "koza"); - assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55); - assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.56); - assertThat(chatProperties.getOptions().getRequestId()).isEqualTo("RequestId"); - assertThat(chatProperties.getOptions().getDoSample()).isEqualTo(Boolean.TRUE); - - JSONAssert.assertEquals("{\"type\":\"function\",\"function\":{\"name\":\"toolChoiceFunctionName\"}}", - chatProperties.getOptions().getToolChoice(), JSONCompareMode.LENIENT); - - assertThat(chatProperties.getOptions().getUser()).isEqualTo("userXYZ"); - - assertThat(chatProperties.getOptions().getTools()).hasSize(1); - var tool = chatProperties.getOptions().getTools().get(0); - assertThat(tool.getType()).isEqualTo(ZhiPuAiApi.FunctionTool.Type.FUNCTION); - var function = tool.getFunction(); - assertThat(function.getName()).isEqualTo("myFunction1"); - assertThat(function.getDescription()).isEqualTo("function description"); - assertThat(function.getParameters()).isNotEmpty(); - }); - } - - @Test - public void embeddingOptionsTest() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.zhipuai.api-key=API_KEY", - "spring.ai.zhipuai.base-url=TEST_BASE_URL", - - "spring.ai.zhipuai.embedding.options.model=MODEL_XYZ", - "spring.ai.zhipuai.embedding.options.encodingFormat=MyEncodingFormat", - "spring.ai.zhipuai.embedding.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - var embeddingProperties = context.getBean(ZhiPuAiEmbeddingProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - - assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - }); - } - - @Test - public void imageOptionsTest() { - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.zhipuai.api-key=API_KEY", - "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.image.options.model=MODEL_XYZ", - "spring.ai.zhipuai.image.options.user=userXYZ" - ) - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - var imageProperties = context.getBean(ZhiPuAiImageProperties.class); - var connectionProperties = context.getBean(ZhiPuAiConnectionProperties.class); - - assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL"); - assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY"); - assertThat(imageProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ"); - assertThat(imageProperties.getOptions().getUser()).isEqualTo("userXYZ"); - }); - } - - @Test - void embeddingActivation() { - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.embedding.enabled=false") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(ZhiPuAiEmbeddingModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(ZhiPuAiEmbeddingModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.embedding.enabled=true") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiEmbeddingProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(ZhiPuAiEmbeddingModel.class)).isNotEmpty(); - }); - } - - @Test - void chatActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(ZhiPuAiChatModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(ZhiPuAiChatModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiChatProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(ZhiPuAiChatModel.class)).isNotEmpty(); - }); - - } - - @Test - void imageActivation() { - new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.image.enabled=false") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(ZhiPuAiImageModel.class)).isEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(ZhiPuAiImageModel.class)).isNotEmpty(); - }); - - new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL", - "spring.ai.zhipuai.image.enabled=true") - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .run(context -> { - assertThat(context.getBeansOfType(ZhiPuAiImageProperties.class)).isNotEmpty(); - assertThat(context.getBeansOfType(ZhiPuAiImageModel.class)).isNotEmpty(); - }); - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackInPromptIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackInPromptIT.java deleted file mode 100644 index 12dc83d6d..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackInPromptIT.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure.tool; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.zhipuai.ZhiPuAiChatModel; -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariable(named = "ZHIPU_AI_API_KEY", matches = ".*") -public class FunctionCallbackInPromptIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallbackInPromptIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.apiKey=" + System.getenv("ZHIPU_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)); - - @Test - void functionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.zhipuai.chat.options.model=glm-4").run(context -> { - - ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - var promptOptions = ZhiPuAiChatOptions.builder() - .functionCallbacks(List.of(FunctionCallback.builder() - .function("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - // .responseConverter(response -> "" + response.temp() + - // response.unit()) - .build())) - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - }); - } - - @Test - void streamingFunctionCallTest() { - - this.contextRunner.withPropertyValues("spring.ai.zhipuai.chat.options.model=glm-4").run(context -> { - - ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - var promptOptions = ZhiPuAiChatOptions.builder() - .functionCallbacks(List.of(FunctionCallback.builder() - .function("CurrentWeatherService", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - .build())) - .build(); - - Flux response = chatModel.stream(new Prompt(List.of(userMessage), promptOptions)); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java deleted file mode 100644 index df21b42ec..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure.tool; - -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.tool.ToolCallingChatOptions; -import org.springframework.ai.zhipuai.ZhiPuAiChatModel; -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Description; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariable(named = "ZHIPU_AI_API_KEY", matches = ".*") -class FunctionCallbackWithPlainFunctionBeanIT { - - private final Logger logger = LoggerFactory.getLogger(FunctionCallbackWithPlainFunctionBeanIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.apiKey=" + System.getenv("ZHIPU_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.zhipuai.chat.options.model=glm-4").run(context -> { - - ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - ChatResponse response = chatModel.call( - new Prompt(List.of(userMessage), ZhiPuAiChatOptions.builder().function("weatherFunction").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - // Test weatherFunctionTwo - response = chatModel.call(new Prompt(List.of(userMessage), - ZhiPuAiChatOptions.builder().function("weatherFunctionTwo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void functionCallWithPortableFunctionCallingOptions() { - this.contextRunner.withPropertyValues("spring.ai.zhipuai.chat.options.model=glm-4").run(context -> { - - ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - ToolCallingChatOptions functionOptions = ToolCallingChatOptions.builder() - .toolNames("weatherFunction") - .build(); - - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), functionOptions)); - - logger.info("Response: {}", response); - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.zhipuai.chat.options.model=glm-4").run(context -> { - - ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); - - // Test weatherFunction - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - Flux response = chatModel.stream( - new Prompt(List.of(userMessage), ZhiPuAiChatOptions.builder().function("weatherFunction").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - - // Test weatherFunctionTwo - response = chatModel.stream(new Prompt(List.of(userMessage), - ZhiPuAiChatOptions.builder().function("weatherFunctionTwo").build())); - - content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - }); - } - - @Configuration - static class Config { - - @Bean - @Description("Get the weather in location") - public Function weatherFunction() { - return new MockWeatherService(); - } - - // Relies on the Request's JsonClassDescription annotation to provide the - // function description. - @Bean - public Function weatherFunctionTwo() { - MockWeatherService weatherService = new MockWeatherService(); - return (weatherService::apply); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/MockWeatherService.java deleted file mode 100644 index 9d3e97312..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/MockWeatherService.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure.tool; - -import java.util.function.Function; - -import com.fasterxml.jackson.annotation.JsonClassDescription; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonPropertyDescription; - -/** - * Mock 3rd party weather service. - * - * @author Geng Rong - */ -public class MockWeatherService implements Function { - - @Override - public Response apply(Request request) { - - double temperature = 0; - if (request.location().contains("Paris")) { - temperature = 15; - } - else if (request.location().contains("Tokyo")) { - temperature = 10; - } - else if (request.location().contains("San Francisco")) { - temperature = 30; - } - - return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); - } - - /** - * Temperature units. - */ - public enum Unit { - - /** - * Celsius. - */ - C("metric"), - /** - * Fahrenheit. - */ - F("imperial"); - - /** - * Human readable unit name. - */ - public final String unitName; - - Unit(String text) { - this.unitName = text; - } - - } - - /** - * Weather Function request. - */ - @JsonInclude(Include.NON_NULL) - @JsonClassDescription("Weather API request") - public record Request(@JsonProperty(required = true, - value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, - @JsonProperty(required = true, value = "lat") @JsonPropertyDescription("The city latitude") double lat, - @JsonProperty(required = true, value = "lon") @JsonPropertyDescription("The city longitude") double lon, - @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { - - } - - /** - * Weather Function response. - */ - public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity, - Unit unit) { - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/ZhipuAiFunctionCallbackIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/ZhipuAiFunctionCallbackIT.java deleted file mode 100644 index b7e636592..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/tool/ZhipuAiFunctionCallbackIT.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.model.zhipuai.autoconfigure.tool; - -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; - -import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration; -import org.springframework.ai.model.zhipuai.autoconfigure.ZhiPuAiAutoConfiguration; -import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.UserMessage; -import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.function.FunctionCallback; -import org.springframework.ai.zhipuai.ZhiPuAiChatModel; -import org.springframework.ai.zhipuai.ZhiPuAiChatOptions; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Geng Rong - */ -@EnabledIfEnvironmentVariable(named = "ZHIPU_AI_API_KEY", matches = ".*") -public class ZhipuAiFunctionCallbackIT { - - private final Logger logger = LoggerFactory.getLogger(ZhipuAiFunctionCallbackIT.class); - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.zhipuai.apiKey=" + System.getenv("ZHIPU_AI_API_KEY")) - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class)) - .withUserConfiguration(Config.class); - - @Test - void functionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.zhipuai.chat.options.model=glm-4").run(context -> { - - ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - ChatResponse response = chatModel - .call(new Prompt(List.of(userMessage), ZhiPuAiChatOptions.builder().function("WeatherInfo").build())); - - logger.info("Response: {}", response); - - assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15"); - - }); - } - - @Test - void streamFunctionCallTest() { - this.contextRunner.withPropertyValues("spring.ai.zhipuai.chat.options.model=glm-4").run(context -> { - - ZhiPuAiChatModel chatModel = context.getBean(ZhiPuAiChatModel.class); - - UserMessage userMessage = new UserMessage( - "What's the weather like in San Francisco, Tokyo, and Paris? Return the temperature in Celsius."); - - Flux response = chatModel - .stream(new Prompt(List.of(userMessage), ZhiPuAiChatOptions.builder().function("WeatherInfo").build())); - - String content = response.collectList() - .block() - .stream() - .map(ChatResponse::getResults) - .flatMap(List::stream) - .map(Generation::getOutput) - .map(AssistantMessage::getText) - .collect(Collectors.joining()); - logger.info("Response: {}", content); - - assertThat(content).containsAnyOf("30.0", "30"); - assertThat(content).containsAnyOf("10.0", "10"); - assertThat(content).containsAnyOf("15.0", "15"); - - }); - } - - @Configuration - static class Config { - - @Bean - public FunctionCallback weatherFunctionInfo() { - - return FunctionCallback.builder() - .function("WeatherInfo", new MockWeatherService()) - .description("Get the weather in location") - .inputType(MockWeatherService.Request.class) - // .responseConverter(response -> "" + response.temp() + response.unit()) - .build(); - } - - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfigurationIT.java deleted file mode 100644 index 4b712f050..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryAutoConfigurationIT.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.retry.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.web.client.ResponseErrorHandler; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -public class SpringAiRetryAutoConfigurationIT { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withConfiguration( - AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class)); - - @Test - void testRetryAutoConfiguration() { - this.contextRunner.run(context -> { - assertThat(context).hasSingleBean(RetryTemplate.class); - assertThat(context).hasSingleBean(ResponseErrorHandler.class); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryPropertiesTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryPropertiesTests.java deleted file mode 100644 index 64cefc6c2..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/retry/autoconfigure/SpringAiRetryPropertiesTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2023-2024 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.springframework.ai.retry.autoconfigure; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Unit Tests for {@link SpringAiRetryProperties}. - * - * @author Christian Tzolov - */ -public class SpringAiRetryPropertiesTests { - - @Test - public void retryDefaultProperties() { - - new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class)) - .run(context -> { - var retryProperties = context.getBean(SpringAiRetryProperties.class); - - assertThat(retryProperties.getMaxAttempts()).isEqualTo(10); - // do not retry on 4xx errors - assertThat(retryProperties.isOnClientErrors()).isFalse(); - assertThat(retryProperties.getExcludeOnHttpCodes()).isEmpty(); - assertThat(retryProperties.getOnHttpCodes()).isEmpty(); - assertThat(retryProperties.getBackoff().getInitialInterval().toMillis()).isEqualTo(2000); - assertThat(retryProperties.getBackoff().getMultiplier()).isEqualTo(5); - assertThat(retryProperties.getBackoff().getMaxInterval().toMillis()).isEqualTo(3 * 60000); - }); - } - - @Test - public void retryCustomProperties() { - - new ApplicationContextRunner().withPropertyValues( - // @formatter:off - "spring.ai.retry.max-attempts=100", - "spring.ai.retry.on-client-errors=false", - "spring.ai.retry.exclude-on-http-codes=404,500", - "spring.ai.retry.on-http-codes=429", - "spring.ai.retry.backoff.initial-interval=1000", - "spring.ai.retry.backoff.multiplier=2", - "spring.ai.retry.backoff.max-interval=60000") - // @formatter:on - .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class)) - .run(context -> { - var retryProperties = context.getBean(SpringAiRetryProperties.class); - - assertThat(retryProperties.getMaxAttempts()).isEqualTo(100); - assertThat(retryProperties.isOnClientErrors()).isFalse(); - assertThat(retryProperties.getExcludeOnHttpCodes()).containsExactly(404, 500); - assertThat(retryProperties.getOnHttpCodes()).containsExactly(429); - assertThat(retryProperties.getBackoff().getInitialInterval().toMillis()).isEqualTo(1000); - assertThat(retryProperties.getBackoff().getMultiplier()).isEqualTo(2); - assertThat(retryProperties.getBackoff().getMaxInterval().toMillis()).isEqualTo(60000); - }); - } - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/resources/application-test.properties b/spring-ai-spring-boot-autoconfigure/src/test/resources/application-test.properties deleted file mode 100644 index 9107b9e40..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/resources/application-test.properties +++ /dev/null @@ -1,10 +0,0 @@ -# Test MCP STDIO client configuration -spring.ai.mcp.client.stdio.enabled=true -spring.ai.mcp.client.stdio.version=test-version -spring.ai.mcp.client.stdio.request-timeout=15s -spring.ai.mcp.client.stdio.root-change-notification=false - -# Test server configuration -spring.ai.mcp.client.stdio.stdio-connections.test-server.command=echo -spring.ai.mcp.client.stdio.stdio-connections.test-server.args[0]=test -spring.ai.mcp.client.stdio.stdio-connections.test-server.env.TEST_ENV=test-value diff --git a/spring-ai-spring-boot-autoconfigure/src/test/resources/speech/jfk.flac b/spring-ai-spring-boot-autoconfigure/src/test/resources/speech/jfk.flac deleted file mode 100644 index e44b7c138..000000000 Binary files a/spring-ai-spring-boot-autoconfigure/src/test/resources/speech/jfk.flac and /dev/null differ diff --git a/spring-ai-spring-boot-autoconfigure/src/test/resources/spring_framework.png b/spring-ai-spring-boot-autoconfigure/src/test/resources/spring_framework.png deleted file mode 100644 index 735057a6b..000000000 Binary files a/spring-ai-spring-boot-autoconfigure/src/test/resources/spring_framework.png and /dev/null differ diff --git a/spring-ai-spring-boot-docker-compose/pom.xml b/spring-ai-spring-boot-docker-compose/pom.xml index 21f60e00c..9aa2bc4c3 100644 --- a/spring-ai-spring-boot-docker-compose/pom.xml +++ b/spring-ai-spring-boot-docker-compose/pom.xml @@ -43,12 +43,6 @@ - - org.springframework.ai - spring-ai-spring-boot-autoconfigure - ${project.parent.version} - - org.springframework.ai spring-ai-autoconfigure-vector-store-opensearch @@ -107,6 +101,13 @@ true + + org.springframework.ai + spring-ai-autoconfigure-model-ollama + ${project.parent.version} + true + + org.springframework.ai diff --git a/spring-ai-spring-boot-starters/spring-ai-starter-coherence-store/pom.xml b/spring-ai-spring-boot-starters/spring-ai-starter-coherence-store/pom.xml index 993ba3e84..b05c43c4c 100644 --- a/spring-ai-spring-boot-starters/spring-ai-starter-coherence-store/pom.xml +++ b/spring-ai-spring-boot-starters/spring-ai-starter-coherence-store/pom.xml @@ -42,12 +42,6 @@ spring-boot-starter - - org.springframework.ai - spring-ai-spring-boot-autoconfigure - ${project.parent.version} - - org.springframework.ai spring-ai-coherence-store diff --git a/spring-ai-spring-boot-testcontainers/pom.xml b/spring-ai-spring-boot-testcontainers/pom.xml index b60ef7e77..c90437477 100644 --- a/spring-ai-spring-boot-testcontainers/pom.xml +++ b/spring-ai-spring-boot-testcontainers/pom.xml @@ -46,7 +46,7 @@ org.springframework.ai - spring-ai-spring-boot-autoconfigure + spring-ai-autoconfigure-model-ollama ${project.parent.version} 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 65c58f5db..d3217fc2c 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 @@ -28,8 +28,8 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.ollama.OllamaContainer; -import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration; import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration; import org.springframework.ai.ollama.OllamaEmbeddingModel; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; @@ -79,7 +79,7 @@ class OllamaContainerConnectionDetailsFactoryIT { } @Configuration(proxyBeanMethods = false) - @ImportAutoConfiguration({ RestClientAutoConfiguration.class, OllamaAutoConfiguration.class }) + @ImportAutoConfiguration({ RestClientAutoConfiguration.class, OllamaEmbeddingAutoConfiguration.class }) static class Config { }