Refactor auto-configurations
- Split model autoconfigurations based on the model
- Change the autoconfiguration class into model specific autoconfigurations - chat, embedding, image etc.,
- Update/add tests based on this change
- Make sure the conditional logic to enable the model auto configuration is at the class level so that the configuration properties as well as the models are not enabled when the model is explicitly disabled. By default, the condition will allow enabling the beans if not explicitly overridden.
- Remove spring-ai-spring-boot-autoconfigure as a dedicated auto-configuration module
Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
This commit is contained in:
committed by
Soby Chacko
parent
32a2580635
commit
d9c40652e9
@@ -39,21 +39,18 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
|
||||
@@ -56,9 +56,11 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
ToolCallingAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ AnthropicChatProperties.class, AnthropicConnectionProperties.class })
|
||||
@ConditionalOnClass(AnthropicApi.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.ANTHROPIC,
|
||||
matchIfMissing = true)
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
ToolCallingAutoConfiguration.class, WebClientAutoConfiguration.class })
|
||||
public class AnthropicAutoConfiguration {
|
||||
public class AnthropicChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@@ -74,8 +76,6 @@ public class AnthropicAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.ANTHROPIC,
|
||||
matchIfMissing = true)
|
||||
public AnthropicChatModel anthropicChatModel(AnthropicApi anthropicApi, AnthropicChatProperties chatProperties,
|
||||
RetryTemplate retryTemplate, ToolCallingManager toolCallingManager,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
@@ -13,4 +13,4 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
org.springframework.ai.model.anthropic.autoconfigure.AnthropicAutoConfiguration
|
||||
org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration
|
||||
|
||||
@@ -39,13 +39,13 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@EnabledIfEnvironmentVariable(named = "ANTHROPIC_API_KEY", matches = ".*")
|
||||
public class AnthropicAutoConfigurationIT {
|
||||
public class AnthropicChatAutoConfigurationIT {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AnthropicAutoConfigurationIT.class);
|
||||
private static final Log logger = LogFactory.getLog(AnthropicChatAutoConfigurationIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(AnthropicChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void call() {
|
||||
@@ -25,7 +25,7 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link AnthropicAutoConfiguration}'s conditional enabling of models.
|
||||
* Unit Tests for {@link AnthropicChatAutoConfiguration}'s conditional enabling of models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@@ -33,7 +33,7 @@ public class AnthropicModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(AnthropicChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
@@ -42,7 +42,7 @@ public class AnthropicModelConfigurationTests {
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AnthropicChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public class AnthropicPropertiesTests {
|
||||
"spring.ai.anthropic.chat.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(AnthropicChatProperties.class);
|
||||
var connectionProperties = context.getBean(AnthropicConnectionProperties.class);
|
||||
@@ -80,7 +80,7 @@ public class AnthropicPropertiesTests {
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(AnthropicChatProperties.class);
|
||||
var connectionProperties = context.getBean(AnthropicConnectionProperties.class);
|
||||
@@ -104,7 +104,7 @@ public class AnthropicPropertiesTests {
|
||||
// It is enabled by default
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AnthropicChatModel.class)).isNotEmpty();
|
||||
@@ -113,7 +113,7 @@ public class AnthropicPropertiesTests {
|
||||
// Explicitly enable the chat auto-configuration.
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.anthropic.chat.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AnthropicChatModel.class)).isNotEmpty();
|
||||
@@ -122,7 +122,7 @@ public class AnthropicPropertiesTests {
|
||||
// Explicitly disable the chat auto-configuration.
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, AnthropicChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AnthropicChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
@@ -27,13 +27,12 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.model.anthropic.autoconfigure.AnthropicAutoConfiguration;
|
||||
import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration;
|
||||
import org.springframework.ai.model.anthropic.autoconfigure.tool.MockWeatherService.Request;
|
||||
import org.springframework.ai.model.anthropic.autoconfigure.tool.MockWeatherService.Response;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@@ -50,7 +49,7 @@ class FunctionCallWithFunctionBeanIT {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(AnthropicChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.AnthropicChatOptions;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.model.anthropic.autoconfigure.AnthropicAutoConfiguration;
|
||||
import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
@@ -43,7 +43,7 @@ public class FunctionCallWithPromptFunctionIT {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(AnthropicChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
|
||||
@@ -39,35 +39,30 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.model.azure.openai.autoconfigure;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Azure OpenAI.
|
||||
*
|
||||
* @author Piotr Olaszewski
|
||||
* @author Soby Chacko
|
||||
* @author Manuel Andreo Garcia
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({ AzureOpenAiAudioTranscriptionModel.class })
|
||||
@EnableConfigurationProperties(AzureOpenAiAudioTranscriptionProperties.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.AUDIO_TRANSCRIPTION_MODEL,
|
||||
havingValue = SpringAIModels.AZURE_OPENAI, matchIfMissing = true)
|
||||
@ImportAutoConfiguration(classes = AzureOpenAiClientBuilderAutoConfiguration.class)
|
||||
public class AzureOpenAiAudioTranscriptionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AzureOpenAiAudioTranscriptionModel azureOpenAiAudioTranscriptionModel(OpenAIClientBuilder openAIClient,
|
||||
AzureOpenAiAudioTranscriptionProperties audioProperties) {
|
||||
return new AzureOpenAiAudioTranscriptionModel(openAIClient.buildClient(), audioProperties.getOptions());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,195 +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.model.azure.openai.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import com.azure.core.credential.KeyCredential;
|
||||
import com.azure.core.credential.TokenCredential;
|
||||
import com.azure.core.util.ClientOptions;
|
||||
import com.azure.core.util.Header;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiImageModel;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Azure OpenAI.
|
||||
*
|
||||
* @author Piotr Olaszewski
|
||||
* @author Soby Chacko
|
||||
* @author Manuel Andreo Garcia
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { ToolCallingAutoConfiguration.class })
|
||||
@ConditionalOnClass({ OpenAIClientBuilder.class, AzureOpenAiChatModel.class })
|
||||
@EnableConfigurationProperties({ AzureOpenAiChatProperties.class, AzureOpenAiEmbeddingProperties.class,
|
||||
AzureOpenAiConnectionProperties.class, AzureOpenAiImageOptionsProperties.class,
|
||||
AzureOpenAiAudioTranscriptionProperties.class })
|
||||
@ImportAutoConfiguration(classes = { ToolCallingAutoConfiguration.class })
|
||||
public class AzureOpenAiAutoConfiguration {
|
||||
|
||||
private static final String APPLICATION_ID = "spring-ai";
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean // ({ OpenAIClient.class, TokenCredential.class })
|
||||
public OpenAIClientBuilder openAIClientBuilder(AzureOpenAiConnectionProperties connectionProperties,
|
||||
ObjectProvider<AzureOpenAIClientBuilderCustomizer> customizers) {
|
||||
|
||||
if (StringUtils.hasText(connectionProperties.getApiKey())) {
|
||||
|
||||
Assert.hasText(connectionProperties.getEndpoint(), "Endpoint must not be empty");
|
||||
|
||||
Map<String, String> customHeaders = connectionProperties.getCustomHeaders();
|
||||
List<Header> headers = customHeaders.entrySet()
|
||||
.stream()
|
||||
.map(entry -> new Header(entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
ClientOptions clientOptions = new ClientOptions().setApplicationId(APPLICATION_ID).setHeaders(headers);
|
||||
OpenAIClientBuilder clientBuilder = new OpenAIClientBuilder().endpoint(connectionProperties.getEndpoint())
|
||||
.credential(new AzureKeyCredential(connectionProperties.getApiKey()))
|
||||
.clientOptions(clientOptions);
|
||||
applyOpenAIClientBuilderCustomizers(clientBuilder, customizers);
|
||||
return clientBuilder;
|
||||
}
|
||||
|
||||
// Connect to OpenAI (e.g. not the Azure OpenAI). The deploymentName property is
|
||||
// used as OpenAI model name.
|
||||
if (StringUtils.hasText(connectionProperties.getOpenAiApiKey())) {
|
||||
OpenAIClientBuilder clientBuilder = new OpenAIClientBuilder().endpoint("https://api.openai.com/v1")
|
||||
.credential(new KeyCredential(connectionProperties.getOpenAiApiKey()))
|
||||
.clientOptions(new ClientOptions().setApplicationId(APPLICATION_ID));
|
||||
applyOpenAIClientBuilderCustomizers(clientBuilder, customizers);
|
||||
return clientBuilder;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Either API key or OpenAI API key must not be empty");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(TokenCredential.class)
|
||||
public OpenAIClientBuilder openAIClientWithTokenCredential(AzureOpenAiConnectionProperties connectionProperties,
|
||||
TokenCredential tokenCredential, ObjectProvider<AzureOpenAIClientBuilderCustomizer> customizers) {
|
||||
|
||||
Assert.notNull(tokenCredential, "TokenCredential must not be null");
|
||||
Assert.hasText(connectionProperties.getEndpoint(), "Endpoint must not be empty");
|
||||
|
||||
OpenAIClientBuilder clientBuilder = new OpenAIClientBuilder().endpoint(connectionProperties.getEndpoint())
|
||||
.credential(tokenCredential)
|
||||
.clientOptions(new ClientOptions().setApplicationId(APPLICATION_ID));
|
||||
applyOpenAIClientBuilderCustomizers(clientBuilder, customizers);
|
||||
return clientBuilder;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.AZURE_OPENAI,
|
||||
matchIfMissing = true)
|
||||
public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder,
|
||||
AzureOpenAiChatProperties chatProperties, ToolCallingManager toolCallingManager,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
|
||||
var chatModel = AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAIClientBuilder)
|
||||
.defaultOptions(chatProperties.getOptions())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.build();
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.AZURE_OPENAI,
|
||||
matchIfMissing = true)
|
||||
public AzureOpenAiEmbeddingModel azureOpenAiEmbeddingModel(OpenAIClientBuilder openAIClient,
|
||||
AzureOpenAiEmbeddingProperties embeddingProperties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
|
||||
|
||||
var embeddingModel = new AzureOpenAiEmbeddingModel(openAIClient.buildClient(),
|
||||
embeddingProperties.getMetadataMode(), embeddingProperties.getOptions(),
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
observationConvention.ifAvailable(embeddingModel::setObservationConvention);
|
||||
|
||||
return embeddingModel;
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.AZURE_OPENAI,
|
||||
matchIfMissing = true)
|
||||
public AzureOpenAiImageModel azureOpenAiImageModel(OpenAIClientBuilder openAIClientBuilder,
|
||||
AzureOpenAiImageOptionsProperties imageProperties) {
|
||||
|
||||
return new AzureOpenAiImageModel(openAIClientBuilder.buildClient(), imageProperties.getOptions());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.AUDIO_TRANSCRIPTION_MODEL,
|
||||
havingValue = SpringAIModels.AZURE_OPENAI, matchIfMissing = true)
|
||||
public AzureOpenAiAudioTranscriptionModel azureOpenAiAudioTranscriptionModel(OpenAIClientBuilder openAIClient,
|
||||
AzureOpenAiAudioTranscriptionProperties audioProperties) {
|
||||
return new AzureOpenAiAudioTranscriptionModel(openAIClient.buildClient(), audioProperties.getOptions());
|
||||
}
|
||||
|
||||
private void applyOpenAIClientBuilderCustomizers(OpenAIClientBuilder clientBuilder,
|
||||
ObjectProvider<AzureOpenAIClientBuilderCustomizer> customizers) {
|
||||
customizers.orderedStream().forEach(customizer -> customizer.customize(clientBuilder));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
|
||||
DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
|
||||
manager.setApplicationContext(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2024-2024 the original author or authors.
|
||||
* 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.
|
||||
@@ -14,73 +14,59 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.converse;
|
||||
package org.springframework.ai.model.azure.openai.autoconfigure;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
|
||||
import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient;
|
||||
import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient;
|
||||
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionConfiguration;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionProperties;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Bedrock Converse Proxy Chat Client.
|
||||
* {@link AutoConfiguration Auto-configuration} for Azure OpenAI.
|
||||
*
|
||||
* Leverages the Spring Cloud AWS to resolve the {@link AwsCredentialsProvider}.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Wei Jiang
|
||||
* @author Piotr Olaszewski
|
||||
* @author Soby Chacko
|
||||
* @author Manuel Andreo Garcia
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { ToolCallingAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ BedrockConverseProxyChatProperties.class, BedrockAwsConnectionConfiguration.class })
|
||||
@ConditionalOnClass({ BedrockProxyChatModel.class, BedrockRuntimeClient.class, BedrockRuntimeAsyncClient.class })
|
||||
@ConditionalOnProperty(prefix = BedrockConverseProxyChatProperties.CONFIG_PREFIX, name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
@Import(BedrockAwsConnectionConfiguration.class)
|
||||
@ImportAutoConfiguration({ ToolCallingAutoConfiguration.class })
|
||||
public class BedrockConverseProxyChatAutoConfiguration {
|
||||
@ConditionalOnClass({ AzureOpenAiChatModel.class })
|
||||
@EnableConfigurationProperties({ AzureOpenAiChatProperties.class })
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.AZURE_OPENAI,
|
||||
matchIfMissing = true)
|
||||
@ImportAutoConfiguration(
|
||||
classes = { AzureOpenAiClientBuilderAutoConfiguration.class, ToolCallingAutoConfiguration.class })
|
||||
public class AzureOpenAiChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class })
|
||||
public BedrockProxyChatModel bedrockProxyChatModel(AwsCredentialsProvider credentialsProvider,
|
||||
AwsRegionProvider regionProvider, BedrockAwsConnectionProperties connectionProperties,
|
||||
BedrockConverseProxyChatProperties chatProperties, ToolCallingManager toolCallingManager,
|
||||
public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder,
|
||||
AzureOpenAiChatProperties chatProperties, ToolCallingManager toolCallingManager,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention,
|
||||
ObjectProvider<BedrockRuntimeClient> bedrockRuntimeClient,
|
||||
ObjectProvider<BedrockRuntimeAsyncClient> bedrockRuntimeAsyncClient) {
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
|
||||
var chatModel = BedrockProxyChatModel.builder()
|
||||
.credentialsProvider(credentialsProvider)
|
||||
.region(regionProvider.getRegion())
|
||||
.timeout(connectionProperties.getTimeout())
|
||||
var chatModel = AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAIClientBuilder)
|
||||
.defaultOptions(chatProperties.getOptions())
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.bedrockRuntimeClient(bedrockRuntimeClient.getIfAvailable())
|
||||
.bedrockRuntimeAsyncClient(bedrockRuntimeAsyncClient.getIfAvailable())
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.build();
|
||||
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
@@ -26,27 +26,13 @@ import com.azure.core.credential.KeyCredential;
|
||||
import com.azure.core.credential.TokenCredential;
|
||||
import com.azure.core.util.ClientOptions;
|
||||
import com.azure.core.util.Header;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiImageModel;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -59,13 +45,10 @@ import org.springframework.util.StringUtils;
|
||||
* @author Manuel Andreo Garcia
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { ToolCallingAutoConfiguration.class })
|
||||
@ConditionalOnClass({ OpenAIClientBuilder.class, AzureOpenAiChatModel.class })
|
||||
@EnableConfigurationProperties({ AzureOpenAiChatProperties.class, AzureOpenAiEmbeddingProperties.class,
|
||||
AzureOpenAiConnectionProperties.class, AzureOpenAiImageOptionsProperties.class,
|
||||
AzureOpenAiAudioTranscriptionProperties.class })
|
||||
@ImportAutoConfiguration(classes = { ToolCallingAutoConfiguration.class })
|
||||
public class AzureOpenAiAutoConfiguration {
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({ OpenAIClientBuilder.class })
|
||||
@EnableConfigurationProperties(AzureOpenAiConnectionProperties.class)
|
||||
public class AzureOpenAiClientBuilderAutoConfiguration {
|
||||
|
||||
private static final String APPLICATION_ID = "spring-ai";
|
||||
|
||||
@@ -120,71 +103,6 @@ public class AzureOpenAiAutoConfiguration {
|
||||
return clientBuilder;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = AzureOpenAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder,
|
||||
AzureOpenAiChatProperties chatProperties, ToolCallingManager toolCallingManager,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
|
||||
var chatModel = AzureOpenAiChatModel.builder()
|
||||
.openAIClientBuilder(openAIClientBuilder)
|
||||
.defaultOptions(chatProperties.getOptions())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.build();
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = AzureOpenAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public AzureOpenAiEmbeddingModel azureOpenAiEmbeddingModel(OpenAIClientBuilder openAIClient,
|
||||
AzureOpenAiEmbeddingProperties embeddingProperties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
|
||||
|
||||
var embeddingModel = new AzureOpenAiEmbeddingModel(openAIClient.buildClient(),
|
||||
embeddingProperties.getMetadataMode(), embeddingProperties.getOptions(),
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
observationConvention.ifAvailable(embeddingModel::setObservationConvention);
|
||||
|
||||
return embeddingModel;
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
|
||||
DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
|
||||
manager.setApplicationContext(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = AzureOpenAiImageOptionsProperties.CONFIG_PREFIX, name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public AzureOpenAiImageModel azureOpenAiImageClient(OpenAIClientBuilder openAIClientBuilder,
|
||||
AzureOpenAiImageOptionsProperties imageProperties) {
|
||||
|
||||
return new AzureOpenAiImageModel(openAIClientBuilder.buildClient(), imageProperties.getOptions());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = AzureOpenAiAudioTranscriptionProperties.CONFIG_PREFIX, name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public AzureOpenAiAudioTranscriptionModel azureOpenAiAudioTranscriptionModel(OpenAIClientBuilder openAIClient,
|
||||
AzureOpenAiAudioTranscriptionProperties audioProperties) {
|
||||
return new AzureOpenAiAudioTranscriptionModel(openAIClient.buildClient(), audioProperties.getOptions());
|
||||
}
|
||||
|
||||
private void applyOpenAIClientBuilderCustomizers(OpenAIClientBuilder clientBuilder,
|
||||
ObjectProvider<AzureOpenAIClientBuilderCustomizer> customizers) {
|
||||
customizers.orderedStream().forEach(customizer -> customizer.customize(clientBuilder));
|
||||
@@ -14,16 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.transformers.autoconfigure;
|
||||
package org.springframework.ai.model.azure.openai.autoconfigure;
|
||||
|
||||
import ai.djl.huggingface.tokenizers.HuggingFaceTokenizer;
|
||||
import ai.onnxruntime.OrtSession;
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.transformers.TransformersEmbeddingModel;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
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;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -31,41 +33,35 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Transformers Embedding Model.
|
||||
* {@link AutoConfiguration Auto-configuration} for Azure OpenAI.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Piotr Olaszewski
|
||||
* @author Soby Chacko
|
||||
* @author Manuel Andreo Garcia
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableConfigurationProperties({ TransformersEmbeddingModelProperties.class })
|
||||
@ConditionalOnClass({ OrtSession.class, HuggingFaceTokenizer.class, TransformersEmbeddingModel.class })
|
||||
public class TransformersEmbeddingModelAutoConfiguration {
|
||||
@ConditionalOnClass({ AzureOpenAiEmbeddingModel.class })
|
||||
@EnableConfigurationProperties({ AzureOpenAiEmbeddingProperties.class })
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.AZURE_OPENAI,
|
||||
matchIfMissing = true)
|
||||
@ImportAutoConfiguration(classes = AzureOpenAiClientBuilderAutoConfiguration.class)
|
||||
public class AzureOpenAiEmbeddingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = TransformersEmbeddingModelProperties.CONFIG_PREFIX, name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public TransformersEmbeddingModel embeddingModel(TransformersEmbeddingModelProperties properties,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
public AzureOpenAiEmbeddingModel azureOpenAiEmbeddingModel(OpenAIClientBuilder openAIClient,
|
||||
AzureOpenAiEmbeddingProperties embeddingProperties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
|
||||
|
||||
TransformersEmbeddingModel embeddingModel = new TransformersEmbeddingModel(properties.getMetadataMode(),
|
||||
var embeddingModel = new AzureOpenAiEmbeddingModel(openAIClient.buildClient(),
|
||||
embeddingProperties.getMetadataMode(), embeddingProperties.getOptions(),
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
embeddingModel.setDisableCaching(!properties.getCache().isEnabled());
|
||||
embeddingModel.setResourceCacheDirectory(properties.getCache().getDirectory());
|
||||
|
||||
embeddingModel.setTokenizerResource(properties.getTokenizer().getUri());
|
||||
embeddingModel.setTokenizerOptions(properties.getTokenizer().getOptions());
|
||||
|
||||
embeddingModel.setModelResource(properties.getOnnx().getModelUri());
|
||||
|
||||
embeddingModel.setGpuDeviceId(properties.getOnnx().getGpuDeviceId());
|
||||
|
||||
embeddingModel.setModelOutputName(properties.getOnnx().getModelOutputName());
|
||||
|
||||
observationConvention.ifAvailable(embeddingModel::setObservationConvention);
|
||||
|
||||
return embeddingModel;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.model.azure.openai.autoconfigure;
|
||||
|
||||
import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiImageModel;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Azure OpenAI.
|
||||
*
|
||||
* @author Piotr Olaszewski
|
||||
* @author Soby Chacko
|
||||
* @author Manuel Andreo Garcia
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(AzureOpenAiImageModel.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.AZURE_OPENAI,
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties(AzureOpenAiImageOptionsProperties.class)
|
||||
@ImportAutoConfiguration(classes = AzureOpenAiClientBuilderAutoConfiguration.class)
|
||||
public class AzureOpenAiImageAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AzureOpenAiImageModel azureOpenAiImageModel(OpenAIClientBuilder openAIClientBuilder,
|
||||
AzureOpenAiImageOptionsProperties imageProperties) {
|
||||
|
||||
return new AzureOpenAiImageModel(openAIClientBuilder.buildClient(), imageProperties.getOptions());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,4 +13,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration
|
||||
org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration
|
||||
org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingAutoConfiguration
|
||||
org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiImageAutoConfiguration
|
||||
org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAudioTranscriptionAutoConfiguration
|
||||
|
||||
@@ -35,11 +35,8 @@ import com.azure.core.http.HttpResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAIClientBuilderCustomizer;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||
@@ -86,7 +83,7 @@ class AzureOpenAiAutoConfigurationIT {
|
||||
"spring.ai.azure.openai.embedding.options.deployment-name=" + EMBEDDING_MODEL_NAME,
|
||||
"spring.ai.azure.openai.audio.transcription.options.deployment-name=" + System.getenv("AZURE_OPENAI_TRANSCRIPTION_DEPLOYMENT_NAME")
|
||||
// @formatter:on
|
||||
).withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class));
|
||||
);
|
||||
|
||||
private final Message systemMessage = new SystemPromptTemplate("""
|
||||
You are a helpful AI assistant. Your name is {name}.
|
||||
@@ -100,16 +97,17 @@ class AzureOpenAiAutoConfigurationIT {
|
||||
|
||||
@Test
|
||||
void chatCompletion() {
|
||||
this.contextRunner.run(context -> {
|
||||
AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class);
|
||||
ChatResponse response = chatModel.call(new Prompt(List.of(this.userMessage, this.systemMessage)));
|
||||
assertThat(response.getResult().getOutput().getText()).contains("Blackbeard");
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class);
|
||||
ChatResponse response = chatModel.call(new Prompt(List.of(this.userMessage, this.systemMessage)));
|
||||
assertThat(response.getResult().getOutput().getText()).contains("Blackbeard");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void httpRequestContainsUserAgentAndCustomHeaders() {
|
||||
this.contextRunner
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.azure.openai.custom-headers.foo=bar",
|
||||
"spring.ai.azure.openai.custom-headers.fizz=buzz")
|
||||
.run(context -> {
|
||||
@@ -136,100 +134,137 @@ class AzureOpenAiAutoConfigurationIT {
|
||||
|
||||
@Test
|
||||
void chatCompletionStreaming() {
|
||||
this.contextRunner.run(context -> {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
|
||||
AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class);
|
||||
AzureOpenAiChatModel chatModel = context.getBean(AzureOpenAiChatModel.class);
|
||||
|
||||
Flux<ChatResponse> response = chatModel.stream(new Prompt(List.of(this.userMessage, this.systemMessage)));
|
||||
Flux<ChatResponse> response = chatModel
|
||||
.stream(new Prompt(List.of(this.userMessage, this.systemMessage)));
|
||||
|
||||
List<ChatResponse> responses = response.collectList().block();
|
||||
assertThat(responses.size()).isGreaterThan(10);
|
||||
List<ChatResponse> responses = response.collectList().block();
|
||||
assertThat(responses.size()).isGreaterThan(10);
|
||||
|
||||
String stitchedResponseContent = responses.stream()
|
||||
.map(ChatResponse::getResults)
|
||||
.flatMap(List::stream)
|
||||
.map(Generation::getOutput)
|
||||
.map(AssistantMessage::getText)
|
||||
.collect(Collectors.joining());
|
||||
String stitchedResponseContent = responses.stream()
|
||||
.map(ChatResponse::getResults)
|
||||
.flatMap(List::stream)
|
||||
.map(Generation::getOutput)
|
||||
.map(AssistantMessage::getText)
|
||||
.collect(Collectors.joining());
|
||||
|
||||
assertThat(stitchedResponseContent).contains("Blackbeard");
|
||||
});
|
||||
assertThat(stitchedResponseContent).contains("Blackbeard");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embedding() {
|
||||
this.contextRunner.run(context -> {
|
||||
AzureOpenAiEmbeddingModel embeddingModel = context.getBean(AzureOpenAiEmbeddingModel.class);
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
AzureOpenAiEmbeddingModel embeddingModel = context.getBean(AzureOpenAiEmbeddingModel.class);
|
||||
|
||||
EmbeddingResponse embeddingResponse = embeddingModel
|
||||
.embedForResponse(List.of("Hello World", "World is big and salvation is near"));
|
||||
assertThat(embeddingResponse.getResults()).hasSize(2);
|
||||
assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0);
|
||||
assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1);
|
||||
EmbeddingResponse embeddingResponse = embeddingModel
|
||||
.embedForResponse(List.of("Hello World", "World is big and salvation is near"));
|
||||
assertThat(embeddingResponse.getResults()).hasSize(2);
|
||||
assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0);
|
||||
assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1);
|
||||
|
||||
assertThat(embeddingModel.dimensions()).isEqualTo(1536);
|
||||
});
|
||||
|
||||
assertThat(embeddingModel.dimensions()).isEqualTo(1536);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_TRANSCRIPTION_DEPLOYMENT_NAME", matches = ".+")
|
||||
void transcribe() {
|
||||
this.contextRunner.run(context -> {
|
||||
AzureOpenAiAudioTranscriptionModel transcriptionModel = context
|
||||
.getBean(AzureOpenAiAudioTranscriptionModel.class);
|
||||
Resource audioFile = new ClassPathResource("/speech/jfk.flac");
|
||||
String response = transcriptionModel.call(audioFile);
|
||||
assertThat(response).isEqualTo(
|
||||
"And so my fellow Americans, ask not what your country can do for you, ask what you can do for your country.");
|
||||
});
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
AzureOpenAiAudioTranscriptionModel transcriptionModel = context
|
||||
.getBean(AzureOpenAiAudioTranscriptionModel.class);
|
||||
Resource audioFile = new ClassPathResource("/speech/jfk.flac");
|
||||
String response = transcriptionModel.call(audioFile);
|
||||
assertThat(response).isEqualTo(
|
||||
"And so my fellow Americans, ask not what your country can do for you, ask what you can do for your country.");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void chatActivation() {
|
||||
|
||||
// Disable the chat auto-configuration.
|
||||
this.contextRunner.withPropertyValues("spring.ai.azure.openai.chat.enabled=false")
|
||||
.run(context -> assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty());
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
// The chat auto-configuration is enabled by default.
|
||||
this.contextRunner.run(context -> assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty());
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
// Explicitly enable the chat auto-configuration.
|
||||
this.contextRunner.withPropertyValues("spring.ai.azure.openai.chat.enabled=true")
|
||||
.run(context -> assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty());
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=azure-openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embeddingActivation() {
|
||||
|
||||
// Disable the embedding auto-configuration.
|
||||
this.contextRunner.withPropertyValues("spring.ai.azure.openai.embedding.enabled=false")
|
||||
.run(context -> assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty());
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isEmpty();
|
||||
});
|
||||
|
||||
// The embedding auto-configuration is enabled by default.
|
||||
this.contextRunner
|
||||
.run(context -> assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty());
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
// Explicitly enable the embedding auto-configuration.
|
||||
this.contextRunner.withPropertyValues("spring.ai.azure.openai.embedding.enabled=true")
|
||||
.run(context -> assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty());
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=azure-openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void audioTranscriptionActivation() {
|
||||
|
||||
// Disable the transcription auto-configuration.
|
||||
this.contextRunner.withPropertyValues("spring.ai.azure.openai.audio.transcription.enabled=false")
|
||||
.run(context -> assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty());
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.audio.transcription=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isEmpty();
|
||||
});
|
||||
|
||||
// The transcription auto-configuration is enabled by default.
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty());
|
||||
|
||||
// Explicitly enable the transcription auto-configuration.
|
||||
this.contextRunner.withPropertyValues("spring.ai.azure.openai.audio.transcription.enabled=true")
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.audio.transcription=azure-openai")
|
||||
.run(context -> assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty());
|
||||
}
|
||||
|
||||
@@ -237,7 +272,7 @@ class AzureOpenAiAutoConfigurationIT {
|
||||
void openAIClientBuilderCustomizer() {
|
||||
AtomicBoolean firstCustomizationApplied = new AtomicBoolean(false);
|
||||
AtomicBoolean secondCustomizationApplied = new AtomicBoolean(false);
|
||||
this.contextRunner
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.withBean("first", AzureOpenAIClientBuilderCustomizer.class,
|
||||
() -> clientBuilder -> firstCustomizationApplied.set(true))
|
||||
.withBean("second", AzureOpenAIClientBuilderCustomizer.class,
|
||||
|
||||
@@ -18,10 +18,6 @@ package org.springframework.ai.model.azure.openai.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatProperties;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiConnectionProperties;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiEmbeddingProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
@@ -40,7 +36,7 @@ public class AzureOpenAiAutoConfigurationPropertyTests {
|
||||
.withPropertyValues("spring.ai.azure.openai.api-key=TEST_API_KEY",
|
||||
"spring.ai.azure.openai.endpoint=TEST_ENDPOINT",
|
||||
"spring.ai.azure.openai.embedding.options.deployment-name=MODEL_XYZ")
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(AzureOpenAiEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(AzureOpenAiConnectionProperties.class);
|
||||
@@ -72,7 +68,8 @@ public class AzureOpenAiAutoConfigurationPropertyTests {
|
||||
"spring.ai.azure.openai.chat.options.user=userXYZ"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class,
|
||||
AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(AzureOpenAiChatProperties.class);
|
||||
var connectionProperties = context.getBean(AzureOpenAiConnectionProperties.class);
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
@@ -60,7 +59,9 @@ public class AzureOpenAiDirectOpenAiAutoConfigurationIT {
|
||||
"spring.ai.azure.openai.chat.options.maxTokens=123",
|
||||
"spring.ai.azure.openai.embedding.options.deployment-name=" + EMBEDDING_MODEL_NAME
|
||||
// @formatter:on
|
||||
).withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class));
|
||||
)
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class,
|
||||
AzureOpenAiEmbeddingAutoConfiguration.class));
|
||||
|
||||
private final Message systemMessage = new SystemPromptTemplate("""
|
||||
You are a helpful AI assistant. Your name is {name}.
|
||||
|
||||
@@ -29,38 +29,42 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link AzureOpenAiAutoConfiguration}'s conditional enabling of models.
|
||||
* Unit Tests for Azure OpenAI auto-configurations conditional enabling of models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".*")
|
||||
public class AzureOpenAiModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.azure.openai.openai-api-key=" + System.getenv("OPENAI_API_KEY"),
|
||||
"spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class));
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().withPropertyValues(
|
||||
"spring.ai.azure.openai.openai-api-key=" + System.getenv("OPENAI_API_KEY"),
|
||||
"spring.ai.openai.base-url=TEST_BASE_URL");
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=azure-openai").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=azure-openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=azure-openai", "spring.ai.model.embedding=none",
|
||||
"spring.ai.model.image=none", "spring.ai.model.audio.speech=none",
|
||||
"spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none")
|
||||
@@ -74,24 +78,29 @@ public class AzureOpenAiModelConfigurationTests {
|
||||
|
||||
@Test
|
||||
void embeddingModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=azure-openai").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=azure-openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=azure-openai",
|
||||
"spring.ai.model.image=none", "spring.ai.model.audio.speech=none",
|
||||
"spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none")
|
||||
@@ -105,24 +114,29 @@ public class AzureOpenAiModelConfigurationTests {
|
||||
|
||||
@Test
|
||||
void imageModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.image=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.image=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.image=azure-openai").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.image=azure-openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(AzureOpenAiImageAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none",
|
||||
"spring.ai.model.image=azure-openai", "spring.ai.model.audio.speech=none",
|
||||
"spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none")
|
||||
@@ -136,24 +150,33 @@ public class AzureOpenAiModelConfigurationTests {
|
||||
|
||||
@Test
|
||||
void audioTranscriptionModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.audio.transcription=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.audio.transcription=azure-openai").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.audio.transcription=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.audio.transcription=azure-openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AzureOpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none",
|
||||
"spring.ai.model.image=none", "spring.ai.model.audio.speech=none",
|
||||
"spring.ai.model.audio.transcription=azure-openai", "spring.ai.model.moderation=none")
|
||||
|
||||
@@ -24,13 +24,13 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@@ -51,7 +51,7 @@ class FunctionCallWithFunctionBeanIT {
|
||||
"spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"),
|
||||
"spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT"))
|
||||
// @formatter:onn
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -23,12 +23,12 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
@@ -49,7 +49,7 @@ public class FunctionCallWithFunctionWrapperIT {
|
||||
"spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"),
|
||||
"spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT"))
|
||||
// @formatter:onn
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -23,12 +23,12 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatModel;
|
||||
import org.springframework.ai.azure.openai.AzureOpenAiChatOptions;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.azure.openai.autoconfigure.AzureOpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@@ -46,7 +46,7 @@ public class FunctionCallWithPromptFunctionIT {
|
||||
"spring.ai.azure.openai.api-key=" + System.getenv("AZURE_OPENAI_API_KEY"),
|
||||
"spring.ai.azure.openai.endpoint=" + System.getenv("AZURE_OPENAI_ENDPOINT"))
|
||||
// @formatter:onn
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(AzureOpenAiChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
|
||||
@@ -46,35 +46,30 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024-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.model.bedrock.autoconfigure.converse;
|
||||
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Configuration properties for Bedrock Converse.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(BedrockConverseProxyChatProperties.CONFIG_PREFIX)
|
||||
public class BedrockConverseProxyChatProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.bedrock.converse.chat";
|
||||
|
||||
/**
|
||||
* Enable Bedrock Converse chat model.
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private ToolCallingChatOptions options = ToolCallingChatOptions.builder()
|
||||
.temperature(0.7)
|
||||
.maxTokens(300)
|
||||
.topK(10)
|
||||
.build();
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public ToolCallingChatOptions getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public void setOptions(ToolCallingChatOptions options) {
|
||||
Assert.notNull(options, "FunctionCallingOptions must not be null");
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +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.model.bedrock.autoconfigure.titan;
|
||||
|
||||
import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel.InputType;
|
||||
import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi.TitanEmbeddingModel;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Bedrock Titan Embedding autoconfiguration properties.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @since 0.8.0
|
||||
*/
|
||||
@ConfigurationProperties(BedrockTitanEmbeddingProperties.CONFIG_PREFIX)
|
||||
public class BedrockTitanEmbeddingProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.bedrock.titan.embedding";
|
||||
|
||||
/**
|
||||
* Enable Bedrock Titan Embedding Model. False by default.
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* Bedrock Titan Embedding generative name. Defaults to 'amazon.titan-embed-image-v1'.
|
||||
*/
|
||||
private String model = TitanEmbeddingModel.TITAN_EMBED_IMAGE_V1.id();
|
||||
|
||||
/**
|
||||
* Titan Embedding API input types. Could be either text or image (encoded in base64).
|
||||
* Defaults to {@link InputType#IMAGE}.
|
||||
*/
|
||||
private InputType inputType = InputType.IMAGE;
|
||||
|
||||
public static String getConfigPrefix() {
|
||||
return CONFIG_PREFIX;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getModel() {
|
||||
return this.model;
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public InputType getInputType() {
|
||||
return this.inputType;
|
||||
}
|
||||
|
||||
public void setInputType(InputType inputType) {
|
||||
this.inputType = inputType;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.cohere;
|
||||
package org.springframework.ai.model.bedrock.cohere.autoconfigure;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.cohere;
|
||||
package org.springframework.ai.model.bedrock.cohere.autoconfigure;
|
||||
|
||||
import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingOptions;
|
||||
import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingModel;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.converse;
|
||||
package org.springframework.ai.model.bedrock.converse.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.converse;
|
||||
package org.springframework.ai.model.bedrock.converse.autoconfigure;
|
||||
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.titan;
|
||||
package org.springframework.ai.model.bedrock.titan.autoconfigure;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.titan;
|
||||
package org.springframework.ai.model.bedrock.titan.autoconfigure;
|
||||
|
||||
import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel.InputType;
|
||||
import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi.TitanEmbeddingModel;
|
||||
@@ -13,6 +13,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
cohere.org.springframework.ai.model.bedrock.autoconfigure.BedrockCohereEmbeddingAutoConfiguration
|
||||
titan.org.springframework.ai.model.bedrock.autoconfigure.BedrockTitanEmbeddingAutoConfiguration
|
||||
converse.org.springframework.ai.model.bedrock.autoconfigure.BedrockConverseProxyChatAutoConfiguration
|
||||
org.springframework.ai.model.bedrock.cohere.autoconfigure.BedrockCohereEmbeddingAutoConfiguration
|
||||
org.springframework.ai.model.bedrock.titan.autoconfigure.BedrockTitanEmbeddingAutoConfiguration
|
||||
org.springframework.ai.model.bedrock.converse.autoconfigure.BedrockConverseProxyChatAutoConfiguration
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.model.bedrock.autoconfigure.converse.tool;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration;
|
||||
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
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 org.springframework.context.annotation.Description;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@RequiresAwsCredentials
|
||||
class FunctionCallWithFunctionBeanIT {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionBeanIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = BedrockTestUtils.getContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
|
||||
this.contextRunner
|
||||
.withPropertyValues(
|
||||
"spring.ai.bedrock.converse.chat.options.model=" + "anthropic.claude-3-5-sonnet-20240620-v1:0")
|
||||
.run(context -> {
|
||||
|
||||
BedrockProxyChatModel chatModel = context.getBean(BedrockProxyChatModel.class);
|
||||
|
||||
var userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius.");
|
||||
|
||||
ChatResponse response = chatModel.call(new Prompt(List.of(userMessage),
|
||||
ToolCallingChatOptions.builder().toolNames("weatherFunction").build()));
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15");
|
||||
|
||||
response = chatModel.call(new Prompt(List.of(userMessage),
|
||||
ToolCallingChatOptions.builder().toolNames("weatherFunction3").build()));
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
assertThat(response.getResult().getOutput().getText()).contains("30", "10", "15");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionStreamTest() {
|
||||
|
||||
this.contextRunner
|
||||
.withPropertyValues(
|
||||
"spring.ai.bedrock.converse.chat.options.model=" + "anthropic.claude-3-5-sonnet-20240620-v1:0")
|
||||
.run(context -> {
|
||||
|
||||
BedrockProxyChatModel chatModel = context.getBean(BedrockProxyChatModel.class);
|
||||
|
||||
var userMessage = new UserMessage(
|
||||
"What's the weather like in San Francisco, in Paris, France and in Tokyo, Japan? Return the temperature in Celsius.");
|
||||
|
||||
Flux<ChatResponse> responses = chatModel.stream(new Prompt(List.of(userMessage),
|
||||
ToolCallingChatOptions.builder().toolNames("weatherFunction").build()));
|
||||
|
||||
String content = responses.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
.filter(cr -> cr.getResult() != null)
|
||||
.map(cr -> cr.getResult().getOutput().getText())
|
||||
.collect(Collectors.joining());
|
||||
|
||||
logger.info("Response: {}", content);
|
||||
assertThat(content).contains("30", "10", "15");
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
@Description("Get the weather in location. Return temperature in 36°F or 36°C format.")
|
||||
public Function<MockWeatherService.Request, MockWeatherService.Response> weatherFunction() {
|
||||
return new MockWeatherService();
|
||||
}
|
||||
|
||||
// Relies on the Request's JsonClassDescription annotation to provide the
|
||||
// function description.
|
||||
@Bean
|
||||
public Function<MockWeatherService.Request, MockWeatherService.Response> weatherFunction3() {
|
||||
MockWeatherService weatherService = new MockWeatherService();
|
||||
return (weatherService::apply);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,95 +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.model.bedrock.autoconfigure.converse.tool;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonClassDescription;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
|
||||
|
||||
/**
|
||||
* Mock 3rd party weather service.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
*/
|
||||
public class MockWeatherService implements Function<MockWeatherService.Request, MockWeatherService.Response> {
|
||||
|
||||
@Override
|
||||
public Response apply(Request request) {
|
||||
|
||||
double temperature = 0;
|
||||
if (request.location().contains("Paris")) {
|
||||
temperature = 15;
|
||||
}
|
||||
else if (request.location().contains("Tokyo")) {
|
||||
temperature = 10;
|
||||
}
|
||||
else if (request.location().contains("San Francisco")) {
|
||||
temperature = 30;
|
||||
}
|
||||
|
||||
return new Response(temperature, 15, 20, 2, 53, 45, Unit.C);
|
||||
}
|
||||
|
||||
/**
|
||||
* Temperature units.
|
||||
*/
|
||||
public enum Unit {
|
||||
|
||||
/**
|
||||
* Celsius.
|
||||
*/
|
||||
C("metric"),
|
||||
/**
|
||||
* Fahrenheit.
|
||||
*/
|
||||
F("imperial");
|
||||
|
||||
/**
|
||||
* Human readable unit name.
|
||||
*/
|
||||
public final String unitName;
|
||||
|
||||
Unit(String text) {
|
||||
this.unitName = text;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Weather Function request.
|
||||
*/
|
||||
@JsonInclude(Include.NON_NULL)
|
||||
@JsonClassDescription("Weather API request")
|
||||
public record Request(@JsonProperty(required = true,
|
||||
value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location,
|
||||
@JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Weather Function response.
|
||||
*/
|
||||
public record Response(double temp, double feels_like, double temp_min, double temp_max, int pressure, int humidity,
|
||||
Unit unit) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.cohere;
|
||||
package org.springframework.ai.model.bedrock.cohere.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -14,15 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.cohere;
|
||||
package org.springframework.ai.model.bedrock.cohere.autoconfigure;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingModel;
|
||||
import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.titan.BedrockTitanEmbeddingAutoConfiguration;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.titan.BedrockTitanEmbeddingProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.converse;
|
||||
package org.springframework.ai.model.bedrock.converse.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -25,9 +25,8 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for
|
||||
* {@link org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration}'s
|
||||
* conditional enabling of models.
|
||||
* Unit Tests for {@link BedrockConverseProxyChatAutoConfiguration}'s conditional enabling
|
||||
* of models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.converse;
|
||||
package org.springframework.ai.model.bedrock.converse.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.converse;
|
||||
package org.springframework.ai.model.bedrock.converse.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.converse.tool;
|
||||
package org.springframework.ai.model.bedrock.converse.autoconfigure.tool;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
@@ -27,7 +27,7 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration;
|
||||
import org.springframework.ai.model.bedrock.converse.autoconfigure.BedrockConverseProxyChatAutoConfiguration;
|
||||
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.converse.tool;
|
||||
package org.springframework.ai.model.bedrock.converse.autoconfigure.tool;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockTestUtils;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.RequiresAwsCredentials;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration;
|
||||
import org.springframework.ai.model.bedrock.converse.autoconfigure.BedrockConverseProxyChatAutoConfiguration;
|
||||
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.anthropic.autoconfigure.tool;
|
||||
package org.springframework.ai.model.bedrock.converse.autoconfigure.tool;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.titan;
|
||||
package org.springframework.ai.model.bedrock.titan.autoconfigure;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
@@ -14,15 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.bedrock.autoconfigure.titan;
|
||||
package org.springframework.ai.model.bedrock.titan.autoconfigure;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
|
||||
import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatAutoConfiguration;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.converse.BedrockConverseProxyChatProperties;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
|
||||
@@ -29,12 +29,12 @@ import org.springframework.context.annotation.Bean;
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(HuggingfaceChatModel.class)
|
||||
@EnableConfigurationProperties(HuggingfaceChatProperties.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.HUGGINGFACE,
|
||||
matchIfMissing = true)
|
||||
public class HuggingfaceChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.HUGGINGFACE,
|
||||
matchIfMissing = true)
|
||||
public HuggingfaceChatModel huggingfaceChatModel(HuggingfaceChatProperties huggingfaceChatProperties) {
|
||||
return new HuggingfaceChatModel(huggingfaceChatProperties.getApiKey(), huggingfaceChatProperties.getUrl());
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class HuggingfaceModelConfigurationTests {
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(HuggingfaceChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(HuggingfaceChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(HuggingfaceChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
|
||||
@@ -39,28 +39,24 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
|
||||
@@ -20,17 +20,15 @@ import java.util.List;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi;
|
||||
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.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@@ -47,20 +45,20 @@ import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for MiniMax Chat and Embedding Models.
|
||||
* {@link AutoConfiguration Auto-configuration} for MiniMax Chat Model.
|
||||
*
|
||||
* @author Geng Rong
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class })
|
||||
@ConditionalOnClass(MiniMaxApi.class)
|
||||
@EnableConfigurationProperties({ MiniMaxConnectionProperties.class, MiniMaxChatProperties.class,
|
||||
MiniMaxEmbeddingProperties.class })
|
||||
public class MiniMaxAutoConfiguration {
|
||||
@EnableConfigurationProperties({ MiniMaxConnectionProperties.class, MiniMaxChatProperties.class })
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MINIMAX,
|
||||
matchIfMissing = true)
|
||||
public class MiniMaxChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MINIMAX,
|
||||
matchIfMissing = true)
|
||||
public MiniMaxChatModel miniMaxChatModel(MiniMaxConnectionProperties commonProperties,
|
||||
MiniMaxChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver,
|
||||
@@ -79,29 +77,6 @@ public class MiniMaxAutoConfiguration {
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MINIMAX,
|
||||
matchIfMissing = true)
|
||||
public MiniMaxEmbeddingModel miniMaxEmbeddingModel(MiniMaxConnectionProperties commonProperties,
|
||||
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(),
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
|
||||
|
||||
var embeddingModel = new MiniMaxEmbeddingModel(miniMaxApi, embeddingProperties.getMetadataMode(),
|
||||
embeddingProperties.getOptions(), retryTemplate,
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
observationConvention.ifAvailable(embeddingModel::setObservationConvention);
|
||||
|
||||
return embeddingModel;
|
||||
}
|
||||
|
||||
private MiniMaxApi miniMaxApi(String baseUrl, String commonBaseUrl, String apiKey, String commonApiKey,
|
||||
RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
@@ -16,19 +16,14 @@
|
||||
|
||||
package org.springframework.ai.model.minimax.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
|
||||
import org.springframework.ai.minimax.api.MiniMaxApi;
|
||||
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.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@@ -36,7 +31,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -45,42 +39,20 @@ import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for MiniMax Chat and Embedding Models.
|
||||
* {@link AutoConfiguration Auto-configuration} for MiniMax Embedding Model.
|
||||
*
|
||||
* @author Geng Rong
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class })
|
||||
@ConditionalOnClass(MiniMaxApi.class)
|
||||
@EnableConfigurationProperties({ MiniMaxConnectionProperties.class, MiniMaxChatProperties.class,
|
||||
MiniMaxEmbeddingProperties.class })
|
||||
public class MiniMaxAutoConfiguration {
|
||||
@EnableConfigurationProperties({ MiniMaxConnectionProperties.class, MiniMaxEmbeddingProperties.class })
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MINIMAX,
|
||||
matchIfMissing = true)
|
||||
public class MiniMaxEmbeddingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = MiniMaxChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public MiniMaxChatModel miniMaxChatModel(MiniMaxConnectionProperties commonProperties,
|
||||
MiniMaxChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver,
|
||||
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
|
||||
var miniMaxApi = miniMaxApi(chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
|
||||
chatProperties.getApiKey(), commonProperties.getApiKey(),
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
|
||||
|
||||
var chatModel = new MiniMaxChatModel(miniMaxApi, chatProperties.getOptions(), functionCallbackResolver,
|
||||
toolFunctionCallbacks, retryTemplate, observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = MiniMaxEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public MiniMaxEmbeddingModel miniMaxEmbeddingModel(MiniMaxConnectionProperties commonProperties,
|
||||
MiniMaxEmbeddingProperties embeddingProperties,
|
||||
ObjectProvider<RestClient.Builder> restClientBuilderProvider, RetryTemplate retryTemplate,
|
||||
@@ -112,12 +84,4 @@ public class MiniMaxAutoConfiguration {
|
||||
return new MiniMaxApi(resolvedBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
|
||||
DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
|
||||
manager.setApplicationContext(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,4 +13,5 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
org.springframework.ai.model.minimax.autoconfigure.MiniMaxAutoConfiguration
|
||||
org.springframework.ai.model.minimax.autoconfigure.MiniMaxChatAutoConfiguration
|
||||
org.springframework.ai.model.minimax.autoconfigure.MiniMaxEmbeddingAutoConfiguration
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
@@ -34,6 +33,7 @@ import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@@ -51,7 +51,7 @@ public class FunctionCallbackInPromptIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class));
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
|
||||
@@ -55,7 +55,7 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
// FIXME: multiple function calls may stop prematurely due to model performance
|
||||
|
||||
@@ -47,51 +47,58 @@ public class MiniMaxAutoConfigurationIT {
|
||||
private static final Log logger = LogFactory.getLog(MiniMaxAutoConfigurationIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class));
|
||||
.withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY"));
|
||||
|
||||
@Test
|
||||
void generate() {
|
||||
this.contextRunner.run(context -> {
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
String response = chatModel.call("Hello");
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
String response = chatModel.call("Hello");
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateStreaming() {
|
||||
this.contextRunner.run(context -> {
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
Flux<ChatResponse> responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello")));
|
||||
String response = responseFlux.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
.map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText())
|
||||
.collect(Collectors.joining());
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
MiniMaxChatModel chatModel = context.getBean(MiniMaxChatModel.class);
|
||||
Flux<ChatResponse> responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello")));
|
||||
String response = responseFlux.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
.map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText())
|
||||
.collect(Collectors.joining());
|
||||
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embedding() {
|
||||
this.contextRunner.run(context -> {
|
||||
MiniMaxEmbeddingModel embeddingModel = context.getBean(MiniMaxEmbeddingModel.class);
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
MiniMaxEmbeddingModel embeddingModel = context.getBean(MiniMaxEmbeddingModel.class);
|
||||
|
||||
EmbeddingResponse embeddingResponse = embeddingModel
|
||||
.embedForResponse(List.of("Hello World", "World is big and salvation is near"));
|
||||
assertThat(embeddingResponse.getResults()).hasSize(2);
|
||||
assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0);
|
||||
assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1);
|
||||
EmbeddingResponse embeddingResponse = embeddingModel
|
||||
.embedForResponse(List.of("Hello World", "World is big and salvation is near"));
|
||||
assertThat(embeddingResponse.getResults()).hasSize(2);
|
||||
assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0);
|
||||
assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1);
|
||||
|
||||
assertThat(embeddingModel.dimensions()).isEqualTo(1536);
|
||||
});
|
||||
assertThat(embeddingModel.dimensions()).isEqualTo(1536);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
@@ -34,6 +33,7 @@ import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxChatOptions;
|
||||
import org.springframework.ai.model.function.FunctionCallback;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@@ -53,7 +53,7 @@ public class MiniMaxFunctionCallbackIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.minimax.apiKey=" + System.getenv("MINIMAX_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -50,7 +50,7 @@ public class MiniMaxPropertiesTests {
|
||||
"spring.ai.minimax.chat.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(MiniMaxChatProperties.class);
|
||||
var connectionProperties = context.getBean(MiniMaxConnectionProperties.class);
|
||||
@@ -79,7 +79,7 @@ public class MiniMaxPropertiesTests {
|
||||
"spring.ai.minimax.chat.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(MiniMaxChatProperties.class);
|
||||
var connectionProperties = context.getBean(MiniMaxConnectionProperties.class);
|
||||
@@ -105,7 +105,7 @@ public class MiniMaxPropertiesTests {
|
||||
"spring.ai.minimax.embedding.options.model=MODEL_XYZ")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(MiniMaxConnectionProperties.class);
|
||||
@@ -132,7 +132,7 @@ public class MiniMaxPropertiesTests {
|
||||
"spring.ai.minimax.embedding.options.model=MODEL_XYZ")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(MiniMaxConnectionProperties.class);
|
||||
@@ -199,17 +199,14 @@ public class MiniMaxPropertiesTests {
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(MiniMaxChatProperties.class);
|
||||
var connectionProperties = context.getBean(MiniMaxConnectionProperties.class);
|
||||
var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY");
|
||||
|
||||
assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("embo-01");
|
||||
|
||||
assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5);
|
||||
assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123);
|
||||
@@ -248,7 +245,7 @@ public class MiniMaxPropertiesTests {
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var connectionProperties = context.getBean(MiniMaxConnectionProperties.class);
|
||||
var embeddingProperties = context.getBean(MiniMaxEmbeddingProperties.class);
|
||||
@@ -267,16 +264,16 @@ public class MiniMaxPropertiesTests {
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL",
|
||||
"spring.ai.model.embedding=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty();
|
||||
@@ -286,7 +283,7 @@ public class MiniMaxPropertiesTests {
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL",
|
||||
"spring.ai.minimax.embedding.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty();
|
||||
@@ -299,16 +296,16 @@ public class MiniMaxPropertiesTests {
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL",
|
||||
"spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty();
|
||||
@@ -318,7 +315,7 @@ public class MiniMaxPropertiesTests {
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL",
|
||||
"spring.ai.model.chat=minimax")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MiniMaxChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty();
|
||||
|
||||
@@ -27,57 +27,59 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link MiniMaxAutoConfiguration}'s conditional enabling of models.
|
||||
* Unit Tests for MiniMax auto-configurations' conditional enabling of models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class MinimaxModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(MiniMaxAutoConfiguration.class, SpringAiRetryAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL");
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxChatAutoConfiguration.class)).run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none")
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=minimax", "spring.ai.model.embedding=none")
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=minimax", "spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embeddingModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxChatModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=minimax").run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MiniMaxEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=minimax")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,35 +39,30 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
|
||||
@@ -18,18 +18,16 @@ package org.springframework.ai.model.mistralai.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.mistralai.MistralAiChatModel;
|
||||
import org.springframework.ai.mistralai.api.MistralAiApi;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.mistralai.MistralAiChatModel;
|
||||
import org.springframework.ai.mistralai.MistralAiEmbeddingModel;
|
||||
import org.springframework.ai.mistralai.api.MistralAiApi;
|
||||
import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
@@ -47,49 +45,26 @@ import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Mistral AI.
|
||||
* Chat {@link AutoConfiguration Auto-configuration} for Mistral AI.
|
||||
*
|
||||
* @author Ricken Bazolo
|
||||
* @author Christian Tzolov
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @since 0.8.1
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class,
|
||||
ToolCallingAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ MistralAiEmbeddingProperties.class, MistralAiCommonProperties.class,
|
||||
MistralAiChatProperties.class })
|
||||
@EnableConfigurationProperties({ MistralAiCommonProperties.class, MistralAiChatProperties.class })
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MISTRAL,
|
||||
matchIfMissing = true)
|
||||
@ConditionalOnClass(MistralAiApi.class)
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
ToolCallingAutoConfiguration.class })
|
||||
public class MistralAiAutoConfiguration {
|
||||
public class MistralAiChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MISTRAL,
|
||||
matchIfMissing = true)
|
||||
public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiCommonProperties commonProperties,
|
||||
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(),
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
|
||||
|
||||
var embeddingModel = new MistralAiEmbeddingModel(mistralAiApi, embeddingProperties.getMetadataMode(),
|
||||
embeddingProperties.getOptions(), retryTemplate,
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
observationConvention.ifAvailable(embeddingModel::setObservationConvention);
|
||||
|
||||
return embeddingModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MISTRAL,
|
||||
matchIfMissing = true)
|
||||
public MistralAiChatModel mistralAiChatModel(MistralAiCommonProperties commonProperties,
|
||||
MistralAiChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ToolCallingManager toolCallingManager, RetryTemplate retryTemplate,
|
||||
@@ -18,16 +18,12 @@ package org.springframework.ai.model.mistralai.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.mistralai.MistralAiChatModel;
|
||||
import org.springframework.ai.mistralai.MistralAiEmbeddingModel;
|
||||
import org.springframework.ai.mistralai.api.MistralAiApi;
|
||||
import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
@@ -36,7 +32,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -45,26 +40,24 @@ import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Mistral AI.
|
||||
* Embedding {@link AutoConfiguration Auto-configuration} for Mistral AI.
|
||||
*
|
||||
* @author Ricken Bazolo
|
||||
* @author Christian Tzolov
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @since 0.8.1
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class,
|
||||
ToolCallingAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ MistralAiEmbeddingProperties.class, MistralAiCommonProperties.class,
|
||||
MistralAiChatProperties.class })
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ MistralAiCommonProperties.class, MistralAiEmbeddingProperties.class })
|
||||
@ConditionalOnClass(MistralAiApi.class)
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
ToolCallingAutoConfiguration.class })
|
||||
public class MistralAiAutoConfiguration {
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MISTRAL,
|
||||
matchIfMissing = true)
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class })
|
||||
public class MistralAiEmbeddingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = MistralAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiCommonProperties commonProperties,
|
||||
MistralAiEmbeddingProperties embeddingProperties,
|
||||
ObjectProvider<RestClient.Builder> restClientBuilderProvider, RetryTemplate retryTemplate,
|
||||
@@ -84,33 +77,6 @@ public class MistralAiAutoConfiguration {
|
||||
return embeddingModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = MistralAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public MistralAiChatModel mistralAiChatModel(MistralAiCommonProperties commonProperties,
|
||||
MistralAiChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ToolCallingManager toolCallingManager, RetryTemplate retryTemplate,
|
||||
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
|
||||
var mistralAiApi = mistralAiApi(chatProperties.getApiKey(), commonProperties.getApiKey(),
|
||||
chatProperties.getBaseUrl(), commonProperties.getBaseUrl(),
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder), responseErrorHandler);
|
||||
|
||||
var chatModel = MistralAiChatModel.builder()
|
||||
.mistralAiApi(mistralAiApi)
|
||||
.defaultOptions(chatProperties.getOptions())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.retryTemplate(retryTemplate)
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.build();
|
||||
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
private MistralAiApi mistralAiApi(String apiKey, String commonApiKey, String baseUrl, String commonBaseUrl,
|
||||
RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
@@ -123,12 +89,4 @@ public class MistralAiAutoConfiguration {
|
||||
return new MistralAiApi(resoledBaseUrl, resolvedApiKey, restClientBuilder, responseErrorHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
|
||||
DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
|
||||
manager.setApplicationContext(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,4 +13,5 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration
|
||||
org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration
|
||||
org.springframework.ai.model.mistralai.autoconfigure.MistralAiEmbeddingAutoConfiguration
|
||||
|
||||
@@ -38,6 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Christian Tzolov
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @since 0.8.1
|
||||
*/
|
||||
@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".*")
|
||||
@@ -46,50 +47,52 @@ public class MistralAiAutoConfigurationIT {
|
||||
private static final Log logger = LogFactory.getLog(MistralAiAutoConfigurationIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class));
|
||||
.withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY"));
|
||||
|
||||
@Test
|
||||
void generate() {
|
||||
this.contextRunner.run(context -> {
|
||||
MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class);
|
||||
String response = chatModel.call("Hello");
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class);
|
||||
String response = chatModel.call("Hello");
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateStreaming() {
|
||||
this.contextRunner.run(context -> {
|
||||
MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class);
|
||||
Flux<ChatResponse> responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello")));
|
||||
String response = responseFlux.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
.map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText())
|
||||
.collect(Collectors.joining());
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
MistralAiChatModel chatModel = context.getBean(MistralAiChatModel.class);
|
||||
Flux<ChatResponse> responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello")));
|
||||
String response = responseFlux.collectList()
|
||||
.block()
|
||||
.stream()
|
||||
.map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText())
|
||||
.collect(Collectors.joining());
|
||||
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embedding() {
|
||||
this.contextRunner.run(context -> {
|
||||
MistralAiEmbeddingModel embeddingModel = context.getBean(MistralAiEmbeddingModel.class);
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
MistralAiEmbeddingModel embeddingModel = context.getBean(MistralAiEmbeddingModel.class);
|
||||
|
||||
EmbeddingResponse embeddingResponse = embeddingModel
|
||||
.embedForResponse(List.of("Hello World", "World is big and salvation is near"));
|
||||
assertThat(embeddingResponse.getResults()).hasSize(2);
|
||||
assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0);
|
||||
assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1);
|
||||
EmbeddingResponse embeddingResponse = embeddingModel
|
||||
.embedForResponse(List.of("Hello World", "World is big and salvation is near"));
|
||||
assertThat(embeddingResponse.getResults()).hasSize(2);
|
||||
assertThat(embeddingResponse.getResults().get(0).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(0).getIndex()).isEqualTo(0);
|
||||
assertThat(embeddingResponse.getResults().get(1).getOutput()).isNotEmpty();
|
||||
assertThat(embeddingResponse.getResults().get(1).getIndex()).isEqualTo(1);
|
||||
|
||||
assertThat(embeddingModel.dimensions()).isEqualTo(1024);
|
||||
});
|
||||
assertThat(embeddingModel.dimensions()).isEqualTo(1024);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class MistralAiPropertiesTests {
|
||||
.withPropertyValues("spring.ai.mistralai.base-url=TEST_BASE_URL", "spring.ai.mistralai.api-key=abc123",
|
||||
"spring.ai.mistralai.embedding.options.model=MODEL_XYZ")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MistralAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var embeddingProperties = context.getBean(MistralAiEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(MistralAiCommonProperties.class);
|
||||
@@ -87,7 +87,7 @@ public class MistralAiPropertiesTests {
|
||||
"spring.ai.mistralai.api-key=abc123", "spring.ai.mistralai.embedding.base-url=TEST_BASE_URL2",
|
||||
"spring.ai.mistralai.embedding.api-key=456", "spring.ai.mistralai.embedding.options.model=MODEL_XYZ")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MistralAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
|
||||
var chatProperties = context.getBean(MistralAiChatProperties.class);
|
||||
@@ -108,7 +108,7 @@ public class MistralAiPropertiesTests {
|
||||
"spring.ai.mistralai.api-key=abc123", "spring.ai.mistralai.embedding.base-url=TEST_BASE_URL2",
|
||||
"spring.ai.mistralai.embedding.api-key=456", "spring.ai.mistralai.embedding.options.model=MODEL_XYZ")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MistralAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var embeddingProperties = context.getBean(MistralAiEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(MistralAiCommonProperties.class);
|
||||
@@ -132,7 +132,7 @@ public class MistralAiPropertiesTests {
|
||||
"spring.ai.mistralai.embedding.options.model=MODEL_XYZ",
|
||||
"spring.ai.mistralai.embedding.options.encodingFormat=MyEncodingFormat")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MistralAiAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MistralAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var connectionProperties = context.getBean(MistralAiCommonProperties.class);
|
||||
var embeddingProperties = context.getBean(MistralAiEmbeddingProperties.class);
|
||||
|
||||
@@ -26,57 +26,64 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link MistralAiAutoConfiguration}'s conditional enabling of models.
|
||||
* Unit Tests for Mistral AI auto-configurations conditional enabling of models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class MistralModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY"));
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=mistral", "spring.ai.model.embedding=none")
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class,
|
||||
MistralAiEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=mistral", "spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embeddingModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=mistral").run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(MistralAiEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=mistral")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.mistralai.MistralAiChatModel;
|
||||
import org.springframework.ai.mistralai.MistralAiChatOptions;
|
||||
import org.springframework.ai.mistralai.api.MistralAiApi;
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -53,7 +53,7 @@ class PaymentStatusBeanIT {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -25,13 +25,13 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.mistralai.MistralAiChatModel;
|
||||
import org.springframework.ai.mistralai.MistralAiChatOptions;
|
||||
import org.springframework.ai.mistralai.api.MistralAiApi;
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@@ -51,7 +51,7 @@ public class PaymentStatusPromptIT {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.mistralai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
|
||||
@@ -27,9 +27,6 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration;
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Request;
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Response;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
@@ -37,6 +34,9 @@ import org.springframework.ai.mistralai.MistralAiChatModel;
|
||||
import org.springframework.ai.mistralai.MistralAiChatOptions;
|
||||
import org.springframework.ai.mistralai.api.MistralAiApi;
|
||||
import org.springframework.ai.mistralai.api.MistralAiApi.ChatCompletionRequest.ToolChoice;
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Request;
|
||||
import org.springframework.ai.model.mistralai.autoconfigure.tool.WeatherServicePromptIT.MyWeatherService.Response;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
@@ -56,7 +56,7 @@ public class WeatherServicePromptIT {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.mistralai.api-key=" + System.getenv("MISTRAL_AI_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(MistralAiAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(MistralAiChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void promptFunctionCall() {
|
||||
|
||||
@@ -39,21 +39,18 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
@@ -48,19 +48,17 @@ import org.springframework.web.client.RestClient;
|
||||
* {@link AutoConfiguration Auto-configuration} for Moonshot Chat Model.
|
||||
*
|
||||
* @author Geng Rong
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ MoonshotCommonProperties.class, MoonshotChatProperties.class })
|
||||
@ConditionalOnClass(MoonshotApi.class)
|
||||
public class MoonshotAutoConfiguration {
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MOONSHOT,
|
||||
matchIfMissing = true)
|
||||
public class MoonshotChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
// @ConditionalOnProperty(prefix = MoonshotChatProperties.CONFIG_PREFIX, name =
|
||||
// "enabled", havingValue = "true",
|
||||
// matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MOONSHOT,
|
||||
matchIfMissing = true)
|
||||
public MoonshotChatModel moonshotChatModel(MoonshotCommonProperties commonProperties,
|
||||
MoonshotChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
List<FunctionCallback> toolFunctionCallbacks, FunctionCallbackResolver functionCallbackResolver,
|
||||
@@ -13,4 +13,4 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration
|
||||
org.springframework.ai.model.moonshot.autoconfigure.MoonshotChatAutoConfiguration
|
||||
|
||||
@@ -1,77 +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.model.moonshot.autoconfigure;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.moonshot.MoonshotChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Geng Rong
|
||||
*/
|
||||
@EnabledIfEnvironmentVariable(named = "MOONSHOT_API_KEY", matches = ".*")
|
||||
public class MoonshotAutoConfigurationIT {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(MoonshotAutoConfigurationIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void generate() {
|
||||
this.contextRunner.run(context -> {
|
||||
MoonshotChatModel client = context.getBean(MoonshotChatModel.class);
|
||||
String response = client.call("Hello");
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void generateStreaming() {
|
||||
this.contextRunner.run(context -> {
|
||||
MoonshotChatModel client = context.getBean(MoonshotChatModel.class);
|
||||
Flux<ChatResponse> responseFlux = client.stream(new Prompt(new UserMessage("Hello")));
|
||||
String response = Objects.requireNonNull(responseFlux.collectList().block())
|
||||
.stream()
|
||||
.map(chatResponse -> chatResponse.getResults().get(0).getOutput().getText())
|
||||
.collect(Collectors.joining());
|
||||
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -40,14 +40,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Geng Rong
|
||||
*/
|
||||
@EnabledIfEnvironmentVariable(named = "MOONSHOT_API_KEY", matches = ".*")
|
||||
public class MoonshotAutoConfigurationIT {
|
||||
public class MoonshotChatAutoConfigurationIT {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(MoonshotAutoConfigurationIT.class);
|
||||
private static final Log logger = LogFactory.getLog(MoonshotChatAutoConfigurationIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class));
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void generate() {
|
||||
@@ -42,7 +42,7 @@ public class MoonshotPropertiesTests {
|
||||
"spring.ai.moonshot.chat.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(MoonshotChatProperties.class);
|
||||
var connectionProperties = context.getBean(MoonshotCommonProperties.class);
|
||||
@@ -71,7 +71,7 @@ public class MoonshotPropertiesTests {
|
||||
"spring.ai.moonshot.chat.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(MoonshotChatProperties.class);
|
||||
var connectionProperties = context.getBean(MoonshotCommonProperties.class);
|
||||
@@ -110,7 +110,7 @@ public class MoonshotPropertiesTests {
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(MoonshotChatProperties.class);
|
||||
var connectionProperties = context.getBean(MoonshotCommonProperties.class);
|
||||
@@ -137,16 +137,16 @@ public class MoonshotPropertiesTests {
|
||||
.withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL",
|
||||
"spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(MoonshotChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MoonshotChatModel.class)).isNotEmpty();
|
||||
@@ -156,7 +156,7 @@ public class MoonshotPropertiesTests {
|
||||
.withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL",
|
||||
"spring.ai.model.chat=moonshot")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MoonshotChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MoonshotChatModel.class)).isNotEmpty();
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration;
|
||||
import org.springframework.ai.model.moonshot.autoconfigure.MoonshotChatAutoConfiguration;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
@@ -53,7 +53,7 @@ public class FunctionCallbackInPromptIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class));
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration;
|
||||
import org.springframework.ai.model.moonshot.autoconfigure.MoonshotChatAutoConfiguration;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
@@ -57,7 +57,7 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.moonshot.autoconfigure.MoonshotAutoConfiguration;
|
||||
import org.springframework.ai.model.moonshot.autoconfigure.MoonshotChatAutoConfiguration;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
@@ -56,7 +56,7 @@ public class MoonshotFunctionCallbackIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.moonshot.apiKey=" + System.getenv("MOONSHOT_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
RestClientAutoConfiguration.class, MoonshotChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -46,14 +46,12 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
|
||||
@@ -1,115 +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.model.oci.genai.autoconfigure;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.oracle.bmc.ClientConfiguration;
|
||||
import com.oracle.bmc.Region;
|
||||
import com.oracle.bmc.auth.BasicAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.auth.ConfigFileAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.auth.InstancePrincipalsAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.auth.SimplePrivateKeySupplier;
|
||||
import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient;
|
||||
import com.oracle.bmc.retrier.RetryConfiguration;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.oci.OCIEmbeddingModel;
|
||||
import org.springframework.ai.oci.cohere.OCICohereChatModel;
|
||||
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;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Oracle Cloud Infrastructure Generative
|
||||
* AI.
|
||||
*
|
||||
* @author Anders Swanson
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({ GenerativeAiInferenceClient.class, OCIEmbeddingModel.class })
|
||||
@EnableConfigurationProperties({ OCIConnectionProperties.class, OCIEmbeddingModelProperties.class,
|
||||
OCICohereChatModelProperties.class })
|
||||
public class OCIGenAiAutoConfiguration {
|
||||
|
||||
private static BasicAuthenticationDetailsProvider authenticationProvider(OCIConnectionProperties properties)
|
||||
throws IOException {
|
||||
return switch (properties.getAuthenticationType()) {
|
||||
case FILE -> new ConfigFileAuthenticationDetailsProvider(properties.getFile(), properties.getProfile());
|
||||
case INSTANCE_PRINCIPAL -> InstancePrincipalsAuthenticationDetailsProvider.builder().build();
|
||||
case WORKLOAD_IDENTITY -> OkeWorkloadIdentityAuthenticationDetailsProvider.builder().build();
|
||||
case SIMPLE -> SimpleAuthenticationDetailsProvider.builder()
|
||||
.userId(properties.getUserId())
|
||||
.tenantId(properties.getTenantId())
|
||||
.fingerprint(properties.getFingerprint())
|
||||
.privateKeySupplier(new SimplePrivateKeySupplier(properties.getPrivateKey()))
|
||||
.passPhrase(properties.getPassPhrase())
|
||||
.region(Region.valueOf(properties.getRegion()))
|
||||
.build();
|
||||
};
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean
|
||||
@Bean
|
||||
public GenerativeAiInferenceClient generativeAiInferenceClient(OCIConnectionProperties properties)
|
||||
throws IOException {
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
.retryConfiguration(RetryConfiguration.SDK_DEFAULT_RETRY_CONFIGURATION)
|
||||
.build();
|
||||
GenerativeAiInferenceClient.Builder builder = GenerativeAiInferenceClient.builder()
|
||||
.configuration(clientConfiguration);
|
||||
if (StringUtils.hasText(properties.getRegion())) {
|
||||
builder.region(Region.valueOf(properties.getRegion()));
|
||||
}
|
||||
if (StringUtils.hasText(properties.getEndpoint())) {
|
||||
builder.endpoint(properties.getEndpoint());
|
||||
}
|
||||
return builder.build(authenticationProvider(properties));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OCI_GENAI,
|
||||
matchIfMissing = true)
|
||||
public OCIEmbeddingModel ociEmbeddingModel(GenerativeAiInferenceClient generativeAiClient,
|
||||
OCIEmbeddingModelProperties properties) {
|
||||
return new OCIEmbeddingModel(generativeAiClient, properties.getEmbeddingOptions());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OCI_GENAI,
|
||||
matchIfMissing = true)
|
||||
public OCICohereChatModel ociChatModel(GenerativeAiInferenceClient generativeAiClient,
|
||||
OCICohereChatModelProperties properties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
var chatModel = new OCICohereChatModel(generativeAiClient, properties.getOptions(),
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.model.oci.genai.autoconfigure;
|
||||
|
||||
import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.oci.cohere.OCICohereChatModel;
|
||||
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;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Chat {@link AutoConfiguration Auto-configuration} for Oracle Cloud Infrastructure
|
||||
* Generative AI.
|
||||
*
|
||||
* @author Anders Swanson
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(OCICohereChatModel.class)
|
||||
@EnableConfigurationProperties(OCICohereChatModelProperties.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OCI_GENAI,
|
||||
matchIfMissing = true)
|
||||
@ImportAutoConfiguration(OCIGenAiInferenceClientAutoConfiguration.class)
|
||||
public class OCIGenAiChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OCICohereChatModel ociChatModel(GenerativeAiInferenceClient generativeAiClient,
|
||||
OCICohereChatModelProperties properties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
var chatModel = new OCICohereChatModel(generativeAiClient, properties.getOptions(),
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.model.oci.genai.autoconfigure;
|
||||
|
||||
import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient;
|
||||
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.oci.OCIEmbeddingModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Embedding {@link AutoConfiguration Auto-configuration} for Oracle Cloud Infrastructure
|
||||
* Generative AI.
|
||||
*
|
||||
* @author Anders Swanson
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(OCIEmbeddingModel.class)
|
||||
@EnableConfigurationProperties(OCIEmbeddingModelProperties.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OCI_GENAI,
|
||||
matchIfMissing = true)
|
||||
@ImportAutoConfiguration(OCIGenAiInferenceClientAutoConfiguration.class)
|
||||
public class OCIGenAiEmbeddingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OCIEmbeddingModel ociEmbeddingModel(GenerativeAiInferenceClient generativeAiClient,
|
||||
OCIEmbeddingModelProperties properties) {
|
||||
return new OCIEmbeddingModel(generativeAiClient, properties.getEmbeddingOptions());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2023-2024 the original author or authors.
|
||||
* Copyright 2023-2025 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.
|
||||
@@ -28,31 +28,43 @@ import com.oracle.bmc.auth.SimplePrivateKeySupplier;
|
||||
import com.oracle.bmc.auth.okeworkloadidentity.OkeWorkloadIdentityAuthenticationDetailsProvider;
|
||||
import com.oracle.bmc.generativeaiinference.GenerativeAiInferenceClient;
|
||||
import com.oracle.bmc.retrier.RetryConfiguration;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.oci.OCIEmbeddingModel;
|
||||
import org.springframework.ai.oci.cohere.OCICohereChatModel;
|
||||
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;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Oracle Cloud Infrastructure Generative
|
||||
* AI.
|
||||
* AI Inference Client.
|
||||
*
|
||||
* @author Anders Swanson
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass({ GenerativeAiInferenceClient.class, OCIEmbeddingModel.class })
|
||||
@EnableConfigurationProperties({ OCIConnectionProperties.class, OCIEmbeddingModelProperties.class,
|
||||
OCICohereChatModelProperties.class })
|
||||
public class OCIGenAiAutoConfiguration {
|
||||
@ConditionalOnClass(GenerativeAiInferenceClient.class)
|
||||
@EnableConfigurationProperties(OCIConnectionProperties.class)
|
||||
public class OCIGenAiInferenceClientAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GenerativeAiInferenceClient generativeAiInferenceClient(OCIConnectionProperties properties)
|
||||
throws IOException {
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
.retryConfiguration(RetryConfiguration.SDK_DEFAULT_RETRY_CONFIGURATION)
|
||||
.build();
|
||||
GenerativeAiInferenceClient.Builder builder = GenerativeAiInferenceClient.builder()
|
||||
.configuration(clientConfiguration);
|
||||
if (StringUtils.hasText(properties.getRegion())) {
|
||||
builder.region(Region.valueOf(properties.getRegion()));
|
||||
}
|
||||
if (StringUtils.hasText(properties.getEndpoint())) {
|
||||
builder.endpoint(properties.getEndpoint());
|
||||
}
|
||||
return builder.build(authenticationProvider(properties));
|
||||
}
|
||||
|
||||
private static BasicAuthenticationDetailsProvider authenticationProvider(OCIConnectionProperties properties)
|
||||
throws IOException {
|
||||
@@ -71,43 +83,4 @@ public class OCIGenAiAutoConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean
|
||||
@Bean
|
||||
public GenerativeAiInferenceClient generativeAiInferenceClient(OCIConnectionProperties properties)
|
||||
throws IOException {
|
||||
ClientConfiguration clientConfiguration = ClientConfiguration.builder()
|
||||
.retryConfiguration(RetryConfiguration.SDK_DEFAULT_RETRY_CONFIGURATION)
|
||||
.build();
|
||||
GenerativeAiInferenceClient.Builder builder = GenerativeAiInferenceClient.builder()
|
||||
.configuration(clientConfiguration);
|
||||
if (StringUtils.hasText(properties.getRegion())) {
|
||||
builder.region(Region.valueOf(properties.getRegion()));
|
||||
}
|
||||
if (StringUtils.hasText(properties.getEndpoint())) {
|
||||
builder.endpoint(properties.getEndpoint());
|
||||
}
|
||||
return builder.build(authenticationProvider(properties));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = OCIEmbeddingModelProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public OCIEmbeddingModel ociEmbeddingModel(GenerativeAiInferenceClient generativeAiClient,
|
||||
OCIEmbeddingModelProperties properties) {
|
||||
return new OCIEmbeddingModel(generativeAiClient, properties.getEmbeddingOptions());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = OCICohereChatModelProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public OCICohereChatModel ociChatModel(GenerativeAiInferenceClient generativeAiClient,
|
||||
OCICohereChatModelProperties properties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
var chatModel = new OCICohereChatModel(generativeAiClient, properties.getOptions(),
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,4 +13,5 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
org.springframework.ai.model.oci.genai.autoconfigure.OCIGenAiAutoConfiguration
|
||||
org.springframework.ai.model.oci.genai.autoconfigure.OCIGenAiChatAutoConfiguration
|
||||
org.springframework.ai.model.oci.genai.autoconfigure.OCIGenAiEmbeddingAutoConfiguration
|
||||
|
||||
@@ -56,7 +56,7 @@ class OCIGenAIAutoConfigurationTest {
|
||||
"spring.ai.oci.genai.cohere.chat.options.frequencyPenalty=0.1",
|
||||
"spring.ai.oci.genai.cohere.chat.options.presencePenalty=0.2"
|
||||
// @formatter:on
|
||||
).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class));
|
||||
).withConfiguration(AutoConfigurations.of(OCIGenAiChatAutoConfiguration.class));
|
||||
|
||||
contextRunner.run(context -> {
|
||||
OCICohereChatModel chatModel = context.getBean(OCICohereChatModel.class);
|
||||
|
||||
@@ -52,7 +52,7 @@ public class OCIGenAiAutoConfigurationIT {
|
||||
"spring.ai.oci.genai.embedding.servingMode=on-demand",
|
||||
"spring.ai.oci.genai.embedding.model=cohere.embed-english-light-v2.0"
|
||||
// @formatter:on
|
||||
).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class));
|
||||
).withConfiguration(AutoConfigurations.of(OCIGenAiEmbeddingAutoConfiguration.class));
|
||||
|
||||
private final ApplicationContextRunner cohereChatContextRunner = new ApplicationContextRunner().withPropertyValues(
|
||||
// @formatter:off
|
||||
@@ -62,7 +62,7 @@ public class OCIGenAiAutoConfigurationIT {
|
||||
"spring.ai.oci.genai.cohere.chat.options.servingMode=on-demand",
|
||||
"spring.ai.oci.genai.cohere.chat.options.model=" + this.CHAT_MODEL_ID
|
||||
// @formatter:on
|
||||
).withConfiguration(AutoConfigurations.of(OCIGenAiAutoConfiguration.class));
|
||||
).withConfiguration(AutoConfigurations.of(OCIGenAiChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void embeddings() {
|
||||
|
||||
@@ -38,28 +38,18 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.model.ollama.autoconfigure;
|
||||
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
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;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Ollama API.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Eddú Meléndez
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @since 0.8.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(OllamaApi.class)
|
||||
@EnableConfigurationProperties(OllamaConnectionProperties.class)
|
||||
public class OllamaApiAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(OllamaConnectionDetails.class)
|
||||
public PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectionProperties properties) {
|
||||
return new PropertiesOllamaConnectionDetails(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
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));
|
||||
}
|
||||
|
||||
static class PropertiesOllamaConnectionDetails implements OllamaConnectionDetails {
|
||||
|
||||
private final OllamaConnectionProperties properties;
|
||||
|
||||
PropertiesOllamaConnectionDetails(OllamaConnectionProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return this.properties.getBaseUrl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.model.ollama.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.OllamaEmbeddingModel;
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
import org.springframework.ai.ollama.management.ModelManagementOptions;
|
||||
import org.springframework.ai.ollama.management.PullModelStrategy;
|
||||
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;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Ollama Chat Client.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Eddú Meléndez
|
||||
* @author Thomas Vitale
|
||||
* @since 0.8.0
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class })
|
||||
@ConditionalOnClass(OllamaApi.class)
|
||||
@EnableConfigurationProperties({ OllamaChatProperties.class, OllamaEmbeddingProperties.class,
|
||||
OllamaConnectionProperties.class, OllamaInitializationProperties.class })
|
||||
@ImportAutoConfiguration(classes = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class })
|
||||
public class OllamaAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(OllamaConnectionDetails.class)
|
||||
public PropertiesOllamaConnectionDetails ollamaConnectionDetails(OllamaConnectionProperties properties) {
|
||||
return new PropertiesOllamaConnectionDetails(properties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
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
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OLLAMA,
|
||||
matchIfMissing = true)
|
||||
public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties properties,
|
||||
OllamaInitializationProperties initProperties, ToolCallingManager toolCallingManager,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
var chatModelPullStrategy = initProperties.getChat().isInclude() ? initProperties.getPullModelStrategy()
|
||||
: PullModelStrategy.NEVER;
|
||||
|
||||
var chatModel = OllamaChatModel.builder()
|
||||
.ollamaApi(ollamaApi)
|
||||
.defaultOptions(properties.getOptions())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.modelManagementOptions(
|
||||
new ModelManagementOptions(chatModelPullStrategy, initProperties.getChat().getAdditionalModels(),
|
||||
initProperties.getTimeout(), initProperties.getMaxRetries()))
|
||||
.build();
|
||||
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OLLAMA,
|
||||
matchIfMissing = true)
|
||||
public OllamaEmbeddingModel ollamaEmbeddingModel(OllamaApi ollamaApi, OllamaEmbeddingProperties properties,
|
||||
OllamaInitializationProperties initProperties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
|
||||
var embeddingModelPullStrategy = initProperties.getEmbedding().isInclude()
|
||||
? initProperties.getPullModelStrategy() : PullModelStrategy.NEVER;
|
||||
|
||||
var embeddingModel = OllamaEmbeddingModel.builder()
|
||||
.ollamaApi(ollamaApi)
|
||||
.defaultOptions(properties.getOptions())
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.modelManagementOptions(new ModelManagementOptions(embeddingModelPullStrategy,
|
||||
initProperties.getEmbedding().getAdditionalModels(), initProperties.getTimeout(),
|
||||
initProperties.getMaxRetries()))
|
||||
.build();
|
||||
|
||||
observationConvention.ifAvailable(embeddingModel::setObservationConvention);
|
||||
|
||||
return embeddingModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
|
||||
DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
|
||||
manager.setApplicationContext(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
static class PropertiesOllamaConnectionDetails implements OllamaConnectionDetails {
|
||||
|
||||
private final OllamaConnectionProperties properties;
|
||||
|
||||
PropertiesOllamaConnectionDetails(OllamaConnectionProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return this.properties.getBaseUrl();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,18 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.ai.model.anthropic.autoconfigure;
|
||||
package org.springframework.ai.model.ollama.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.model.function.DefaultFunctionCallbackResolver;
|
||||
import org.springframework.ai.model.function.FunctionCallbackResolver;
|
||||
import org.springframework.ai.model.tool.ToolCallingManager;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
import org.springframework.ai.ollama.management.ModelManagementOptions;
|
||||
import org.springframework.ai.ollama.management.PullModelStrategy;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
@@ -37,53 +40,42 @@ import org.springframework.boot.autoconfigure.web.reactive.function.client.WebCl
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Anthropic Chat Model.
|
||||
* {@link AutoConfiguration Auto-configuration} for Ollama Chat model.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Eddú Meléndez
|
||||
* @author Thomas Vitale
|
||||
* @since 1.0.0
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @since 0.8.0
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class,
|
||||
ToolCallingAutoConfiguration.class })
|
||||
@EnableConfigurationProperties({ AnthropicChatProperties.class, AnthropicConnectionProperties.class })
|
||||
@ConditionalOnClass(AnthropicApi.class)
|
||||
@ConditionalOnProperty(prefix = AnthropicChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, ToolCallingAutoConfiguration.class })
|
||||
@ConditionalOnClass(OllamaChatModel.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OLLAMA,
|
||||
matchIfMissing = true)
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
@EnableConfigurationProperties({ OllamaChatProperties.class, OllamaInitializationProperties.class })
|
||||
@ImportAutoConfiguration(classes = { OllamaApiAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
ToolCallingAutoConfiguration.class, WebClientAutoConfiguration.class })
|
||||
public class AnthropicAutoConfiguration {
|
||||
public class OllamaChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AnthropicApi anthropicApi(AnthropicConnectionProperties connectionProperties,
|
||||
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
return new AnthropicApi(connectionProperties.getBaseUrl(), connectionProperties.getApiKey(),
|
||||
connectionProperties.getVersion(), restClientBuilderProvider.getIfAvailable(RestClient::builder),
|
||||
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler,
|
||||
connectionProperties.getBetaVersion());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public AnthropicChatModel anthropicChatModel(AnthropicApi anthropicApi, AnthropicChatProperties chatProperties,
|
||||
RetryTemplate retryTemplate, ToolCallingManager toolCallingManager,
|
||||
public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties properties,
|
||||
OllamaInitializationProperties initProperties, ToolCallingManager toolCallingManager,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
var chatModelPullStrategy = initProperties.getChat().isInclude() ? initProperties.getPullModelStrategy()
|
||||
: PullModelStrategy.NEVER;
|
||||
|
||||
var chatModel = AnthropicChatModel.builder()
|
||||
.anthropicApi(anthropicApi)
|
||||
.defaultOptions(chatProperties.getOptions())
|
||||
var chatModel = OllamaChatModel.builder()
|
||||
.ollamaApi(ollamaApi)
|
||||
.defaultOptions(properties.getOptions())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.retryTemplate(retryTemplate)
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.modelManagementOptions(
|
||||
new ModelManagementOptions(chatModelPullStrategy, initProperties.getChat().getAdditionalModels(),
|
||||
initProperties.getTimeout(), initProperties.getMaxRetries()))
|
||||
.build();
|
||||
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2023-2025 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.model.ollama.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.ollama.OllamaEmbeddingModel;
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
import org.springframework.ai.ollama.management.ModelManagementOptions;
|
||||
import org.springframework.ai.ollama.management.PullModelStrategy;
|
||||
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;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Ollama Chat Client.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Eddú Meléndez
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @since 0.8.0
|
||||
*/
|
||||
@AutoConfiguration(after = RestClientAutoConfiguration.class)
|
||||
@ConditionalOnClass(OllamaEmbeddingModel.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OLLAMA,
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties({ OllamaEmbeddingProperties.class, OllamaInitializationProperties.class })
|
||||
@ImportAutoConfiguration(classes = { OllamaApiAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class })
|
||||
public class OllamaEmbeddingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OllamaEmbeddingModel ollamaEmbeddingModel(OllamaApi ollamaApi, OllamaEmbeddingProperties properties,
|
||||
OllamaInitializationProperties initProperties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
|
||||
var embeddingModelPullStrategy = initProperties.getEmbedding().isInclude()
|
||||
? initProperties.getPullModelStrategy() : PullModelStrategy.NEVER;
|
||||
|
||||
var embeddingModel = OllamaEmbeddingModel.builder()
|
||||
.ollamaApi(ollamaApi)
|
||||
.defaultOptions(properties.getOptions())
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.modelManagementOptions(new ModelManagementOptions(embeddingModelPullStrategy,
|
||||
initProperties.getEmbedding().getAdditionalModels(), initProperties.getTimeout(),
|
||||
initProperties.getMaxRetries()))
|
||||
.build();
|
||||
|
||||
observationConvention.ifAvailable(embeddingModel::setObservationConvention);
|
||||
|
||||
return embeddingModel;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,4 +13,5 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration
|
||||
org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration
|
||||
org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration
|
||||
|
||||
@@ -55,7 +55,7 @@ public class OllamaChatAutoConfigurationIT extends BaseOllamaIT {
|
||||
"spring.ai.ollama.chat.options.temperature=0.5",
|
||||
"spring.ai.ollama.chat.options.topK=10")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class));
|
||||
|
||||
private final UserMessage userMessage = new UserMessage("What's the capital of Denmark?");
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ public class OllamaChatAutoConfigurationTests {
|
||||
"spring.ai.ollama.chat.options.topP=0.56",
|
||||
"spring.ai.ollama.chat.options.topK=123")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaAutoConfiguration.class))
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(OllamaChatProperties.class);
|
||||
var connectionProperties = context.getBean(OllamaConnectionProperties.class);
|
||||
|
||||
@@ -45,7 +45,8 @@ public class OllamaEmbeddingAutoConfigurationIT extends BaseOllamaIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.ollama.embedding.options.model=" + MODEL_NAME,
|
||||
"spring.ai.ollama.base-url=" + getBaseUrl())
|
||||
.withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaAutoConfiguration.class));
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaEmbeddingAutoConfiguration.class));
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeAll() throws IOException, InterruptedException {
|
||||
|
||||
@@ -41,7 +41,8 @@ public class OllamaEmbeddingAutoConfigurationTests {
|
||||
"spring.ai.ollama.embedding.options.topK=13"
|
||||
// @formatter:on
|
||||
)
|
||||
.withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaAutoConfiguration.class))
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(RestClientAutoConfiguration.class, OllamaEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var embeddingProperties = context.getBean(OllamaEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(OllamaConnectionProperties.class);
|
||||
|
||||
@@ -26,56 +26,59 @@ import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link OllamaAutoConfiguration}'s conditional enabling of models.
|
||||
* Unit Tests for Ollama auto-configurations conditional enabling of models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class OllamaModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class));
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class)).run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none")
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=ollama", "spring.ai.model.embedding=none")
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=ollama")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embeddingModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=ollama").run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OllamaEmbeddingAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.embedding=ollama")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
@@ -53,7 +53,7 @@ public class FunctionCallbackInPromptIT extends BaseOllamaIT {
|
||||
"spring.ai.ollama.chat.options.temperature=0.5",
|
||||
"spring.ai.ollama.chat.options.topK=10")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class));
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeAll() {
|
||||
|
||||
@@ -25,13 +25,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||
@@ -57,7 +57,7 @@ public class OllamaFunctionCallbackIT extends BaseOllamaIT {
|
||||
"spring.ai.ollama.chat.options.temperature=0.5",
|
||||
"spring.ai.ollama.chat.options.topK=10")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@BeforeAll
|
||||
|
||||
@@ -26,13 +26,13 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT;
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.api.OllamaOptions;
|
||||
@@ -64,7 +64,7 @@ public class OllamaFunctionToolBeanIT extends BaseOllamaIT {
|
||||
"spring.ai.ollama.chat.options.temperature=0.5",
|
||||
"spring.ai.ollama.chat.options.topK=10")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@BeforeAll
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import org.springframework.ai.model.ollama.autoconfigure.BaseOllamaIT
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration
|
||||
import org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration
|
||||
import org.springframework.ai.chat.messages.UserMessage
|
||||
import org.springframework.ai.chat.prompt.Prompt
|
||||
import org.springframework.ai.model.function.FunctionCallingOptions
|
||||
@@ -56,7 +56,7 @@ class FunctionCallbackResolverKotlinIT : BaseOllamaIT() {
|
||||
"spring.ai.ollama.chat.options.temperature=0.5",
|
||||
"spring.ai.ollama.chat.options.topK=10"
|
||||
)
|
||||
.withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration::class.java))
|
||||
.withConfiguration(AutoConfigurations.of(OllamaChatAutoConfiguration::class.java))
|
||||
.withUserConfiguration(Config::class.java)
|
||||
|
||||
@Test
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user