diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml
index 61d3e56ee..35a65286e 100644
--- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml
+++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client/pom.xml
@@ -9,7 +9,7 @@
1.0.0-SNAPSHOT
../../../pom.xml
- spring-ai-mcp-client-spring-boot-autoconfigure
+ spring-ai-autoconfigure-mcp-client
jar
Spring AI MCP Client Auto Configuration
Spring AI MCP Client Auto Configuration
diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml
index a68c11af5..68fc8eaf9 100644
--- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml
+++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/pom.xml
@@ -9,7 +9,7 @@
1.0.0-SNAPSHOT
../../../pom.xml
- spring-ai-mcp-server-spring-boot-autoconfigure
+ spring-ai-autoconfigure-mcp-server
jar
Spring AI MCP Server Auto Configuration
Spring AI MCP Server Auto Configuration
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/pom.xml b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml
similarity index 97%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/pom.xml
rename to auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml
index 699d59ab5..2277c3167 100644
--- a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/pom.xml
+++ b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/pom.xml
@@ -7,7 +7,7 @@
org.springframework.ai
spring-ai
1.0.0-SNAPSHOT
- ../../../pom.xml
+ ../../../../../pom.xml
spring-ai-autoconfigure-model-chat-client
jar
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfiguration.java b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfiguration.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfiguration.java
rename to auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientAutoConfiguration.java
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderConfigurer.java b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderConfigurer.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderConfigurer.java
rename to auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderConfigurer.java
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderProperties.java b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderProperties.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderProperties.java
rename to auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/main/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientBuilderProperties.java
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
rename to auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientObservationAutoConfigurationTests.java b/auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientObservationAutoConfigurationTests.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-client/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientObservationAutoConfigurationTests.java
rename to auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client/src/test/java/org/springframework/ai/model/chat/client/autoconfigure/ChatClientObservationAutoConfigurationTests.java
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/pom.xml b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml
similarity index 98%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/pom.xml
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml
index 397554baa..31f7a6dbe 100644
--- a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/pom.xml
+++ b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/pom.xml
@@ -7,7 +7,7 @@
org.springframework.ai
spring-ai
1.0.0-SNAPSHOT
- ../../../pom.xml
+ ../../../../../pom.xml
spring-ai-autoconfigure-model-chat-memory
jar
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/CommonChatMemoryProperties.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/CommonChatMemoryProperties.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/CommonChatMemoryProperties.java
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/autoconfigure/CommonChatMemoryProperties.java
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryAutoConfiguration.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryAutoConfiguration.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryAutoConfiguration.java
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryAutoConfiguration.java
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryProperties.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryProperties.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryProperties.java
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryProperties.java
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/CassandraChatMemoryPropertiesTest.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/CassandraChatMemoryPropertiesTest.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/CassandraChatMemoryPropertiesTest.java
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/test/java/org/springframework/ai/model/chat/memory/autoconfigure/CassandraChatMemoryPropertiesTest.java
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/test/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryAutoConfigurationIT.java b/auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/test/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryAutoConfigurationIT.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory/src/test/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryAutoConfigurationIT.java
rename to auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory/src/test/java/org/springframework/ai/model/chat/memory/cassandra/autoconfigure/CassandraChatMemoryAutoConfigurationIT.java
diff --git a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml
new file mode 100644
index 000000000..cef7c81bb
--- /dev/null
+++ b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/pom.xml
@@ -0,0 +1,72 @@
+
+
+ 4.0.0
+
+ org.springframework.ai
+ spring-ai
+ 1.0.0-SNAPSHOT
+ ../../../../../pom.xml
+
+ spring-ai-autoconfigure-model-chat-observation
+ jar
+ Spring AI Chat Observation Auto Configuration
+ Spring AI Chat Observation Auto Configuration
+ https://github.com/spring-projects/spring-ai
+
+
+ https://github.com/spring-projects/spring-ai
+ git://github.com/spring-projects/spring-ai.git
+ git@github.com:spring-projects/spring-ai.git
+
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-core
+ ${parent.version}
+
+
+
+ io.micrometer
+ micrometer-tracing-bridge-otel
+ true
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+ org.springframework.ai
+ spring-ai-test
+ ${project.parent.version}
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
diff --git a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfiguration.java b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfiguration.java
new file mode 100644
index 000000000..81aee1992
--- /dev/null
+++ b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfiguration.java
@@ -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) {
+ 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));
+ }
+
+ }
+
+}
diff --git a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java
new file mode 100644
index 000000000..be37d1b26
--- /dev/null
+++ b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java
@@ -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;
+ }
+
+}
diff --git a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/package-info.java b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/package-info.java
new file mode 100644
index 000000000..66bef736b
--- /dev/null
+++ b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/package-info.java
@@ -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;
diff --git a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..37557ec19
--- /dev/null
+++ b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -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
diff --git a/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/test/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfigurationTests.java b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/test/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfigurationTests.java
new file mode 100644
index 000000000..3ac3f12ca
--- /dev/null
+++ b/auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation/src/test/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfigurationTests.java
@@ -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));
+ }
+
+}
diff --git a/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml
new file mode 100644
index 000000000..b41cdd752
--- /dev/null
+++ b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ org.springframework.ai
+ spring-ai
+ 1.0.0-SNAPSHOT
+ ../../../../../pom.xml
+
+ spring-ai-autoconfigure-model-embedding-observation
+ jar
+ Spring AI Embedding Observation Auto Configuration
+ Spring AI Embedding Observation Auto Configuration
+ https://github.com/spring-projects/spring-ai
+
+
+ https://github.com/spring-projects/spring-ai
+ git://github.com/spring-projects/spring-ai.git
+ git@github.com:spring-projects/spring-ai.git
+
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-core
+ ${parent.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+ org.springframework.ai
+ spring-ai-test
+ ${project.parent.version}
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
diff --git a/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfiguration.java b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfiguration.java
new file mode 100644
index 000000000..ee0902343
--- /dev/null
+++ b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfiguration.java
@@ -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) {
+ return new EmbeddingModelMeterObservationHandler(meterRegistry.getObject());
+ }
+
+}
diff --git a/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/package-info.java b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/package-info.java
new file mode 100644
index 000000000..654b59b6f
--- /dev/null
+++ b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/package-info.java
@@ -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;
diff --git a/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..2d3c503d8
--- /dev/null
+++ b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -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
diff --git a/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/test/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfigurationTests.java b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/test/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfigurationTests.java
new file mode 100644
index 000000000..10c9feb7c
--- /dev/null
+++ b/auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation/src/test/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfigurationTests.java
@@ -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));
+ }
+
+}
diff --git a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml
new file mode 100644
index 000000000..428a5953f
--- /dev/null
+++ b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/pom.xml
@@ -0,0 +1,66 @@
+
+
+ 4.0.0
+
+ org.springframework.ai
+ spring-ai
+ 1.0.0-SNAPSHOT
+ ../../../../../pom.xml
+
+ spring-ai-autoconfigure-model-image-observation
+ jar
+ Spring AI Image Observation Auto Configuration
+ Spring AI Image Observation Auto Configuration
+ https://github.com/spring-projects/spring-ai
+
+
+ https://github.com/spring-projects/spring-ai
+ git://github.com/spring-projects/spring-ai.git
+ git@github.com:spring-projects/spring-ai.git
+
+
+
+
+
+
+ org.springframework.ai
+ spring-ai-core
+ ${parent.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+ org.springframework.ai
+ spring-ai-test
+ ${project.parent.version}
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+
+
diff --git a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfiguration.java b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfiguration.java
new file mode 100644
index 000000000..f97e12f03
--- /dev/null
+++ b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfiguration.java
@@ -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();
+ }
+
+}
diff --git a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationProperties.java b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationProperties.java
new file mode 100644
index 000000000..7d372d986
--- /dev/null
+++ b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationProperties.java
@@ -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;
+ }
+
+}
diff --git a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/package-info.java b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/package-info.java
new file mode 100644
index 000000000..983cc5a16
--- /dev/null
+++ b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/package-info.java
@@ -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;
diff --git a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..788c24087
--- /dev/null
+++ b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -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
diff --git a/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/test/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfigurationTests.java b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/test/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfigurationTests.java
new file mode 100644
index 000000000..23417b67b
--- /dev/null
+++ b/auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation/src/test/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfigurationTests.java
@@ -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));
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml
index 50a6f2359..890375ca2 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/pom.xml
@@ -35,13 +35,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-model-tool
@@ -58,21 +51,7 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
- ${project.parent.version}
- true
-
-
-
- org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
- ${project.parent.version}
- true
-
-
-
- org.springframework.ai
- spring-ai-autoconfigure-observation-image
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java
index 4c422e37a..eb064bec9 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/main/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicAutoConfiguration.java
@@ -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,
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicModelConfigurationTests.java
new file mode 100644
index 000000000..387556f6f
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java
index ceb714227..186437c48 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/AnthropicPropertiesTests.java
@@ -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();
});
}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java
index 2af2da9c0..740cf066a 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-anthropic/src/test/java/org/springframework/ai/model/anthropic/autoconfigure/tool/FunctionCallWithFunctionBeanIT.java
@@ -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);
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml
index f6bb5feb7..595864404 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/pom.xml
@@ -35,13 +35,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-model-tool
@@ -58,21 +51,21 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-image
+ spring-ai-autoconfigure-model-image-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java
index f01962f43..c442995b1 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/main/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiAutoConfiguration.java
@@ -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,
ObjectProvider 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;
+ }
+
}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiModelConfigurationTests.java
new file mode 100644
index 000000000..19a2ce674
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/java/org/springframework/ai/model/azure/openai/autoconfigure/AzureOpenAiModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/resources/speech/jfk.flac b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/resources/speech/jfk.flac
new file mode 100644
index 000000000..e44b7c138
Binary files /dev/null and b/auto-configurations/models/spring-ai-autoconfigure-model-azure-openai/src/test/resources/speech/jfk.flac differ
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml
index e90e926d6..7c0112362 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/pom.xml
@@ -42,13 +42,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-model-tool
@@ -65,21 +58,21 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-image
+ spring-ai-autoconfigure-model-image-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java
index ce60731c0..57765ad55 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfiguration.java
@@ -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 {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java
index 0a9591ed2..e063bc3af 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatAutoConfiguration.java
@@ -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 {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java
index 1cfea4c07..474ec9030 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/main/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfiguration.java
@@ -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());
}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java
index f612425cf..4e6f0c4eb 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java
@@ -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();
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereModelConfigurationTests.java
new file mode 100644
index 000000000..019261105
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/cohere/BedrockCohereModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseModelConfigurationTests.java
new file mode 100644
index 000000000..0192e85d9
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java
index b71900197..65913a7f0 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/converse/BedrockConverseProxyChatPropertiesTests.java
@@ -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();
+ });
}
}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java
index 107bb2a6d..8764167dc 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanEmbeddingAutoConfigurationIT.java
@@ -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();
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanModelConfigurationTests.java
new file mode 100644
index 000000000..83d194a9e
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/java/org/springframework/ai/model/bedrock/autoconfigure/titan/BedrockTitanModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/resources/spring_framework.png b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/resources/spring_framework.png
new file mode 100644
index 000000000..735057a6b
Binary files /dev/null and b/auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai/src/test/resources/spring_framework.png differ
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml
index 9f460fb71..09da01d8f 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/pom.xml
@@ -37,14 +37,7 @@
org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
-
- org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java
index a79a6517d..9b937deab 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/main/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceChatAutoConfiguration.java
@@ -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());
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceModelConfigurationTests.java
new file mode 100644
index 000000000..835036219
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-huggingface/src/test/java/org/springframework/ai/model/huggingface/autoconfigure/HuggingfaceModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml
index 4bd9d8ea7..0ead4e2ab 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/pom.xml
@@ -35,13 +35,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-model-tool
@@ -58,14 +51,14 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java
index caea39372..f74e3ba21 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/main/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxAutoConfiguration.java
@@ -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 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,
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java
index b0a75f743..962b2e114 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MiniMaxPropertiesTests.java
@@ -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 -> {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MinimaxModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MinimaxModelConfigurationTests.java
new file mode 100644
index 000000000..d1cdd2709
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-minimax/src/test/java/org/springframework/ai/model/minimax/autoconfigure/MinimaxModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml
index 0574907d5..e75110f3f 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/pom.xml
@@ -35,13 +35,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-model-tool
@@ -58,21 +51,21 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-image
+ spring-ai-autoconfigure-model-image-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java
index b58a75200..77bab0242 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiAutoConfiguration.java
@@ -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 restClientBuilderProvider,
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatProperties.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatProperties.java
index 4d307b5a8..19a782a8e 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatProperties.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/main/java/org/springframework/ai/model/mistralai/autoconfigure/MistralAiChatProperties.java
@@ -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;
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralModelConfigurationTests.java
new file mode 100644
index 000000000..fb4d5013d
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/MistralModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanOpenAiIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanOpenAiIT.java
index 9c4b1c71a..ad66bc99d 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanOpenAiIT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai/src/test/java/org/springframework/ai/model/mistralai/autoconfigure/tool/PaymentStatusBeanOpenAiIT.java
@@ -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
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/pom.xml
index 60f725ddb..10b41e644 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/pom.xml
@@ -35,13 +35,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-retry
@@ -51,14 +44,14 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java
index 51674fc76..d1d836c61 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/main/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotAutoConfiguration.java
@@ -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 restClientBuilderProvider,
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java
index c97df8b12..119b0fbfe 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-moonshot/src/test/java/org/springframework/ai/model/moonshot/autoconfigure/MoonshotPropertiesTests.java
@@ -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 -> {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml
index d5fdfffcc..4bd179f43 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/pom.xml
@@ -35,13 +35,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-retry
@@ -51,14 +44,14 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java
index 84d872137..777c89718 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-oci-genai/src/main/java/org/springframework/ai/model/oci/genai/autoconfigure/OCIGenAiAutoConfiguration.java
@@ -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,
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
index 9f24276ae..cfece9d07 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/pom.xml
@@ -34,14 +34,6 @@
-
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-model-tool
@@ -51,21 +43,21 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-image
+ spring-ai-autoconfigure-model-image-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java
index 7d7948f8e..0a0af3125 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/main/java/org/springframework/ai/model/ollama/autoconfigure/OllamaAutoConfiguration.java
@@ -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,
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaModelConfigurationTests.java
new file mode 100644
index 000000000..a02d8a02d
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-ollama/src/test/java/org/springframework/ai/model/ollama/autoconfigure/OllamaModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java
new file mode 100644
index 000000000..344e2f7e3
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAIAutoConfigurationUtil.java
@@ -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> 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 headers) {
+
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechAutoConfiguration.java
new file mode 100644
index 000000000..7c32b0446
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioSpeechAutoConfiguration.java
@@ -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 restClientBuilderProvider,
+ ObjectProvider 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());
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionAutoConfiguration.java
new file mode 100644
index 000000000..c01dc0856
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAudioTranscriptionAutoConfiguration.java
@@ -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 restClientBuilderProvider,
+ ObjectProvider 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);
+
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfiguration.java
deleted file mode 100644
index 1d6ddc51a..000000000
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfiguration.java
+++ /dev/null
@@ -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> 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 restClientBuilderProvider,
- ObjectProvider webClientBuilderProvider, ToolCallingManager toolCallingManager,
- RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
- ObjectProvider observationRegistry,
- ObjectProvider 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 restClientBuilderProvider,
- ObjectProvider webClientBuilderProvider, RetryTemplate retryTemplate,
- ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry,
- ObjectProvider 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 restClientBuilderProvider,
- RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
- ObjectProvider observationRegistry,
- ObjectProvider 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 restClientBuilderProvider,
- ObjectProvider 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 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 restClientBuilderProvider,
- ObjectProvider 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 headers) {
-
- }
-
-}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatAutoConfiguration.java
new file mode 100644
index 000000000..96e297267
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiChatAutoConfiguration.java
@@ -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 restClientBuilderProvider,
+ ObjectProvider webClientBuilderProvider, ToolCallingManager toolCallingManager,
+ RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
+ ObjectProvider observationRegistry,
+ ObjectProvider 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;
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingAutoConfiguration.java
new file mode 100644
index 000000000..3367d0d38
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiEmbeddingAutoConfiguration.java
@@ -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 restClientBuilderProvider,
+ ObjectProvider webClientBuilderProvider, RetryTemplate retryTemplate,
+ ResponseErrorHandler responseErrorHandler, ObjectProvider observationRegistry,
+ ObjectProvider 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();
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageAutoConfiguration.java
new file mode 100644
index 000000000..4a9966072
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiImageAutoConfiguration.java
@@ -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 restClientBuilderProvider,
+ RetryTemplate retryTemplate, ResponseErrorHandler responseErrorHandler,
+ ObjectProvider observationRegistry,
+ ObjectProvider 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;
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationAutoConfiguration.java
new file mode 100644
index 000000000..86b72a103
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModerationAutoConfiguration.java
@@ -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 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());
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 8edc3fa17..d31ef583d 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -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
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/ChatClientAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/ChatClientAutoConfigurationIT.java
index 428ae5ed6..40eeaab33 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/ChatClientAutoConfigurationIT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/ChatClientAutoConfigurationIT.java
@@ -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() {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java
index e384541c0..a8054a7cc 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiAutoConfigurationIT.java
@@ -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 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 responseFlux = chatModel.stream(new Prompt(new UserMessage("Hello")));
+ Flux 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"));
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModelConfigurationTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModelConfigurationTests.java
new file mode 100644
index 000000000..a7f301514
--- /dev/null
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiModelConfigurationTests.java
@@ -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();
+ });
+ }
+
+}
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java
index bbff01e58..1e3098a7f 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiPropertiesTests.java
@@ -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();
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java
index a863c88b7..e83a9c717 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/OpenAiResponseFormatPropertiesTests.java
@@ -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();
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java
index 8e238be52..23acefa0c 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPrompt2IT.java
@@ -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() {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java
index 45596ff5e..c3073a56d 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackInPromptIT.java
@@ -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() {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java
index b1ddf745d..7216afeae 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/FunctionCallbackWithPlainFunctionBeanIT.java
@@ -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 feedback = new ConcurrentHashMap<>();
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java
index 682a8dacb..bb22b7bfd 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallback2IT.java
@@ -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
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java
index 6ab701eb2..67d6d8076 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/java/org/springframework/ai/model/openai/autoconfigure/tool/OpenAiFunctionCallbackIT.java
@@ -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
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/resources/speech/jfk.flac b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/resources/speech/jfk.flac
new file mode 100644
index 000000000..e44b7c138
Binary files /dev/null and b/auto-configurations/models/spring-ai-autoconfigure-model-openai/src/test/resources/speech/jfk.flac differ
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml
index 802627a43..7be055456 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/pom.xml
@@ -51,7 +51,7 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java
index ae0092ee8..d35ed9c23 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding/src/main/java/org/springframework/ai/model/postgresml/autoconfigure/PostgresMlAutoConfiguration.java
@@ -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) {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/pom.xml
index d4c03036e..abb2d58da 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/pom.xml
@@ -35,13 +35,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-retry
@@ -51,14 +44,14 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java
index a48870d9d..98d456ef9 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/main/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanAutoConfiguration.java
@@ -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 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 restClientBuilderProvider,
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java
index 8d976f971..d10e3b704 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-qianfan/src/test/java/org/springframework/ai/model/qianfan/autoconfigure/QianFanPropertiesTests.java
@@ -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))
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml
index 121ea1264..f73cc84ad 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/pom.xml
@@ -35,13 +35,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-retry
@@ -51,7 +44,7 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-image
+ spring-ai-autoconfigure-model-image-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java
index 7c143a941..c6d83e9cd 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/main/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImageAutoConfiguration.java
@@ -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) {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java
index 74e1624f9..9cbc4e9f1 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-stability-ai/src/test/java/org/springframework/ai/model/stabilityai/autoconfigure/StabilityAiImagePropertiesTests.java
@@ -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();
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml
index ebc45c2d9..248c9968a 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/pom.xml
@@ -37,7 +37,7 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java
index 495428766..d4896a80d 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-transformers/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-transformers/src/main/java/org/springframework/ai/model/transformers/autoconfigure/TransformersEmbeddingModelAutoConfiguration.java
@@ -21,6 +21,8 @@ import ai.onnxruntime.OrtSession;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.embedding.observation.EmbeddingModelObservationConvention;
+import org.springframework.ai.model.SpringAIModelProperties;
+import org.springframework.ai.model.SpringAIModels;
import org.springframework.ai.transformers.TransformersEmbeddingModel;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -42,8 +44,8 @@ public class TransformersEmbeddingModelAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = TransformersEmbeddingModelProperties.CONFIG_PREFIX, name = "enabled",
- havingValue = "true", matchIfMissing = true)
+ @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.TRANSFORMERS,
+ matchIfMissing = true)
public TransformersEmbeddingModel embeddingModel(TransformersEmbeddingModelProperties properties,
ObjectProvider observationRegistry,
ObjectProvider observationConvention) {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml
index b48b27df6..c4b091a78 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/pom.xml
@@ -44,13 +44,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-model-tool
@@ -67,21 +60,21 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-image
+ spring-ai-autoconfigure-model-image-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java
index ec4aa79b5..cd67ccf95 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/embedding/VertexAiEmbeddingAutoConfiguration.java
@@ -21,6 +21,8 @@ import java.io.IOException;
import com.google.cloud.vertexai.VertexAI;
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.embedding.observation.EmbeddingModelObservationConvention;
import org.springframework.ai.vertexai.embedding.VertexAiEmbeddingConnectionDetails;
@@ -74,8 +76,8 @@ public class VertexAiEmbeddingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = VertexAiTextEmbeddingProperties.CONFIG_PREFIX, name = "enabled",
- havingValue = "true", matchIfMissing = true)
+ @ConditionalOnProperty(name = SpringAIModelProperties.TEXT_EMBEDDING_MODEL, havingValue = SpringAIModels.VERTEX_AI,
+ matchIfMissing = true)
public VertexAiTextEmbeddingModel textEmbedding(VertexAiEmbeddingConnectionDetails connectionDetails,
VertexAiTextEmbeddingProperties textEmbeddingProperties, RetryTemplate retryTemplate,
ObjectProvider observationRegistry,
@@ -91,8 +93,8 @@ public class VertexAiEmbeddingAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = VertexAiMultimodalEmbeddingProperties.CONFIG_PREFIX, name = "enabled",
- havingValue = "true", matchIfMissing = true)
+ @ConditionalOnProperty(name = SpringAIModelProperties.MULTI_MODAL_EMBEDDING_MODEL,
+ havingValue = SpringAIModels.VERTEX_AI, matchIfMissing = true)
public VertexAiMultimodalEmbeddingModel multimodalEmbedding(VertexAiEmbeddingConnectionDetails connectionDetails,
VertexAiMultimodalEmbeddingProperties multimodalEmbeddingProperties) throws IOException {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java
index 4a059efa2..7a4ceec79 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-vertex-ai/src/main/java/org/springframework/ai/model/vertexai/autoconfigure/gemini/VertexAiGeminiAutoConfiguration.java
@@ -22,6 +22,8 @@ import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.vertexai.VertexAI;
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;
@@ -85,7 +87,7 @@ public class VertexAiGeminiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = VertexAiGeminiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
+ @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.VERTEX_AI,
matchIfMissing = true)
public VertexAiGeminiChatModel vertexAiGeminiChat(VertexAI vertexAi, VertexAiGeminiChatProperties chatProperties,
ToolCallingManager toolCallingManager, ApplicationContext context, RetryTemplate retryTemplate,
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/pom.xml
index ef8166b0d..00e937e6a 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/pom.xml
@@ -37,21 +37,14 @@
org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
- ${project.parent.version}
- true
-
-
-
- org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java
index fbb20c697..2477069c6 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai/src/main/java/org/springframework/ai/model/watsonxai/autoconfigure/WatsonxAiAutoConfiguration.java
@@ -16,6 +16,8 @@
package org.springframework.ai.model.watsonxai.autoconfigure;
+import org.springframework.ai.model.SpringAIModelProperties;
+import org.springframework.ai.model.SpringAIModels;
import org.springframework.ai.watsonx.WatsonxAiChatModel;
import org.springframework.ai.watsonx.WatsonxAiEmbeddingModel;
import org.springframework.ai.watsonx.api.WatsonxAiApi;
@@ -41,8 +43,6 @@ import org.springframework.web.client.RestClient;
@ConditionalOnClass(WatsonxAiApi.class)
@EnableConfigurationProperties({ WatsonxAiConnectionProperties.class, WatsonxAiChatProperties.class,
WatsonxAiEmbeddingProperties.class })
-@ConditionalOnProperty(prefix = WatsonxAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
- matchIfMissing = true)
public class WatsonxAiAutoConfiguration {
@Bean
@@ -56,7 +56,7 @@ public class WatsonxAiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = WatsonxAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
+ @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.WATSONX_AI,
matchIfMissing = true)
public WatsonxAiChatModel watsonxChatModel(WatsonxAiApi watsonxApi, WatsonxAiChatProperties chatProperties) {
return new WatsonxAiChatModel(watsonxApi, chatProperties.getOptions());
@@ -64,7 +64,7 @@ public class WatsonxAiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = WatsonxAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
+ @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.WATSONX_AI,
matchIfMissing = true)
public WatsonxAiEmbeddingModel watsonxAiEmbeddingModel(WatsonxAiApi watsonxApi,
WatsonxAiEmbeddingProperties properties) {
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml
index aa367db5e..61719f3a0 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/pom.xml
@@ -35,13 +35,6 @@
-
- org.springframework.ai
- spring-ai-autoconfigure-model-chat-client
- ${project.parent.version}
- true
-
-
org.springframework.ai
spring-ai-autoconfigure-retry
@@ -51,21 +44,21 @@
org.springframework.ai
- spring-ai-autoconfigure-observation-chat
+ spring-ai-autoconfigure-model-chat-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-embedding
+ spring-ai-autoconfigure-model-embedding-observation
${project.parent.version}
true
org.springframework.ai
- spring-ai-autoconfigure-observation-image
+ spring-ai-autoconfigure-model-image-observation
${project.parent.version}
true
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java
index db27df995..4202ce2ef 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/main/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiAutoConfiguration.java
@@ -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;
@@ -59,7 +61,7 @@ public class ZhiPuAiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = ZhiPuAiChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
+ @ConditionalOnProperty(name = SpringAIModelProperties.CHAT_MODEL, havingValue = SpringAIModels.ZHIPUAI,
matchIfMissing = true)
public ZhiPuAiChatModel zhiPuAiChatModel(ZhiPuAiConnectionProperties commonProperties,
ZhiPuAiChatProperties chatProperties, ObjectProvider restClientBuilderProvider,
@@ -82,7 +84,7 @@ public class ZhiPuAiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = ZhiPuAiEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
+ @ConditionalOnProperty(name = SpringAIModelProperties.EMBEDDING_MODEL, havingValue = SpringAIModels.ZHIPUAI,
matchIfMissing = true)
public ZhiPuAiEmbeddingModel zhiPuAiEmbeddingModel(ZhiPuAiConnectionProperties commonProperties,
ZhiPuAiEmbeddingProperties embeddingProperties, RestClient.Builder restClientBuilder,
@@ -116,7 +118,7 @@ public class ZhiPuAiAutoConfiguration {
@Bean
@ConditionalOnMissingBean
- @ConditionalOnProperty(prefix = ZhiPuAiImageProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
+ @ConditionalOnProperty(name = SpringAIModelProperties.IMAGE_MODEL, havingValue = SpringAIModels.ZHIPUAI,
matchIfMissing = true)
public ZhiPuAiImageModel zhiPuAiImageModel(ZhiPuAiConnectionProperties commonProperties,
ZhiPuAiImageProperties imageProperties, RestClient.Builder restClientBuilder, RetryTemplate retryTemplate,
diff --git a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java
index ec1b9387a..79b98dfed 100644
--- a/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java
+++ b/auto-configurations/models/spring-ai-autoconfigure-model-zhipuai/src/test/java/org/springframework/ai/model/zhipuai/autoconfigure/ZhiPuAiPropertiesTests.java
@@ -335,7 +335,7 @@ public class ZhiPuAiPropertiesTests {
new ApplicationContextRunner()
.withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL",
- "spring.ai.zhipuai.embedding.enabled=false")
+ "spring.ai.model.embedding=none")
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class))
.run(context -> {
@@ -354,7 +354,7 @@ public class ZhiPuAiPropertiesTests {
new ApplicationContextRunner()
.withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL",
- "spring.ai.zhipuai.embedding.enabled=true")
+ "spring.ai.model.embedding=zhipuai")
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class))
.run(context -> {
@@ -367,7 +367,7 @@ public class ZhiPuAiPropertiesTests {
void chatActivation() {
new ApplicationContextRunner()
.withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL",
- "spring.ai.zhipuai.chat.enabled=false")
+ "spring.ai.model.chat=none")
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class))
.run(context -> {
@@ -386,7 +386,7 @@ public class ZhiPuAiPropertiesTests {
new ApplicationContextRunner()
.withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL",
- "spring.ai.zhipuai.chat.enabled=true")
+ "spring.ai.model.chat=zhipuai")
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class))
.run(context -> {
@@ -400,7 +400,7 @@ public class ZhiPuAiPropertiesTests {
void imageActivation() {
new ApplicationContextRunner()
.withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL",
- "spring.ai.zhipuai.image.enabled=false")
+ "spring.ai.model.image=none")
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class))
.run(context -> {
@@ -419,7 +419,7 @@ public class ZhiPuAiPropertiesTests {
new ApplicationContextRunner()
.withPropertyValues("spring.ai.zhipuai.api-key=API_KEY", "spring.ai.zhipuai.base-url=TEST_BASE_URL",
- "spring.ai.zhipuai.image.enabled=true")
+ "spring.ai.model.image=zhipuai")
.withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class,
RestClientAutoConfiguration.class, ZhiPuAiAutoConfiguration.class))
.run(context -> {
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-tool/pom.xml b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml
similarity index 97%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-tool/pom.xml
rename to auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml
index ffdff50b4..6eebc504d 100644
--- a/auto-configurations/chat/spring-ai-autoconfigure-model-tool/pom.xml
+++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml
@@ -7,7 +7,7 @@
org.springframework.ai
spring-ai
1.0.0-SNAPSHOT
- ../../../pom.xml
+ ../../../../pom.xml
spring-ai-autoconfigure-model-tool
jar
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java
rename to auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-tool/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-tool/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
rename to auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
diff --git a/auto-configurations/chat/spring-ai-autoconfigure-model-tool/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java
similarity index 100%
rename from auto-configurations/chat/spring-ai-autoconfigure-model-tool/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java
rename to auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java
index 565eb6f2a..94395dcb8 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-elasticsearch/src/test/java/org/springframework/ai/vectorstore/elasticsearch/autoconfigure/ElasticsearchVectorStoreAutoConfigurationIT.java
@@ -29,7 +29,7 @@ import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
-import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration;
+import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration;
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
import org.springframework.ai.document.Document;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
@@ -61,7 +61,7 @@ class ElasticsearchVectorStoreAutoConfigurationIT {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class,
ElasticsearchVectorStoreAutoConfiguration.class, RestClientAutoConfiguration.class,
- SpringAiRetryAutoConfiguration.class, OpenAiAutoConfiguration.class))
+ SpringAiRetryAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class))
.withUserConfiguration(Config.class)
.withPropertyValues("spring.elasticsearch.uris=" + elasticsearchContainer.getHttpHostAddress(),
"spring.ai.vectorstore.elasticsearch.initializeSchema=true",
@@ -129,7 +129,7 @@ class ElasticsearchVectorStoreAutoConfigurationIT {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ElasticsearchRestClientAutoConfiguration.class,
ElasticsearchVectorStoreAutoConfiguration.class, RestClientAutoConfiguration.class,
- SpringAiRetryAutoConfiguration.class, OpenAiAutoConfiguration.class))
+ SpringAiRetryAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class))
.withPropertyValues("spring.elasticsearch.uris=" + elasticsearchContainer.getHttpHostAddress(),
"spring.ai.openai.api-key=" + System.getenv("OPENAI_API_KEY"),
"spring.ai.vectorstore.elasticsearch.index-name=example",
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-hanadb/src/test/java/org/springframework/ai/vectorstore/hanadb/autoconfigure/HanaCloudVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-hanadb/src/test/java/org/springframework/ai/vectorstore/hanadb/autoconfigure/HanaCloudVectorStoreAutoConfigurationIT.java
index 9699aa6a1..c407fe7cf 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-hanadb/src/test/java/org/springframework/ai/vectorstore/hanadb/autoconfigure/HanaCloudVectorStoreAutoConfigurationIT.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-hanadb/src/test/java/org/springframework/ai/vectorstore/hanadb/autoconfigure/HanaCloudVectorStoreAutoConfigurationIT.java
@@ -23,7 +23,7 @@ import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
-import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration;
+import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration;
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
@@ -41,8 +41,8 @@ public class HanaCloudVectorStoreAutoConfigurationIT {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HanaCloudVectorStoreAutoConfiguration.class,
- OpenAiAutoConfiguration.class, RestClientAutoConfiguration.class, SpringAiRetryAutoConfiguration.class,
- JdbcRepositoriesAutoConfiguration.class))
+ OpenAiEmbeddingAutoConfiguration.class, RestClientAutoConfiguration.class,
+ SpringAiRetryAutoConfiguration.class, JdbcRepositoriesAutoConfiguration.class))
.withPropertyValues("spring.ai.openai.api-key=" + System.getenv("OPENAI_API_KEY"),
"spring.ai.openai.embedding.options.model=text-embedding-ada-002",
"spring.datasource.url=" + System.getenv("HANA_DATASOURCE_URL"),
diff --git a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/test/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfigurationIT.java b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/test/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfigurationIT.java
index 6ff225d2e..cdbdd8481 100644
--- a/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/test/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfigurationIT.java
+++ b/auto-configurations/vector-stores/spring-ai-autoconfigure-vector-store-mongodb-atlas/src/test/java/org/springframework/ai/vectorstore/mongodb/autoconfigure/MongoDBAtlasVectorStoreAutoConfigurationIT.java
@@ -27,7 +27,7 @@ import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.mongodb.MongoDBAtlasLocalContainer;
-import org.springframework.ai.model.openai.autoconfigure.OpenAiAutoConfiguration;
+import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration;
import org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration;
import org.springframework.ai.document.Document;
import org.springframework.ai.observation.conventions.VectorStoreProvider;
@@ -64,7 +64,7 @@ class MongoDBAtlasVectorStoreAutoConfigurationIT {
.withUserConfiguration(Config.class)
.withConfiguration(AutoConfigurations.of(MongoAutoConfiguration.class, MongoDataAutoConfiguration.class,
MongoDBAtlasVectorStoreAutoConfiguration.class, RestClientAutoConfiguration.class,
- SpringAiRetryAutoConfiguration.class, OpenAiAutoConfiguration.class))
+ SpringAiRetryAutoConfiguration.class, OpenAiEmbeddingAutoConfiguration.class))
.withPropertyValues("spring.data.mongodb.database=springaisample",
"spring.ai.vectorstore.mongodb.initialize-schema=true",
"spring.ai.vectorstore.mongodb.collection-name=test_collection",
diff --git a/pom.xml b/pom.xml
index 1de797edb..6643a452d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,20 +37,26 @@
auto-configurations/common/spring-ai-autoconfigure-retry
- auto-configurations/chat/spring-ai-autoconfigure-model-chat-client
- auto-configurations/chat/spring-ai-autoconfigure-model-tool
- auto-configurations/chat/spring-ai-autoconfigure-model-chat-memory
+ auto-configurations/models/tool/spring-ai-autoconfigure-model-tool
+
+ auto-configurations/models/chat/client/spring-ai-autoconfigure-model-chat-client
+ auto-configurations/models/chat/memory/spring-ai-autoconfigure-model-chat-memory
+ auto-configurations/models/chat/observation/spring-ai-autoconfigure-model-chat-observation
+
+ auto-configurations/models/embedding/observation/spring-ai-autoconfigure-model-embedding-observation
+ auto-configurations/models/image/observation/spring-ai-autoconfigure-model-image-observation
auto-configurations/models/spring-ai-autoconfigure-model-anthropic
auto-configurations/models/spring-ai-autoconfigure-model-azure-openai
auto-configurations/models/spring-ai-autoconfigure-model-bedrock-ai
auto-configurations/models/spring-ai-autoconfigure-model-huggingface
+ auto-configurations/models/spring-ai-autoconfigure-model-openai
auto-configurations/models/spring-ai-autoconfigure-model-minimax
auto-configurations/models/spring-ai-autoconfigure-model-mistral-ai
auto-configurations/models/spring-ai-autoconfigure-model-moonshot
auto-configurations/models/spring-ai-autoconfigure-model-oci-genai
auto-configurations/models/spring-ai-autoconfigure-model-ollama
- auto-configurations/models/spring-ai-autoconfigure-model-openai
+
auto-configurations/models/spring-ai-autoconfigure-model-postgresml-embedding
auto-configurations/models/spring-ai-autoconfigure-model-qianfan
auto-configurations/models/spring-ai-autoconfigure-model-stability-ai
@@ -59,9 +65,7 @@
auto-configurations/models/spring-ai-autoconfigure-model-watsonx-ai
auto-configurations/models/spring-ai-autoconfigure-model-zhipuai
- auto-configurations/observation/chat/spring-ai-autoconfigure-observation-chat
- auto-configurations/observation/embedding/spring-ai-autoconfigure-observation-embedding
- auto-configurations/observation/image/spring-ai-autoconfigure-observation-image
+
auto-configurations/mcp/spring-ai-autoconfigure-mcp-client
auto-configurations/mcp/spring-ai-autoconfigure-mcp-server
diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModelProperties.java b/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModelProperties.java
new file mode 100644
index 000000000..c1fb667e9
--- /dev/null
+++ b/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModelProperties.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+public class SpringAIModelProperties {
+
+ public static final String MODEL_PREFIX = "spring.ai.model";
+
+ public static final String CHAT_MODEL = MODEL_PREFIX + ".chat";
+
+ public static final String EMBEDDING_MODEL = MODEL_PREFIX + ".embedding";
+
+ public static final String TEXT_EMBEDDING_MODEL = MODEL_PREFIX + ".text.embedding";
+
+ public static final String MULTI_MODAL_EMBEDDING_MODEL = MODEL_PREFIX + ".multi-modal.embedding";
+
+ public static final String IMAGE_MODEL = MODEL_PREFIX + ".image";
+
+ public static final String AUDIO_TRANSCRIPTION_MODEL = MODEL_PREFIX + ".audio.transcription";
+
+ public static final String AUDIO_SPEECH_MODEL = MODEL_PREFIX + ".audio.speech";
+
+ public static final String MODERATION_MODEL = MODEL_PREFIX + ".moderation";
+
+}
diff --git a/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModels.java b/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModels.java
new file mode 100644
index 000000000..f937d7671
--- /dev/null
+++ b/spring-ai-core/src/main/java/org/springframework/ai/model/SpringAIModels.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+public class SpringAIModels {
+
+ public static final String ANTHROPIC = "anthropic";
+
+ public static final String AZURE_OPENAI = "azure-openai";
+
+ public static final String BEDROCK_COHERE = "bedrock-cohere";
+
+ public static final String BEDROCK_CONVERSE = "bedrock-converse";
+
+ public static final String BEDROCK_TITAN = "bedrock-titan";
+
+ public static final String HUGGINGFACE = "huggingface";
+
+ public static final String MINIMAX = "minimax";
+
+ public static final String MISTRAL = "mistral";
+
+ public static final String MOONSHOT = "moonshot";
+
+ public static final String OCI_GENAI = "oci-genai";
+
+ public static final String OLLAMA = "ollama";
+
+ public static final String OPENAI = "openai";
+
+ public static final String POSTGRESML = "postgresml";
+
+ public static final String QIANFAN = "qianfan";
+
+ public static final String STABILITY = "stabilityai";
+
+ public static final String TRANSFORMERS = "transformers";
+
+ public static final String VERTEX_AI = "vertex";
+
+ public static final String WATSONX_AI = "watsonx";
+
+ public static final String ZHIPUAI = "zhipuai";
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfiguration.java
new file mode 100644
index 000000000..81aee1992
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfiguration.java
@@ -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) {
+ 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));
+ }
+
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java
new file mode 100644
index 000000000..be37d1b26
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationProperties.java
@@ -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;
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/package-info.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/package-info.java
new file mode 100644
index 000000000..66bef736b
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/chat/observation/autoconfigure/package-info.java
@@ -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;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfiguration.java
new file mode 100644
index 000000000..ee0902343
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfiguration.java
@@ -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) {
+ return new EmbeddingModelMeterObservationHandler(meterRegistry.getObject());
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/package-info.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/package-info.java
new file mode 100644
index 000000000..654b59b6f
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/embedding/observation/autoconfigure/package-info.java
@@ -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;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfiguration.java
new file mode 100644
index 000000000..f97e12f03
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfiguration.java
@@ -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();
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationProperties.java
new file mode 100644
index 000000000..7d372d986
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationProperties.java
@@ -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;
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/package-info.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/package-info.java
new file mode 100644
index 000000000..983cc5a16
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/model/image/observation/autoconfigure/package-info.java
@@ -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;
diff --git a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index c31d7de6e..7cb623cc6 100644
--- a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -13,8 +13,6 @@
# 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.azure.openai.autoconfigure.AzureOpenAiAutoConfiguration
org.springframework.ai.model.oci.genai.autoconfigure.OCIGenAiAutoConfiguration
org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration
@@ -24,9 +22,9 @@ gemini.org.springframework.ai.model.vertexai.autoconfigure.VertexAiGeminiAutoCon
cohere.org.springframework.ai.model.bedrock.autoconfigure.BedrockCohereEmbeddingAutoConfiguration
titan.org.springframework.ai.model.bedrock.autoconfigure.BedrockTitanEmbeddingAutoConfiguration
converse.org.springframework.ai.model.bedrock.autoconfigure.BedrockConverseProxyChatAutoConfiguration
-org.springframework.ai.autoconfigure.chat.observation.ChatObservationAutoConfiguration
-org.springframework.ai.autoconfigure.embedding.observation.EmbeddingObservationAutoConfiguration
-org.springframework.ai.autoconfigure.image.observation.ImageObservationAutoConfiguration
+org.springframework.ai.model.chat.observation.autoconfigure.ChatObservationAutoConfiguration
+org.springframework.ai.model.embedding.observation.autoconfigure.EmbeddingObservationAutoConfiguration
+org.springframework.ai.model.image.observation.autoconfigure.ImageObservationAutoConfiguration
org.springframework.ai.model.ollama.autoconfigure.OllamaAutoConfiguration
org.springframework.ai.model.mistralai.autoconfigure.MistralAiAutoConfiguration
org.springframework.ai.retry.autoconfigure.SpringAiRetryAutoConfiguration
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfigurationTests.java
new file mode 100644
index 000000000..3ac3f12ca
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/chat/observation/autoconfigure/ChatObservationAutoConfigurationTests.java
@@ -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));
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfigurationTests.java
new file mode 100644
index 000000000..10c9feb7c
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/embedding/observation/autoconfigure/EmbeddingObservationAutoConfigurationTests.java
@@ -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));
+ }
+
+}
diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfigurationTests.java
new file mode 100644
index 000000000..23417b67b
--- /dev/null
+++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/model/image/observation/autoconfigure/ImageObservationAutoConfigurationTests.java
@@ -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));
+ }
+
+}