之前做个几个大模型的应用,都是使用Python语言,后来有一个项目使用了Java,并使用了Spring AI框架。随着Spring AI不断地完善,最近它发布了1.0正式版,意味着它已经能很好的作为企业级生产环境的使用。对于Java开发者来说真是一个福音,其功能已经能满足基于大模型开发企业级应用。借着这次机会,给大家分享一下Spring AI框架。
注意:由于框架不同版本改造会有些使用的不同,因此本次系列中使用基本框架是 Spring AI-1.0.0,JDK版本使用的是19,Spring-AI-Alibaba-1.0.0.3-SNAPSHOT。
代码参考: https://github.com/forever1986/springai-study
目录
- 1 示例演示
- 1.1 初始化
- 1.2 构建图和节点
- 1.3 演示效果
上一章演示了Graph框架的并行执行流程,并剖析了其中ParallelNode的实现逻辑。这一章来讲一下Graph如何访问MCP。
1 示例演示
代码参考lesson26子模块中的graph-mcp子模块
示例说明:通过构建一个Graph图,其中定义一个访问MCP节点
1.1 初始化
1)本次MCP服务采用前面lesson10子模块的sse-server子模块作为MCP Server,启动该服务
2)在lesson26子模块下,新建graph-mcp子模块,其pom引入如下:
<dependencies><!-- 引入智谱的model插件 --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-zhipuai</artifactId></dependency><dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-graph-core</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client-webflux</artifactId></dependency><!-- 需要引入gson插件 --><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.6</version></dependency>
</dependencies>
3)新建application.properties配置文件
# 聊天模型
spring.ai.zhipuai.api-key=你的智谱API KEY
spring.ai.zhipuai.chat.options.model=GLM-4-Flash-250414
spring.ai.zhipuai.chat.options.temperature=0.7# 指定mcp-servers的URL
spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.sse.connections.server1.url=http://localhost:8081spring.ai.graph.nodes.node2servers.mcp-node=server1
4)新建McpNodeProperties读取配置spring.ai.graph.nodes开头
import org.springframework.boot.context.properties.ConfigurationProperties;import java.util.Map;
import java.util.Set;/*** 为了方便解析配置多少个MCP服务*/
@ConfigurationProperties(prefix = McpNodeProperties.PREFIX)
public class McpNodeProperties {public static final String PREFIX = "spring.ai.graph.nodes";private Map<String, Set<String>> node2servers;public Map<String, Set<String>> getNode2servers() {return node2servers;}public void setNode2servers(Map<String, Set<String>> node2servers) {this.node2servers = node2servers;}
}
1.2 构建图和节点
1)新建McpClientToolCallbackProvider类,用于读取toolcall
import com.demo.lesson26.mcp.config.McpNodeProperties;
import org.apache.commons.compress.utils.Lists;
import org.glassfish.jersey.internal.guava.Sets;
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.mcp.client.autoconfigure.properties.McpClientCommonProperties;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Set;@Service
public class McpClientToolCallbackProvider {private final ToolCallbackProvider toolCallbackProvider;private final McpClientCommonProperties commonProperties;private final McpNodeProperties mcpNodeProperties;public McpClientToolCallbackProvider(ToolCallbackProvider toolCallbackProvider,McpClientCommonProperties commonProperties, McpNodeProperties mcpNodeProperties) {this.toolCallbackProvider = toolCallbackProvider;this.commonProperties = commonProperties;this.mcpNodeProperties = mcpNodeProperties;}/*** 通过配置的spring.ai.graph.nodes的名称,获取从Spring AI中获取到的toolCall*/public Set<ToolCallback> findToolCallbacks(String nodeName) {// 获取配置文件中spring.ai.graph.nodes开头的数据Set<ToolCallback> defineCallback = Sets.newHashSet();Set<String> mcpClients = mcpNodeProperties.getNode2servers().get(nodeName);if (mcpClients == null || mcpClients.isEmpty()) {return defineCallback;}List<String> exceptMcpClientNames = Lists.newArrayList();for (String mcpClient : mcpClients) {// my-mcp-clientString name = commonProperties.getName();// mymcpclientserver1String prefixedMcpClientName = McpToolUtils.prefixedToolName(name, mcpClient);exceptMcpClientNames.add(prefixedMcpClientName);}// 从Spring AI的MCP客户端获取到的toolCall,放到defineCallback,以方便注册到MCPNode中的chatClientToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();for (ToolCallback toolCallback : toolCallbacks) {ToolDefinition toolDefinition = toolCallback.getToolDefinition();String name = toolDefinition.name();for (String exceptMcpClientName : exceptMcpClientNames) {if (name.startsWith(exceptMcpClientName)) {defineCallback.add(toolCallback);}}}return defineCallback;}
}
2)构建McpNode节点
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import com.demo.lesson26.mcp.tool.McpClientToolCallbackProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallback;
import reactor.core.publisher.Flux;import java.util.HashMap;
import java.util.Map;
import java.util.Set;/*** 自定义MCP节点*/
public class McpNode implements NodeAction {private static final Logger logger = LoggerFactory.getLogger(McpNode.class);private static final String NODENAME = "mcp-node";private final ChatClient chatClient;public McpNode(ChatClient.Builder chatClientBuilder, McpClientToolCallbackProvider mcpClientToolCallbackProvider) {// 获取mcp-node前缀定义的工具,注册到chatClient中Set<ToolCallback> toolCallbacks = mcpClientToolCallbackProvider.findToolCallbacks(NODENAME);for (ToolCallback toolCallback : toolCallbacks) {logger.info("Mcp Node load ToolCallback: " + toolCallback.getToolDefinition().name());}this.chatClient = chatClientBuilder.defaultToolCallbacks(toolCallbacks.toArray(ToolCallback[]::new)).build();}@Overridepublic Map<String, Object> apply(OverAllState state) {String query = state.value("query", "");Flux<String> streamResult = chatClient.prompt(query).stream().content();String result = streamResult.reduce("", (acc, item) -> acc + item).block();HashMap<String, Object> resultMap = new HashMap<>();resultMap.put("mcpcontent", result);return resultMap;}
}
3)设置配置类McpGaphConfiguration构建图:
import com.alibaba.cloud.ai.graph.GraphRepresentation;
import com.alibaba.cloud.ai.graph.KeyStrategy;
import com.alibaba.cloud.ai.graph.KeyStrategyFactory;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.action.AsyncNodeAction;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;
import com.demo.lesson26.mcp.node.McpNode;
import com.demo.lesson26.mcp.tool.McpClientToolCallbackProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.HashMap;@Configuration
@EnableConfigurationProperties({ McpNodeProperties.class })
public class McpGaphConfiguration {private static final Logger logger = LoggerFactory.getLogger(McpGaphConfiguration.class);@Autowiredprivate McpClientToolCallbackProvider mcpClientToolCallbackProvider;@Beanpublic StateGraph mcpGraph(ChatClient.Builder chatClientBuilder) throws GraphStateException {KeyStrategyFactory keyStrategyFactory = () -> {HashMap<String, KeyStrategy> keyStrategyHashMap = new HashMap<>();// 用户输入keyStrategyHashMap.put("query", new ReplaceStrategy());keyStrategyHashMap.put("mcpcontent", new ReplaceStrategy());return keyStrategyHashMap;};// 构建图StateGraph stateGraph = new StateGraph(keyStrategyFactory).addNode("mcp", AsyncNodeAction.node_async(new McpNode(chatClientBuilder, mcpClientToolCallbackProvider))).addEdge(StateGraph.START, "mcp").addEdge("mcp", StateGraph.END);// 添加 PlantUML 打印GraphRepresentation representation = stateGraph.getGraph(GraphRepresentation.Type.PLANTUML,"mcp flow");logger.info("\n=== mcp UML Flow ===");logger.info(representation.content());logger.info("==================================\n");return stateGraph;}
}
4)新建启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Lesson26MCPApplication {public static void main(String[] args) {SpringApplication.run(Lesson26MCPApplication.class, args);}}
1.3 演示效果
http://localhost:8080/graph/mcp
结语:本章演示了Graph中如何访问MCP服务,可见其架构的可扩展性,在Spring AI Alibaba中有一个com.alibaba.cloud.ai.graph.node.McpNode的MCP访问节点实现,但是该节点只是一个固定MCP访问,即需要传入方法和参数,并没有配置大模型。如果你构建的Graph中只是简单调用MCP服务,则可以直接使用com.alibaba.cloud.ai.graph.node.McpNode节点。前面通过几章对Graph框架进行了比较详细的讲解,这是因为在实际应用中,一个应用一般都是一个流程,而非一撮而就,所以使用Graph场景非常多。下一章将讲Spring AI Alibaba的nl2sql,这个是一个基于Graph构建的生成SQL的实际案例,你就可以见识到复杂的工作流。
Spring AI系列上一章:《Spring AI 系列之三十四 - Spring AI Alibaba-Graph框架之并行执行》
Spring AI系列下一章:《Spring AI 系列之三十六 - Spring AI Alibaba-nl2sql》