From e54423bf677e55bb711fefa38ff2f81570a73989 Mon Sep 17 00:00:00 2001 From: Dmitry Bedrin Date: Fri, 7 Nov 2025 17:57:27 +0100 Subject: [PATCH] Fixed Spring AOT support and regression issue introduced in GH-4751 PR https://github.com/spring-projects/spring-ai/pull/4751/ introduced a new error when using Spring AOT: ``` Exception in thread "main" org.springframework.beans.factory.aot.AotBeanProcessingException: Error processing bean with name 'org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration.toolcallbackprovider.mcp-excluded': instance supplier is not supported at org.springframework.beans.factory.aot.DefaultBeanRegistrationCodeFragments.getTarget(DefaultBeanRegistrationCodeFragments.java:82) at org.springframework.beans.factory.aot.BeanDefinitionMethodGenerator.generateBeanDefinitionMethod(BeanDefinitionMethodGenerator.java:85) at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution$BeanDefinitionsRegistrationGenerator.generateBeanRegistration(BeanRegistrationsAotContribution.java:289) at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution$BeanDefinitionsRegistrationGenerator.lambda$generateRegisterBeanDefinitionMethods$0(BeanRegistrationsAotContribution.java:272) at java.base/java.lang.Iterable.forEach(Iterable.java:75) at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution$BeanDefinitionsRegistrationGenerator.generateRegisterBeanDefinitionMethods(BeanRegistrationsAotContribution.java:270) at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution$BeanDefinitionsRegistrationGenerator.generateBeanRegistrationsCode(BeanRegistrationsAotContribution.java:238) at org.springframework.beans.factory.aot.BeanRegistrationsAotContribution.lambda$generateBeanRegistrationClass$0(BeanRegistrationsAotContribution.java:116) ``` This PR fixes this issue by refactoring `ToolCallingAutoConfiguration` from using `BeanDefinitionRegistryPostProcessor` to `ObjectProvider` Signed-off-by: Dmitry Bedrin --- .../pom.xml | 21 ++++++++ .../ToolCallingAutoConfiguration.java | 51 +------------------ .../ToolCallingAutoConfigurationTests.java | 2 + 3 files changed, 25 insertions(+), 49 deletions(-) diff --git a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml index 3c50f01d5f1..354fb069b0a 100644 --- a/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml +++ b/auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/pom.xml @@ -21,6 +21,27 @@ git@github.com:spring-projects/spring-ai.git + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + process-test-aot + + process-test-aot + + process-test-classes + + + + + diff --git a/auto-configurations/models/tool/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 index e5d6699cd69..662f78d2cd7 100644 --- a/auto-configurations/models/tool/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 @@ -17,7 +17,6 @@ package org.springframework.ai.model.tool.autoconfigure; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import io.micrometer.observation.ObservationRegistry; @@ -36,14 +35,7 @@ import org.springframework.ai.tool.resolution.SpringBeanToolCallbackResolver; import org.springframework.ai.tool.resolution.StaticToolCallbackResolver; import org.springframework.ai.tool.resolution.ToolCallbackResolver; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -65,13 +57,10 @@ @AutoConfiguration @ConditionalOnClass(ChatModel.class) @EnableConfigurationProperties(ToolCallingProperties.class) -public class ToolCallingAutoConfiguration implements BeanDefinitionRegistryPostProcessor { +public class ToolCallingAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(ToolCallingAutoConfiguration.class); - // Marker qualifier to exclude MCP-related ToolCallbackProviders - private static final String EXCLUDE_MCP_TOOL_CALLBACK_PROVIDER = "org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration.toolcallbackprovider.mcp-excluded"; - /** * The default {@link ToolCallbackResolver} resolves tools by name for methods, * functions, and {@link ToolCallbackProvider} beans. @@ -83,8 +72,7 @@ public class ToolCallingAutoConfiguration implements BeanDefinitionRegistryPostP @Bean @ConditionalOnMissingBean ToolCallbackResolver toolCallbackResolver(GenericApplicationContext applicationContext, - List toolCallbacks, - @Qualifier(EXCLUDE_MCP_TOOL_CALLBACK_PROVIDER) List tcbProviders) { + List toolCallbacks, ObjectProvider tcbProviders) { List allFunctionAndToolCallbacks = new ArrayList<>(toolCallbacks); tcbProviders.stream() .filter(pr -> !isMcpToolCallbackProvider(ResolvableType.forInstance(pr))) @@ -100,41 +88,6 @@ ToolCallbackResolver toolCallbackResolver(GenericApplicationContext applicationC return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver, springBeanToolCallbackResolver)); } - /** - * Wrap {@link ToolCallbackProvider} beans that are not MCP-related into a named bean, - * which will be picked up by the - * {@link ToolCallingAutoConfiguration#toolCallbackResolver}. - *

- * MCP providers must be excluded, because they may depend on a {@code ChatClient} to - * do sampling. The chat client, in turn, depends on a {@link ToolCallbackResolver}. - * To do the detection, we depend on the exposed bean type. If a bean uses a factory - * method which returns a {@link ToolCallbackProvider}, which is an MCP provider under - * the hood, it will be included in the list. - */ - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { - if (!(registry instanceof DefaultListableBeanFactory beanFactory)) { - return; - } - - var excludeMcpToolCallbackProviderBeanDefinition = BeanDefinitionBuilder - .genericBeanDefinition(List.class, () -> { - var providerNames = beanFactory.getBeanNamesForType(ToolCallbackProvider.class); - return Arrays.stream(providerNames) - .filter(name -> !isMcpToolCallbackProvider(beanFactory.getBeanDefinition(name).getResolvableType())) - .map(beanFactory::getBean) - .filter(ToolCallbackProvider.class::isInstance) - .map(ToolCallbackProvider.class::cast) - .toList(); - }) - .setScope(BeanDefinition.SCOPE_SINGLETON) - .setLazyInit(true) - .getBeanDefinition(); - - registry.registerBeanDefinition(EXCLUDE_MCP_TOOL_CALLBACK_PROVIDER, - excludeMcpToolCallbackProviderBeanDefinition); - } - private static boolean isMcpToolCallbackProvider(ResolvableType type) { if (type.getType().getTypeName().equals("org.springframework.ai.mcp.SyncMcpToolCallbackProvider") || type.getType().getTypeName().equals("org.springframework.ai.mcp.AsyncMcpToolCallbackProvider")) { diff --git a/auto-configurations/models/tool/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 index 64a080489ee..3adadefbf55 100644 --- a/auto-configurations/models/tool/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 @@ -40,6 +40,7 @@ import org.springframework.ai.tool.resolution.ToolCallbackResolver; import org.springframework.ai.tool.support.ToolDefinitions; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -59,6 +60,7 @@ * @author Christian Tzolov * @author Yanming Zhou */ +@SpringBootTest(classes = ToolCallingAutoConfiguration.class) // required for AOT tests class ToolCallingAutoConfigurationTests { @Test