From 355d5d41ae1f8d3aaee351c04f81d08b51c649a5 Mon Sep 17 00:00:00 2001 From: Ricken Bazolo Date: Mon, 14 Apr 2025 18:59:10 +0200 Subject: [PATCH 1/3] fix mistral moderation auto config Signed-off-by: Ricken Bazolo --- .../MistralAiChatAutoConfiguration.java | 23 ------ .../MistralAiModerationAutoConfiguration.java | 77 +++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + 3 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java index d021e84b0f4..fd5209ba6a5 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java @@ -21,8 +21,6 @@ import org.springframework.ai.chat.observation.ChatModelObservationConvention; import org.springframework.ai.mistralai.MistralAiChatModel; import org.springframework.ai.mistralai.api.MistralAiApi; -import org.springframework.ai.mistralai.api.MistralAiModerationApi; -import org.springframework.ai.mistralai.moderation.MistralAiModerationModel; import org.springframework.ai.model.SpringAIModelProperties; import org.springframework.ai.model.SpringAIModels; import org.springframework.ai.model.function.DefaultFunctionCallbackResolver; @@ -96,27 +94,6 @@ public MistralAiChatModel mistralAiChatModel(MistralAiCommonProperties commonPro return chatModel; } - @Bean - @ConditionalOnMissingBean - public MistralAiModerationModel mistralAiModerationModel(MistralAiCommonProperties commonProperties, - MistralAiModerationProperties moderationProperties, RetryTemplate retryTemplate, - ObjectProvider restClientBuilderProvider, ResponseErrorHandler responseErrorHandler) { - - var apiKey = moderationProperties.getApiKey(); - var baseUrl = moderationProperties.getBaseUrl(); - - var resolvedApiKey = StringUtils.hasText(apiKey) ? apiKey : commonProperties.getApiKey(); - var resoledBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonProperties.getBaseUrl(); - - Assert.hasText(resolvedApiKey, "Mistral API key must be set"); - Assert.hasText(resoledBaseUrl, "Mistral base URL must be set"); - - var mistralAiModerationAi = new MistralAiModerationApi(resoledBaseUrl, resolvedApiKey, - restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); - - return new MistralAiModerationModel(mistralAiModerationAi, retryTemplate, moderationProperties.getOptions()); - } - private MistralAiApi mistralAiApi(String apiKey, String commonApiKey, String baseUrl, String commonBaseUrl, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java new file mode 100644 index 00000000000..fe9320172a4 --- /dev/null +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiModerationAutoConfiguration.java @@ -0,0 +1,77 @@ +/* + * 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.mistralai.autoconfigure; + +import org.springframework.ai.mistralai.api.MistralAiApi; +import org.springframework.ai.mistralai.api.MistralAiModerationApi; +import org.springframework.ai.mistralai.moderation.MistralAiModerationModel; +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; +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; +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; + +/** + * Moderation {@link AutoConfiguration Auto-configuration} for Mistral AI. + * + * @author Ricken Bazolo + */ +@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, + WebClientAutoConfiguration.class }) +@EnableConfigurationProperties({ MistralAiCommonProperties.class, MistralAiModerationProperties.class }) +@ConditionalOnProperty(name = SpringAIModelProperties.MODERATION_MODEL, havingValue = SpringAIModels.MISTRAL, + matchIfMissing = true) +@ConditionalOnClass(MistralAiApi.class) +@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class, + WebClientAutoConfiguration.class }) +public class MistralAiModerationAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + public MistralAiModerationModel mistralAiModerationModel(MistralAiCommonProperties commonProperties, + MistralAiModerationProperties moderationProperties, RetryTemplate retryTemplate, + ObjectProvider restClientBuilderProvider, ResponseErrorHandler responseErrorHandler) { + + var apiKey = moderationProperties.getApiKey(); + var baseUrl = moderationProperties.getBaseUrl(); + + var resolvedApiKey = StringUtils.hasText(apiKey) ? apiKey : commonProperties.getApiKey(); + var resoledBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonProperties.getBaseUrl(); + + Assert.hasText(resolvedApiKey, "Mistral API key must be set"); + Assert.hasText(resoledBaseUrl, "Mistral base URL must be set"); + + var mistralAiModerationAi = new MistralAiModerationApi(resoledBaseUrl, resolvedApiKey, + restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler); + + return new MistralAiModerationModel(mistralAiModerationAi, retryTemplate, moderationProperties.getOptions()); + } + +} 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 167a445dcb2..9c1b9186c62 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 @@ -15,3 +15,4 @@ # org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration org.springframework.ai.model.mistralai.autoconfigure.MistralAiEmbeddingAutoConfiguration +org.springframework.ai.model.mistralai.autoconfigure.MistralAiModerationAutoConfiguration From ff9ade91f35bd6c7552eb193ed94554343a748ae Mon Sep 17 00:00:00 2001 From: Ricken Bazolo Date: Tue, 15 Apr 2025 13:32:37 +0200 Subject: [PATCH 2/3] Add/update MistralModelConfigurationTests --- .../MistralAiChatAutoConfiguration.java | 3 +- .../MistralModelConfigurationTests.java | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java index fd5209ba6a5..cc2dc41d558 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatAutoConfiguration.java @@ -57,8 +57,7 @@ */ @AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class }) -@EnableConfigurationProperties({ MistralAiCommonProperties.class, MistralAiChatProperties.class, - MistralAiModerationProperties.class }) +@EnableConfigurationProperties({ MistralAiCommonProperties.class, MistralAiChatProperties.class }) @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MISTRAL, matchIfMissing = true) @ConditionalOnClass(MistralAiApi.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 002aef833ca..23b14e2d411 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 @@ -20,6 +20,7 @@ import org.springframework.ai.mistralai.MistralAiChatModel; import org.springframework.ai.mistralai.MistralAiEmbeddingModel; +import org.springframework.ai.mistralai.moderation.MistralAiModerationModel; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -29,6 +30,7 @@ * Unit Tests for Mistral AI auto-configurations conditional enabling of models. * * @author Ilayaperumal Gopinathan + * @author Ricken Bazolo */ public class MistralModelConfigurationTests { @@ -43,6 +45,8 @@ void chatModelActivation() { assertThat(context.getBeansOfType(MistralAiChatModel.class)).isNotEmpty(); assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiModerationProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiModerationModel.class)).isEmpty(); }); this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class)) @@ -61,6 +65,8 @@ void chatModelActivation() { assertThat(context.getBeansOfType(MistralAiChatModel.class)).isNotEmpty(); assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiModerationProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiModerationModel.class)).isEmpty(); }); } @@ -84,4 +90,42 @@ void embeddingModelActivation() { }); } + @Test + void moderationModelActivation() { + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiModerationAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(MistralAiModerationModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MistralAiModerationProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MistralAiChatModel.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty(); + }); + + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiModerationAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.moderation=none") + .run(context -> { + assertThat(context.getBeansOfType(MistralAiModerationProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiModerationModel.class)).isEmpty(); + }); + + this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiModerationAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.moderation=mistral") + .run(context -> { + assertThat(context.getBeansOfType(MistralAiModerationProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MistralAiModerationModel.class)).isNotEmpty(); + }); + + this.contextRunner + .withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class, + MistralAiEmbeddingAutoConfiguration.class, MistralAiModerationAutoConfiguration.class)) + .withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none", + "spring.ai.model.moderation=mistral") + .run(context -> { + assertThat(context.getBeansOfType(MistralAiModerationModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty(); + assertThat(context.getBeansOfType(MistralAiChatModel.class)).isEmpty(); + }); + } + } From b12ba120dd3be7c6138dc011dfaf69b5ca4259ae Mon Sep 17 00:00:00 2001 From: Ricken Bazolo Date: Tue, 15 Apr 2025 13:54:44 +0200 Subject: [PATCH 3/3] fix mistral moderationOptionsTest Signed-off-by: Ricken Bazolo --- .../model/mistralai/autoconfigure/MistralAiPropertiesTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 916781ca71c..c02a37ad570 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 @@ -151,7 +151,7 @@ public void moderationOptionsTest() { .withPropertyValues("spring.ai.mistralai.base-url=TEST_BASE_URL", "spring.ai.mistralai.api-key=abc123", "spring.ai.mistralai.moderation.options.model=MODERATION_MODEL") .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, - RestClientAutoConfiguration.class, MistralAiChatAutoConfiguration.class)) + RestClientAutoConfiguration.class, MistralAiModerationAutoConfiguration.class)) .run(context -> { var moderationProperties = context.getBean(MistralAiModerationProperties.class); assertThat(moderationProperties.getOptions().getModel()).isEqualTo("MODERATION_MODEL");