diff --git a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java index 679f4166..8363c957 100644 --- a/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java +++ b/ruoyi-admin/src/main/java/org/ruoyi/RuoYiAIApplication.java @@ -16,7 +16,7 @@ public class RuoYiAIApplication { SpringApplication application = new SpringApplication(RuoYiAIApplication.class); application.setApplicationStartup(new BufferingApplicationStartup(2048)); application.run(args); - System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)゙"); + System.out.println("(♥◠‿◠)ノ゙ RuoYi-AI启动成功 ლ(´ڡ`ლ)゙"); } } diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index e3918042..8bcc34bb 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -11,6 +11,17 @@ spring.boot.admin.client: username: @monitor.username@ password: @monitor.password@ +--- # mcp配置信息 +mcp: + sse: + enabled: false + url: http://localhost:8085/sse + +--- # 上传文件地址 +sys: + upload: + path: D:\\DownLoad + --- # snail-job 配置 snail-job: enabled: false @@ -47,9 +58,9 @@ spring: driverClassName: com.mysql.cj.jdbc.Driver # jdbc 所有参数配置参考 https://lionli.blog.csdn.net/article/details/122018562 # rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题) - url: jdbc:mysql://127.0.0.1:3306/ruoyi-ai-v3?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true - username: ruoyi-ai-v3 - password: ruoyi-ai-v3 + url: jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + username: root + password: 123456 hikari: # 最大连接池数量 maxPoolSize: 20 @@ -87,7 +98,7 @@ spring.data: # 数据库索引 database: 0 # redis 密码必须配置 - password: 123456 +# password: 123456 # 连接超时时间 timeout: 10s # 是否开启ssl diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 186bb459..2a17ad30 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -124,6 +124,7 @@ security: - /*/api-docs - /*/api-docs/** - /warm-flow-ui/config + - /workflow/run # 多租户配置 tenant: diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatService.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatService.java index 1aeb2456..01a02567 100644 --- a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatService.java +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/Service/IChatService.java @@ -1,8 +1,7 @@ package org.ruoyi.common.chat.Service; -import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; -import org.ruoyi.common.chat.domain.dto.request.ChatRequest; -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import jakarta.validation.Valid; +import org.ruoyi.common.chat.domain.entity.chat.ChatContext; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; /** @@ -13,12 +12,7 @@ public interface IChatService { /** * 客户端发送对话消息到服务端 */ - SseEmitter chat(ChatModelVo chatModelVo, ChatRequest chatRequest, SseEmitter emitter, Long userId, String tokenValue); - - /** - * 工作流专用对话 - */ - SseEmitter chat(ChatModelVo chatModelVo, ChatRequest chatRequest,SseEmitter emitter,Long userId,String tokenValue, StreamingChatResponseHandler handler); + SseEmitter chat(@Valid ChatContext chatContext); /** * 获取服务提供商名称 diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/chat/ChatContext.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/chat/ChatContext.java new file mode 100644 index 00000000..13a5b4b9 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/domain/entity/chat/ChatContext.java @@ -0,0 +1,57 @@ +package org.ruoyi.common.chat.domain.entity.chat; + +import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +/** + * 聊天对话上下文对象 + * + * @author zengxb + * @date 2026-02-14 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Builder +public class ChatContext { + + /** + * 模型管理视图对象 + */ + @NotNull(message = "模型管理视图对象不能为空") + private ChatModelVo chatModelVo; + + /** + * 对话请求对象 + */ + @NotNull(message = "对话请求对象不能为空") + private ChatRequest chatRequest; + + /** + * SSe连接对象 + */ + @NotNull(message = "SSe连接对象不能为空") + private SseEmitter emitter; + + /** + * 用户ID + */ + @NotNull(message = "用户ID不能为空") + private Long userId; + + /** + * Token + */ + @NotNull(message = "Token不能为空") + private String tokenValue; + + /** + * 响应处理器 + */ + private StreamingChatResponseHandler handler; +} diff --git a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java index e57b324c..176ed7d6 100644 --- a/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java +++ b/ruoyi-modules/ruoyi-aiflow/src/main/java/org/ruoyi/workflow/workflow/WorkflowUtil.java @@ -14,6 +14,7 @@ import org.bsc.langgraph4j.state.AgentState; import org.ruoyi.common.chat.Service.IChatModelService; import org.ruoyi.common.chat.Service.IChatService; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.entity.chat.ChatContext; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.chat.factory.ChatServiceFactory; import org.ruoyi.workflow.base.NodeInputConfigTypeHandler; @@ -135,9 +136,11 @@ public class WorkflowUtil { .startingState(state) .build(); + // 获取用户信息和Token以及SSe连接对象(对话接口需要使用) Long userId = wfState.getUserId(); String tokenValue = wfState.getTokenValue(); SseEmitter sseEmitter = wfState.getSseEmitter(); + StreamingChatResponseHandler handler = streamingGenerator.handler(); // 构建 ruoyi-ai 的 ChatRequest List chatMessages = new ArrayList<>(); @@ -151,9 +154,18 @@ public class WorkflowUtil { chatRequest.setModel(modelName); chatRequest.setChatMessages(chatMessages); + //构建聊天对话上下文参数 + ChatContext chatContext = ChatContext.builder() + .chatModelVo(chatModelVo) + .chatRequest(chatRequest) + .emitter(sseEmitter) + .userId(userId) + .tokenValue(tokenValue) + .handler(handler) + .build(); + // 使用工作流专用方法 - StreamingChatResponseHandler handler = streamingGenerator.handler(); - chatService.chat(chatModelVo, chatRequest, sseEmitter, userId, tokenValue, handler); + chatService.chat(chatContext); wfState.getNodeToStreamingGenerator().put(node.getUuid(), streamingGenerator); } diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java index 9664c9ae..75894c9a 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/AbstractStreamingChatService.java @@ -26,6 +26,7 @@ import org.ruoyi.agent.tool.QueryAllTablesTool; import org.ruoyi.agent.tool.QueryTableSchemaTool; import org.ruoyi.common.chat.Service.IChatService; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.entity.chat.ChatContext; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.core.utils.ObjectUtils; import org.ruoyi.common.core.utils.SpringUtils; @@ -36,6 +37,7 @@ import org.ruoyi.enums.RoleType; import org.ruoyi.service.chat.IChatMessageService; import org.ruoyi.service.chat.impl.memory.PersistentChatMemoryStore; import org.springframework.util.CollectionUtils; +import org.springframework.validation.annotation.Validated; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.util.*; @@ -55,6 +57,7 @@ import java.util.concurrent.ConcurrentHashMap; * @date 2025/12/13 */ @Slf4j +@Validated public abstract class AbstractStreamingChatService implements IChatService { /** @@ -73,29 +76,23 @@ public abstract class AbstractStreamingChatService implements IChatService { */ private static final Map memoryCache = new ConcurrentHashMap<>(); - - - /** * 定义聊天流程骨架 */ @Override - public SseEmitter chat(ChatModelVo chatModelVo, ChatRequest chatRequest, SseEmitter emitter, Long userId, String tokenValue) { - return executeChat(chatModelVo, chatRequest, emitter, userId, tokenValue, null); - } - - /** - * 定义聊天流程骨架(包含流式回调结构) - */ - @Override - public SseEmitter chat(ChatModelVo chatModelVo, ChatRequest chatRequest, SseEmitter emitter, Long userId, String tokenValue, StreamingChatResponseHandler handler) { - return executeChat(chatModelVo, chatRequest, emitter, userId, tokenValue, handler); - } - - /** - * 定义聊天流程骨架 - */ - public SseEmitter executeChat(ChatModelVo chatModelVo, ChatRequest chatRequest, SseEmitter emitter, Long userId, String tokenValue, StreamingChatResponseHandler handler) { + public SseEmitter chat(ChatContext chatContext) { + // 获取模型管理视图对象 + ChatModelVo chatModelVo = chatContext.getChatModelVo(); + // 获取对话请求对象 + ChatRequest chatRequest = chatContext.getChatRequest(); + // 获取SSe连接对象 + SseEmitter emitter = chatContext.getEmitter(); + // 获取用户ID + Long userId = chatContext.getUserId(); + // 获取Token + String tokenValue = chatContext.getTokenValue(); + // 获取响应处理器 + StreamingChatResponseHandler handler = chatContext.getHandler(); try { String content = Optional.ofNullable(chatRequest.getMessages()).filter(messages -> !messages.isEmpty()) // 对话逻辑:从 messages 筛选第一个元素 @@ -103,11 +100,11 @@ public abstract class AbstractStreamingChatService implements IChatService { .filter(StringUtils::isNotBlank) // 工作流逻辑:从 chatMessages 筛选 UserMessage 的文本 .orElseGet(() -> Optional.ofNullable(chatRequest.getChatMessages()).orElse(List.of()).stream() - .filter(message -> message instanceof UserMessage um) - .map(message -> ((UserMessage) message).singleText()) - .filter(StringUtils::isNotBlank) - .findFirst() - .orElse("")); + .filter(message -> message instanceof UserMessage um) + .map(message -> ((UserMessage) message).singleText()) + .filter(StringUtils::isNotBlank) + .findFirst() + .orElse("")); // 保存用户消息 saveChatMessage(chatRequest, userId, content, RoleType.USER.getName(), chatModelVo); diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java index 4e9a9f7c..6b63e254 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/ChatServiceFacade.java @@ -6,6 +6,7 @@ import org.ruoyi.common.chat.Service.IChatModelService; import org.ruoyi.common.chat.Service.IChatService; import org.ruoyi.common.chat.domain.dto.ChatMessageDTO; import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.entity.chat.ChatContext; import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; import org.ruoyi.common.chat.factory.ChatServiceFactory; import org.ruoyi.common.satoken.utils.LoginHelper; @@ -71,7 +72,16 @@ public class ChatServiceFacade { Long userId = LoginHelper.getUserId(); String tokenValue = StpUtil.getTokenValue(); SseEmitter emitter = sseEmitterManager.connect(userId, tokenValue); - return chatService.chat(chatModelVo, chatRequest,emitter,userId, tokenValue); + + // 5. 创建对话上下文对象 + ChatContext chatContext = ChatContext.builder() + .chatModelVo(chatModelVo) + .chatRequest(chatRequest) + .emitter(emitter) + .userId(userId) + .tokenValue(tokenValue) + .build(); + return chatService.chat(chatContext); } /** diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java index a8966dee..ab583520 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/service/chat/impl/provider/QianWenChatServiceImpl.java @@ -1,18 +1,6 @@ package org.ruoyi.service.chat.impl.provider; -import java.util.ArrayList; -import java.util.List; - -import org.ruoyi.agent.EchartsAgent; -import org.ruoyi.common.chat.domain.dto.request.ChatRequest; -import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; -import org.ruoyi.config.McpSseConfig; -import org.ruoyi.enums.ChatModeType; -import org.ruoyi.service.chat.impl.AbstractStreamingChatService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; - +import dev.langchain4j.agent.tool.ToolSpecification; import dev.langchain4j.agentic.AgenticServices; import dev.langchain4j.agentic.supervisor.SupervisorAgent; import dev.langchain4j.agentic.supervisor.SupervisorResponseStrategy; @@ -25,11 +13,23 @@ import dev.langchain4j.mcp.McpToolProvider; import dev.langchain4j.mcp.client.DefaultMcpClient; import dev.langchain4j.mcp.client.McpClient; import dev.langchain4j.mcp.client.transport.McpTransport; -import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport; +import dev.langchain4j.mcp.client.transport.http.StreamableHttpMcpTransport; import dev.langchain4j.model.chat.StreamingChatModel; import dev.langchain4j.model.chat.response.StreamingChatResponseHandler; import dev.langchain4j.service.tool.ToolProvider; import lombok.extern.slf4j.Slf4j; +import org.ruoyi.agent.McpAgent; +import org.ruoyi.config.McpSseConfig; +import org.ruoyi.enums.ChatModeType; +import org.ruoyi.service.chat.impl.AbstractStreamingChatService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.ruoyi.common.chat.domain.dto.request.ChatRequest; +import org.ruoyi.common.chat.domain.vo.chat.ChatModelVo; + +import java.util.ArrayList; +import java.util.List; /** * qianWenAI服务调用 @@ -41,6 +41,13 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class QianWenChatServiceImpl extends AbstractStreamingChatService { + @Autowired + private McpSseConfig mcpSseConfig; + + /** + * 千问开发者默认地址 + */ + private static final String QWEN_API_HOST = "https://dashscope.aliyuncs.com/api/v1"; // 添加文档解析的前缀字段 private static final String UPLOAD_FILE_API_PREFIX = "fileid"; @@ -101,45 +108,49 @@ public class QianWenChatServiceImpl extends AbstractStreamingChatService { * @return 返回LLM信息 */ protected String doAgent(String userMessage,ChatModelVo chatModelVo) { - + // 判断是否开启MCP服务 + if (!mcpSseConfig.isEnabled()) { + return ""; + } + + // 步骤1:根据SSE对外暴露端点连接 + McpTransport httpMcpTransport = new StreamableHttpMcpTransport.Builder(). + url(mcpSseConfig.getUrl()). + logRequests(true). + build(); + + // 步骤2:开启客户端连接 + McpClient mcpClient = new DefaultMcpClient.Builder() + .transport(httpMcpTransport) + .build(); + + // 获取所有mcp工具 + List toolSpecifications = mcpClient.listTools(); + System.out.println(toolSpecifications); + + // 步骤3:将mcp对象包装 + ToolProvider toolProvider = McpToolProvider.builder() + .mcpClients(List.of(mcpClient)) + .build(); + // 步骤4:加载LLM模型对话 QwenChatModel qwenChatModel = QwenChatModel.builder() + .baseUrl(QWEN_API_HOST) .apiKey(chatModelVo.getApiKey()) .modelName(chatModelVo.getModelName()) .build(); - McpTransport echart = new StdioMcpTransport.Builder() - .command(List.of("uv", - "--directory", - "/Users/zhangmingming/data/coder/LLM/MCP/cicd-pipeline-example/", - "run", - "text2sql-mcp" - )) - .logEvents(true) - .build(); - // 步骤2: 创建MCP客户端 - McpClient echartClient = new DefaultMcpClient.Builder() - .transport(echart) - .build(); - - // 步骤3: 配置工具提供者 - ToolProvider echartToolProvider = McpToolProvider.builder() - .mcpClients(List.of( - echartClient)) - .build(); - - EchartsAgent chartGenerationAgent = AgenticServices.agentBuilder( - EchartsAgent.class) + // 步骤5:将MCP对象由智能体Agent管控 + McpAgent mcpAgent = AgenticServices.agentBuilder(McpAgent.class) .chatModel(qwenChatModel) - .toolProvider(echartToolProvider) + .toolProvider(toolProvider) .build(); - // 步骤6:将所有MCP对象由超级智能体管控 SupervisorAgent supervisor = AgenticServices .supervisorBuilder() .chatModel(qwenChatModel) - .subAgents(chartGenerationAgent) + .subAgents(mcpAgent) .responseStrategy(SupervisorResponseStrategy.LAST) .build();