Skip to content

Commit d28d00d

Browse files
committed
Defer call of getToolCallbacks() to prevent initializing ToolCallbackProvider eagerly
Before this commit, `McpSyncClient` is initialized even if `spring.ai.mcp.client.initialized` is set to `false`. See GH-3232 Signed-off-by: Yanming Zhou <[email protected]>
1 parent 84efb6a commit d28d00d

File tree

2 files changed

+38
-13
lines changed

2 files changed

+38
-13
lines changed

auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/main/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfiguration.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
* @author Thomas Vitale
5252
* @author Christian Tzolov
5353
* @author Daniel Garnier-Moiroux
54+
* @author Yanming Zhou
5455
* @since 1.0.0
5556
*/
5657
@AutoConfiguration
@@ -65,16 +66,23 @@ public class ToolCallingAutoConfiguration {
6566
ToolCallbackResolver toolCallbackResolver(GenericApplicationContext applicationContext,
6667
List<ToolCallback> toolCallbacks, List<ToolCallbackProvider> tcbProviders) {
6768

68-
List<ToolCallback> allFunctionAndToolCallbacks = new ArrayList<>(toolCallbacks);
69-
tcbProviders.stream().map(pr -> List.of(pr.getToolCallbacks())).forEach(allFunctionAndToolCallbacks::addAll);
69+
return toolName -> { // defer call of getToolCallbacks() to prevent initializing
70+
// ToolCallbackProvider eagerly
71+
List<ToolCallback> allFunctionAndToolCallbacks = new ArrayList<>(toolCallbacks);
72+
tcbProviders.stream()
73+
.map(pr -> List.of(pr.getToolCallbacks()))
74+
.forEach(allFunctionAndToolCallbacks::addAll);
7075

71-
var staticToolCallbackResolver = new StaticToolCallbackResolver(allFunctionAndToolCallbacks);
76+
var staticToolCallbackResolver = new StaticToolCallbackResolver(allFunctionAndToolCallbacks);
7277

73-
var springBeanToolCallbackResolver = SpringBeanToolCallbackResolver.builder()
74-
.applicationContext(applicationContext)
75-
.build();
78+
var springBeanToolCallbackResolver = SpringBeanToolCallbackResolver.builder()
79+
.applicationContext(applicationContext)
80+
.build();
7681

77-
return new DelegatingToolCallbackResolver(List.of(staticToolCallbackResolver, springBeanToolCallbackResolver));
82+
return new DelegatingToolCallbackResolver(
83+
List.of(staticToolCallbackResolver, springBeanToolCallbackResolver))
84+
.resolve(toolName);
85+
};
7886
}
7987

8088
@Bean

auto-configurations/models/tool/spring-ai-autoconfigure-model-tool/src/test/java/org/springframework/ai/model/tool/autoconfigure/ToolCallingAutoConfigurationTests.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.function.Function;
2020

2121
import org.junit.jupiter.api.Test;
22+
import org.mockito.Mockito;
2223

2324
import org.springframework.ai.model.tool.DefaultToolCallingManager;
2425
import org.springframework.ai.model.tool.ToolCallingManager;
@@ -34,7 +35,6 @@
3435
import org.springframework.ai.tool.method.MethodToolCallback;
3536
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
3637
import org.springframework.ai.tool.observation.ToolCallingContentObservationFilter;
37-
import org.springframework.ai.tool.resolution.DelegatingToolCallbackResolver;
3838
import org.springframework.ai.tool.resolution.ToolCallbackResolver;
3939
import org.springframework.ai.tool.support.ToolDefinitions;
4040
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -45,21 +45,24 @@
4545
import org.springframework.util.ReflectionUtils;
4646

4747
import static org.assertj.core.api.Assertions.assertThat;
48+
import static org.mockito.BDDMockito.then;
49+
import static org.mockito.Mockito.never;
50+
import static org.mockito.Mockito.times;
4851

4952
/**
5053
* Unit tests for {@link ToolCallingAutoConfiguration}.
5154
*
5255
* @author Thomas Vitale
5356
* @author Christian Tzolov
57+
* @author Yanming Zhou
5458
*/
5559
class ToolCallingAutoConfigurationTests {
5660

5761
@Test
5862
void beansAreCreated() {
5963
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(ToolCallingAutoConfiguration.class))
6064
.run(context -> {
61-
var toolCallbackResolver = context.getBean(ToolCallbackResolver.class);
62-
assertThat(toolCallbackResolver).isInstanceOf(DelegatingToolCallbackResolver.class);
65+
assertThat(context).hasSingleBean(ToolCallbackResolver.class);
6366

6467
var toolExecutionExceptionProcessor = context.getBean(ToolExecutionExceptionProcessor.class);
6568
assertThat(toolExecutionExceptionProcessor).isInstanceOf(DefaultToolExecutionExceptionProcessor.class);
@@ -69,13 +72,25 @@ void beansAreCreated() {
6972
});
7073
}
7174

75+
@Test
76+
void deferGetToolCallbacks() {
77+
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(ToolCallingAutoConfiguration.class))
78+
.withUserConfiguration(Config.class)
79+
.run(context -> {
80+
var toolCallbackResolver = context.getBean(ToolCallbackResolver.class);
81+
var toolCallbackProvider = context.getBean("toolCallbacks", ToolCallbackProvider.class);
82+
then(toolCallbackProvider).should(never()).getToolCallbacks();
83+
assertThat(toolCallbackResolver.resolve("getForecast")).isNotNull();
84+
then(toolCallbackProvider).should(times(1)).getToolCallbacks();
85+
});
86+
}
87+
7288
@Test
7389
void resolveMultipleFunctionAndToolCallbacks() {
7490
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(ToolCallingAutoConfiguration.class))
7591
.withUserConfiguration(Config.class)
7692
.run(context -> {
7793
var toolCallbackResolver = context.getBean(ToolCallbackResolver.class);
78-
assertThat(toolCallbackResolver).isInstanceOf(DelegatingToolCallbackResolver.class);
7994

8095
assertThat(toolCallbackResolver.resolve("getForecast")).isNotNull();
8196
assertThat(toolCallbackResolver.resolve("getForecast").getToolDefinition().name())
@@ -108,7 +123,6 @@ void resolveMissingToolCallbacks() {
108123
.withUserConfiguration(Config.class)
109124
.run(context -> {
110125
var toolCallbackResolver = context.getBean(ToolCallbackResolver.class);
111-
assertThat(toolCallbackResolver).isInstanceOf(DelegatingToolCallbackResolver.class);
112126

113127
assertThat(toolCallbackResolver.resolve("NonExisting")).isNull();
114128
});
@@ -212,7 +226,10 @@ static class Config {
212226
// ToolCallbacks.from(...) utility method.
213227
@Bean
214228
public ToolCallbackProvider toolCallbacks() {
215-
return MethodToolCallbackProvider.builder().toolObjects(new WeatherService()).build();
229+
ToolCallbackProvider toolCallbackProvider = MethodToolCallbackProvider.builder()
230+
.toolObjects(new WeatherService())
231+
.build();
232+
return Mockito.spy(toolCallbackProvider);
216233
}
217234

218235
@Bean

0 commit comments

Comments
 (0)