From adb3e28530750a8ace8b78ab0f34f21d7e6b1a47 Mon Sep 17 00:00:00 2001 From: Lubaoshuai <128781758+Lubaoshuai@users.noreply.github.com> Date: Sat, 12 Sep 2026 02:18:25 +0800 Subject: [PATCH] fix(spring-ai): order SpringAI auto-configuration after Spring AI model auto-configurations The @ConditionalOnBean guards on the SpringAI bean methods were evaluated before the Spring AI provider auto-configurations had run: with no ordering declared, alphabetical sorting puts this configuration (com.google.adk...) ahead of org.springframework.ai.model.*.autoconfigure .*AutoConfiguration in real applications, so every condition misses and the SpringAI/SpringAIEmbedding beans are silently never created (#1501). Declare afterName over the Spring AI 2.x chat and embedding model auto-configurations (class names verified against each module's AutoConfiguration.imports at v2.0.1). String-based afterName is required because this module only compiles against spring-ai-model, and unknown names are ignored, so versions without a listed configuration are unaffected. Add an ordering regression test that drives the real OpenAI chat and embedding auto-configurations through AutoConfigurations (same sorter as a real application): it fails while the ordering is missing and passes with it. --- contrib/spring-ai/pom.xml | 10 +++ .../SpringAIAutoConfiguration.java | 22 +++++- ...SpringAIAutoConfigurationOrderingTest.java | 75 +++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 contrib/spring-ai/src/test/java/com/google/adk/models/springai/autoconfigure/SpringAIAutoConfigurationOrderingTest.java diff --git a/contrib/spring-ai/pom.xml b/contrib/spring-ai/pom.xml index 2a6b1143b..27e0e4ff0 100644 --- a/contrib/spring-ai/pom.xml +++ b/contrib/spring-ai/pom.xml @@ -122,6 +122,16 @@ spring-ai-openai test + + org.springframework.ai + spring-ai-autoconfigure-model-openai + test + + + org.springframework.ai + spring-ai-autoconfigure-model-tool + test + org.springframework.ai spring-ai-anthropic diff --git a/contrib/spring-ai/src/main/java/com/google/adk/models/springai/autoconfigure/SpringAIAutoConfiguration.java b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/autoconfigure/SpringAIAutoConfiguration.java index 7a312ca88..314b8f918 100644 --- a/contrib/spring-ai/src/main/java/com/google/adk/models/springai/autoconfigure/SpringAIAutoConfiguration.java +++ b/contrib/spring-ai/src/main/java/com/google/adk/models/springai/autoconfigure/SpringAIAutoConfiguration.java @@ -58,7 +58,27 @@ * adk.spring-ai.validation.enabled=true * */ -@AutoConfiguration +@AutoConfiguration( + afterName = { + "org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration", + "org.springframework.ai.model.bedrock.converse.autoconfigure.BedrockConverseProxyChatAutoConfiguration", + "org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration", + "org.springframework.ai.model.google.genai.autoconfigure.chat.GoogleGenAiChatAutoConfiguration", + "org.springframework.ai.model.mistralai.autoconfigure.MistralAiChatAutoConfiguration", + "org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration", + "org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration", + "org.springframework.ai.model.bedrock.cohere.autoconfigure.BedrockCohereEmbeddingAutoConfiguration", + "org.springframework.ai.model.bedrock.titan.autoconfigure.BedrockTitanEmbeddingAutoConfiguration", + "org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiTextEmbeddingAutoConfiguration", + "org.springframework.ai.model.mistralai.autoconfigure.MistralAiEmbeddingAutoConfiguration", + "org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration", + "org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration", + "org.springframework.ai.model.postgresml.autoconfigure.PostgresMlEmbeddingAutoConfiguration", + "org.springframework.ai.model.transformers.autoconfigure.TransformersEmbeddingModelAutoConfiguration", + "org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiEmbeddingConnectionAutoConfiguration", + "org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiTextEmbeddingAutoConfiguration", + "org.springframework.ai.model.vertexai.autoconfigure.embedding.VertexAiMultiModalEmbeddingAutoConfiguration", + }) @ConditionalOnClass({SpringAI.class, ChatModel.class}) @ConditionalOnProperty( prefix = "adk.spring-ai.auto-configuration", diff --git a/contrib/spring-ai/src/test/java/com/google/adk/models/springai/autoconfigure/SpringAIAutoConfigurationOrderingTest.java b/contrib/spring-ai/src/test/java/com/google/adk/models/springai/autoconfigure/SpringAIAutoConfigurationOrderingTest.java new file mode 100644 index 000000000..d1ff29267 --- /dev/null +++ b/contrib/spring-ai/src/test/java/com/google/adk/models/springai/autoconfigure/SpringAIAutoConfigurationOrderingTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 Google LLC + * + * 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 + * + * http://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 com.google.adk.models.springai.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.adk.models.springai.SpringAI; +import com.google.adk.models.springai.SpringAIEmbedding; +import org.junit.jupiter.api.Test; +import org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration; +import org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration; +import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +/** + * Verifies that {@link SpringAIAutoConfiguration} is ordered after the Spring AI model + * auto-configurations. + * + *

{@link org.springframework.boot.autoconfigure.condition.ConditionalOnBean} conditions are + * evaluated when the configuration is processed, so {@code @ConditionalOnBean(ChatModel)} on this + * auto-configuration only sees the model beans if the provider auto-configurations ran first. In a + * real application the configurations are otherwise sorted alphabetically, which puts {@code + * com.google.adk...} before {@code org.springframework.ai...} and makes every SpringAI bean + * silently disappear (see issue #1501). + */ +class SpringAIAutoConfigurationOrderingTest { + + private final ApplicationContextRunner contextRunner = + new ApplicationContextRunner() + .withConfiguration( + AutoConfigurations.of( + SpringAIAutoConfiguration.class, + OpenAiChatAutoConfiguration.class, + OpenAiEmbeddingAutoConfiguration.class, + ToolCallingAutoConfiguration.class)); + + @Test + void springAIBeansAreCreatedWhenModelsComeFromSpringAIModelAutoConfigurations() { + contextRunner + .withPropertyValues( + "spring.ai.openai.api-key=dummy-key", "adk.spring-ai.validation.enabled=false") + .run( + context -> { + assertThat(context).hasSingleBean(SpringAI.class); + assertThat(context).hasSingleBean(SpringAIEmbedding.class); + }); + } + + @Test + void disabledAutoConfigurationStillBacksOff() { + contextRunner + .withPropertyValues( + "spring.ai.openai.api-key=dummy-key", "adk.spring-ai.auto-configuration.enabled=false") + .run( + context -> { + assertThat(context).doesNotHaveBean(SpringAI.class); + assertThat(context).doesNotHaveBean(SpringAIEmbedding.class); + assertThat(context).hasSingleBean(org.springframework.ai.chat.model.ChatModel.class); + }); + } +}