diff --git a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApiKey.java b/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApiKey.java deleted file mode 100644 index 6222adaee..000000000 --- a/models/spring-ai-openai/src/main/java/org/springframework/ai/openai/api/OpenAiApiKey.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.openai.api; - -import org.springframework.beans.factory.annotation.Qualifier; - -import java.lang.annotation.*; - -/** - * Qualifier annotation for OpenAI API key beans. Used to distinguish OpenAI API keys from - * other provider API keys. - * - * @author Mark Pollack - */ -@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Qualifier -public @interface OpenAiApiKey { - -} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/openai/OpenAiAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/openai/OpenAiAutoConfiguration.java index f6e6ec7fc..35e1fcf9e 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/openai/OpenAiAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/openai/OpenAiAutoConfiguration.java @@ -27,12 +27,10 @@ import org.springframework.ai.autoconfigure.retry.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.FunctionCallback; import org.springframework.ai.model.function.FunctionCallbackResolver; -import org.springframework.ai.model.ApiKey; -import org.springframework.ai.model.SimpleApiKey; -import org.springframework.ai.openai.api.OpenAiApiKey; import org.springframework.ai.openai.OpenAiAudioSpeechModel; import org.springframework.ai.openai.OpenAiAudioTranscriptionModel; import org.springframework.ai.openai.OpenAiChatModel; @@ -80,13 +78,6 @@ import org.springframework.web.reactive.function.client.WebClient; WebClientAutoConfiguration.class }) public class OpenAiAutoConfiguration { - @Bean - @ConditionalOnMissingBean(ApiKey.class) - @OpenAiApiKey - public ApiKey openAiApiKey(OpenAiConnectionProperties properties) { - return new SimpleApiKey(properties.getApiKey()); - } - private static @NotNull ResolvedConnectionProperties resolveConnectionProperties( OpenAiParentProperties commonProperties, OpenAiParentProperties modelProperties, String modelType) { @@ -126,11 +117,11 @@ public class OpenAiAutoConfiguration { ObjectProvider webClientBuilderProvider, List toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver, RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, - ObjectProvider observationConvention, @OpenAiApiKey ApiKey apiKey) { + ObjectProvider observationConvention) { var openAiApi = openAiApi(chatProperties, commonProperties, restClientBuilderProvider.getIfAvailable(RestClient::builder), - webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "chat", apiKey); + webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "chat"); var chatModel = new OpenAiChatModel(openAiApi, chatProperties.getOptions(), functionCallbackResolver, toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)); @@ -148,11 +139,11 @@ public class OpenAiAutoConfiguration { OpenAiEmbeddingProperties embeddingProperties, ObjectProvider restClientBuilderProvider, ObjectProvider webClientBuilderProvider, RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry, - ObjectProvider observationConvention, @OpenAiApiKey ApiKey apiKey) { + ObjectProvider observationConvention) { var openAiApi = openAiApi(embeddingProperties, commonProperties, restClientBuilderProvider.getIfAvailable(RestClient::builder), - webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "embedding", apiKey); + webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "embedding"); var embeddingModel = new OpenAiEmbeddingModel(openAiApi, embeddingProperties.getMetadataMode(), embeddingProperties.getOptions(), retryTemplate, @@ -165,14 +156,14 @@ public class OpenAiAutoConfiguration { private OpenAiApi openAiApi(OpenAiChatProperties chatProperties, OpenAiConnectionProperties commonProperties, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, - ResponseErrorHandler responseErrorHandler, String modelType, ApiKey apiKey) { + ResponseErrorHandler responseErrorHandler, String modelType) { ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, chatProperties, modelType); return OpenAiApi.builder() .baseUrl(resolved.baseUrl()) - .apiKey(apiKey) + .apiKey(new SimpleApiKey(resolved.apiKey())) .headers(resolved.headers()) .completionsPath(chatProperties.getCompletionsPath()) .embeddingsPath(OpenAiEmbeddingProperties.DEFAULT_EMBEDDINGS_PATH) @@ -184,15 +175,14 @@ public class OpenAiAutoConfiguration { private OpenAiApi openAiApi(OpenAiEmbeddingProperties embeddingProperties, OpenAiConnectionProperties commonProperties, RestClient.Builder restClientBuilder, - WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler, String modelType, - ApiKey apiKey) { + WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler, String modelType) { ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, embeddingProperties, modelType); return OpenAiApi.builder() .baseUrl(resolved.baseUrl()) - .apiKey(apiKey) + .apiKey(new SimpleApiKey(resolved.apiKey())) .headers(resolved.headers()) .completionsPath(OpenAiChatProperties.DEFAULT_COMPLETIONS_PATH) .embeddingsPath(embeddingProperties.getEmbeddingsPath()) diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/OpenAiApiKeyConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/OpenAiApiKeyConfigurationTests.java deleted file mode 100644 index 3adbe2719..000000000 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/openai/OpenAiApiKeyConfigurationTests.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.autoconfigure.openai; - -import org.junit.jupiter.api.Test; - -import org.springframework.ai.model.ApiKey; -import org.springframework.ai.model.SimpleApiKey; -import org.springframework.ai.openai.api.OpenAiApiKey; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -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; - -/** - * Tests for OpenAI ApiKey configuration behavior. - * - * @author Mark Pollack - */ -class OpenAiApiKeyConfigurationTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withPropertyValues("spring.ai.openai.apiKey=test-key") - .withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class)); - - @Configuration - static class CustomConfiguration { - - @Bean - @OpenAiApiKey - public ApiKey customOpenAiKey() { - return new ApiKey() { - @Override - public String getValue() { - return "custom-key"; - } - }; - } - - } - - @Test - void defaultApiKeyConfiguration() { - this.contextRunner.run(context -> { - ApiKey apiKey = context.getBean(ApiKey.class, ApiKey.class); - assertThat(apiKey).isNotNull(); - assertThat(apiKey).isInstanceOf(SimpleApiKey.class); - assertThat(apiKey.getValue()).isEqualTo("test-key"); - }); - } - - @Test - void customApiKeyConfiguration() { - this.contextRunner.withUserConfiguration(CustomConfiguration.class).run(context -> { - ApiKey apiKey = context.getBean(ApiKey.class, ApiKey.class); - assertThat(apiKey).isNotNull(); - assertThat(apiKey).isNotInstanceOf(SimpleApiKey.class); - assertThat(apiKey.getValue()).isEqualTo("custom-key"); - }); - } - - @Test - void multipleUnqualifiedApiKeysFailsToStart() { - this.contextRunner.withUserConfiguration(MultipleUnqualifiedApiKeysConfiguration.class).run(context -> { - assertThat(context).hasFailed(); - Throwable failure = context.getStartupFailure(); - while (failure.getCause() != null && !(failure instanceof NoSuchBeanDefinitionException)) { - failure = failure.getCause(); - } - assertThat(failure).isInstanceOf(NoSuchBeanDefinitionException.class) - .hasMessageContaining( - "No qualifying bean of type 'org.springframework.ai.model.ApiKey' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.ai.openai.api.OpenAiApiKey()}"); - }); - } - - @Configuration - static class MultipleUnqualifiedApiKeysConfiguration { - - @Bean - public ApiKey openAiKey() { - return () -> "openai-key"; - } - - @Bean - public ApiKey otherKey() { - return () -> "other-key"; - } - - } - - @Configuration - static class MultipleQualifiedApiKeysConfiguration { - - @Bean - @OpenAiApiKey - public ApiKey openAiKey() { - return () -> "openai-key"; - } - - @Bean - @OtherApiKey - public ApiKey otherKey() { - return () -> "other-key"; - } - - } - -}