Streamline dependencies and improve API consistency

- Remove unnecessary spring-web dependencies
 - Update third-party library versions
 - Refactor API classes to use consistent header handling
 - Remove ApiUtils class and inline its functionality
 - Adjust RestClient and WebClient builder usage in autoconfiguration
 - Replace direct RestClient.Builder injections with ObjectProvider<RestClient.Builder>
   and WebClient.Builder injections with ObjectProvider<WebClient.Builder>
 - Update ChromaVectorStoreAutoConfiguration to use ObjectProvider
 - Rename MongoDbAtlasLocalContainerConnectionDetailsFactoryTest to IT
 - Switch spring-ai-chroma-store dependency from spring-web to spring-webflux
 - Simplify ChromaApi constructor by using method reference for default headers
 - Adjust import order

 Resolves #1066
 Resolves #524
This commit is contained in:
Christian Tzolov
2024-10-15 15:52:44 +02:00
committed by Mark Pollack
parent a69f6bb57b
commit 2e0a51fac5
36 changed files with 230 additions and 285 deletions

View File

@@ -29,12 +29,6 @@
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>

View File

@@ -15,30 +15,34 @@
*/
package org.springframework.ai.minimax.api;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.springframework.ai.model.ChatModelDescription;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.util.api.ApiUtils;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
// @formatter:off
/**
* Single class implementation of the <a href="https://www.minimaxi.com/document/guides/chat-model/V2">MiniMax Chat Completion API</a> and
@@ -98,15 +102,20 @@ public class MiniMaxApi {
*/
public MiniMaxApi(String baseUrl, String miniMaxToken, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
Consumer<HttpHeaders> authHeaders = (headers) -> {
headers.setBearerAuth(miniMaxToken);
headers.setContentType(MediaType.APPLICATION_JSON);
};
this.restClient = restClientBuilder
.baseUrl(baseUrl)
.defaultHeaders(ApiUtils.getJsonContentHeaders(miniMaxToken))
.defaultHeaders(authHeaders)
.defaultStatusHandler(responseErrorHandler)
.build();
this.webClient = WebClient.builder()
.baseUrl(baseUrl)
.defaultHeaders(ApiUtils.getJsonContentHeaders(miniMaxToken))
.defaultHeaders(authHeaders)
.build();
}

View File

@@ -41,12 +41,6 @@
<version>${oci-sdk-version}</version>
</dependency>
<!-- NOTE: Required only by the @ConstructorBinding. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>

View File

@@ -22,11 +22,6 @@
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-core</artifactId>
@@ -34,14 +29,9 @@
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-retry</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-retry</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>

View File

@@ -21,7 +21,7 @@
</scm>
<dependencies>
<!-- production dependencies -->
<dependency>
<groupId>org.springframework.ai</groupId>

View File

@@ -18,6 +18,7 @@ package org.springframework.ai.openai.api;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.springframework.ai.model.ChatModelDescription;
@@ -26,6 +27,7 @@ import org.springframework.ai.openai.api.common.OpenAiApiConstants;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
@@ -154,22 +156,19 @@ public class OpenAiApi {
this.completionsPath = completionsPath;
this.embeddingsPath = embeddingsPath;
// @formatter:off
Consumer<HttpHeaders> finalHeaders = h -> {
h.setBearerAuth(apiKey);
h.setContentType(MediaType.APPLICATION_JSON);
h.addAll(headers);
};
this.restClient = restClientBuilder.baseUrl(baseUrl)
.defaultHeaders(h -> {
h.setBearerAuth(apiKey);
h.setContentType(MediaType.APPLICATION_JSON);
h.addAll(headers);
})
.defaultHeaders(finalHeaders)
.defaultStatusHandler(responseErrorHandler)
.build();
this.webClient = webClientBuilder
.baseUrl(baseUrl)
.defaultHeaders(h -> {
h.setBearerAuth(apiKey);
h.setContentType(MediaType.APPLICATION_JSON);
h.addAll(headers);
})
.defaultHeaders(finalHeaders)
.build();// @formatter:on
}

View File

@@ -17,14 +17,10 @@ package org.springframework.ai.openai.api;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.function.Consumer;
import org.springframework.ai.openai.api.common.OpenAiApiConstants;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.util.api.ApiUtils;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -36,6 +32,11 @@ import org.springframework.util.MultiValueMap;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -71,13 +72,14 @@ public class OpenAiAudioApi {
public OpenAiAudioApi(String baseUrl, String openAiToken, RestClient.Builder restClientBuilder,
ResponseErrorHandler responseErrorHandler) {
this.restClient = restClientBuilder.baseUrl(baseUrl).defaultHeaders(headers -> {
headers.setBearerAuth(openAiToken);
}).defaultStatusHandler(responseErrorHandler).build();
Consumer<HttpHeaders> authHeaders = h -> h.setBearerAuth(openAiToken);
this.webClient = WebClient.builder().baseUrl(baseUrl).defaultHeaders(headers -> {
headers.setBearerAuth(openAiToken);
}).defaultHeaders(ApiUtils.getJsonContentHeaders(openAiToken)).build();
this.restClient = restClientBuilder.baseUrl(baseUrl)
.defaultHeaders(authHeaders)
.defaultStatusHandler(responseErrorHandler)
.build();
this.webClient = WebClient.builder().baseUrl(baseUrl).defaultHeaders(authHeaders).build();
}
/**
@@ -108,23 +110,18 @@ public class OpenAiAudioApi {
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
ResponseErrorHandler responseErrorHandler) {
// @formatter:off
this.restClient = restClientBuilder
.baseUrl(baseUrl)
.defaultHeaders(h -> {
h.setBearerAuth(apiKey);
h.addAll(headers);
})
.defaultStatusHandler(responseErrorHandler).build();
Consumer<HttpHeaders> authHeaders = h -> {
h.setBearerAuth(apiKey);
h.addAll(headers);
// h.setContentType(MediaType.APPLICATION_JSON);
};
this.webClient = webClientBuilder
.baseUrl(baseUrl)
.defaultHeaders(h -> {
h.setBearerAuth(apiKey);
h.addAll(headers);
})
.defaultHeaders(ApiUtils.getJsonContentHeaders(apiKey)).build();
// @formatter:on
this.restClient = restClientBuilder.baseUrl(baseUrl)
.defaultHeaders(authHeaders)
.defaultStatusHandler(responseErrorHandler)
.build();
this.webClient = webClientBuilder.baseUrl(baseUrl).defaultHeaders(authHeaders).build();
}
/**

View File

@@ -15,21 +15,17 @@
*/
package org.springframework.ai.openai.api;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import java.io.IOException;
import java.util.function.Consumer;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* OpenAI Moderation API.
@@ -61,15 +57,10 @@ public class OpenAiModerationApi {
this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Consumer<HttpHeaders> jsonContentHeaders = headers -> {
headers.setBearerAuth(openAiToken);
headers.setContentType(MediaType.APPLICATION_JSON);
};
this.restClient = restClientBuilder.baseUrl(baseUrl)
.defaultHeaders(jsonContentHeaders)
.defaultStatusHandler(responseErrorHandler)
.build();
this.restClient = restClientBuilder.baseUrl(baseUrl).defaultHeaders(h -> {
h.setBearerAuth(openAiToken);
h.setContentType(MediaType.APPLICATION_JSON);
}).defaultStatusHandler(responseErrorHandler).build();
}
// @formatter:off

View File

@@ -15,6 +15,14 @@
*/
package org.springframework.ai.openai.audio.transcription;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.header;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import java.time.Duration;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.ai.audio.transcription.AudioTranscriptionMetadata;
@@ -38,12 +46,6 @@ import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestClient;
import java.time.Duration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
/**
* @author Michael Lavelle
*/
@@ -53,7 +55,7 @@ public class OpenAiTranscriptionModelWithTranscriptionResponseMetadataTests {
private static String TEST_API_KEY = "sk-1234567890";
@Autowired
private OpenAiAudioTranscriptionModel openAiTranscriptionClient;
private OpenAiAudioTranscriptionModel openAiTranscriptionModel;
@Autowired
private MockRestServiceServer server;
@@ -72,7 +74,7 @@ public class OpenAiTranscriptionModelWithTranscriptionResponseMetadataTests {
AudioTranscriptionPrompt transcriptionRequest = new AudioTranscriptionPrompt(audioFile);
AudioTranscriptionResponse response = this.openAiTranscriptionClient.call(transcriptionRequest);
AudioTranscriptionResponse response = this.openAiTranscriptionModel.call(transcriptionRequest);
assertThat(response).isNotNull();

View File

@@ -58,13 +58,6 @@
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Spring Framework -->
<dependency>
<groupId>org.springframework</groupId>

View File

@@ -58,12 +58,6 @@
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-framework.version}</version>
</dependency>
<!-- Spring Framework -->
<dependency>

View File

@@ -31,8 +31,7 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring-framework.version}</version>
<artifactId>spring-web</artifactId>
</dependency>

View File

@@ -21,6 +21,7 @@
<dependencies>
<!-- NOTE: Required only by the @ConstructorBinding. -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
@@ -38,11 +39,6 @@
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>

View File

@@ -21,7 +21,6 @@
</scm>
<dependencies>
<!-- production dependencies -->
<dependency>
<groupId>org.springframework.ai</groupId>

View File

@@ -15,31 +15,35 @@
*/
package org.springframework.ai.zhipuai.api;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.springframework.ai.model.ChatModelDescription;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.util.api.ApiUtils;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
// @formatter:off
/**
* Single class implementation of the <a href="https://open.bigmodel.cn/dev/api#http">ZhiPuAI Chat Completion API</a> and
@@ -99,15 +103,20 @@ public class ZhiPuAiApi {
*/
public ZhiPuAiApi(String baseUrl, String zhiPuAiToken, RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
Consumer<HttpHeaders> authHeaders = h -> {
h.setBearerAuth(zhiPuAiToken);
h.setContentType(MediaType.APPLICATION_JSON);
};
this.restClient = restClientBuilder
.baseUrl(baseUrl)
.defaultHeaders(ApiUtils.getJsonContentHeaders(zhiPuAiToken))
.defaultHeaders(authHeaders)
.defaultStatusHandler(responseErrorHandler)
.build();
this.webClient = WebClient.builder()
.baseUrl(baseUrl)
.defaultHeaders(ApiUtils.getJsonContentHeaders(zhiPuAiToken))
.defaultHeaders(authHeaders)
.build();
}

View File

@@ -15,16 +15,18 @@
*/
package org.springframework.ai.zhipuai.api;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.ai.util.api.ApiUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.util.Assert;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* ZhiPuAI Image API.
@@ -68,10 +70,10 @@ public class ZhiPuAiImageApi {
public ZhiPuAiImageApi(String baseUrl, String zhiPuAiToken, RestClient.Builder restClientBuilder,
ResponseErrorHandler responseErrorHandler) {
this.restClient = restClientBuilder.baseUrl(baseUrl)
.defaultHeaders(ApiUtils.getJsonContentHeaders(zhiPuAiToken))
.defaultStatusHandler(responseErrorHandler)
.build();
this.restClient = restClientBuilder.baseUrl(baseUrl).defaultHeaders(h -> {
h.setBearerAuth(zhiPuAiToken);
// h.setContentType(MediaType.APPLICATION_JSON);
}).defaultStatusHandler(responseErrorHandler).build();
}
/**

11
pom.xml
View File

@@ -148,7 +148,6 @@
<!-- production dependencies -->
<spring-cloud-function-context.version>4.1.3</spring-cloud-function-context.version>
<spring-boot.version>3.3.4</spring-boot.version>
<spring-framework.version>6.1.4</spring-framework.version>
<ST4.version>4.3.4</ST4.version>
<azure-open-ai-client.version>1.0.0-beta.10</azure-open-ai-client.version>
<jtokkit.version>1.1.0</jtokkit.version>
@@ -163,21 +162,21 @@
<djl.version>0.30.0</djl.version>
<onnxruntime.version>1.19.2</onnxruntime.version>
<oci-sdk-version>3.46.1</oci-sdk-version>
<com.google.cloud.version>26.41.0</com.google.cloud.version>
<com.google.cloud.version>26.48.0</com.google.cloud.version>
<qdrant.version>1.9.1</qdrant.version>
<spring-retry.version>2.0.5</spring-retry.version>
<spring-retry.version>2.0.9</spring-retry.version>
<ibm.sdk.version>9.20.0</ibm.sdk.version>
<jsonschema.version>4.35.0</jsonschema.version>
<swagger-annotations.version>2.2.20</swagger-annotations.version>
<spring-cloud-bindings.version>2.0.2</spring-cloud-bindings.version>
<spring-cloud-bindings.version>2.0.3</spring-cloud-bindings.version>
<!-- Protobuf -->
<protobuf-java.version>3.25.2</protobuf-java.version>
<!-- readers/writer/stores dependencies-->
<cassandra.java-driver.version>4.18.1</cassandra.java-driver.version>
<pdfbox.version>3.0.1</pdfbox.version>
<pgvector.version>0.1.4</pgvector.version>
<pdfbox.version>3.0.3</pdfbox.version>
<pgvector.version>0.1.6</pgvector.version>
<sap.hanadb.version>2.20.11</sap.hanadb.version>
<oracle.version>23.4.0.24.05</oracle.version>
<postgresql.version>42.7.2</postgresql.version>

View File

@@ -74,11 +74,6 @@
<artifactId>spring-messaging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>

View File

@@ -1,41 +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.util.api;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import java.util.function.Consumer;
/**
* The ApiUtils class provides utility methods for working with API requests and
* responses.
*
* @author Geng Rong
* @author Christian Tzolov
* @author Piotr Olaszewski
* @since 1.0.0 M1
*/
public class ApiUtils {
public static Consumer<HttpHeaders> getJsonContentHeaders(String apiKey) {
return (headers) -> {
headers.setBearerAuth(apiKey);
headers.setContentType(MediaType.APPLICATION_JSON);
};
}
}

View File

@@ -33,7 +33,7 @@
<artifactId>spring-retry</artifactId>
<version>${spring-retry.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>

View File

@@ -17,7 +17,6 @@ package org.springframework.ai.autoconfigure.anthropic;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.anthropic.AnthropicChatModel;
import org.springframework.ai.anthropic.api.AnthropicApi;
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
@@ -40,6 +39,8 @@ import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;
import io.micrometer.observation.ObservationRegistry;
/**
* @author Christian Tzolov
* @author Thomas Vitale
@@ -57,11 +58,12 @@ public class AnthropicAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public AnthropicApi anthropicApi(AnthropicConnectionProperties connectionProperties,
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
ResponseErrorHandler responseErrorHandler) {
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
return new AnthropicApi(connectionProperties.getBaseUrl(), connectionProperties.getApiKey(),
connectionProperties.getVersion(), restClientBuilder, webClientBuilder, responseErrorHandler,
connectionProperties.getVersion(), restClientBuilderProvider.getIfAvailable(RestClient::builder),
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler,
connectionProperties.getBetaVersion());
}

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.ai.autoconfigure.minimax;
import io.micrometer.observation.ObservationRegistry;
import java.util.List;
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
@@ -35,15 +36,11 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import io.micrometer.observation.ObservationRegistry;
/**
* @author Geng Rong
@@ -59,14 +56,15 @@ public class MiniMaxAutoConfiguration {
@ConditionalOnProperty(prefix = MiniMaxChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public MiniMaxChatModel miniMaxChatModel(MiniMaxConnectionProperties commonProperties,
MiniMaxChatProperties chatProperties, RestClient.Builder restClientBuilder,
MiniMaxChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackContext functionCallbackContext,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatModelObservationConvention> observationConvention) {
var miniMaxApi = miniMaxApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, responseErrorHandler);
chatProperties.getApiKey(), commonProperties.getApiKey(),
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
var chatModel = new MiniMaxChatModel(miniMaxApi, chatProperties.getOptions(), functionCallbackContext,
toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
@@ -80,13 +78,14 @@ public class MiniMaxAutoConfiguration {
@ConditionalOnProperty(prefix = MiniMaxEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public MiniMaxEmbeddingModel miniMaxEmbeddingModel(MiniMaxConnectionProperties commonProperties,
MiniMaxEmbeddingProperties embeddingProperties, RestClient.Builder restClientBuilder,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
MiniMaxEmbeddingProperties embeddingProperties,
ObjectProvider<RestClient.Builder> restClientBuilderProvider, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
var miniMaxApi = miniMaxApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(),
embeddingProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, responseErrorHandler);
embeddingProperties.getApiKey(), commonProperties.getApiKey(),
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
var embeddingModel = new MiniMaxEmbeddingModel(miniMaxApi, embeddingProperties.getMetadataMode(),
embeddingProperties.getOptions(), retryTemplate,

View File

@@ -17,7 +17,6 @@ package org.springframework.ai.autoconfigure.mistralai;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
@@ -43,6 +42,8 @@ import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import io.micrometer.observation.ObservationRegistry;
/**
* @author Ricken Bazolo
* @author Christian Tzolov
@@ -62,14 +63,14 @@ public class MistralAiAutoConfiguration {
@ConditionalOnProperty(prefix = MistralAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiCommonProperties commonProperties,
MistralAiEmbeddingProperties embeddingProperties, RestClient.Builder restClientBuilder,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
MistralAiEmbeddingProperties embeddingProperties,
ObjectProvider<RestClient.Builder> restClientBuilderProvider, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
var mistralAiApi = mistralAiApi(embeddingProperties.getApiKey(), commonProperties.getApiKey(),
embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(), restClientBuilder,
responseErrorHandler);
embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(),
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
var embeddingModel = new MistralAiEmbeddingModel(mistralAiApi, embeddingProperties.getMetadataMode(),
embeddingProperties.getOptions(), retryTemplate,
@@ -85,14 +86,15 @@ public class MistralAiAutoConfiguration {
@ConditionalOnProperty(prefix = MistralAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public MistralAiChatModel mistralAiChatModel(MistralAiCommonProperties commonProperties,
MistralAiChatProperties chatProperties, RestClient.Builder restClientBuilder,
MistralAiChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackContext functionCallbackContext,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatModelObservationConvention> observationConvention) {
var mistralAiApi = mistralAiApi(chatProperties.getApiKey(), commonProperties.getApiKey(),
chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), restClientBuilder, responseErrorHandler);
chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
var chatModel = new MistralAiChatModel(mistralAiApi, chatProperties.getOptions(), functionCallbackContext,
toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.ai.autoconfigure.moonshot;
import io.micrometer.observation.ObservationRegistry;
import java.util.List;
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
import org.springframework.ai.model.function.FunctionCallback;
@@ -37,7 +38,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
/**
* @author Geng Rong
@@ -52,14 +53,15 @@ public class MoonshotAutoConfiguration {
@ConditionalOnProperty(prefix = MoonshotChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public MoonshotChatModel moonshotChatModel(MoonshotCommonProperties commonProperties,
MoonshotChatProperties chatProperties, RestClient.Builder restClientBuilder,
MoonshotChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackContext functionCallbackContext,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatModelObservationConvention> observationConvention) {
var moonshotApi = moonshotApi(chatProperties.getApiKey(), commonProperties.getApiKey(),
chatProperties.getBaseUrl(), commonProperties.getBaseUrl(), restClientBuilder, responseErrorHandler);
chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
var chatModel = new MoonshotChatModel(moonshotApi, chatProperties.getOptions(), functionCallbackContext,
toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));

View File

@@ -17,7 +17,6 @@ package org.springframework.ai.autoconfigure.ollama;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
import org.springframework.ai.model.function.FunctionCallback;
@@ -39,6 +38,8 @@ import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestClient;
import org.springframework.web.reactive.function.client.WebClient;
import io.micrometer.observation.ObservationRegistry;
/**
* {@link AutoConfiguration Auto-configuration} for Ollama Chat Client.
*
@@ -62,9 +63,12 @@ public class OllamaAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails, RestClient.Builder restClientBuilder,
WebClient.Builder webClientBuilder) {
return new OllamaApi(connectionDetails.getBaseUrl(), restClientBuilder, webClientBuilder);
public OllamaApi ollamaApi(OllamaConnectionDetails connectionDetails,
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
ObjectProvider<WebClient.Builder> webClientBuilderProvider) {
return new OllamaApi(connectionDetails.getBaseUrl(),
restClientBuilderProvider.getIfAvailable(RestClient::builder),
webClientBuilderProvider.getIfAvailable(WebClient::builder));
}
@Bean

View File

@@ -78,14 +78,15 @@ public class OpenAiAutoConfiguration {
@ConditionalOnProperty(prefix = OpenAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public OpenAiChatModel openAiChatModel(OpenAiConnectionProperties commonProperties,
OpenAiChatProperties chatProperties, RestClient.Builder restClientBuilder,
WebClient.Builder webClientBuilder, List<FunctionCallback> toolFunctionCallbacks,
OpenAiChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
ObjectProvider<WebClient.Builder> webClientBuilderProvider, List<FunctionCallback> toolFunctionCallbacks,
FunctionCallbackContext functionCallbackContext, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatModelObservationConvention> observationConvention) {
var openAiApi = openAiApi(chatProperties, commonProperties, restClientBuilder, webClientBuilder,
responseErrorHandler, "chat");
var openAiApi = openAiApi(chatProperties, commonProperties,
restClientBuilderProvider.getIfAvailable(RestClient::builder),
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "chat");
var chatModel = new OpenAiChatModel(openAiApi, chatProperties.getOptions(), functionCallbackContext,
toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
@@ -100,13 +101,14 @@ public class OpenAiAutoConfiguration {
@ConditionalOnProperty(prefix = OpenAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public OpenAiEmbeddingModel openAiEmbeddingModel(OpenAiConnectionProperties commonProperties,
OpenAiEmbeddingProperties embeddingProperties, RestClient.Builder restClientBuilder,
WebClient.Builder webClientBuilder, RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
OpenAiEmbeddingProperties embeddingProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
ObjectProvider<WebClient.Builder> webClientBuilderProvider, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
var openAiApi = openAiApi(embeddingProperties, commonProperties, restClientBuilder, webClientBuilder,
responseErrorHandler, "embedding");
var openAiApi = openAiApi(embeddingProperties, commonProperties,
restClientBuilderProvider.getIfAvailable(RestClient::builder),
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "embedding");
var embeddingModel = new OpenAiEmbeddingModel(openAiApi, embeddingProperties.getMetadataMode(),
embeddingProperties.getOptions(), retryTemplate,
@@ -146,14 +148,15 @@ public class OpenAiAutoConfiguration {
@ConditionalOnProperty(prefix = OpenAiImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public OpenAiImageModel openAiImageModel(OpenAiConnectionProperties commonProperties,
OpenAiImageProperties imageProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
OpenAiImageProperties imageProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ImageModelObservationConvention> observationConvention) {
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, imageProperties, "image");
var openAiImageApi = new OpenAiImageApi(resolved.baseUrl(), resolved.apiKey(), resolved.headers(),
restClientBuilder, responseErrorHandler);
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
var imageModel = new OpenAiImageModel(openAiImageApi, imageProperties.getOptions(), retryTemplate,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
@@ -169,14 +172,15 @@ public class OpenAiAutoConfiguration {
havingValue = "true", matchIfMissing = true)
public OpenAiAudioTranscriptionModel openAiAudioTranscriptionModel(OpenAiConnectionProperties commonProperties,
OpenAiAudioTranscriptionProperties transcriptionProperties, RetryTemplate retryTemplate,
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
ResponseErrorHandler responseErrorHandler) {
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, transcriptionProperties,
"transcription");
var openAiAudioApi = new OpenAiAudioApi(resolved.baseUrl(), resolved.apiKey(), resolved.headers(),
restClientBuilder, webClientBuilder, responseErrorHandler);
restClientBuilderProvider.getIfAvailable(RestClient::builder),
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler);
return new OpenAiAudioTranscriptionModel(openAiAudioApi, transcriptionProperties.getOptions(), retryTemplate);
@@ -186,13 +190,13 @@ public class OpenAiAutoConfiguration {
@ConditionalOnMissingBean
public OpenAiModerationModel openAiModerationClient(OpenAiConnectionProperties commonProperties,
OpenAiModerationProperties moderationProperties, RetryTemplate retryTemplate,
RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
ObjectProvider<RestClient.Builder> restClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, moderationProperties,
"moderation");
var openAiModerationApi = new OpenAiModerationApi(resolved.baseUrl, resolved.apiKey(), restClientBuilder,
responseErrorHandler);
var openAiModerationApi = new OpenAiModerationApi(resolved.baseUrl, resolved.apiKey(),
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
return new OpenAiModerationModel(openAiModerationApi, retryTemplate)
.withDefaultOptions(moderationProperties.getOptions());
@@ -204,14 +208,15 @@ public class OpenAiAutoConfiguration {
matchIfMissing = true)
public OpenAiAudioSpeechModel openAiAudioSpeechClient(OpenAiConnectionProperties commonProperties,
OpenAiAudioSpeechProperties speechProperties, RetryTemplate retryTemplate,
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
ResponseErrorHandler responseErrorHandler) {
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, speechProperties,
"speach");
var openAiAudioApi = new OpenAiAudioApi(resolved.baseUrl(), resolved.apiKey(), resolved.headers(),
restClientBuilder, webClientBuilder, responseErrorHandler);
restClientBuilderProvider.getIfAvailable(RestClient::builder),
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler);
return new OpenAiAudioSpeechModel(openAiAudioApi, speechProperties.getOptions());
}

View File

@@ -15,7 +15,6 @@
*/
package org.springframework.ai.autoconfigure.qianfan;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
@@ -41,6 +40,8 @@ import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import io.micrometer.observation.ObservationRegistry;
/**
* @author Geng Rong
*/
@@ -55,13 +56,15 @@ public class QianFanAutoConfiguration {
@ConditionalOnProperty(prefix = QianFanChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public QianFanChatModel qianFanChatModel(QianFanConnectionProperties commonProperties,
QianFanChatProperties chatProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
QianFanChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatModelObservationConvention> observationConvention) {
var qianFanApi = qianFanApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
chatProperties.getApiKey(), commonProperties.getApiKey(), chatProperties.getSecretKey(),
commonProperties.getSecretKey(), restClientBuilder, responseErrorHandler);
commonProperties.getSecretKey(), restClientBuilderProvider.getIfAvailable(RestClient::builder),
responseErrorHandler);
var chatModel = new QianFanChatModel(qianFanApi, chatProperties.getOptions(), retryTemplate,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
@@ -76,14 +79,15 @@ public class QianFanAutoConfiguration {
@ConditionalOnProperty(prefix = QianFanEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public QianFanEmbeddingModel qianFanEmbeddingModel(QianFanConnectionProperties commonProperties,
QianFanEmbeddingProperties embeddingProperties, RestClient.Builder restClientBuilder,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
QianFanEmbeddingProperties embeddingProperties,
ObjectProvider<RestClient.Builder> restClientBuilderProvider, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
var qianFanApi = qianFanApi(embeddingProperties.getBaseUrl(), commonProperties.getBaseUrl(),
embeddingProperties.getApiKey(), commonProperties.getApiKey(), embeddingProperties.getSecretKey(),
commonProperties.getSecretKey(), restClientBuilder, responseErrorHandler);
commonProperties.getSecretKey(), restClientBuilderProvider.getIfAvailable(RestClient::builder),
responseErrorHandler);
var embeddingModel = new QianFanEmbeddingModel(qianFanApi, embeddingProperties.getMetadataMode(),
embeddingProperties.getOptions(), retryTemplate,
@@ -99,8 +103,9 @@ public class QianFanAutoConfiguration {
@ConditionalOnProperty(prefix = QianFanImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public QianFanImageModel qianFanImageModel(QianFanConnectionProperties commonProperties,
QianFanImageProperties imageProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate,
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
QianFanImageProperties imageProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ImageModelObservationConvention> observationConvention) {
String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey()
@@ -116,7 +121,8 @@ public class QianFanAutoConfiguration {
Assert.hasText(secretKey, "QianFan secret key must be set. Use the property: spring.ai.qianfan.secret-key");
Assert.hasText(baseUrl, "QianFan base URL must be set. Use the property: spring.ai.qianfan.base-url");
var qianFanImageApi = new QianFanImageApi(baseUrl, apiKey, secretKey, restClientBuilder, responseErrorHandler);
var qianFanImageApi = new QianFanImageApi(baseUrl, apiKey, secretKey,
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
var imageModel = new QianFanImageModel(qianFanImageApi, imageProperties.getOptions(), retryTemplate,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));

View File

@@ -17,6 +17,7 @@ package org.springframework.ai.autoconfigure.stabilityai;
import org.springframework.ai.stabilityai.StabilityAiImageModel;
import org.springframework.ai.stabilityai.api.StabilityAiApi;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -26,6 +27,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClient;
/**
* @author Mark Pollack
@@ -40,7 +42,7 @@ public class StabilityAiImageAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public StabilityAiApi stabilityAiApi(StabilityAiConnectionProperties commonProperties,
StabilityAiImageProperties imageProperties) {
StabilityAiImageProperties imageProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider) {
String apiKey = StringUtils.hasText(imageProperties.getApiKey()) ? imageProperties.getApiKey()
: commonProperties.getApiKey();
@@ -51,7 +53,8 @@ public class StabilityAiImageAutoConfiguration {
Assert.hasText(apiKey, "StabilityAI API key must be set");
Assert.hasText(baseUrl, "StabilityAI base URL must be set");
return new StabilityAiApi(apiKey, imageProperties.getOptions().getModel(), baseUrl);
return new StabilityAiApi(apiKey, imageProperties.getOptions().getModel(), baseUrl,
restClientBuilderProvider.getIfAvailable(RestClient::builder));
}
@Bean

View File

@@ -28,7 +28,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClient;
@@ -54,18 +53,13 @@ public class ChromaVectorStoreAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public RestClient.Builder builder() {
return RestClient.builder().requestFactory(new SimpleClientHttpRequestFactory());
}
@Bean
@ConditionalOnMissingBean
public ChromaApi chromaApi(ChromaApiProperties apiProperties, RestClient.Builder restClientBuilder,
ChromaConnectionDetails connectionDetails) {
public ChromaApi chromaApi(ChromaApiProperties apiProperties,
ObjectProvider<RestClient.Builder> restClientBuilderProvider, ChromaConnectionDetails connectionDetails) {
String chromaUrl = String.format("%s:%s", connectionDetails.getHost(), connectionDetails.getPort());
var chromaApi = new ChromaApi(chromaUrl, restClientBuilder, new ObjectMapper());
var chromaApi = new ChromaApi(chromaUrl, restClientBuilderProvider.getIfAvailable(RestClient::builder),
new ObjectMapper());
if (StringUtils.hasText(connectionDetails.getKeyToken())) {
chromaApi.withKeyToken(connectionDetails.getKeyToken());

View File

@@ -40,6 +40,7 @@ import org.springframework.util.StringUtils;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.vertexai.VertexAI;
import io.micrometer.observation.ObservationRegistry;
/**

View File

@@ -19,6 +19,7 @@ import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration
import org.springframework.ai.vertexai.palm2.VertexAiPaLm2ChatModel;
import org.springframework.ai.vertexai.palm2.VertexAiPaLm2EmbeddingModel;
import org.springframework.ai.vertexai.palm2.api.VertexAiPaLm2Api;
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;
@@ -42,10 +43,11 @@ public class VertexAiPalm2AutoConfiguration {
@ConditionalOnMissingBean
public VertexAiPaLm2Api vertexAiApi(VertexAiPalm2ConnectionProperties connectionProperties,
VertexAiPalm2EmbeddingProperties embeddingAiProperties, VertexAiPlam2ChatProperties chatProperties,
RestClient.Builder restClientBuilder) {
ObjectProvider<RestClient.Builder> restClientBuilderProvider) {
return new VertexAiPaLm2Api(connectionProperties.getBaseUrl(), connectionProperties.getApiKey(),
chatProperties.getModel(), embeddingAiProperties.getModel(), restClientBuilder);
chatProperties.getModel(), embeddingAiProperties.getModel(),
restClientBuilderProvider.getIfAvailable(RestClient::builder));
}
@Bean

View File

@@ -18,6 +18,7 @@ package org.springframework.ai.autoconfigure.watsonxai;
import org.springframework.ai.watsonx.WatsonxAiChatModel;
import org.springframework.ai.watsonx.WatsonxAiEmbeddingModel;
import org.springframework.ai.watsonx.api.WatsonxAiApi;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -45,10 +46,11 @@ public class WatsonxAiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public WatsonxAiApi watsonxApi(WatsonxAiConnectionProperties properties, RestClient.Builder restClientBuilder) {
public WatsonxAiApi watsonxApi(WatsonxAiConnectionProperties properties,
ObjectProvider<RestClient.Builder> restClientBuilderProvider) {
return new WatsonxAiApi(properties.getBaseUrl(), properties.getStreamEndpoint(), properties.getTextEndpoint(),
properties.getEmbeddingEndpoint(), properties.getProjectId(), properties.getIAMToken(),
restClientBuilder);
restClientBuilderProvider.getIfAvailable(RestClient::builder));
}
@Bean

View File

@@ -15,7 +15,8 @@
*/
package org.springframework.ai.autoconfigure.zhipuai;
import io.micrometer.observation.ObservationRegistry;
import java.util.List;
import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration;
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
@@ -41,7 +42,7 @@ import org.springframework.util.StringUtils;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestClient;
import java.util.List;
import io.micrometer.observation.ObservationRegistry;
/**
* @author Geng Rong
@@ -57,14 +58,15 @@ public class ZhiPuAiAutoConfiguration {
@ConditionalOnProperty(prefix = ZhiPuAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
matchIfMissing = true)
public ZhiPuAiChatModel zhiPuAiChatModel(ZhiPuAiConnectionProperties commonProperties,
ZhiPuAiChatProperties chatProperties, RestClient.Builder restClientBuilder,
ZhiPuAiChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackContext functionCallbackContext,
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatModelObservationConvention> observationConvention) {
var zhiPuAiApi = zhiPuAiApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
chatProperties.getApiKey(), commonProperties.getApiKey(), restClientBuilder, responseErrorHandler);
chatProperties.getApiKey(), commonProperties.getApiKey(),
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
var chatModel = new ZhiPuAiChatModel(zhiPuAiApi, chatProperties.getOptions(), functionCallbackContext,
toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));

View File

@@ -48,7 +48,7 @@ import static org.assertj.core.api.Assertions.assertThat;
"spring.ai.vectorstore.mongodb.initialize-schema=true",
"spring.ai.vectorstore.mongodb.collection-name=test_collection",
"spring.ai.vectorstore.mongodb.index-name=text_index" })
class MongoDbAtlasLocalContainerConnectionDetailsFactoryTest {
class MongoDbAtlasLocalContainerConnectionDetailsFactoryIT {
@Container
@ServiceConnection

View File

@@ -30,7 +30,7 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<artifactId>spring-webflux</artifactId>
</dependency>
<!-- TESTING -->

View File

@@ -69,10 +69,10 @@ public class ChromaApi {
}
public ChromaApi(String baseUrl, RestClient.Builder restClientBuilder, ObjectMapper objectMapper) {
Consumer<HttpHeaders> defaultHeaders = headers -> {
headers.setContentType(MediaType.APPLICATION_JSON);
};
this.restClient = restClientBuilder.baseUrl(baseUrl).defaultHeaders(defaultHeaders).build();
this.restClient = restClientBuilder.baseUrl(baseUrl)
.defaultHeaders(h -> h.setContentType(MediaType.APPLICATION_JSON))
.build();
this.objectMapper = objectMapper;
}