From 31afae9650064a855cdcf96fa6f32b0c3fa81805 Mon Sep 17 00:00:00 2001 From: Ilayaperumal Gopinathan Date: Thu, 10 Apr 2025 12:04:20 +0100 Subject: [PATCH] Fix AzureOpenAI auto-configuration opt-in - Move AzureOpenAiClientBuilderAutoConfiguration to a simple AzureOpenAiClientBuilderConfiguration - This will make sure that the Azure OpenAI client builder will only be imported when the underlying Azure OpenAI auto-configuration is enabled - Update tests Signed-off-by: Ilayaperumal Gopinathan --- ...penAiAudioTranscriptionAutoConfiguration.java | 3 ++- .../AzureOpenAiChatAutoConfiguration.java | 5 +++-- ...> AzureOpenAiClientBuilderConfiguration.java} | 5 ++--- .../AzureOpenAiEmbeddingAutoConfiguration.java | 3 ++- .../AzureOpenAiImageAutoConfiguration.java | 3 ++- .../AzureOpenAiModelConfigurationTests.java | 16 ++++++++++++++++ 6 files changed, 27 insertions(+), 8 deletions(-) rename auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/{AzureOpenAiClientBuilderAutoConfiguration.java => AzureOpenAiClientBuilderConfiguration.java} (97%) 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 index 7c456d037..ae34ca326 100644 --- 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 @@ -28,6 +28,7 @@ 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 Azure OpenAI. @@ -42,7 +43,7 @@ import org.springframework.context.annotation.Bean; @EnableConfigurationProperties(AzureOpenAiAudioTranscriptionProperties.class) @ConditionalOnProperty(name = SpringAIModelProperties.AUDIO_TRANSCRIPTION_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, matchIfMissing = true) -@ImportAutoConfiguration(classes = AzureOpenAiClientBuilderAutoConfiguration.class) +@Import(AzureOpenAiClientBuilderConfiguration.class) public class AzureOpenAiAudioTranscriptionAutoConfiguration { @Bean diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatAutoConfiguration.java index b7c82f057..21106cc24 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiChatAutoConfiguration.java @@ -38,6 +38,7 @@ 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 Azure OpenAI. @@ -52,8 +53,8 @@ import org.springframework.context.annotation.Bean; @EnableConfigurationProperties({ AzureOpenAiChatProperties.class }) @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, matchIfMissing = true) -@ImportAutoConfiguration( - classes = { AzureOpenAiClientBuilderAutoConfiguration.class, ToolCallingAutoConfiguration.class }) +@ImportAutoConfiguration(classes = { ToolCallingAutoConfiguration.class }) +@Import(AzureOpenAiClientBuilderConfiguration.class) public class AzureOpenAiChatAutoConfiguration { @Bean diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderConfiguration.java similarity index 97% rename from auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderAutoConfiguration.java rename to auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderConfiguration.java index 4dafd1ced..3838a3224 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiClientBuilderConfiguration.java @@ -38,17 +38,16 @@ import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** - * {@link AutoConfiguration Auto-configuration} for Azure OpenAI. + * Azure OpenAI Client Builder configuration. * * @author Piotr Olaszewski * @author Soby Chacko * @author Manuel Andreo Garcia * @author Ilayaperumal Gopinathan */ -@AutoConfiguration @ConditionalOnClass({ OpenAIClientBuilder.class }) @EnableConfigurationProperties(AzureOpenAiConnectionProperties.class) -public class AzureOpenAiClientBuilderAutoConfiguration { +public class AzureOpenAiClientBuilderConfiguration { private static final String APPLICATION_ID = "spring-ai"; diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingAutoConfiguration.java index 13dc388bd..11ee2a3f8 100644 --- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingAutoConfiguration.java +++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiEmbeddingAutoConfiguration.java @@ -31,6 +31,7 @@ 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 Azure OpenAI. @@ -45,7 +46,7 @@ import org.springframework.context.annotation.Bean; @EnableConfigurationProperties({ AzureOpenAiEmbeddingProperties.class }) @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, matchIfMissing = true) -@ImportAutoConfiguration(classes = AzureOpenAiClientBuilderAutoConfiguration.class) +@Import(AzureOpenAiClientBuilderConfiguration.class) public class AzureOpenAiEmbeddingAutoConfiguration { @Bean 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 index df74ab365..2d34db412 100644 --- 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 @@ -28,6 +28,7 @@ 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 Azure OpenAI. @@ -42,7 +43,7 @@ import org.springframework.context.annotation.Bean; @ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.AZURE_OPENAI, matchIfMissing = true) @EnableConfigurationProperties(AzureOpenAiImageOptionsProperties.class) -@ImportAutoConfiguration(classes = AzureOpenAiClientBuilderAutoConfiguration.class) +@Import(AzureOpenAiClientBuilderConfiguration.class) public class AzureOpenAiImageAutoConfiguration { @Bean 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 7c3a933f2..b19483ae8 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 @@ -16,6 +16,7 @@ package org.springframework.ai.model.azure.openai.autoconfigure; +import com.azure.ai.openai.OpenAIClientBuilder; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; @@ -45,6 +46,7 @@ public class AzureOpenAiModelConfigurationTests { this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); @@ -55,6 +57,7 @@ public class AzureOpenAiModelConfigurationTests { .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isEmpty(); }); this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class)) @@ -70,6 +73,7 @@ public class AzureOpenAiModelConfigurationTests { "spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none") .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); @@ -84,6 +88,7 @@ public class AzureOpenAiModelConfigurationTests { assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); }); this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) @@ -91,6 +96,7 @@ public class AzureOpenAiModelConfigurationTests { .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isEmpty(); }); this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) @@ -98,6 +104,7 @@ public class AzureOpenAiModelConfigurationTests { .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); }); this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class)) @@ -107,6 +114,7 @@ public class AzureOpenAiModelConfigurationTests { .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); }); @@ -119,6 +127,7 @@ public class AzureOpenAiModelConfigurationTests { assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); }); @@ -127,6 +136,7 @@ public class AzureOpenAiModelConfigurationTests { .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isEmpty(); }); this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class)) @@ -134,6 +144,7 @@ public class AzureOpenAiModelConfigurationTests { .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); }); this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class)) @@ -144,6 +155,7 @@ public class AzureOpenAiModelConfigurationTests { assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); }); } @@ -157,6 +169,7 @@ public class AzureOpenAiModelConfigurationTests { assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); }); this.contextRunner @@ -165,6 +178,7 @@ public class AzureOpenAiModelConfigurationTests { .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isEmpty(); }); this.contextRunner @@ -173,6 +187,7 @@ public class AzureOpenAiModelConfigurationTests { .run(context -> { assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); }); this.contextRunner @@ -185,6 +200,7 @@ public class AzureOpenAiModelConfigurationTests { assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty(); assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty(); + assertThat(context.getBeansOfType(OpenAIClientBuilder.class)).isNotEmpty(); }); }