From 72880e455d25a6632f7e4092fe0cead661670360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=99=8E=E9=B8=A3?= Date: Wed, 23 Apr 2025 15:27:44 +0800 Subject: [PATCH] chore: Add ToolContextCreator. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 虎鸣 --- .../ToolCallingAutoConfiguration.java | 11 +++++++ .../springframework/ai/mcp/McpToolUtils.java | 30 ++++++++++++++++++- .../ai/chat/model/ToolContext.java | 2 +- .../ai/chat/model/ToolContextCreator.java | 15 ++++++++++ .../model/tool/DefaultToolCallingManager.java | 21 +++++++++++-- .../model/tool/DefaultToolContextCreator.java | 19 ++++++++++++ 6 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 spring-ai-model/src/main/java/org/springframework/ai/chat/model/ToolContextCreator.java create mode 100644 spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolContextCreator.java 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 0090a6610ef..a32c9960c9d 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 @@ -22,6 +22,9 @@ import io.micrometer.observation.ObservationRegistry; import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.chat.model.ToolContextCreator; +import org.springframework.ai.model.tool.DefaultToolContextCreator; import org.springframework.ai.model.tool.ToolCallingManager; import org.springframework.ai.tool.ToolCallback; import org.springframework.ai.tool.ToolCallbackProvider; @@ -72,14 +75,22 @@ ToolExecutionExceptionProcessor toolExecutionExceptionProcessor() { return new DefaultToolExecutionExceptionProcessor(false); } + @Bean + @ConditionalOnMissingBean + ToolContextCreator toolContextCreator() { + return new DefaultToolContextCreator(); + } + @Bean @ConditionalOnMissingBean ToolCallingManager toolCallingManager(ToolCallbackResolver toolCallbackResolver, + ToolContextCreator toolContextCreator, ToolExecutionExceptionProcessor toolExecutionExceptionProcessor, ObjectProvider observationRegistry) { return ToolCallingManager.builder() .observationRegistry(observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP)) .toolCallbackResolver(toolCallbackResolver) + .toolContextCreator(toolContextCreator) .toolExecutionExceptionProcessor(toolExecutionExceptionProcessor) .build(); } diff --git a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java index 4ab5ca02d10..62154e66e54 100644 --- a/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java +++ b/mcp/common/src/main/java/org/springframework/ai/mcp/McpToolUtils.java @@ -30,6 +30,8 @@ import io.modelcontextprotocol.server.McpSyncServerExchange; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.Role; +import org.springframework.ai.chat.model.ToolContextCreator; +import org.springframework.ai.model.tool.DefaultToolContextCreator; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; @@ -166,6 +168,32 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To */ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(ToolCallback toolCallback, MimeType mimeType) { + return toSyncToolSpecification(toolCallback, new DefaultToolContextCreator(), mimeType); + } + + /** + * Converts a Spring AI ToolCallback to an MCP SyncToolSpecification. This enables + * Spring AI functions to be exposed as MCP tools that can be discovered and invoked + * by language models. + * + *

+ * The conversion process: + *

    + *
  • Creates an MCP Tool with the function's name and input schema
  • + *
  • Wraps the function's execution in a SyncToolSpecification that handles the MCP + * protocol
  • + *
  • Provides error handling and result formatting according to MCP + * specifications
  • + *
+ * @param toolCallback the Spring AI function callback to convert + * @param toolContextCreator the tool context creator to use for creating the tool + * context + * @param mimeType the MIME type of the output content + * @return an MCP SyncToolRegistration that wraps the function callback + * @throws RuntimeException if there's an error during the function execution + */ + public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(ToolCallback toolCallback, + ToolContextCreator toolContextCreator, MimeType mimeType) { var tool = new McpSchema.Tool(toolCallback.getToolDefinition().name(), toolCallback.getToolDefinition().description(), toolCallback.getToolDefinition().inputSchema()); @@ -173,7 +201,7 @@ public static McpServerFeatures.SyncToolSpecification toSyncToolSpecification(To return new McpServerFeatures.SyncToolSpecification(tool, (exchange, request) -> { try { String callResult = toolCallback.call(ModelOptionsUtils.toJsonString(request), - new ToolContext(Map.of(TOOL_CONTEXT_MCP_EXCHANGE_KEY, exchange))); + toolContextCreator.create(Map.of(TOOL_CONTEXT_MCP_EXCHANGE_KEY, exchange))); if (mimeType != null && mimeType.toString().startsWith("image")) { return new McpSchema.CallToolResult(List .of(new McpSchema.ImageContent(List.of(Role.ASSISTANT), null, callResult, mimeType.toString())), diff --git a/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ToolContext.java b/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ToolContext.java index b9eb024ec6c..51df663c703 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ToolContext.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ToolContext.java @@ -43,7 +43,7 @@ * @author Christian Tzolov * @since 1.0.0 */ -public final class ToolContext { +public class ToolContext { /** * The key for the running, tool call history stored in the context map. diff --git a/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ToolContextCreator.java b/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ToolContextCreator.java new file mode 100644 index 00000000000..5d8a2735d25 --- /dev/null +++ b/spring-ai-model/src/main/java/org/springframework/ai/chat/model/ToolContextCreator.java @@ -0,0 +1,15 @@ +package org.springframework.ai.chat.model; + +import java.util.Map; + +/** + * A functional interface for creating a {@link ToolContext} instance. + */ +public interface ToolContextCreator { + + /** + * Create a new instance of {@link ToolContext} with the provided context map. + */ + Ctx create(final Map context); + +} diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java index a3afa93e517..c7bb3e0c16b 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolCallingManager.java @@ -32,6 +32,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.model.ToolContextCreator; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallback; import org.springframework.ai.tool.ToolCallback; @@ -62,6 +63,9 @@ public class DefaultToolCallingManager implements ToolCallingManager { private static final ToolCallbackResolver DEFAULT_TOOL_CALLBACK_RESOLVER = new DelegatingToolCallbackResolver(List.of()); + private static final DefaultToolContextCreator DEFAULT_TOOL_CONTEXT_CREATOR + = new DefaultToolContextCreator(); + private static final ToolExecutionExceptionProcessor DEFAULT_TOOL_EXECUTION_EXCEPTION_PROCESSOR = DefaultToolExecutionExceptionProcessor.builder().build(); @@ -71,9 +75,12 @@ public class DefaultToolCallingManager implements ToolCallingManager { private final ToolCallbackResolver toolCallbackResolver; + private final ToolContextCreator toolContextCreator; + private final ToolExecutionExceptionProcessor toolExecutionExceptionProcessor; public DefaultToolCallingManager(ObservationRegistry observationRegistry, ToolCallbackResolver toolCallbackResolver, + ToolContextCreator toolContextCreator, ToolExecutionExceptionProcessor toolExecutionExceptionProcessor) { Assert.notNull(observationRegistry, "observationRegistry cannot be null"); Assert.notNull(toolCallbackResolver, "toolCallbackResolver cannot be null"); @@ -81,6 +88,7 @@ public DefaultToolCallingManager(ObservationRegistry observationRegistry, ToolCa this.observationRegistry = observationRegistry; this.toolCallbackResolver = toolCallbackResolver; + this.toolContextCreator = toolContextCreator; this.toolExecutionExceptionProcessor = toolExecutionExceptionProcessor; } @@ -136,7 +144,7 @@ public ToolExecutionResult executeToolCalls(Prompt prompt, ChatResponse chatResp .build(); } - private static ToolContext buildToolContext(Prompt prompt, AssistantMessage assistantMessage) { + private ToolContext buildToolContext(Prompt prompt, AssistantMessage assistantMessage) { Map toolContextMap = Map.of(); if (prompt.getOptions() instanceof ToolCallingChatOptions toolCallingChatOptions @@ -151,7 +159,7 @@ private static ToolContext buildToolContext(Prompt prompt, AssistantMessage assi buildConversationHistoryBeforeToolExecution(prompt, assistantMessage)); } - return new ToolContext(toolContextMap); + return toolContextCreator.create(toolContextMap); } private static List buildConversationHistoryBeforeToolExecution(Prompt prompt, @@ -236,6 +244,8 @@ public final static class Builder { private ToolCallbackResolver toolCallbackResolver = DEFAULT_TOOL_CALLBACK_RESOLVER; + private ToolContextCreator toolContextCreator = DEFAULT_TOOL_CONTEXT_CREATOR; + private ToolExecutionExceptionProcessor toolExecutionExceptionProcessor = DEFAULT_TOOL_EXECUTION_EXCEPTION_PROCESSOR; private Builder() { @@ -251,6 +261,11 @@ public Builder toolCallbackResolver(ToolCallbackResolver toolCallbackResolver) { return this; } + public Builder toolContextCreator(ToolContextCreator toolContextCreator) { + this.toolContextCreator = toolContextCreator; + return this; + } + public Builder toolExecutionExceptionProcessor( ToolExecutionExceptionProcessor toolExecutionExceptionProcessor) { this.toolExecutionExceptionProcessor = toolExecutionExceptionProcessor; @@ -259,7 +274,7 @@ public Builder toolExecutionExceptionProcessor( public DefaultToolCallingManager build() { return new DefaultToolCallingManager(this.observationRegistry, this.toolCallbackResolver, - this.toolExecutionExceptionProcessor); + this.toolContextCreator, this.toolExecutionExceptionProcessor); } } diff --git a/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolContextCreator.java b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolContextCreator.java new file mode 100644 index 00000000000..4866824650d --- /dev/null +++ b/spring-ai-model/src/main/java/org/springframework/ai/model/tool/DefaultToolContextCreator.java @@ -0,0 +1,19 @@ +package org.springframework.ai.model.tool; + +import org.springframework.ai.chat.model.ToolContext; +import org.springframework.ai.chat.model.ToolContextCreator; + +import java.util.Map; + +/** + * A default implementation of {@link ToolContextCreator} that creates a new instance of + * {@link ToolContext}. + */ +public class DefaultToolContextCreator implements ToolContextCreator { + + @Override + public ToolContext create(final Map context) { + return new ToolContext(context); + } + +}