Use WebClient.Builder and RestClient.Builder as ctor args in OpenAiAutoConfiguration

Fixes #609
This commit is contained in:
deepak
2024-05-04 01:16:04 +05:30
committed by Mark Pollack
parent 8257c0dc43
commit 377b5ffa61
4 changed files with 55 additions and 42 deletions

View File

@@ -72,7 +72,7 @@ public class OpenAiApi {
* @param openAiToken OpenAI apiKey.
*/
public OpenAiApi(String baseUrl, String openAiToken) {
this(baseUrl, openAiToken, RestClient.builder());
this(baseUrl, openAiToken, RestClient.builder(), WebClient.builder());
}
/**
@@ -82,8 +82,8 @@ public class OpenAiApi {
* @param openAiToken OpenAI apiKey.
* @param restClientBuilder RestClient builder.
*/
public OpenAiApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder) {
this(baseUrl, openAiToken, restClientBuilder, RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER);
public OpenAiApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder) {
this(baseUrl, openAiToken, restClientBuilder, webClientBuilder, RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER);
}
/**
@@ -94,7 +94,7 @@ public class OpenAiApi {
* @param restClientBuilder RestClient builder.
* @param responseErrorHandler Response error handler.
*/
public OpenAiApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
public OpenAiApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) {
this.restClient = restClientBuilder
.baseUrl(baseUrl)
@@ -102,7 +102,7 @@ public class OpenAiApi {
.defaultStatusHandler(responseErrorHandler)
.build();
this.webClient = WebClient.builder()
this.webClient = webClientBuilder
.baseUrl(baseUrl)
.defaultHeaders(ApiUtils.getJsonContentHeaders(openAiToken))
.build();

View File

@@ -39,6 +39,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
@@ -166,8 +167,8 @@ public class OpenAiChatModelWithChatResponseMetadataTests {
static class Config {
@Bean
public OpenAiApi chatCompletionApi(RestClient.Builder builder) {
return new OpenAiApi("", TEST_API_KEY, builder);
public OpenAiApi chatCompletionApi(RestClient.Builder builder, WebClient.Builder webClientBuilder) {
return new OpenAiApi("", TEST_API_KEY, builder, webClientBuilder);
}
@Bean

View File

@@ -39,6 +39,7 @@ import org.springframework.util.CollectionUtils;
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;
/**
* @author Christian Tzolov
@@ -56,11 +57,13 @@ public class OpenAiAutoConfiguration {
matchIfMissing = true)
public OpenAiChatModel openAiChatModel(OpenAiConnectionProperties commonProperties,
OpenAiChatProperties chatProperties, RestClient.Builder restClientBuilder,
List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackContext functionCallbackContext,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler) {
WebClient.Builder webClientBuilder, List<FunctionCallback> toolFunctionCallbacks,
FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler) {
var openAiApi = openAiApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, responseErrorHandler);
chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, webClientBuilder,
responseErrorHandler);
if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) {
chatProperties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks);
@@ -75,17 +78,20 @@ public class OpenAiAutoConfiguration {
matchIfMissing = true)
public OpenAiEmbeddingModel openAiEmbeddingModel(OpenAiConnectionProperties commonProperties,
OpenAiEmbeddingProperties embeddingProperties, RestClient.Builder restClientBuilder,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler) {
WebClient.Builder webClientBuilder, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler) {
var openAiApi = openAiApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(),
embeddingProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, responseErrorHandler);
embeddingProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, webClientBuilder,
responseErrorHandler);
return new OpenAiEmbeddingModel(openAiApi, embeddingProperties.getMetadataMode(),
embeddingProperties.getOptions(), retryTemplate);
}
private OpenAiApi openAiApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey,
RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
ResponseErrorHandler responseErrorHandler) {
String resolvedBaseUrl = StringUtils.hasText(baseUrl) ? baseUrl : commonBaseUrl;
Assert.hasText(resolvedBaseUrl, "OpenAI base URL must be set");
@@ -93,7 +99,8 @@ public class OpenAiAutoConfiguration {
String resolvedApiKey = StringUtils.hasText(apiKey) ? apiKey : commonApiKey;
Assert.hasText(resolvedApiKey, "OpenAI API key must be set");
return new OpenAiApi(resolvedBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler);
return new OpenAiApi(resolvedBaseUrl, resolvedApiKey, restClientBuilder, webClientBuilder,
responseErrorHandler);
}
@Bean
@@ -122,6 +129,7 @@ public class OpenAiAutoConfiguration {
@ConditionalOnMissingBean
public OpenAiAudioTranscriptionModel openAiAudioTranscriptionModel(OpenAiConnectionProperties commonProperties,
OpenAiAudioTranscriptionProperties transcriptionProperties, RetryTemplate retryTemplate,
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
ResponseErrorHandler responseErrorHandler) {
String apiKey = StringUtils.hasText(transcriptionProperties.getApiKey()) ? transcriptionProperties.getApiKey()
@@ -133,7 +141,8 @@ public class OpenAiAutoConfiguration {
Assert.hasText(apiKey, "OpenAI API key must be set");
Assert.hasText(baseUrl, "OpenAI base URL must be set");
var openAiAudioApi = new OpenAiAudioApi(baseUrl, apiKey, RestClient.builder(), responseErrorHandler);
var openAiAudioApi = new OpenAiAudioApi(baseUrl, apiKey, restClientBuilder, webClientBuilder,
responseErrorHandler);
OpenAiAudioTranscriptionModel openAiChatModel = new OpenAiAudioTranscriptionModel(openAiAudioApi,
transcriptionProperties.getOptions(), retryTemplate);
@@ -143,8 +152,9 @@ public class OpenAiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OpenAiAudioSpeechModel openAiAudioSpeechModel(OpenAiConnectionProperties commonProperties,
OpenAiAudioSpeechProperties speechProperties, ResponseErrorHandler responseErrorHandler) {
public OpenAiAudioSpeechModel openAiAudioSpeechClient(OpenAiConnectionProperties commonProperties,
OpenAiAudioSpeechProperties speechProperties, RestClient.Builder restClientBuilder,
WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler) {
String apiKey = StringUtils.hasText(speechProperties.getApiKey()) ? speechProperties.getApiKey()
: commonProperties.getApiKey();
@@ -155,7 +165,8 @@ public class OpenAiAutoConfiguration {
Assert.hasText(apiKey, "OpenAI API key must be set");
Assert.hasText(baseUrl, "OpenAI base URL must be set");
var openAiAudioApi = new OpenAiAudioApi(baseUrl, apiKey, RestClient.builder(), responseErrorHandler);
var openAiAudioApi = new OpenAiAudioApi(baseUrl, apiKey, restClientBuilder, webClientBuilder,
responseErrorHandler);
OpenAiAudioSpeechModel openAiSpeechModel = new OpenAiAudioSpeechModel(openAiAudioApi,
speechProperties.getOptions());

View File

@@ -30,6 +30,7 @@ import org.springframework.ai.openai.api.OpenAiApi.FunctionTool.Type;
import org.springframework.ai.openai.api.OpenAiAudioApi;
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;
@@ -55,7 +56,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.chat.options.temperature=0.55")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var chatProperties = context.getBean(OpenAiChatProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -82,7 +83,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.audio.transcription.options.temperature=0.55")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -111,7 +112,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.chat.options.temperature=0.55")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var chatProperties = context.getBean(OpenAiChatProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -140,7 +141,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.audio.transcription.options.temperature=0.55")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -169,7 +170,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.audio.speech.options.speed=0.75")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var speechProperties = context.getBean(OpenAiAudioSpeechProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -201,7 +202,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.audio.speech.options.speed=0.75")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var speechProperties = context.getBean(OpenAiAudioSpeechProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -232,7 +233,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.audio.speech.options.speed=0.5")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var speechProperties = context.getBean(OpenAiAudioSpeechProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -261,7 +262,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.embedding.options.model=MODEL_XYZ")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -288,7 +289,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.embedding.options.model=MODEL_XYZ")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -313,7 +314,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.image.options.n=3")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var imageProperties = context.getBean(OpenAiImageProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -341,7 +342,7 @@ public class OpenAiPropertiesTests {
"spring.ai.openai.image.options.n=3")
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var imageProperties = context.getBean(OpenAiImageProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -410,7 +411,7 @@ public class OpenAiPropertiesTests {
)
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var chatProperties = context.getBean(OpenAiChatProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -464,7 +465,7 @@ public class OpenAiPropertiesTests {
)
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -498,7 +499,7 @@ public class OpenAiPropertiesTests {
)
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
@@ -531,7 +532,7 @@ public class OpenAiPropertiesTests {
)
// @formatter:on
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
var imageProperties = context.getBean(OpenAiImageProperties.class);
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
@@ -558,7 +559,7 @@ public class OpenAiPropertiesTests {
.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(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
@@ -567,7 +568,7 @@ public class OpenAiPropertiesTests {
new ApplicationContextRunner()
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty();
@@ -577,7 +578,7 @@ public class OpenAiPropertiesTests {
.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(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty();
@@ -590,7 +591,7 @@ public class OpenAiPropertiesTests {
.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(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
@@ -599,7 +600,7 @@ public class OpenAiPropertiesTests {
new ApplicationContextRunner()
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
@@ -609,7 +610,7 @@ public class OpenAiPropertiesTests {
.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(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
@@ -623,7 +624,7 @@ public class OpenAiPropertiesTests {
.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(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
@@ -632,7 +633,7 @@ public class OpenAiPropertiesTests {
new ApplicationContextRunner()
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();
@@ -642,7 +643,7 @@ public class OpenAiPropertiesTests {
.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(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
RestClientAutoConfiguration.class, WebClientAutoConfiguration.class, OpenAiAutoConfiguration.class))
.run(context -> {
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();