Refactor autoconfigurations for models
- Conditionally enable invidividual models within the provider
- Introduce top level properties to support conditional logic
- The properties will have the format like "spring.ai.model.<chat/embedding/image etc.,>=<provider>" to enable specific chat/embedding/image/audio/moderation models by the provider. By default, these will be enabled when no specific properties are set. To disable, set any value other than the provider name for example, "none"
- For the auto configurations where the provider has multiple models, split the autoconfiguration into per model auto-configuration classes. This will enable the isolated auto-configurations for each provider and its model.
- This PR addresses this for OpenAI and others will follow in subsequent PRs
Signed-off-by: Ilayaperumal Gopinathan <ilayaperumal.gopinathan@broadcom.com>
This commit is contained in:
committed by
Soby Chacko
parent
34c19796d6
commit
431a7eda49
@@ -9,7 +9,7 @@
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-mcp-client-spring-boot-autoconfigure</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-mcp-client</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring AI MCP Client Auto Configuration</name>
|
||||
<description>Spring AI MCP Client Auto Configuration</description>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-mcp-server-spring-boot-autoconfigure</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-mcp-server</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring AI MCP Server Auto Configuration</name>
|
||||
<description>Spring AI MCP Server Auto Configuration</description>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
<relativePath>../../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -7,7 +7,7 @@
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../pom.xml</relativePath>
|
||||
<relativePath>../../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-memory</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring AI Chat Observation Auto Configuration</name>
|
||||
<description>Spring AI Chat Observation Auto Configuration</description>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
<connection>git://github.com/spring-projects/spring-ai.git</connection>
|
||||
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-core</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-tracing-bridge-otel</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-test</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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.chat.observation.autoconfigure;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.tracing.Tracer;
|
||||
import io.micrometer.tracing.otel.bridge.OtelTracer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationContext;
|
||||
import org.springframework.ai.chat.client.observation.ChatClientObservationContext;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.observation.ChatModelCompletionObservationFilter;
|
||||
import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler;
|
||||
import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationContext;
|
||||
import org.springframework.ai.chat.observation.ChatModelPromptContentObservationFilter;
|
||||
import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext;
|
||||
import org.springframework.ai.image.observation.ImageModelObservationContext;
|
||||
import org.springframework.ai.model.observation.ErrorLoggingObservationHandler;
|
||||
import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
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.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Auto-configuration for Spring AI chat model observations.
|
||||
*
|
||||
* @author Thomas Vitale
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration(
|
||||
afterName = { "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration" })
|
||||
@ConditionalOnClass(ChatModel.class)
|
||||
@EnableConfigurationProperties({ ChatObservationProperties.class })
|
||||
public class ChatObservationAutoConfiguration {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ChatObservationAutoConfiguration.class);
|
||||
|
||||
private static void logPromptContentWarning() {
|
||||
logger.warn(
|
||||
"You have enabled the inclusion of the prompt content in the observations, with the risk of exposing sensitive or private information. Please, be careful!");
|
||||
}
|
||||
|
||||
private static void logCompletionWarning() {
|
||||
logger.warn(
|
||||
"You have enabled the inclusion of the completion content in the observations, with the risk of exposing sensitive or private information. Please, be careful!");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(MeterRegistry.class)
|
||||
ChatModelMeterObservationHandler chatModelMeterObservationHandler(ObjectProvider<MeterRegistry> meterRegistry) {
|
||||
return new ChatModelMeterObservationHandler(meterRegistry.getObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* The chat content is typically too big to be included in an observation as span
|
||||
* attributes. That's why the preferred way to store it is as span events, which are
|
||||
* supported by OpenTelemetry but not yet surfaced through the Micrometer APIs. This
|
||||
* primary/fallback configuration is a temporary solution until
|
||||
* https://github.com/micrometer-metrics/micrometer/issues/5238 is delivered.
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(OtelTracer.class)
|
||||
@ConditionalOnBean(OtelTracer.class)
|
||||
static class PrimaryChatContentObservationConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-prompt",
|
||||
havingValue = "true")
|
||||
ChatModelPromptContentObservationHandler chatModelPromptContentObservationHandler() {
|
||||
logPromptContentWarning();
|
||||
return new ChatModelPromptContentObservationHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-completion",
|
||||
havingValue = "true")
|
||||
ChatModelCompletionObservationHandler chatModelCompletionObservationHandler() {
|
||||
logCompletionWarning();
|
||||
return new ChatModelCompletionObservationHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingClass("io.micrometer.tracing.otel.bridge.OtelTracer")
|
||||
static class FallbackChatContentObservationConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-prompt",
|
||||
havingValue = "true")
|
||||
ChatModelPromptContentObservationFilter chatModelPromptObservationFilter() {
|
||||
logPromptContentWarning();
|
||||
return new ChatModelPromptContentObservationFilter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-completion",
|
||||
havingValue = "true")
|
||||
ChatModelCompletionObservationFilter chatModelCompletionObservationFilter() {
|
||||
logCompletionWarning();
|
||||
return new ChatModelCompletionObservationFilter();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Tracer.class)
|
||||
@ConditionalOnBean(Tracer.class)
|
||||
static class TracingChatContentObservationConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = ChatObservationProperties.CONFIG_PREFIX, name = "include-error-logging",
|
||||
havingValue = "true")
|
||||
public ErrorLoggingObservationHandler errorLoggingObservationHandler(Tracer tracer) {
|
||||
return new ErrorLoggingObservationHandler(tracer,
|
||||
List.of(EmbeddingModelObservationContext.class, ImageModelObservationContext.class,
|
||||
ChatModelObservationContext.class, ChatClientObservationContext.class,
|
||||
AdvisorObservationContext.class, VectorStoreObservationContext.class));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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.chat.observation.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Configuration properties for chat model observations.
|
||||
*
|
||||
* @author Thomas Vitale
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(ChatObservationProperties.CONFIG_PREFIX)
|
||||
public class ChatObservationProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.chat.observations";
|
||||
|
||||
/**
|
||||
* Whether to include the completion content in the observations.
|
||||
*/
|
||||
private boolean includeCompletion = false;
|
||||
|
||||
/**
|
||||
* Whether to include the prompt content in the observations.
|
||||
*/
|
||||
private boolean includePrompt = false;
|
||||
|
||||
/**
|
||||
* Whether to include error logging in the observations.
|
||||
*/
|
||||
private boolean includeErrorLogging = false;
|
||||
|
||||
public boolean isIncludeCompletion() {
|
||||
return this.includeCompletion;
|
||||
}
|
||||
|
||||
public void setIncludeCompletion(boolean includeCompletion) {
|
||||
this.includeCompletion = includeCompletion;
|
||||
}
|
||||
|
||||
public boolean isIncludePrompt() {
|
||||
return this.includePrompt;
|
||||
}
|
||||
|
||||
public void setIncludePrompt(boolean includePrompt) {
|
||||
this.includePrompt = includePrompt;
|
||||
}
|
||||
|
||||
public boolean isIncludeErrorLogging() {
|
||||
return this.includeErrorLogging;
|
||||
}
|
||||
|
||||
public void setIncludeErrorLogging(boolean includeErrorLogging) {
|
||||
this.includeErrorLogging = includeErrorLogging;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for chat observation.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.ai.model.chat.observation.autoconfigure;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# Copyright 2025-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.
|
||||
#
|
||||
org.springframework.ai.model.chat.observation.autoconfigure.ChatObservationAutoConfiguration
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.chat.observation.autoconfigure;
|
||||
|
||||
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
||||
import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
|
||||
import io.micrometer.tracing.otel.bridge.OtelTracer;
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.chat.observation.ChatModelCompletionObservationFilter;
|
||||
import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler;
|
||||
import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler;
|
||||
import org.springframework.ai.chat.observation.ChatModelPromptContentObservationFilter;
|
||||
import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ChatObservationAutoConfiguration}.
|
||||
*
|
||||
* @author Thomas Vitale
|
||||
*/
|
||||
class ChatObservationAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ChatObservationAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void meterObservationHandlerEnabled() {
|
||||
this.contextRunner.withBean(CompositeMeterRegistry.class)
|
||||
.run(context -> assertThat(context).hasSingleBean(ChatModelMeterObservationHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void meterObservationHandlerDisabled() {
|
||||
this.contextRunner.run(context -> assertThat(context).doesNotHaveBean(ChatModelMeterObservationHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void promptFilterDefault() {
|
||||
this.contextRunner
|
||||
.run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationFilter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void promptHandlerDefault() {
|
||||
this.contextRunner
|
||||
.run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void promptHandlerEnabled() {
|
||||
this.contextRunner
|
||||
.withBean(OtelTracer.class, OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null)
|
||||
.withPropertyValues("spring.ai.chat.observations.include-prompt=true")
|
||||
.run(context -> assertThat(context).hasSingleBean(ChatModelPromptContentObservationHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void promptHandlerDisabled() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.chat.observations.include-prompt=true")
|
||||
.run(context -> assertThat(context).doesNotHaveBean(ChatModelPromptContentObservationHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void completionFilterDefault() {
|
||||
this.contextRunner
|
||||
.run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationFilter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void completionHandlerDefault() {
|
||||
this.contextRunner
|
||||
.run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void completionHandlerEnabled() {
|
||||
this.contextRunner
|
||||
.withBean(OtelTracer.class, OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null)
|
||||
.withPropertyValues("spring.ai.chat.observations.include-completion=true")
|
||||
.run(context -> assertThat(context).hasSingleBean(ChatModelCompletionObservationHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void completionHandlerDisabled() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.chat.observations.include-completion=true")
|
||||
.run(context -> assertThat(context).doesNotHaveBean(ChatModelCompletionObservationHandler.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring AI Embedding Observation Auto Configuration</name>
|
||||
<description>Spring AI Embedding Observation Auto Configuration</description>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
<connection>git://github.com/spring-projects/spring-ai.git</connection>
|
||||
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-core</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-test</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.embedding.observation.autoconfigure;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
import org.springframework.ai.embedding.EmbeddingModel;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelMeterObservationHandler;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
/**
|
||||
* Auto-configuration for Spring AI embedding model observations.
|
||||
*
|
||||
* @author Thomas Vitale
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration(
|
||||
afterName = "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration")
|
||||
@ConditionalOnClass(EmbeddingModel.class)
|
||||
public class EmbeddingObservationAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnBean(MeterRegistry.class)
|
||||
EmbeddingModelMeterObservationHandler embeddingModelMeterObservationHandler(
|
||||
ObjectProvider<MeterRegistry> meterRegistry) {
|
||||
return new EmbeddingModelMeterObservationHandler(meterRegistry.getObject());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for embedding observation.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.ai.model.embedding.observation.autoconfigure;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# Copyright 2025-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.
|
||||
#
|
||||
org.springframework.ai.model.embedding.observation.autoconfigure.EmbeddingObservationAutoConfiguration
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.embedding.observation.autoconfigure;
|
||||
|
||||
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelMeterObservationHandler;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link EmbeddingObservationAutoConfiguration}.
|
||||
*
|
||||
* @author Thomas Vitale
|
||||
*/
|
||||
class EmbeddingObservationAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(EmbeddingObservationAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void meterObservationHandlerEnabled() {
|
||||
this.contextRunner.withBean(CompositeMeterRegistry.class)
|
||||
.run(context -> assertThat(context).hasSingleBean(EmbeddingModelMeterObservationHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void meterObservationHandlerDisabled() {
|
||||
this.contextRunner
|
||||
.run(context -> assertThat(context).doesNotHaveBean(EmbeddingModelMeterObservationHandler.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../../../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>Spring AI Image Observation Auto Configuration</name>
|
||||
<description>Spring AI Image Observation Auto Configuration</description>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects/spring-ai</url>
|
||||
<connection>git://github.com/spring-projects/spring-ai.git</connection>
|
||||
<developerConnection>git@github.com:spring-projects/spring-ai.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-core</artifactId>
|
||||
<version>${parent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Boot dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-test</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.image.observation.autoconfigure;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.image.ImageModel;
|
||||
import org.springframework.ai.image.observation.ImageModelPromptContentObservationFilter;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Auto-configuration for Spring AI image model observations.
|
||||
*
|
||||
* @author Thomas Vitale
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration(
|
||||
afterName = "org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration.class")
|
||||
@ConditionalOnClass(ImageModel.class)
|
||||
@EnableConfigurationProperties({ ImageObservationProperties.class })
|
||||
public class ImageObservationAutoConfiguration {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ImageObservationAutoConfiguration.class);
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = ImageObservationProperties.CONFIG_PREFIX, name = "include-prompt",
|
||||
havingValue = "true")
|
||||
ImageModelPromptContentObservationFilter imageModelPromptObservationFilter() {
|
||||
logger.warn(
|
||||
"You have enabled the inclusion of the image prompt content in the observations, with the risk of exposing sensitive or private information. Please, be careful!");
|
||||
return new ImageModelPromptContentObservationFilter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.image.observation.autoconfigure;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* Configuration properties for image model observations.
|
||||
*
|
||||
* @author Thomas Vitale
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(ImageObservationProperties.CONFIG_PREFIX)
|
||||
public class ImageObservationProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.image.observations";
|
||||
|
||||
/**
|
||||
* Whether to include the prompt content in the observations.
|
||||
*/
|
||||
private boolean includePrompt = false;
|
||||
|
||||
public boolean isIncludePrompt() {
|
||||
return this.includePrompt;
|
||||
}
|
||||
|
||||
public void setIncludePrompt(boolean includePrompt) {
|
||||
this.includePrompt = includePrompt;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for image observation.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.ai.model.image.observation.autoconfigure;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# Copyright 2025-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.
|
||||
#
|
||||
org.springframework.ai.model.image.observation.autoconfigure.ImageObservationAutoConfiguration
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.image.observation.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.image.observation.ImageModelPromptContentObservationFilter;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ImageObservationAutoConfiguration}.
|
||||
*
|
||||
* @author Thomas Vitale
|
||||
*/
|
||||
class ImageObservationAutoConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(ImageObservationAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void promptFilterDefault() {
|
||||
this.contextRunner
|
||||
.run(context -> assertThat(context).doesNotHaveBean(ImageModelPromptContentObservationFilter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void promptFilterEnabled() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.image.observations.include-prompt=true")
|
||||
.run(context -> assertThat(context).hasSingleBean(ImageModelPromptContentObservationFilter.class));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,13 +35,6 @@
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
@@ -58,21 +51,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-embedding</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-image</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -20,6 +20,8 @@ import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.ai.anthropic.api.AnthropicApi;
|
||||
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;
|
||||
@@ -47,14 +49,13 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
* @since 1.0.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",
|
||||
matchIfMissing = true)
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
ToolCallingAutoConfiguration.class, WebClientAutoConfiguration.class })
|
||||
public class AnthropicAutoConfiguration {
|
||||
@@ -73,6 +74,8 @@ 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,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2025-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.anthropic.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.anthropic.AnthropicChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
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.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class AnthropicModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.anthropic.apiKey=" + System.getenv("ANTHROPIC_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(AnthropicAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(AnthropicChatModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AnthropicChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=anthropic").run(context -> {
|
||||
assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(AnthropicChatModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -120,11 +120,10 @@ public class AnthropicPropertiesTests {
|
||||
});
|
||||
|
||||
// Explicitly disable the chat auto-configuration.
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.anthropic.chat.enabled=false")
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, AnthropicAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(AnthropicChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(AnthropicChatModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ 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;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -96,7 +97,7 @@ class FunctionCallWithFunctionBeanIT {
|
||||
"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),
|
||||
FunctionCallingOptions.builder().function("weatherFunction").build()));
|
||||
ToolCallingChatOptions.builder().toolNames("weatherFunction").build()));
|
||||
|
||||
logger.info("Response: {}", response);
|
||||
|
||||
|
||||
@@ -35,13 +35,6 @@
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
@@ -58,21 +51,21 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<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-observation-embedding</artifactId>
|
||||
<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-observation-image</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -28,6 +28,8 @@ 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;
|
||||
@@ -122,7 +124,7 @@ public class AzureOpenAiAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = AzureOpenAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.AZURE_OPENAI,
|
||||
matchIfMissing = true)
|
||||
public AzureOpenAiChatModel azureOpenAiChatModel(OpenAIClientBuilder openAIClientBuilder,
|
||||
AzureOpenAiChatProperties chatProperties, ToolCallingManager toolCallingManager,
|
||||
@@ -142,8 +144,8 @@ public class AzureOpenAiAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = AzureOpenAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.AZURE_OPENAI,
|
||||
matchIfMissing = true)
|
||||
public AzureOpenAiEmbeddingModel azureOpenAiEmbeddingModel(OpenAIClientBuilder openAIClient,
|
||||
AzureOpenAiEmbeddingProperties embeddingProperties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
|
||||
@@ -160,17 +162,9 @@ public class AzureOpenAiAutoConfiguration {
|
||||
|
||||
@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,
|
||||
@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());
|
||||
@@ -178,8 +172,8 @@ public class AzureOpenAiAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = AzureOpenAiAudioTranscriptionProperties.CONFIG_PREFIX, name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
@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());
|
||||
@@ -190,4 +184,12 @@ public class AzureOpenAiAutoConfiguration {
|
||||
customizers.orderedStream().forEach(customizer -> customizer.customize(clientBuilder));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
|
||||
DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
|
||||
manager.setApplicationContext(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2025-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.azure.openai.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
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.boot.autoconfigure.AutoConfigurations;
|
||||
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.
|
||||
*
|
||||
* @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));
|
||||
|
||||
@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.withPropertyValues("spring.ai.model.chat=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiChatProperties.class)).isNotEmpty();
|
||||
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
|
||||
.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")
|
||||
.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();
|
||||
});
|
||||
}
|
||||
|
||||
@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.withPropertyValues("spring.ai.model.embedding=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
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
|
||||
.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")
|
||||
.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();
|
||||
});
|
||||
}
|
||||
|
||||
@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.withPropertyValues("spring.ai.model.image=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(AzureOpenAiImageOptionsProperties.class)).isNotEmpty();
|
||||
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
|
||||
.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")
|
||||
.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();
|
||||
});
|
||||
}
|
||||
|
||||
@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
|
||||
.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")
|
||||
.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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
@@ -42,13 +42,6 @@
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
@@ -65,21 +58,21 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<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-observation-embedding</artifactId>
|
||||
<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-observation-image</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -20,6 +20,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
|
||||
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionConfiguration;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionProperties;
|
||||
import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingModel;
|
||||
@@ -43,7 +45,8 @@ import org.springframework.context.annotation.Import;
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(CohereEmbeddingBedrockApi.class)
|
||||
@EnableConfigurationProperties({ BedrockCohereEmbeddingProperties.class, BedrockAwsConnectionProperties.class })
|
||||
@ConditionalOnProperty(prefix = BedrockCohereEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true")
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.BEDROCK_COHERE,
|
||||
matchIfMissing = true)
|
||||
@Import(BedrockAwsConnectionConfiguration.class)
|
||||
public class BedrockCohereEmbeddingAutoConfiguration {
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ 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.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionConfiguration;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionProperties;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
@@ -53,8 +55,8 @@ import org.springframework.context.annotation.Import;
|
||||
@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)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.BEDROCK_CONVERSE,
|
||||
matchIfMissing = true)
|
||||
@Import(BedrockAwsConnectionConfiguration.class)
|
||||
@ImportAutoConfiguration({ ToolCallingAutoConfiguration.class })
|
||||
public class BedrockConverseProxyChatAutoConfiguration {
|
||||
|
||||
@@ -20,6 +20,8 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.providers.AwsRegionProvider;
|
||||
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionConfiguration;
|
||||
import org.springframework.ai.model.bedrock.autoconfigure.BedrockAwsConnectionProperties;
|
||||
import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel;
|
||||
@@ -43,7 +45,8 @@ import org.springframework.context.annotation.Import;
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(TitanEmbeddingBedrockApi.class)
|
||||
@EnableConfigurationProperties({ BedrockTitanEmbeddingProperties.class, BedrockAwsConnectionProperties.class })
|
||||
@ConditionalOnProperty(prefix = BedrockTitanEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true")
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.BEDROCK_TITAN,
|
||||
matchIfMissing = true)
|
||||
@Import(BedrockAwsConnectionConfiguration.class)
|
||||
public class BedrockTitanEmbeddingAutoConfiguration {
|
||||
|
||||
@@ -62,7 +65,6 @@ public class BedrockTitanEmbeddingAutoConfiguration {
|
||||
@ConditionalOnBean(TitanEmbeddingBedrockApi.class)
|
||||
public BedrockTitanEmbeddingModel titanEmbeddingModel(TitanEmbeddingBedrockApi titanEmbeddingApi,
|
||||
BedrockTitanEmbeddingProperties properties) {
|
||||
|
||||
return new BedrockTitanEmbeddingModel(titanEmbeddingApi).withInputType(properties.getInputType());
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class BedrockCohereEmbeddingAutoConfigurationIT {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = BedrockTestUtils.getContextRunner()
|
||||
.withPropertyValues("spring.ai.bedrock.cohere.embedding.enabled=true",
|
||||
.withPropertyValues("spring.ai.model.embedding=bedrock-cohere",
|
||||
"spring.ai.bedrock.cohere.embedding.model=" + CohereEmbeddingModel.COHERE_EMBED_MULTILINGUAL_V3.id(),
|
||||
"spring.ai.bedrock.cohere.embedding.options.inputType=SEARCH_DOCUMENT",
|
||||
"spring.ai.bedrock.cohere.embedding.options.truncate=NONE")
|
||||
@@ -109,19 +109,18 @@ public class BedrockCohereEmbeddingAutoConfigurationIT {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void embeddingDisabled() {
|
||||
public void embeddingActivation() {
|
||||
|
||||
// It is disabled by default
|
||||
BedrockTestUtils.getContextRunnerWithUserConfiguration()
|
||||
.withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
// Explicitly enable the embedding auto-configuration.
|
||||
BedrockTestUtils.getContextRunnerWithUserConfiguration()
|
||||
.withPropertyValues("spring.ai.bedrock.cohere.embedding.enabled=true")
|
||||
.withPropertyValues("spring.ai.model.embedding=bedrock-cohere")
|
||||
.withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isNotEmpty();
|
||||
@@ -130,7 +129,7 @@ public class BedrockCohereEmbeddingAutoConfigurationIT {
|
||||
|
||||
// Explicitly disable the embedding auto-configuration.
|
||||
BedrockTestUtils.getContextRunnerWithUserConfiguration()
|
||||
.withPropertyValues("spring.ai.bedrock.cohere.embedding.enabled=false")
|
||||
.withPropertyValues("spring.ai.model.embedding=none")
|
||||
.withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isEmpty();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2025-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.cohere;
|
||||
|
||||
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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link BedrockCohereEmbeddingAutoConfiguration}'s conditional enabling
|
||||
* of models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class BedrockCohereModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class))
|
||||
.withBean(ObjectMapper.class, ObjectMapper::new);
|
||||
|
||||
@Test
|
||||
void embeddingModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=bedrock-cohere").run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2025-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;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
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.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class BedrockConverseModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockProxyChatModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockProxyChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=bedrock-converse").run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockProxyChatModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package org.springframework.ai.model.bedrock.autoconfigure.converse;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.bedrock.converse.BedrockProxyChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
@@ -71,14 +72,20 @@ public class BedrockConverseProxyChatPropertiesTests {
|
||||
.run(context -> assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isNotEmpty());
|
||||
|
||||
// Explicitly enable the chat auto-configuration.
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.converse.chat.enabled=true")
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.model.chat=bedrock-converse")
|
||||
.withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class))
|
||||
.run(context -> assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isNotEmpty());
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockProxyChatModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
// Explicitly disable the chat auto-configuration.
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.converse.chat.enabled=false")
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(BedrockConverseProxyChatAutoConfiguration.class))
|
||||
.run(context -> assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isEmpty());
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockConverseProxyChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockProxyChatModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -108,19 +108,18 @@ public class BedrockTitanEmbeddingAutoConfigurationIT {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void embeddingDisabled() {
|
||||
public void embeddingActivation() {
|
||||
|
||||
// It is disabled by default
|
||||
BedrockTestUtils.getContextRunnerWithUserConfiguration()
|
||||
.withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
// Explicitly enable the embedding auto-configuration.
|
||||
BedrockTestUtils.getContextRunnerWithUserConfiguration()
|
||||
.withPropertyValues("spring.ai.bedrock.titan.embedding.enabled=true")
|
||||
.withPropertyValues("spring.ai.model.embedding=bedrock-titan")
|
||||
.withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isNotEmpty();
|
||||
@@ -129,7 +128,7 @@ public class BedrockTitanEmbeddingAutoConfigurationIT {
|
||||
|
||||
// Explicitly disable the embedding auto-configuration.
|
||||
BedrockTestUtils.getContextRunnerWithUserConfiguration()
|
||||
.withPropertyValues("spring.ai.bedrock.titan.embedding.enabled=false")
|
||||
.withPropertyValues("spring.ai.model.embedding=none")
|
||||
.withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isEmpty();
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2025-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.titan;
|
||||
|
||||
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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link BedrockTitanEmbeddingAutoConfiguration}'s conditional enabling of
|
||||
* models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class BedrockTitanModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class))
|
||||
.withBean(ObjectMapper.class, ObjectMapper::new);
|
||||
|
||||
@Test
|
||||
void embeddingModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=bedrock-titan").run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -37,14 +37,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.ai.model.huggingface.autoconfigure;
|
||||
|
||||
import org.springframework.ai.huggingface.HuggingfaceChatModel;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -31,7 +33,7 @@ public class HuggingfaceChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = HuggingfaceChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.HUGGINGFACE,
|
||||
matchIfMissing = true)
|
||||
public HuggingfaceChatModel huggingfaceChatModel(HuggingfaceChatProperties huggingfaceChatProperties) {
|
||||
return new HuggingfaceChatModel(huggingfaceChatProperties.getApiKey(), huggingfaceChatProperties.getUrl());
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2025-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.huggingface.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.huggingface.HuggingfaceChatModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for {@link HuggingfaceChatAutoConfiguration}'s conditional enabling of
|
||||
* models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class HuggingfaceModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(HuggingfaceChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
assertThat(context.getBeansOfType(HuggingfaceChatModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(HuggingfaceChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(HuggingfaceChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.chat=huggingface").run(context -> {
|
||||
assertThat(context.getBeansOfType(HuggingfaceChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(HuggingfaceChatModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,13 +35,6 @@
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
@@ -58,14 +51,14 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<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-observation-embedding</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.util.List;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
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;
|
||||
@@ -57,7 +59,7 @@ public class MiniMaxAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = MiniMaxChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MINIMAX,
|
||||
matchIfMissing = true)
|
||||
public MiniMaxChatModel miniMaxChatModel(MiniMaxConnectionProperties commonProperties,
|
||||
MiniMaxChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
@@ -79,7 +81,7 @@ public class MiniMaxAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = MiniMaxEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MINIMAX,
|
||||
matchIfMissing = true)
|
||||
public MiniMaxEmbeddingModel miniMaxEmbeddingModel(MiniMaxConnectionProperties commonProperties,
|
||||
MiniMaxEmbeddingProperties embeddingProperties,
|
||||
|
||||
@@ -265,7 +265,7 @@ public class MiniMaxPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL",
|
||||
"spring.ai.minimax.embedding.enabled=false")
|
||||
"spring.ai.model.embedding=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
@@ -297,7 +297,7 @@ public class MiniMaxPropertiesTests {
|
||||
void chatActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL",
|
||||
"spring.ai.minimax.chat.enabled=false")
|
||||
"spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
@@ -316,7 +316,7 @@ public class MiniMaxPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.minimax.api-key=API_KEY", "spring.ai.minimax.base-url=TEST_BASE_URL",
|
||||
"spring.ai.minimax.chat.enabled=true")
|
||||
"spring.ai.model.chat=minimax")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MiniMaxAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2025-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.minimax.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.minimax.MiniMaxChatModel;
|
||||
import org.springframework.ai.minimax.MiniMaxEmbeddingModel;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
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.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class MinimaxModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(MiniMaxAutoConfiguration.class, 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 -> {
|
||||
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")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxChatProperties.class)).isNotEmpty();
|
||||
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")
|
||||
.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.withPropertyValues("spring.ai.model.embedding=none").run(context -> {
|
||||
assertThat(context.getBeansOfType(MiniMaxEmbeddingProperties.class)).isNotEmpty();
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,13 +35,6 @@
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
@@ -58,21 +51,21 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<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-observation-embedding</artifactId>
|
||||
<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-observation-image</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.ai.model.mistralai.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.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
@@ -63,7 +65,7 @@ public class MistralAiAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = MistralAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.MISTRAL,
|
||||
matchIfMissing = true)
|
||||
public MistralAiEmbeddingModel mistralAiEmbeddingModel(MistralAiCommonProperties commonProperties,
|
||||
MistralAiEmbeddingProperties embeddingProperties,
|
||||
@@ -86,7 +88,7 @@ public class MistralAiAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = MistralAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.MISTRAL,
|
||||
matchIfMissing = true)
|
||||
public MistralAiChatModel mistralAiChatModel(MistralAiCommonProperties commonProperties,
|
||||
MistralAiChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
|
||||
@@ -35,7 +35,7 @@ public class MistralAiChatProperties extends MistralAiParentProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.mistralai.chat";
|
||||
|
||||
public static final String DEFAULT_CHAT_MODEL = MistralAiApi.ChatModel.OPEN_MISTRAL_7B.getValue();
|
||||
public static final String DEFAULT_CHAT_MODEL = MistralAiApi.ChatModel.SMALL.getValue();
|
||||
|
||||
private static final Double DEFAULT_TEMPERATURE = 0.7;
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2025-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.mistralai.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.mistralai.MistralAiChatModel;
|
||||
import org.springframework.ai.mistralai.MistralAiEmbeddingModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
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.
|
||||
*
|
||||
* @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")
|
||||
.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)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embeddingModelActivation() {
|
||||
this.contextRunner.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.withPropertyValues("spring.ai.model.embedding=mistral").run(context -> {
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(MistralAiEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,11 +26,11 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration;
|
||||
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.api.MistralAiApi;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
@@ -60,7 +60,7 @@ class PaymentStatusBeanOpenAiIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("MISTRAL_AI_API_KEY"),
|
||||
"spring.ai.openai.chat.base-url=https://api.mistral.ai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -35,13 +35,6 @@
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
@@ -51,14 +44,14 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<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-observation-embedding</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.util.List;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
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.model.function.DefaultFunctionCallbackResolver;
|
||||
@@ -54,7 +56,10 @@ public class MoonshotAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = MoonshotChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
// @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,
|
||||
|
||||
@@ -135,7 +135,7 @@ public class MoonshotPropertiesTests {
|
||||
void chatActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL",
|
||||
"spring.ai.moonshot.chat.enabled=false")
|
||||
"spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
@@ -154,7 +154,7 @@ public class MoonshotPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.moonshot.api-key=API_KEY", "spring.ai.moonshot.base-url=TEST_BASE_URL",
|
||||
"spring.ai.moonshot.chat.enabled=true")
|
||||
"spring.ai.model.chat=moonshot")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, MoonshotAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
|
||||
@@ -35,13 +35,6 @@
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
@@ -51,14 +44,14 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<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-observation-embedding</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -31,6 +31,8 @@ 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;
|
||||
@@ -90,7 +92,7 @@ public class OCIGenAiAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = OCIEmbeddingModelProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OCI_GENAI,
|
||||
matchIfMissing = true)
|
||||
public OCIEmbeddingModel ociEmbeddingModel(GenerativeAiInferenceClient generativeAiClient,
|
||||
OCIEmbeddingModelProperties properties) {
|
||||
@@ -98,7 +100,7 @@ public class OCIGenAiAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = OCICohereChatModelProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OCI_GENAI,
|
||||
matchIfMissing = true)
|
||||
public OCICohereChatModel ociChatModel(GenerativeAiInferenceClient generativeAiClient,
|
||||
OCICohereChatModelProperties properties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
|
||||
@@ -34,14 +34,6 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
|
||||
@@ -51,21 +43,21 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<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-observation-embedding</artifactId>
|
||||
<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-observation-image</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -18,6 +18,8 @@ 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;
|
||||
@@ -77,7 +79,7 @@ public class OllamaAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = OllamaChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OLLAMA,
|
||||
matchIfMissing = true)
|
||||
public OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, OllamaChatProperties properties,
|
||||
OllamaInitializationProperties initProperties, ToolCallingManager toolCallingManager,
|
||||
@@ -103,7 +105,7 @@ public class OllamaAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = OllamaEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OLLAMA,
|
||||
matchIfMissing = true)
|
||||
public OllamaEmbeddingModel ollamaEmbeddingModel(OllamaApi ollamaApi, OllamaEmbeddingProperties properties,
|
||||
OllamaInitializationProperties initProperties, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2025-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.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.OllamaEmbeddingModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
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.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class OllamaModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(OllamaAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
this.contextRunner.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")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaChatProperties.class)).isNotEmpty();
|
||||
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")
|
||||
.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)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embeddingModelActivation() {
|
||||
this.contextRunner.run(context -> {
|
||||
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.withPropertyValues("spring.ai.model.embedding=ollama").run(context -> {
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OllamaEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2025-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.openai.autoconfigure;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class OpenAIAutoConfigurationUtil {
|
||||
|
||||
protected static @NotNull ResolvedConnectionProperties resolveConnectionProperties(
|
||||
OpenAiParentProperties commonProperties, OpenAiParentProperties modelProperties, String modelType) {
|
||||
|
||||
String baseUrl = StringUtils.hasText(modelProperties.getBaseUrl()) ? modelProperties.getBaseUrl()
|
||||
: commonProperties.getBaseUrl();
|
||||
String apiKey = StringUtils.hasText(modelProperties.getApiKey()) ? modelProperties.getApiKey()
|
||||
: commonProperties.getApiKey();
|
||||
String projectId = StringUtils.hasText(modelProperties.getProjectId()) ? modelProperties.getProjectId()
|
||||
: commonProperties.getProjectId();
|
||||
String organizationId = StringUtils.hasText(modelProperties.getOrganizationId())
|
||||
? modelProperties.getOrganizationId() : commonProperties.getOrganizationId();
|
||||
|
||||
Map<String, List<String>> connectionHeaders = new HashMap<>();
|
||||
if (StringUtils.hasText(projectId)) {
|
||||
connectionHeaders.put("OpenAI-Project", List.of(projectId));
|
||||
}
|
||||
if (StringUtils.hasText(organizationId)) {
|
||||
connectionHeaders.put("OpenAI-Organization", List.of(organizationId));
|
||||
}
|
||||
|
||||
Assert.hasText(baseUrl,
|
||||
"OpenAI base URL must be set. Use the connection property: spring.ai.openai.base-url or spring.ai.openai."
|
||||
+ modelType + ".base-url property.");
|
||||
Assert.hasText(apiKey,
|
||||
"OpenAI API key must be set. Use the connection property: spring.ai.openai.api-key or spring.ai.openai."
|
||||
+ modelType + ".api-key property.");
|
||||
|
||||
return new ResolvedConnectionProperties(baseUrl, apiKey, CollectionUtils.toMultiValueMap(connectionHeaders));
|
||||
}
|
||||
|
||||
public record ResolvedConnectionProperties(String baseUrl, String apiKey, MultiValueMap<String, String> headers) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.openai.autoconfigure;
|
||||
|
||||
import org.springframework.ai.model.SimpleApiKey;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.openai.OpenAiAudioSpeechModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.api.OpenAiAudioApi;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
|
||||
import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for OpenAI.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Stefan Vassilev
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class,
|
||||
SpringAiRetryAutoConfiguration.class })
|
||||
@ConditionalOnClass(OpenAiApi.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.AUDIO_SPEECH_MODEL, havingValue = SpringAIModels.OPENAI,
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties({ OpenAiConnectionProperties.class, OpenAiAudioSpeechProperties.class })
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class })
|
||||
public class OpenAiAudioSpeechAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OpenAiAudioSpeechModel openAiAudioSpeechModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiAudioSpeechProperties speechProperties, RetryTemplate retryTemplate,
|
||||
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
OpenAIAutoConfigurationUtil.ResolvedConnectionProperties resolved = resolveConnectionProperties(
|
||||
commonProperties, speechProperties, "speech");
|
||||
|
||||
var openAiAudioApi = OpenAiAudioApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
|
||||
.webClientBuilder(webClientBuilderProvider.getIfAvailable(WebClient::builder))
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
|
||||
return new OpenAiAudioSpeechModel(openAiAudioApi, speechProperties.getOptions());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.openai.autoconfigure;
|
||||
|
||||
import org.springframework.ai.model.SimpleApiKey;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
|
||||
import org.springframework.ai.openai.OpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.api.OpenAiAudioApi;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
|
||||
import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for OpenAI.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Stefan Vassilev
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class,
|
||||
SpringAiRetryAutoConfiguration.class })
|
||||
@ConditionalOnClass(OpenAiApi.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.AUDIO_TRANSCRIPTION_MODEL, havingValue = SpringAIModels.OPENAI,
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties({ OpenAiConnectionProperties.class, OpenAiAudioTranscriptionProperties.class })
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class })
|
||||
public class OpenAiAudioTranscriptionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OpenAiAudioTranscriptionModel openAiAudioTranscriptionModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiAudioTranscriptionProperties transcriptionProperties, RetryTemplate retryTemplate,
|
||||
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
OpenAIAutoConfigurationUtil.ResolvedConnectionProperties resolved = resolveConnectionProperties(
|
||||
commonProperties, transcriptionProperties, "transcription");
|
||||
|
||||
var openAiAudioApi = OpenAiAudioApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
|
||||
.webClientBuilder(webClientBuilderProvider.getIfAvailable(WebClient::builder))
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
|
||||
return new OpenAiAudioTranscriptionModel(openAiAudioApi, transcriptionProperties.getOptions(), retryTemplate);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,310 +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.openai.autoconfigure;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
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.image.observation.ImageModelObservationConvention;
|
||||
import org.springframework.ai.model.SimpleApiKey;
|
||||
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.openai.OpenAiAudioSpeechModel;
|
||||
import org.springframework.ai.openai.OpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiEmbeddingModel;
|
||||
import org.springframework.ai.openai.OpenAiImageModel;
|
||||
import org.springframework.ai.openai.OpenAiModerationModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.api.OpenAiAudioApi;
|
||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
import org.springframework.ai.openai.api.OpenAiModerationApi;
|
||||
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.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for OpenAI.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Stefan Vassilev
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class,
|
||||
SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class })
|
||||
@ConditionalOnClass(OpenAiApi.class)
|
||||
@EnableConfigurationProperties({ OpenAiConnectionProperties.class, OpenAiChatProperties.class,
|
||||
OpenAiEmbeddingProperties.class, OpenAiImageProperties.class, OpenAiAudioTranscriptionProperties.class,
|
||||
OpenAiAudioSpeechProperties.class, OpenAiModerationProperties.class })
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, ToolCallingAutoConfiguration.class })
|
||||
public class OpenAiAutoConfiguration {
|
||||
|
||||
private static @NotNull ResolvedConnectionProperties resolveConnectionProperties(
|
||||
OpenAiParentProperties commonProperties, OpenAiParentProperties modelProperties, String modelType) {
|
||||
|
||||
String baseUrl = StringUtils.hasText(modelProperties.getBaseUrl()) ? modelProperties.getBaseUrl()
|
||||
: commonProperties.getBaseUrl();
|
||||
String apiKey = StringUtils.hasText(modelProperties.getApiKey()) ? modelProperties.getApiKey()
|
||||
: commonProperties.getApiKey();
|
||||
String projectId = StringUtils.hasText(modelProperties.getProjectId()) ? modelProperties.getProjectId()
|
||||
: commonProperties.getProjectId();
|
||||
String organizationId = StringUtils.hasText(modelProperties.getOrganizationId())
|
||||
? modelProperties.getOrganizationId() : commonProperties.getOrganizationId();
|
||||
|
||||
Map<String, List<String>> connectionHeaders = new HashMap<>();
|
||||
if (StringUtils.hasText(projectId)) {
|
||||
connectionHeaders.put("OpenAI-Project", List.of(projectId));
|
||||
}
|
||||
if (StringUtils.hasText(organizationId)) {
|
||||
connectionHeaders.put("OpenAI-Organization", List.of(organizationId));
|
||||
}
|
||||
|
||||
Assert.hasText(baseUrl,
|
||||
"OpenAI base URL must be set. Use the connection property: spring.ai.openai.base-url or spring.ai.openai."
|
||||
+ modelType + ".base-url property.");
|
||||
Assert.hasText(apiKey,
|
||||
"OpenAI API key must be set. Use the connection property: spring.ai.openai.api-key or spring.ai.openai."
|
||||
+ modelType + ".api-key property.");
|
||||
|
||||
return new ResolvedConnectionProperties(baseUrl, apiKey, CollectionUtils.toMultiValueMap(connectionHeaders));
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = OpenAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public OpenAiChatModel openAiChatModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ToolCallingManager toolCallingManager,
|
||||
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
|
||||
var openAiApi = openAiApi(chatProperties, commonProperties,
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder),
|
||||
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "chat");
|
||||
|
||||
var chatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(openAiApi)
|
||||
.defaultOptions(chatProperties.getOptions())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.retryTemplate(retryTemplate)
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.build();
|
||||
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = OpenAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public OpenAiEmbeddingModel openAiEmbeddingModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiEmbeddingProperties embeddingProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ObjectProvider<WebClient.Builder> webClientBuilderProvider, RetryTemplate retryTemplate,
|
||||
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
|
||||
|
||||
var openAiApi = openAiApi(embeddingProperties, commonProperties,
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder),
|
||||
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "embedding");
|
||||
|
||||
var embeddingModel = new OpenAiEmbeddingModel(openAiApi, embeddingProperties.getMetadataMode(),
|
||||
embeddingProperties.getOptions(), retryTemplate,
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
observationConvention.ifAvailable(embeddingModel::setObservationConvention);
|
||||
|
||||
return embeddingModel;
|
||||
}
|
||||
|
||||
private OpenAiApi openAiApi(OpenAiChatProperties chatProperties, OpenAiConnectionProperties commonProperties,
|
||||
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
|
||||
ResponseErrorHandler responseErrorHandler, String modelType) {
|
||||
|
||||
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, chatProperties,
|
||||
modelType);
|
||||
|
||||
return OpenAiApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.completionsPath(chatProperties.getCompletionsPath())
|
||||
.embeddingsPath(OpenAiEmbeddingProperties.DEFAULT_EMBEDDINGS_PATH)
|
||||
.restClientBuilder(restClientBuilder)
|
||||
.webClientBuilder(webClientBuilder)
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
}
|
||||
|
||||
private OpenAiApi openAiApi(OpenAiEmbeddingProperties embeddingProperties,
|
||||
OpenAiConnectionProperties commonProperties, RestClient.Builder restClientBuilder,
|
||||
WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler, String modelType) {
|
||||
|
||||
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, embeddingProperties,
|
||||
modelType);
|
||||
|
||||
return OpenAiApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.completionsPath(OpenAiChatProperties.DEFAULT_COMPLETIONS_PATH)
|
||||
.embeddingsPath(embeddingProperties.getEmbeddingsPath())
|
||||
.restClientBuilder(restClientBuilder)
|
||||
.webClientBuilder(webClientBuilder)
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = OpenAiImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public OpenAiImageModel openAiImageModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiImageProperties imageProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ImageModelObservationConvention> observationConvention) {
|
||||
|
||||
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, imageProperties, "image");
|
||||
|
||||
var openAiImageApi = OpenAiImageApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
var imageModel = new OpenAiImageModel(openAiImageApi, imageProperties.getOptions(), retryTemplate,
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
observationConvention.ifAvailable(imageModel::setObservationConvention);
|
||||
|
||||
return imageModel;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = OpenAiAudioTranscriptionProperties.CONFIG_PREFIX, name = "enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
public OpenAiAudioTranscriptionModel openAiAudioTranscriptionModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiAudioTranscriptionProperties transcriptionProperties, RetryTemplate retryTemplate,
|
||||
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, transcriptionProperties,
|
||||
"transcription");
|
||||
|
||||
var openAiAudioApi = OpenAiAudioApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
|
||||
.webClientBuilder(webClientBuilderProvider.getIfAvailable(WebClient::builder))
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
|
||||
return new OpenAiAudioTranscriptionModel(openAiAudioApi, transcriptionProperties.getOptions(), retryTemplate);
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OpenAiModerationModel openAiModerationClient(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiModerationProperties moderationProperties, RetryTemplate retryTemplate,
|
||||
ObjectProvider<RestClient.Builder> restClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, moderationProperties,
|
||||
"moderation");
|
||||
|
||||
var openAiModerationApi = OpenAiModerationApi.builder()
|
||||
.baseUrl(resolved.baseUrl)
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
return new OpenAiModerationModel(openAiModerationApi, retryTemplate)
|
||||
.withDefaultOptions(moderationProperties.getOptions());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = OpenAiAudioSpeechProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public OpenAiAudioSpeechModel openAiAudioSpeechClient(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiAudioSpeechProperties speechProperties, RetryTemplate retryTemplate,
|
||||
ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
ResolvedConnectionProperties resolved = resolveConnectionProperties(commonProperties, speechProperties,
|
||||
"speach");
|
||||
|
||||
var openAiAudioApi = OpenAiAudioApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
|
||||
.webClientBuilder(webClientBuilderProvider.getIfAvailable(WebClient::builder))
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
|
||||
return new OpenAiAudioSpeechModel(openAiAudioApi, speechProperties.getOptions());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
|
||||
DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
|
||||
manager.setApplicationContext(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
private record ResolvedConnectionProperties(String baseUrl, String apiKey, MultiValueMap<String, String> headers) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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.openai.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.image.observation.ImageModelObservationConvention;
|
||||
import org.springframework.ai.model.SimpleApiKey;
|
||||
import org.springframework.ai.model.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.openai.OpenAiAudioSpeechModel;
|
||||
import org.springframework.ai.openai.OpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiEmbeddingModel;
|
||||
import org.springframework.ai.openai.OpenAiImageModel;
|
||||
import org.springframework.ai.openai.OpenAiModerationModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.api.OpenAiAudioApi;
|
||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
import org.springframework.ai.openai.api.OpenAiModerationApi;
|
||||
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;
|
||||
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.retry.support.RetryTemplate;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for OpenAI.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Stefan Vassilev
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class,
|
||||
SpringAiRetryAutoConfiguration.class, ToolCallingAutoConfiguration.class })
|
||||
@ConditionalOnClass(OpenAiApi.class)
|
||||
@EnableConfigurationProperties({ OpenAiConnectionProperties.class, OpenAiChatProperties.class })
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.OPENAI,
|
||||
matchIfMissing = true)
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, ToolCallingAutoConfiguration.class })
|
||||
public class OpenAiChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OpenAiChatModel openAiChatModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ObjectProvider<WebClient.Builder> webClientBuilderProvider, ToolCallingManager toolCallingManager,
|
||||
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ChatModelObservationConvention> observationConvention) {
|
||||
|
||||
var openAiApi = openAiApi(chatProperties, commonProperties,
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder),
|
||||
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "chat");
|
||||
|
||||
var chatModel = OpenAiChatModel.builder()
|
||||
.openAiApi(openAiApi)
|
||||
.defaultOptions(chatProperties.getOptions())
|
||||
.toolCallingManager(toolCallingManager)
|
||||
.retryTemplate(retryTemplate)
|
||||
.observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP))
|
||||
.build();
|
||||
|
||||
observationConvention.ifAvailable(chatModel::setObservationConvention);
|
||||
|
||||
return chatModel;
|
||||
}
|
||||
|
||||
private OpenAiApi openAiApi(OpenAiChatProperties chatProperties, OpenAiConnectionProperties commonProperties,
|
||||
RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder,
|
||||
ResponseErrorHandler responseErrorHandler, String modelType) {
|
||||
|
||||
OpenAIAutoConfigurationUtil.ResolvedConnectionProperties resolved = resolveConnectionProperties(
|
||||
commonProperties, chatProperties, modelType);
|
||||
|
||||
return OpenAiApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.completionsPath(chatProperties.getCompletionsPath())
|
||||
.embeddingsPath(OpenAiEmbeddingProperties.DEFAULT_EMBEDDINGS_PATH)
|
||||
.restClientBuilder(restClientBuilder)
|
||||
.webClientBuilder(webClientBuilder)
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FunctionCallbackResolver springAiFunctionManager(ApplicationContext context) {
|
||||
DefaultFunctionCallbackResolver manager = new DefaultFunctionCallbackResolver();
|
||||
manager.setApplicationContext(context);
|
||||
return manager;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.openai.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.model.SimpleApiKey;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.openai.OpenAiEmbeddingModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
|
||||
import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for OpenAI.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Stefan Vassilev
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class,
|
||||
SpringAiRetryAutoConfiguration.class })
|
||||
@ConditionalOnClass(OpenAiApi.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.OPENAI,
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties({ OpenAiConnectionProperties.class, OpenAiEmbeddingProperties.class })
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class })
|
||||
public class OpenAiEmbeddingAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OpenAiEmbeddingModel openAiEmbeddingModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiEmbeddingProperties embeddingProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
ObjectProvider<WebClient.Builder> webClientBuilderProvider, RetryTemplate retryTemplate,
|
||||
ResponseErrorHandler responseErrorHandler, ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<EmbeddingModelObservationConvention> observationConvention) {
|
||||
|
||||
var openAiApi = openAiApi(embeddingProperties, commonProperties,
|
||||
restClientBuilderProvider.getIfAvailable(RestClient::builder),
|
||||
webClientBuilderProvider.getIfAvailable(WebClient::builder), responseErrorHandler, "embedding");
|
||||
|
||||
var embeddingModel = new OpenAiEmbeddingModel(openAiApi, embeddingProperties.getMetadataMode(),
|
||||
embeddingProperties.getOptions(), retryTemplate,
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
observationConvention.ifAvailable(embeddingModel::setObservationConvention);
|
||||
|
||||
return embeddingModel;
|
||||
}
|
||||
|
||||
private OpenAiApi openAiApi(OpenAiEmbeddingProperties embeddingProperties,
|
||||
OpenAiConnectionProperties commonProperties, RestClient.Builder restClientBuilder,
|
||||
WebClient.Builder webClientBuilder, ResponseErrorHandler responseErrorHandler, String modelType) {
|
||||
|
||||
OpenAIAutoConfigurationUtil.ResolvedConnectionProperties resolved = resolveConnectionProperties(
|
||||
commonProperties, embeddingProperties, modelType);
|
||||
|
||||
return OpenAiApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.completionsPath(OpenAiChatProperties.DEFAULT_COMPLETIONS_PATH)
|
||||
.embeddingsPath(embeddingProperties.getEmbeddingsPath())
|
||||
.restClientBuilder(restClientBuilder)
|
||||
.webClientBuilder(webClientBuilder)
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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.openai.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.image.observation.ImageModelObservationConvention;
|
||||
import org.springframework.ai.model.SimpleApiKey;
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.openai.OpenAiImageModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
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;
|
||||
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;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for OpenAI.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Stefan Vassilev
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class,
|
||||
SpringAiRetryAutoConfiguration.class })
|
||||
@ConditionalOnClass(OpenAiApi.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.OPENAI,
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties({ OpenAiConnectionProperties.class, OpenAiImageProperties.class })
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class })
|
||||
public class OpenAiImageAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OpenAiImageModel openAiImageModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiImageProperties imageProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
|
||||
ObjectProvider<ObservationRegistry> observationRegistry,
|
||||
ObjectProvider<ImageModelObservationConvention> observationConvention) {
|
||||
|
||||
OpenAIAutoConfigurationUtil.ResolvedConnectionProperties resolved = resolveConnectionProperties(
|
||||
commonProperties, imageProperties, "image");
|
||||
|
||||
var openAiImageApi = OpenAiImageApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
var imageModel = new OpenAiImageModel(openAiImageApi, imageProperties.getOptions(), retryTemplate,
|
||||
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP));
|
||||
|
||||
observationConvention.ifAvailable(imageModel::setObservationConvention);
|
||||
|
||||
return imageModel;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.openai.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
import org.springframework.ai.chat.observation.ChatModelObservationConvention;
|
||||
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
|
||||
import org.springframework.ai.image.observation.ImageModelObservationConvention;
|
||||
import org.springframework.ai.model.SimpleApiKey;
|
||||
import org.springframework.ai.model.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.openai.OpenAiAudioSpeechModel;
|
||||
import org.springframework.ai.openai.OpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiEmbeddingModel;
|
||||
import org.springframework.ai.openai.OpenAiImageModel;
|
||||
import org.springframework.ai.openai.OpenAiModerationModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi;
|
||||
import org.springframework.ai.openai.api.OpenAiAudioApi;
|
||||
import org.springframework.ai.openai.api.OpenAiImageApi;
|
||||
import org.springframework.ai.openai.api.OpenAiModerationApi;
|
||||
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;
|
||||
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.retry.support.RetryTemplate;
|
||||
import org.springframework.web.client.ResponseErrorHandler;
|
||||
import org.springframework.web.client.RestClient;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.springframework.ai.model.openai.autoconfigure.OpenAIAutoConfigurationUtil.resolveConnectionProperties;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for OpenAI.
|
||||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Stefan Vassilev
|
||||
* @author Thomas Vitale
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
@AutoConfiguration(after = { RestClientAutoConfiguration.class, WebClientAutoConfiguration.class,
|
||||
SpringAiRetryAutoConfiguration.class })
|
||||
@ConditionalOnClass(OpenAiApi.class)
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.MODERATION_MODEL, havingValue = SpringAIModels.OPENAI,
|
||||
matchIfMissing = true)
|
||||
@EnableConfigurationProperties({ OpenAiConnectionProperties.class, OpenAiModerationProperties.class })
|
||||
@ImportAutoConfiguration(classes = { SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class })
|
||||
public class OpenAiModerationAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public OpenAiModerationModel openAiModerationModel(OpenAiConnectionProperties commonProperties,
|
||||
OpenAiModerationProperties moderationProperties, RetryTemplate retryTemplate,
|
||||
ObjectProvider<RestClient.Builder> restClientBuilderProvider, ResponseErrorHandler responseErrorHandler) {
|
||||
|
||||
OpenAIAutoConfigurationUtil.ResolvedConnectionProperties resolved = resolveConnectionProperties(
|
||||
commonProperties, moderationProperties, "moderation");
|
||||
|
||||
var openAiModerationApi = OpenAiModerationApi.builder()
|
||||
.baseUrl(resolved.baseUrl())
|
||||
.apiKey(new SimpleApiKey(resolved.apiKey()))
|
||||
.headers(resolved.headers())
|
||||
.restClientBuilder(restClientBuilderProvider.getIfAvailable(RestClient::builder))
|
||||
.responseErrorHandler(responseErrorHandler)
|
||||
.build();
|
||||
return new OpenAiModerationModel(openAiModerationApi, retryTemplate)
|
||||
.withDefaultOptions(moderationProperties.getOptions());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,4 +13,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration
|
||||
org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration
|
||||
org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration
|
||||
org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration
|
||||
org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration
|
||||
org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration
|
||||
org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration
|
||||
|
||||
@@ -23,10 +23,10 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
import org.springframework.ai.model.chat.client.autoconfigure.ChatClientAutoConfiguration;
|
||||
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.ChatClientCustomizer;
|
||||
import org.springframework.ai.model.chat.client.autoconfigure.ChatClientAutoConfiguration;
|
||||
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;
|
||||
@@ -46,8 +46,9 @@ public class ChatClientAutoConfigurationIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"),
|
||||
"spring.ai.openai.chat.options.model=gpt-4o")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, OpenAiAutoConfiguration.class, ChatClientAutoConfiguration.class));
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
OpenAiChatAutoConfiguration.class, ChatClientAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void implicitlyEnabled() {
|
||||
|
||||
@@ -52,12 +52,11 @@ public class OpenAiAutoConfigurationIT {
|
||||
private static final Log logger = LogFactory.getLog(OpenAiAutoConfigurationIT.class);
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class));
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"));
|
||||
|
||||
@Test
|
||||
void chatCall() {
|
||||
this.contextRunner.run(context -> {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class)).run(context -> {
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
String response = chatModel.call("Hello");
|
||||
assertThat(response).isNotEmpty();
|
||||
@@ -73,6 +72,7 @@ public class OpenAiAutoConfigurationIT {
|
||||
"spring.ai.openai.chat.options.output-modalities=text,audio",
|
||||
"spring.ai.openai.chat.options.output-audio.voice=ALLOY",
|
||||
"spring.ai.openai.chat.options.output-audio.format=WAV")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
|
||||
@@ -86,28 +86,30 @@ public class OpenAiAutoConfigurationIT {
|
||||
|
||||
@Test
|
||||
void transcribe() {
|
||||
this.contextRunner.run(context -> {
|
||||
OpenAiAudioTranscriptionModel transcriptionModel = context.getBean(OpenAiAudioTranscriptionModel.class);
|
||||
Resource audioFile = new ClassPathResource("/speech/jfk.flac");
|
||||
String response = transcriptionModel.call(audioFile);
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
OpenAiAudioTranscriptionModel transcriptionModel = context.getBean(OpenAiAudioTranscriptionModel.class);
|
||||
Resource audioFile = new ClassPathResource("/speech/jfk.flac");
|
||||
String response = transcriptionModel.call(audioFile);
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void speech() {
|
||||
this.contextRunner.run(context -> {
|
||||
OpenAiAudioSpeechModel speechModel = context.getBean(OpenAiAudioSpeechModel.class);
|
||||
byte[] response = speechModel.call("H");
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(verifyMp3FrameHeader(response))
|
||||
.withFailMessage("Expected MP3 frame header to be present in the response, but it was not found.")
|
||||
.isTrue();
|
||||
assertThat(response.length).isNotEqualTo(0);
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
OpenAiAudioSpeechModel speechModel = context.getBean(OpenAiAudioSpeechModel.class);
|
||||
byte[] response = speechModel.call("H");
|
||||
assertThat(response).isNotNull();
|
||||
assertThat(verifyMp3FrameHeader(response))
|
||||
.withFailMessage("Expected MP3 frame header to be present in the response, but it was not found.")
|
||||
.isTrue();
|
||||
assertThat(response.length).isNotEqualTo(0);
|
||||
|
||||
logger.debug("Response: " + Arrays.toString(response));
|
||||
});
|
||||
logger.debug("Response: " + Arrays.toString(response));
|
||||
});
|
||||
}
|
||||
|
||||
public boolean verifyMp3FrameHeader(byte[] audioResponse) {
|
||||
@@ -123,7 +125,7 @@ public class OpenAiAutoConfigurationIT {
|
||||
|
||||
@Test
|
||||
void generateStreaming() {
|
||||
this.contextRunner.run(context -> {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class)).run(context -> {
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
Flux<ChatResponse> responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello")));
|
||||
String response = responseFlux.collectList()
|
||||
@@ -139,52 +141,57 @@ public class OpenAiAutoConfigurationIT {
|
||||
|
||||
@Test
|
||||
void streamingWithTokenUsage() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.stream-usage=true").run(context -> {
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
this.contextRunner.withPropertyValues("spring.ai.openai.chat.options.stream-usage=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
OpenAiChatModel chatModel = context.getBean(OpenAiChatModel.class);
|
||||
|
||||
Flux<ChatResponse> responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello")));
|
||||
Flux<ChatResponse> responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello")));
|
||||
|
||||
Usage[] streamingTokenUsage = new Usage[1];
|
||||
String response = responseFlux.collectList().block().stream().map(chatResponse -> {
|
||||
streamingTokenUsage[0] = chatResponse.getMetadata().getUsage();
|
||||
return (chatResponse.getResult() != null) ? chatResponse.getResult().getOutput().getText() : "";
|
||||
}).collect(Collectors.joining());
|
||||
Usage[] streamingTokenUsage = new Usage[1];
|
||||
String response = responseFlux.collectList().block().stream().map(chatResponse -> {
|
||||
streamingTokenUsage[0] = chatResponse.getMetadata().getUsage();
|
||||
return (chatResponse.getResult() != null) ? chatResponse.getResult().getOutput().getText() : "";
|
||||
}).collect(Collectors.joining());
|
||||
|
||||
assertThat(streamingTokenUsage[0].getPromptTokens()).isGreaterThan(0);
|
||||
assertThat(streamingTokenUsage[0].getCompletionTokens()).isGreaterThan(0);
|
||||
assertThat(streamingTokenUsage[0].getTotalTokens()).isGreaterThan(0);
|
||||
assertThat(streamingTokenUsage[0].getPromptTokens()).isGreaterThan(0);
|
||||
assertThat(streamingTokenUsage[0].getCompletionTokens()).isGreaterThan(0);
|
||||
assertThat(streamingTokenUsage[0].getTotalTokens()).isGreaterThan(0);
|
||||
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
assertThat(response).isNotEmpty();
|
||||
logger.info("Response: " + response);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embedding() {
|
||||
this.contextRunner.run(context -> {
|
||||
OpenAiEmbeddingModel embeddingModel = context.getBean(OpenAiEmbeddingModel.class);
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
OpenAiEmbeddingModel embeddingModel = context.getBean(OpenAiEmbeddingModel.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
|
||||
void generateImage() {
|
||||
this.contextRunner.withPropertyValues("spring.ai.openai.image.options.size=1024x1024").run(context -> {
|
||||
OpenAiImageModel imageModel = context.getBean(OpenAiImageModel.class);
|
||||
ImageResponse imageResponse = imageModel.call(new ImagePrompt("forest"));
|
||||
assertThat(imageResponse.getResults()).hasSize(1);
|
||||
assertThat(imageResponse.getResult().getOutput().getUrl()).isNotEmpty();
|
||||
logger.info("Generated image: " + imageResponse.getResult().getOutput().getUrl());
|
||||
});
|
||||
this.contextRunner.withPropertyValues("spring.ai.openai.image.options.size=1024x1024")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
OpenAiImageModel imageModel = context.getBean(OpenAiImageModel.class);
|
||||
ImageResponse imageResponse = imageModel.call(new ImagePrompt("forest"));
|
||||
assertThat(imageResponse.getResults()).hasSize(1);
|
||||
assertThat(imageResponse.getResult().getOutput().getUrl()).isNotEmpty();
|
||||
logger.info("Generated image: " + imageResponse.getResult().getOutput().getUrl());
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -193,6 +200,7 @@ public class OpenAiAutoConfigurationIT {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.ai.openai.image.options.model=dall-e-2",
|
||||
"spring.ai.openai.image.options.size=256x256")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
OpenAiImageModel imageModel = context.getBean(OpenAiImageModel.class);
|
||||
ImageResponse imageResponse = imageModel.call(new ImagePrompt("forest"));
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Copyright 2025-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.openai.autoconfigure;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.ai.openai.OpenAiAudioSpeechModel;
|
||||
import org.springframework.ai.openai.OpenAiAudioTranscriptionModel;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiEmbeddingModel;
|
||||
import org.springframework.ai.openai.OpenAiImageModel;
|
||||
import org.springframework.ai.openai.OpenAiModerationModel;
|
||||
import org.springframework.ai.openai.api.OpenAiAudioApi;
|
||||
import org.springframework.ai.openai.api.ResponseFormat;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit Tests for OpenAI auto configurations' conditional enabling of models.
|
||||
*
|
||||
* @author Ilayaperumal Gopinathan
|
||||
*/
|
||||
public class OpenAiModelConfigurationTests {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL");
|
||||
|
||||
@Test
|
||||
void chatModelActivation() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class)).run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(OpenAiChatAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class,
|
||||
OpenAiImageAutoConfiguration.class, OpenAiAudioSpeechAutoConfiguration.class,
|
||||
OpenAiAudioTranscriptionAutoConfiguration.class, OpenAiModerationAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=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")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void embeddingModelActivation() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withPropertyValues("spring.ai.model.embedding=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(OpenAiChatAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class,
|
||||
OpenAiImageAutoConfiguration.class, OpenAiAudioSpeechAutoConfiguration.class,
|
||||
OpenAiAudioTranscriptionAutoConfiguration.class, OpenAiModerationAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=openai",
|
||||
"spring.ai.model.image=none", "spring.ai.model.audio.speech=none",
|
||||
"spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void imageModelActivation() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class)).run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.image=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.image=openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(OpenAiChatAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class,
|
||||
OpenAiImageAutoConfiguration.class, OpenAiAudioSpeechAutoConfiguration.class,
|
||||
OpenAiAudioTranscriptionAutoConfiguration.class, OpenAiModerationAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none",
|
||||
"spring.ai.model.image=openai", "spring.ai.model.audio.speech=none",
|
||||
"spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void audioSpeechModelActivation() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.audio.speech=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.audio.speech=openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(OpenAiChatAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class,
|
||||
OpenAiImageAutoConfiguration.class, OpenAiAudioSpeechAutoConfiguration.class,
|
||||
OpenAiAudioTranscriptionAutoConfiguration.class, OpenAiModerationAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.chat=none", "spring.ai.model.embedding=none",
|
||||
"spring.ai.model.image=none", "spring.ai.model.audio.speech=openai",
|
||||
"spring.ai.model.audio.transcription=none", "spring.ai.model.moderation=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void audioTranscriptionModelActivation() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.audio.transcription=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.audio.transcription=openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(OpenAiChatAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class,
|
||||
OpenAiImageAutoConfiguration.class, OpenAiAudioSpeechAutoConfiguration.class,
|
||||
OpenAiAudioTranscriptionAutoConfiguration.class, OpenAiModerationAutoConfiguration.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=openai", "spring.ai.model.moderation=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void moderationModelActivation() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiModerationAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiModerationAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.moderation=none")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiModerationProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(OpenAiModerationAutoConfiguration.class))
|
||||
.withPropertyValues("spring.ai.model.moderation=openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiModerationProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
this.contextRunner
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(OpenAiChatAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class,
|
||||
OpenAiImageAutoConfiguration.class, OpenAiAudioSpeechAutoConfiguration.class,
|
||||
OpenAiAudioTranscriptionAutoConfiguration.class, OpenAiModerationAutoConfiguration.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=none", "spring.ai.model.moderation=openai")
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiModerationModel.class)).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -54,7 +54,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.chat.options.model=MODEL_XYZ",
|
||||
"spring.ai.openai.chat.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(OpenAiChatProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -80,7 +80,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.audio.transcription.options.model=MODEL_XYZ",
|
||||
"spring.ai.openai.audio.transcription.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -108,7 +108,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.chat.options.model=MODEL_XYZ",
|
||||
"spring.ai.openai.chat.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(OpenAiChatProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -136,7 +136,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.audio.transcription.options.model=MODEL_XYZ",
|
||||
"spring.ai.openai.audio.transcription.options.temperature=0.55")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -164,7 +164,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.audio.speech.options.response-format=mp3",
|
||||
"spring.ai.openai.audio.speech.options.speed=0.75")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var speechProperties = context.getBean(OpenAiAudioSpeechProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -195,7 +195,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.audio.speech.options.response-format=mp3",
|
||||
"spring.ai.openai.audio.speech.options.speed=0.75")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var speechProperties = context.getBean(OpenAiAudioSpeechProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -225,7 +225,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.audio.speech.options.response-format=opus",
|
||||
"spring.ai.openai.audio.speech.options.speed=0.5")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var speechProperties = context.getBean(OpenAiAudioSpeechProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -253,7 +253,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.api-key=abc123",
|
||||
"spring.ai.openai.embedding.options.model=MODEL_XYZ")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -279,7 +279,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.embedding.api-key=456",
|
||||
"spring.ai.openai.embedding.options.model=MODEL_XYZ")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -303,7 +303,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.image.options.model=MODEL_XYZ",
|
||||
"spring.ai.openai.image.options.n=3")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var imageProperties = context.getBean(OpenAiImageProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -330,7 +330,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.image.options.model=MODEL_XYZ",
|
||||
"spring.ai.openai.image.options.n=3")
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var imageProperties = context.getBean(OpenAiImageProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -397,17 +397,14 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.chat.options.user=userXYZ"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(OpenAiChatProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY");
|
||||
|
||||
assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("text-embedding-ada-002");
|
||||
|
||||
assertThat(chatProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(-1.5);
|
||||
assertThat(chatProperties.getOptions().getLogitBias().get("myTokenId")).isEqualTo(-5);
|
||||
@@ -449,17 +446,14 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.audio.transcription.options.temperature=0.55"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY");
|
||||
|
||||
assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("text-embedding-ada-002");
|
||||
|
||||
assertThat(transcriptionProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(transcriptionProperties.getOptions().getLanguage()).isEqualTo("en");
|
||||
assertThat(transcriptionProperties.getOptions().getPrompt()).isEqualTo("Er, yes, I think so");
|
||||
@@ -482,7 +476,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.embedding.options.user=userXYZ"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
|
||||
@@ -514,7 +508,7 @@ public class OpenAiPropertiesTests {
|
||||
"spring.ai.openai.image.options.user=userXYZ"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var imageProperties = context.getBean(OpenAiImageProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -539,16 +533,16 @@ public class OpenAiPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.embedding.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.embedding=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
@@ -556,8 +550,8 @@ public class OpenAiPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.embedding.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.embedding=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
@@ -568,16 +562,16 @@ public class OpenAiPropertiesTests {
|
||||
void chatActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.chat.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
|
||||
@@ -585,8 +579,8 @@ public class OpenAiPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.chat.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.chat=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
|
||||
@@ -598,16 +592,16 @@ public class OpenAiPropertiesTests {
|
||||
void imageActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.image.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.image=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();
|
||||
@@ -615,8 +609,8 @@ public class OpenAiPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.image.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.image=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();
|
||||
@@ -628,16 +622,16 @@ public class OpenAiPropertiesTests {
|
||||
void audioSpeechActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.audio.speech.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.audio.speech=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty();
|
||||
@@ -645,8 +639,8 @@ public class OpenAiPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.audio.speech.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.audio.speech=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty();
|
||||
@@ -658,16 +652,16 @@ public class OpenAiPropertiesTests {
|
||||
void audioTranscriptionActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.audio.transcription.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.audio.transcription=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
@@ -675,8 +669,8 @@ public class OpenAiPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.audio.transcription.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.audio.transcription=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
|
||||
@@ -63,7 +63,7 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
"spring.ai.openai.chat.options.response-format.strict=true"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(OpenAiChatProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -82,7 +82,7 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY",
|
||||
"spring.ai.openai.chat.options.response-format.type=JSON_OBJECT")
|
||||
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(OpenAiChatProperties.class);
|
||||
|
||||
@@ -96,7 +96,7 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.openai.api-key=API_KEY")
|
||||
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(OpenAiChatProperties.class);
|
||||
|
||||
@@ -119,17 +119,13 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
"spring.ai.openai.audio.transcription.options.temperature=0.55"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var transcriptionProperties = context.getBean(OpenAiAudioTranscriptionProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
|
||||
|
||||
assertThat(connectionProperties.getBaseUrl()).isEqualTo("TEST_BASE_URL");
|
||||
assertThat(connectionProperties.getApiKey()).isEqualTo("API_KEY");
|
||||
|
||||
assertThat(embeddingProperties.getOptions().getModel()).isEqualTo("text-embedding-ada-002");
|
||||
|
||||
assertThat(transcriptionProperties.getOptions().getModel()).isEqualTo("MODEL_XYZ");
|
||||
assertThat(transcriptionProperties.getOptions().getLanguage()).isEqualTo("en");
|
||||
assertThat(transcriptionProperties.getOptions().getPrompt()).isEqualTo("Er, yes, I think so");
|
||||
@@ -152,7 +148,7 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
"spring.ai.openai.embedding.options.user=userXYZ"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
var embeddingProperties = context.getBean(OpenAiEmbeddingProperties.class);
|
||||
@@ -184,7 +180,7 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
"spring.ai.openai.image.options.user=userXYZ"
|
||||
)
|
||||
// @formatter:on
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
var imageProperties = context.getBean(OpenAiImageProperties.class);
|
||||
var connectionProperties = context.getBean(OpenAiConnectionProperties.class);
|
||||
@@ -209,16 +205,16 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.embedding.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.embedding=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
@@ -226,8 +222,8 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.embedding.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.embedding=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiEmbeddingAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiEmbeddingModel.class)).isNotEmpty();
|
||||
@@ -238,16 +234,16 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
void chatActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.chat.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
|
||||
@@ -255,8 +251,8 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.chat.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.chat=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiChatModel.class)).isNotEmpty();
|
||||
@@ -268,16 +264,16 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
void imageActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.image.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.image=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();
|
||||
@@ -285,8 +281,8 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.image.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.image=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiImageProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiImageModel.class)).isNotEmpty();
|
||||
@@ -298,16 +294,16 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
void audioSpeechActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.audio.speech.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.audio.speech=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty();
|
||||
@@ -315,8 +311,8 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.audio.speech.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.audio.speech=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioSpeechAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioSpeechModel.class)).isNotEmpty();
|
||||
@@ -328,16 +324,16 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
void audioTranscriptionActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.audio.transcription.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.audio.transcription=none")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isEmpty();
|
||||
});
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
@@ -345,8 +341,8 @@ public class OpenAiResponseFormatPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.api-key=API_KEY", "spring.ai.openai.base-url=TEST_BASE_URL",
|
||||
"spring.ai.openai.audio.transcription.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
"spring.ai.model.audio.transcription=openai")
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAudioTranscriptionAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(OpenAiAudioTranscriptionModel.class)).isNotEmpty();
|
||||
|
||||
@@ -25,8 +25,8 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
import org.springframework.ai.tool.function.FunctionToolCallback;
|
||||
@@ -42,7 +42,7 @@ public class FunctionCallbackInPrompt2IT {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
|
||||
@@ -25,12 +25,12 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration;
|
||||
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.openai.autoconfigure.OpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
@@ -47,7 +47,7 @@ public class FunctionCallbackInPromptIT {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class));
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
void functionCallTest() {
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
@@ -40,6 +39,7 @@ import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
import org.springframework.ai.chat.model.ToolContext;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.model.tool.ToolCallingChatOptions;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
@@ -60,7 +60,7 @@ class FunctionCallbackWithPlainFunctionBeanIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"),
|
||||
"spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName())
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
private static Map<String, Object> feedback = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -23,8 +23,8 @@ import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
import org.springframework.ai.tool.ToolCallback;
|
||||
@@ -44,7 +44,7 @@ public class OpenAiFunctionCallback2IT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"),
|
||||
"spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName())
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
@@ -25,12 +25,12 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration;
|
||||
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.openai.autoconfigure.OpenAiChatAutoConfiguration;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.ai.openai.api.OpenAiApi.ChatModel;
|
||||
@@ -51,7 +51,7 @@ public class OpenAiFunctionCallbackIT {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.openai.apiKey=" + System.getenv("OPENAI_API_KEY"),
|
||||
"spring.ai.openai.chat.options.model=" + ChatModel.GPT_4_O_MINI.getName())
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiAutoConfiguration.class))
|
||||
.withConfiguration(AutoConfigurations.of(OpenAiChatAutoConfiguration.class))
|
||||
.withUserConfiguration(Config.class);
|
||||
|
||||
@Test
|
||||
|
||||
Binary file not shown.
@@ -51,7 +51,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-embedding</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.ai.model.postgresml.autoconfigure;
|
||||
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.postgresml.PostgresMlEmbeddingModel;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
@@ -39,7 +41,7 @@ public class PostgresMlAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = PostgresMlEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.POSTGRESML,
|
||||
matchIfMissing = true)
|
||||
public PostgresMlEmbeddingModel postgresMlEmbeddingModel(JdbcTemplate jdbcTemplate,
|
||||
PostgresMlEmbeddingProperties embeddingProperties) {
|
||||
|
||||
@@ -35,13 +35,6 @@
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
@@ -51,14 +44,14 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-chat</artifactId>
|
||||
<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-observation-embedding</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -18,6 +18,8 @@ package org.springframework.ai.model.qianfan.autoconfigure;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
|
||||
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;
|
||||
@@ -58,7 +60,7 @@ public class QianFanAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = QianFanChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.QIANFAN,
|
||||
matchIfMissing = true)
|
||||
public QianFanChatModel qianFanChatModel(QianFanConnectionProperties commonProperties,
|
||||
QianFanChatProperties chatProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
@@ -81,7 +83,7 @@ public class QianFanAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = QianFanEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.QIANFAN,
|
||||
matchIfMissing = true)
|
||||
public QianFanEmbeddingModel qianFanEmbeddingModel(QianFanConnectionProperties commonProperties,
|
||||
QianFanEmbeddingProperties embeddingProperties,
|
||||
@@ -105,7 +107,7 @@ public class QianFanAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = QianFanImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.QIANFAN,
|
||||
matchIfMissing = true)
|
||||
public QianFanImageModel qianFanImageModel(QianFanConnectionProperties commonProperties,
|
||||
QianFanImageProperties imageProperties, ObjectProvider<RestClient.Builder> restClientBuilderProvider,
|
||||
|
||||
@@ -235,7 +235,7 @@ public class QianFanPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.embedding.enabled=false")
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.embedding=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
@@ -255,7 +255,7 @@ public class QianFanPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.embedding.enabled=true")
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.chat=qianfan")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
@@ -268,7 +268,7 @@ public class QianFanPropertiesTests {
|
||||
void chatActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.chat.enabled=false")
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.chat=none")
|
||||
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
@@ -402,7 +402,7 @@ public class QianFanPropertiesTests {
|
||||
void imageActivation() {
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.image.enabled=false")
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.image=none")
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
@@ -424,7 +424,7 @@ public class QianFanPropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.qianfan.api-key=API_KEY", "spring.ai.qianfan.secret-key=SECRET_KEY",
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.qianfan.image.enabled=true")
|
||||
"spring.ai.qianfan.base-url=TEST_BASE_URL", "spring.ai.model.chat=qianfan")
|
||||
.withConfiguration(
|
||||
AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, RestClientAutoConfiguration.class,
|
||||
WebClientAutoConfiguration.class, QianFanAutoConfiguration.class))
|
||||
|
||||
@@ -35,13 +35,6 @@
|
||||
|
||||
<!-- Spring AI auto configurations -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-retry</artifactId>
|
||||
@@ -51,7 +44,7 @@
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-autoconfigure-observation-image</artifactId>
|
||||
<artifactId>spring-ai-autoconfigure-model-image-observation</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.ai.model.stabilityai.autoconfigure;
|
||||
|
||||
import org.springframework.ai.model.SpringAIModelProperties;
|
||||
import org.springframework.ai.model.SpringAIModels;
|
||||
import org.springframework.ai.stabilityai.StabilityAiImageModel;
|
||||
import org.springframework.ai.stabilityai.api.StabilityAiApi;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
@@ -62,7 +64,7 @@ public class StabilityAiImageAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnProperty(prefix = StabilityAiImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
|
||||
@ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.STABILITY,
|
||||
matchIfMissing = true)
|
||||
public StabilityAiImageModel stabilityAiImageModel(StabilityAiApi stabilityAiApi,
|
||||
StabilityAiImageProperties stabilityAiImageProperties) {
|
||||
|
||||
@@ -77,7 +77,7 @@ public class StabilityAiImagePropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.stabilityai.image.api-key=API_KEY",
|
||||
"spring.ai.stabilityai.image.base-url=ENDPOINT", "spring.ai.stabilityai.image.enabled=false")
|
||||
"spring.ai.stabilityai.image.base-url=ENDPOINT", "spring.ai.model.image=none")
|
||||
.withConfiguration(AutoConfigurations.of(StabilityAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(StabilityAiImageProperties.class)).isNotEmpty();
|
||||
@@ -97,7 +97,7 @@ public class StabilityAiImagePropertiesTests {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.stabilityai.image.api-key=API_KEY",
|
||||
"spring.ai.stabilityai.image.base-url=ENDPOINT", "spring.ai.stabilityai.image.enabled=true")
|
||||
"spring.ai.stabilityai.image.base-url=ENDPOINT", "spring.ai.model.image=stabilityai")
|
||||
.withConfiguration(AutoConfigurations.of(StabilityAiImageAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(StabilityAiImageProperties.class)).isNotEmpty();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user