remove use of @OpenAiApiKey annotation

This commit is contained in:
Mark Pollack
2025-02-05 14:45:58 -05:00
parent 245fefdcb6
commit d126499d7b
3 changed files with 9 additions and 179 deletions

View File

@@ -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 {
}

View File

@@ -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<WebClient.Builder> webClientBuilderProvider, List<FunctionCallback> toolFunctionCallbacks,
FunctionCallbackResolver functionCallbackResolver, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatModelObservationConvention> observationConvention, @OpenAiApiKey ApiKey apiKey) {
ObjectProvider<ChatModelObservationConvention> 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<RestClient.Builder> restClientBuilderProvider,
ObjectProvider<WebClient.Builder> webClientBuilderProvider, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<EmbeddingModelObservationConvention> observationConvention, @OpenAiApiKey ApiKey apiKey) {
ObjectProvider<EmbeddingModelObservationConvention> 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())

View File

@@ -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";
}
}
}