ruoyi-modules-api/ruoyi-chat-api/pom.xml
@@ -18,12 +18,44 @@ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0-M6</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <!-- 对è¯åºç¡æ¨¡å --> <dependencies> <dependency> <groupId>org.ruoyi</groupId> <artifactId>ruoyi-common-chat</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> </dependency> </dependencies> </project> ruoyi-modules/ruoyi-chat/pom.xml
@@ -114,6 +114,20 @@ <artifactId>ruoyi-system-api</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-core</artifactId> <version>1.0.0-M6</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama</artifactId> <version>1.0.0-M6</version> <scope>compile</scope> </dependency> </dependencies> </project> ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/IChatService.java
@@ -16,4 +16,11 @@ * @param chatRequest 请æ±å¯¹è±¡ */ SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter); /** * 客æ·ç«¯åéæ¶æ¯å°æå¡ç«¯ * @param chatRequest 请æ±å¯¹è±¡ */ SseEmitter mcpChat(ChatRequest chatRequest,SseEmitter emitter); } ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OllamaServiceImpl.java
@@ -6,8 +6,6 @@ import io.github.ollama4j.models.chat.OllamaChatRequestBuilder; import io.github.ollama4j.models.chat.OllamaChatRequestModel; import io.github.ollama4j.models.generate.OllamaStreamHandler; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.ruoyi.chat.service.chat.IChatService; import org.ruoyi.chat.util.SSEUtil; @@ -15,7 +13,16 @@ import org.ruoyi.common.chat.request.ChatRequest; import org.ruoyi.domain.vo.ChatModelVo; import org.ruoyi.service.IChatModelService; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.ollama.api.OllamaModel; import org.springframework.ai.ollama.api.OllamaOptions; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; @@ -32,6 +39,13 @@ @Autowired private IChatModelService chatModelService; @Autowired private ChatClient chatClient; @Autowired private ToolCallbackProvider tools; private final ChatMemory chatMemory = new InMemoryChatMemory(); @Override public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) { @@ -77,4 +91,50 @@ return emitter; } @Override public SseEmitter mcpChat(ChatRequest chatRequest, SseEmitter emitter) { List<Message> msgList = chatRequest.getMessages(); // æ·»å è®°å¿ for (int i = 0; i < msgList.size(); i++) { org.springframework.ai.chat.messages.Message springAiMessage = new UserMessage(msgList.get(i).getContent().toString()); chatMemory.add(String.valueOf(i),springAiMessage); } var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, chatRequest.getUserId(), 10); this.chatClient.prompt(chatRequest.getPrompt()) .advisors(messageChatMemoryAdvisor) .tools(tools) .options(OllamaOptions.builder() .model(OllamaModel.QWEN_2_5_7B) .temperature(0.4) .build()) .stream() .chatResponse() .subscribe( chatResponse -> { try { emitter.send(chatResponse, MediaType.APPLICATION_JSON); } catch (IOException e) { e.printStackTrace(); } }, error -> { try { emitter.completeWithError(error); } catch (Exception e) { e.printStackTrace(); } }, () -> { try { emitter.complete(); } catch (Exception e) { e.printStackTrace(); } } ); return emitter; } } ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/OpenAIServiceImpl.java
@@ -5,14 +5,29 @@ import org.ruoyi.chat.listener.SSEEventSourceListener; import org.ruoyi.chat.service.chat.IChatService; import org.ruoyi.common.chat.entity.chat.ChatCompletion; import org.ruoyi.common.chat.entity.chat.Message; import org.ruoyi.common.chat.openai.OpenAiStreamClient; import org.ruoyi.common.chat.request.ChatRequest; import org.ruoyi.domain.vo.ChatModelVo; import org.ruoyi.service.IChatModelService; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.openai.OpenAiChatOptions; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.List; /** * @author ageer */ @Service @Slf4j public class OpenAIServiceImpl implements IChatService { @@ -23,6 +38,13 @@ private ChatConfig chatConfig; @Autowired private OpenAiStreamClient openAiStreamClient; @Autowired private ChatClient chatClient; @Autowired private ToolCallbackProvider tools; private final ChatMemory chatMemory = new InMemoryChatMemory(); @Override public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) { @@ -47,4 +69,46 @@ return emitter; } @Override public SseEmitter mcpChat(ChatRequest chatRequest, SseEmitter emitter) { List<Message> msgList = chatRequest.getMessages(); // æ·»å è®°å¿ for (int i = 0; i < msgList.size(); i++) { org.springframework.ai.chat.messages.Message springAiMessage = new UserMessage(msgList.get(i).getContent().toString()); chatMemory.add(String.valueOf(i),springAiMessage); } var messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, chatRequest.getUserId(), 10); this.chatClient.prompt(chatRequest.getPrompt()) .advisors(messageChatMemoryAdvisor) .tools(tools) .options(OpenAiChatOptions.builder().model(chatRequest.getModel()).build()) .stream() .chatResponse() .subscribe( chatResponse -> { try { emitter.send(chatResponse, MediaType.APPLICATION_JSON); } catch (IOException e) { e.printStackTrace(); } }, error -> { try { emitter.completeWithError(error); } catch (Exception e) { e.printStackTrace(); } }, () -> { try { emitter.complete(); } catch (Exception e) { e.printStackTrace(); } } ); return emitter; } } ruoyi-modules/ruoyi-mcp-server/pom.xml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,63 @@ <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.ruoyi</groupId> <artifactId>ruoyi-modules</artifactId> <version>${revision}</version> <relativePath>../pom.xml</relativePath> </parent> <artifactId>ruoyi-mcp-server</artifactId> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0-M6</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp</artifactId> </dependency> </dependencies> </project> ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/McpServerApplication.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,16 @@ package org.ruoyi.mcp; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author ageer */ @SpringBootApplication public class McpServerApplication { public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } } ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/config/McpServerConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,100 @@ package org.ruoyi.mcp.config; import org.ruoyi.mcp.service.McpCustomService; import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.server.McpServerFeatures; import io.modelcontextprotocol.spec.McpSchema; import org.springframework.ai.tool.ToolCallbackProvider; import org.springframework.ai.tool.method.MethodToolCallbackProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; import java.util.Map; import java.util.function.Consumer; /** * @author ageer */ @Configuration @EnableWebMvc public class McpServerConfig implements WebMvcConfigurer { @Bean public ToolCallbackProvider openLibraryTools(McpCustomService mcpService) { return MethodToolCallbackProvider.builder().toolObjects(mcpService).build(); } @Bean public List<McpServerFeatures.SyncResourceRegistration> resourceRegistrations() { // Create a resource registration for system information var systemInfoResource = new McpSchema.Resource( "system://info", "System Information", "Provides basic system information including Java version, OS, etc.", "application/json", null ); var resourceRegistration = new McpServerFeatures.SyncResourceRegistration(systemInfoResource, (request) -> { try { var systemInfo = Map.of( "javaVersion", System.getProperty("java.version"), "osName", System.getProperty("os.name"), "osVersion", System.getProperty("os.version"), "osArch", System.getProperty("os.arch"), "processors", Runtime.getRuntime().availableProcessors(), "timestamp", System.currentTimeMillis()); String jsonContent = new ObjectMapper().writeValueAsString(systemInfo); return new McpSchema.ReadResourceResult( List.of(new McpSchema.TextResourceContents(request.uri(), "application/json", jsonContent))); } catch (Exception e) { throw new RuntimeException("Failed to generate system info", e); } }); return List.of(resourceRegistration); } @Bean public List<McpServerFeatures.SyncPromptRegistration> promptRegistrations() { var prompt = new McpSchema.Prompt("greeting", "A friendly greeting prompt", List.of(new McpSchema.PromptArgument("name", "The name to greet", true))); var promptRegistration = new McpServerFeatures.SyncPromptRegistration(prompt, getPromptRequest -> { String nameArgument = (String) getPromptRequest.arguments().get("name"); if (nameArgument == null) { nameArgument = "friend"; } var userMessage = new McpSchema.PromptMessage(McpSchema.Role.USER, new McpSchema.TextContent("Hello " + nameArgument + "! How can I assist you today?")); return new McpSchema.GetPromptResult("A personalized greeting message", List.of(userMessage)); }); return List.of(promptRegistration); } @Bean public Consumer<List<McpSchema.Root>> rootsChangeConsumer() { return roots -> { System.out.println("rootsChange"); }; } } ruoyi-modules/ruoyi-mcp-server/src/main/java/org/ruoyi/mcp/service/McpCustomService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,24 @@ package org.ruoyi.mcp.service; import org.springframework.ai.tool.annotation.Tool; import org.springframework.stereotype.Service; import java.util.List; /** * @author ageer */ @Service public class McpCustomService { public record Book(List<String> isbn, String title, List<String> authorName) { } @Tool(description = "Get list of Books by title") public List<Book> getBooks(String title) { // è¿éæ¨¡ææ¥è¯¢DBæä½ return List.of(new Book(List.of("ISBN-888"), "SpringAIæç¨", List.of("çç«å©æåç书"))); } } ruoyi-modules/ruoyi-mcp-server/src/main/resources/application.yaml
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,10 @@ spring: application: name: mcp-server ai: mcp: server: name: webmvc-mcp-server version: 1.0.0 type: SYNC sse-message-endpoint: /mcp/messages ruoyi-modules/ruoyi-system/src/main/resources/application.yml
@@ -318,5 +318,22 @@ token: '' aesKey: '' # spring aié ç½® spring: ai: openai: api-key: sk-xx base-url: https://api.pandarobot.chat/ mcp: client: enabled: true name: call-mcp-server sse: connections: server1: url: http://127.0.0.1:8080 ollama: init: pull-model-strategy: always timeout: 60s max-retries: 1