From 37a8b7dad32dbbad4e28ecd15dabd3147be03665 Mon Sep 17 00:00:00 2001 From: ageerle <ageerle@163.com> Date: 星期五, 11 四月 2025 15:47:05 +0800 Subject: [PATCH] feat: 对话模块重构 --- ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java | 174 +++++++++++++++------------------------------------------ 1 files changed, 47 insertions(+), 127 deletions(-) diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java index 6cf19b4..17a5bd4 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java @@ -6,22 +6,17 @@ import com.google.protobuf.ServiceException; import com.zhipu.oapi.ClientV4; import com.zhipu.oapi.service.v4.tools.*; -import io.github.ollama4j.OllamaAPI; -import io.github.ollama4j.models.chat.OllamaChatMessage; -import io.github.ollama4j.models.chat.OllamaChatMessageRole; -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 okhttp3.*; -import org.ruoyi.chat.config.ChatConfig; -import org.ruoyi.chat.listener.SSEEventSourceListener; import org.ruoyi.chat.service.chat.IChatCostService; +import org.ruoyi.chat.service.chat.IChatService; import org.ruoyi.chat.service.chat.ISseService; +import org.ruoyi.chat.factory.SseServiceFactory; import org.ruoyi.chat.util.IpUtil; +import org.ruoyi.chat.util.SSEUtil; import org.ruoyi.common.chat.request.ChatRequest; import org.ruoyi.common.chat.entity.Tts.TextToSpeech; import org.ruoyi.common.chat.entity.chat.ChatCompletion; @@ -32,15 +27,14 @@ import org.ruoyi.common.chat.entity.whisper.WhisperResponse; import org.ruoyi.common.chat.openai.OpenAiStreamClient; import org.ruoyi.common.core.service.ConfigService; +import org.ruoyi.common.core.utils.DateUtils; import org.ruoyi.common.core.utils.StringUtils; import org.ruoyi.common.core.utils.file.FileUtils; import org.ruoyi.common.core.utils.file.MimeTypeUtils; import org.ruoyi.common.redis.utils.RedisUtils; -import org.ruoyi.domain.vo.ChatModelVo; import org.ruoyi.service.EmbeddingService; -import org.ruoyi.service.IChatModelService; import org.ruoyi.service.VectorStoreService; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; @@ -60,7 +54,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -72,10 +65,6 @@ private final OpenAiStreamClient openAiStreamClient; - private final ChatConfig chatConfig; - - private final IChatModelService chatModelService; - private final EmbeddingService embeddingService; private final VectorStoreService vectorStore; @@ -84,6 +73,8 @@ private final IChatCostService chatCostService; + private final SseServiceFactory sseServiceFactory; + private static final String requestIdTemplate = "company-%d"; private static final ObjectMapper mapper = new ObjectMapper(); @@ -91,78 +82,64 @@ @Override public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) { SseEmitter sseEmitter = new SseEmitter(0L); - SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter); - // 鑾峰彇瀵硅瘽娑堟伅鍒楄〃 - List<Message> messages = chatRequest.getMessages(); try { - // 鏌ヨ妯″瀷淇℃伅 - ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); - - OpenAiStreamClient openAiModelStreamClient; - if(chatModelVo!=null){ - // 寤鸿姹傚鎴风 - openAiModelStreamClient = chatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey()); - // 璁剧疆榛樿鎻愮ず璇� - chatRequest.setSysPrompt(chatModelVo.getSystemPrompt()); - }else { - // 浣跨敤榛樿瀹㈡埛绔� - openAiModelStreamClient = openAiStreamClient; - } // 鏋勫缓娑堟伅鍒楄〃澧炲姞鑱旂綉銆佺煡璇嗗簱绛夊唴瀹� buildChatMessageList(chatRequest); - // 鏍规嵁妯″瀷鍚嶇О鍓嶇紑璋冪敤涓嶅悓鐨勫鐞嗛�昏緫 - switchModelAndHandle(chatRequest); - + switchModelAndHandle(chatRequest,sseEmitter); // 鏈櫥褰曠敤鎴烽檺鍒跺璇濇鏁� - if (!StpUtil.isLogin()) { - String clientIp = IpUtil.getClientIp(request); - // 璁垮姣忓ぉ榛樿鍙兘瀵硅瘽5娆� - int timeWindowInSeconds = 5; - String redisKey = "clientIp:" + clientIp; - int count = 0; - if (RedisUtils.getCacheObject(redisKey) == null) { - // 缂撳瓨鏈夋晥鏃堕棿1澶� - RedisUtils.setCacheObject(redisKey, count, Duration.ofSeconds(86400)); - }else { - count = RedisUtils.getCacheObject(redisKey); - if (count >= timeWindowInSeconds) { - throw new ServiceException("褰撴棩鍏嶈垂娆℃暟宸茬敤瀹�"); - } - count++; - RedisUtils.setCacheObject(redisKey, count); - } - } - - ChatCompletion completion = ChatCompletion - .builder() - .messages(messages) - .model(chatRequest.getModel()) - .stream(chatRequest.getStream()) - .build(); - openAiModelStreamClient.streamChatCompletion(completion, openAIEventSourceListener); - + checkUnauthenticatedUserChatLimit(request); // 淇濆瓨娑堟伅璁板綍 骞舵墸闄よ垂鐢� chatCostService.deductToken(chatRequest); } catch (Exception e) { String message = e.getMessage(); - sendErrorEvent(sseEmitter, message); + SSEUtil.sendErrorEvent(sseEmitter, message); return sseEmitter; } return sseEmitter; } /** + * 妫�鏌ユ湭鐧诲綍鐢ㄦ埛鏄惁瓒呰繃褰撴棩瀵硅瘽娆℃暟闄愬埗 + * + * @param request 褰撳墠璇锋眰 + * @throws ServiceException 濡傛灉褰撴棩鍏嶈垂娆℃暟宸茬敤瀹� + */ + public void checkUnauthenticatedUserChatLimit(HttpServletRequest request) throws ServiceException { + // 鏈櫥褰曠敤鎴烽檺鍒跺璇濇鏁� + if (!StpUtil.isLogin()) { + String clientIp = IpUtil.getClientIp(request); + // 璁垮姣忓ぉ榛樿鍙兘瀵硅瘽5娆� + int timeWindowInSeconds = 5; + String redisKey = "clientIp:" + clientIp; + int count = 0; + // 妫�鏌edis涓殑瀵硅瘽娆℃暟 + if (RedisUtils.getCacheObject(redisKey) == null) { + // 缂撳瓨鏈夋晥鏃堕棿1澶� + RedisUtils.setCacheObject(redisKey, count, Duration.ofSeconds(86400)); + } else { + count = RedisUtils.getCacheObject(redisKey); + if (count >= timeWindowInSeconds) { + throw new ServiceException("褰撴棩鍏嶈垂娆℃暟宸茬敤瀹�"); + } + count++; + RedisUtils.setCacheObject(redisKey, count); + } + } + } + + /** * 鏍规嵁妯″瀷鍚嶇О鍓嶇紑璋冪敤涓嶅悓鐨勫鐞嗛�昏緫 */ - private void switchModelAndHandle(ChatRequest chatRequest) { + private void switchModelAndHandle(ChatRequest chatRequest,SseEmitter emitter) { String model = chatRequest.getModel(); // 濡傛灉妯″瀷鍚嶇О浠llama寮�澶达紝鍒欒皟鐢╫llama涓儴缃茬殑鏈湴妯″瀷 if (model.startsWith("ollama-")) { String[] parts = chatRequest.getModel().split("ollama-", 2); // 闄愬埗鍒嗗壊娆℃暟涓�2 if (parts.length > 1) { chatRequest.setModel(parts[1]); - ollamaChat(chatRequest); + IChatService chatService = sseServiceFactory.getSseService("ollama"); + chatService.chat(chatRequest,emitter); } else { throw new IllegalArgumentException("Invalid ollama model name: " + chatRequest.getModel()); } @@ -177,8 +154,13 @@ private void buildChatMessageList(ChatRequest chatRequest){ // 鑾峰彇瀵硅瘽娑堟伅鍒楄〃 List<Message> messages = chatRequest.getMessages(); + String sysPrompt = chatRequest.getSysPrompt(); + if(StringUtils.isEmpty(sysPrompt)){ + sysPrompt ="浣犳槸涓�涓敱RuoYI-AI寮�鍙戠殑浜哄伐鏅鸿兘鍔╂墜锛屽悕瀛楀彨鐔婄尗鍔╂墜銆備綘鎿呴暱涓嫳鏂囧璇濓紝鑳藉鐞嗚В骞跺鐞嗗悇绉嶉棶棰橈紝鎻愪緵瀹夊叏銆佹湁甯姪銆佸噯纭殑鍥炵瓟銆�" + + "褰撳墠鏃堕棿锛�"+ DateUtils.getDate(); + } // 璁剧疆绯荤粺榛樿鎻愮ず璇� - Message sysMessage = Message.builder().content(chatRequest.getSysPrompt()).role(Message.Role.SYSTEM).build(); + Message sysMessage = Message.builder().content(sysPrompt).role(Message.Role.SYSTEM).build(); messages.add(0,sysMessage); // 鏌ヨ鍚戦噺搴撶浉鍏充俊鎭姞鍏ュ埌涓婁笅鏂� @@ -216,23 +198,6 @@ } } - /** - * 鍙戦�丼SE閿欒浜嬩欢鐨勫皝瑁呮柟娉� - * - * @param sseEmitter - * @param errorMessage - */ - private void sendErrorEvent(SseEmitter sseEmitter, String errorMessage) { - SseEmitter.SseEventBuilder event = SseEmitter.event() - .name("error") - .data(errorMessage); - try { - sseEmitter.send(event); - } catch (IOException e) { - log.error("SSE鍙戦�佸け璐�: {}", e.getMessage()); - } - sseEmitter.complete(); - } /** * 鏂囧瓧杞闊� @@ -323,51 +288,6 @@ return file; } - @Override - public SseEmitter ollamaChat(ChatRequest chatRequest) { - - ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); - final SseEmitter emitter = new SseEmitter(); - String host = chatModelVo.getApiHost(); - List<Message> msgList = chatRequest.getMessages(); - - List<OllamaChatMessage> messages = new ArrayList<>(); - for (Message message : msgList) { - OllamaChatMessage ollamaChatMessage = new OllamaChatMessage(); - ollamaChatMessage.setRole(OllamaChatMessageRole.USER); - ollamaChatMessage.setContent(message.getContent().toString()); - messages.add(ollamaChatMessage); - } - OllamaAPI api = new OllamaAPI(host); - api.setRequestTimeoutSeconds(100); - OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatRequest.getModel()); - - OllamaChatRequestModel requestModel = builder - .withMessages(messages) - .build(); - - // 寮傛鎵ц OllAma API 璋冪敤 - CompletableFuture.runAsync(() -> { - try { - StringBuilder response = new StringBuilder(); - OllamaStreamHandler streamHandler = (s) -> { - String substr = s.substring(response.length()); - response.append(substr); - System.out.println(substr); - try { - emitter.send(substr); - } catch (IOException e) { - sendErrorEvent(emitter, e.getMessage()); - } - }; - api.chat(requestModel, streamHandler); - emitter.complete(); - } catch (Exception e) { - sendErrorEvent(emitter, e.getMessage()); - } - }); - return emitter; - } @Override public String wxCpChat(String prompt) { -- Gitblit v1.9.3