From 250909914676e295b4af615164b508ea1fc84835 Mon Sep 17 00:00:00 2001
From: ageerle <ageerle@163.com>
Date: 星期四, 10 四月 2025 17:25:23 +0800
Subject: [PATCH] feat: 重构模块

---
 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/SseServiceImpl.java |  140 ++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 126 insertions(+), 14 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 2e3e294..df499ee 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
@@ -1,8 +1,11 @@
 package org.ruoyi.chat.service.chat.impl;
 
 import cn.dev33.satoken.stp.StpUtil;
-import cn.hutool.extra.servlet.ServletUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import com.fasterxml.jackson.databind.ObjectMapper;
 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;
@@ -13,11 +16,13 @@
 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.ISseService;
-import org.ruoyi.common.chat.config.ChatConfig;
-import org.ruoyi.common.chat.domain.request.ChatRequest;
+import org.ruoyi.chat.util.IpUtil;
+import org.ruoyi.common.chat.request.ChatRequest;
 import org.ruoyi.common.chat.entity.Tts.TextToSpeech;
 import org.ruoyi.common.chat.entity.chat.ChatCompletion;
 import org.ruoyi.common.chat.entity.chat.ChatCompletionResponse;
@@ -26,12 +31,17 @@
 import org.ruoyi.common.chat.entity.files.UploadFileResponse;
 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.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;
 import org.springframework.http.MediaType;
@@ -48,8 +58,12 @@
 import java.nio.file.Path;
 import java.time.Duration;
 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;
 
 @Service
 @Slf4j
@@ -62,33 +76,80 @@
 
     private final IChatModelService chatModelService;
 
+    private final EmbeddingService embeddingService;
+
+    private final VectorStoreService vectorStore;
+
+    private final ConfigService configService;
+
+    private final IChatCostService chatCostService;
+
+    private static final String requestIdTemplate = "mycompany-%d";
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
     @Override
     public SseEmitter sseChat(ChatRequest chatRequest, HttpServletRequest request) {
         SseEmitter sseEmitter = new SseEmitter(0L);
         SSEEventSourceListener openAIEventSourceListener = new SSEEventSourceListener(sseEmitter);
         // 鑾峰彇瀵硅瘽娑堟伅鍒楄〃
         List<Message> messages = chatRequest.getMessages();
+        // 鐢ㄦ埛瀵硅瘽鍐呭
+        String chatString = null;
         try {
             if (StpUtil.isLogin()) {
                 // 閫氳繃妯″瀷鍚嶇О鏌ヨ妯″瀷淇℃伅
                 ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
                 // 鏋勫缓api璇锋眰瀹㈡埛绔�
                 openAiStreamClient = chatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey());
+                // 璁剧疆榛樿鎻愮ず璇�
+                Message sysMessage = Message.builder().content(chatModelVo.getSystemPrompt()).role(Message.Role.SYSTEM).build();
+                messages.add(0,sysMessage);
 
-                // 妯″瀷璁剧疆榛樿鎻愮ず璇�
+                // 鏌ヨ鍚戦噺搴撶浉鍏充俊鎭姞鍏ュ埌涓婁笅鏂�
+                if(chatRequest.getKid()!=null){
+                    List<Message> knMessages = new ArrayList<>();
+                    String content = messages.get(messages.size() - 1).getContent().toString();
+                    List<String> nearestList;
+                    List<Double> queryVector = embeddingService.getQueryVector(content, chatRequest.getKid());
+                    nearestList = vectorStore.nearest(queryVector, chatRequest.getKid());
+                    for (String prompt : nearestList) {
+                        Message userMessage = Message.builder().content(prompt).role(Message.Role.USER).build();
+                        knMessages.add(userMessage);
+                    }
+                    Message userMessage = Message.builder().content(content + (!nearestList.isEmpty() ? "\n\n娉ㄦ剰锛氬洖绛旈棶棰樻椂锛岄』涓ユ牸鏍规嵁鎴戠粰浣犵殑绯荤粺涓婁笅鏂囧唴瀹瑰師鏂囪繘琛屽洖绛旓紝璇蜂笉瑕佽嚜宸卞彂鎸�,鍥炵瓟鏃朵繚鎸佸師鏉ユ枃鏈殑娈佃惤灞傜骇" : "")).role(Message.Role.USER).build();
+                    knMessages.add(userMessage);
+                    messages.addAll(knMessages);
+                }
 
-                // 鏄惁寮�鍚仈缃戞煡璇�
+                // 鑾峰彇鐢ㄦ埛瀵硅瘽淇℃伅
+                Object content = messages.get(messages.size() - 1).getContent();
+                if (content instanceof List<?> listContent) {
+                    if (CollectionUtil.isNotEmpty(listContent)) {
+                        chatString = listContent.get(0).toString();
+                    }
+                } else if (content instanceof String) {
+                    chatString = (String) content;
+                }
+
+                // 鍔犺浇鑱旂綉淇℃伅
+                if(chatRequest.getSearch()){
+                    Message message = Message.builder().role(Message.Role.ASSISTANT).content("鑱旂綉淇℃伅:"+webSearch(chatString)).build();
+                    messages.add(message);
+                }
             }else {
-                // 鏈櫥褰曠敤鎴烽檺鍒跺璇濇鏁�,榛樿5娆�
-                String clientIp = ServletUtil.getClientIP((javax.servlet.http.HttpServletRequest) request,"X-Forwarded-For");
+                // 鏈櫥褰曠敤鎴烽檺鍒跺璇濇鏁�
+                String clientIp = IpUtil.getClientIp(request);
 
+                // 璁垮姣忓ぉ榛樿鍙兘瀵硅瘽5娆�
                 int timeWindowInSeconds = 5;
 
-                String redisKey = "visitor:" + clientIp;
+                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);
@@ -104,13 +165,11 @@
                     .builder()
                     .messages(messages)
                     .model(chatRequest.getModel())
-                    .temperature(chatRequest.getTemperature())
-                    .topP(chatRequest.getTop_p())
-                    .stream(true)
+                    .stream(chatRequest.getStream())
                     .build();
             openAiStreamClient.streamChatCompletion(completion, openAIEventSourceListener);
             // 淇濆瓨娑堟伅璁板綍 骞舵墸闄よ垂鐢�
-
+            chatCostService.deductToken(chatRequest);
         } catch (Exception e) {
             String message = e.getMessage();
             sendErrorEvent(sseEmitter, message);
@@ -147,7 +206,6 @@
         if (body != null) {
             // 灏哛esponseBody杞崲涓篒nputStreamResource
             InputStreamResource resource = new InputStreamResource(body.byteStream());
-
             // 鍒涘缓骞惰繑鍥濺esponseEntity
             return ResponseEntity.ok()
                 .contentType(MediaType.parseMediaType("audio/mpeg"))
@@ -289,4 +347,58 @@
         ChatCompletionResponse chatCompletionResponse = openAiStreamClient.chatCompletion(chatCompletion);
         return chatCompletionResponse.getChoices().get(0).getMessage().getContent().toString();
     }
+
+    public String webSearch (String prompt) {
+        String zhipuValue = configService.getConfigValue("zhipu", "key");
+        if(StringUtils.isEmpty(zhipuValue)){
+            throw new IllegalStateException("zhipu config value is empty,璇峰湪chat_config涓厤缃畓hipu key淇℃伅");
+        }else {
+            ClientV4 client = new ClientV4.Builder(zhipuValue)
+                    .networkConfig(300, 100, 100, 100, TimeUnit.SECONDS)
+                    .connectionPool(new okhttp3.ConnectionPool(8, 1, TimeUnit.SECONDS))
+                    .build();
+
+            SearchChatMessage jsonNodes = new SearchChatMessage();
+            jsonNodes.setRole(Message.Role.USER.getName());
+            jsonNodes.setContent(prompt);
+
+            String requestId = String.format(requestIdTemplate, System.currentTimeMillis());
+            WebSearchParamsRequest chatCompletionRequest = WebSearchParamsRequest.builder()
+                    .model("web-search-pro")
+                    .stream(Boolean.TRUE)
+                    .messages(Collections.singletonList(jsonNodes))
+                    .requestId(requestId)
+                    .build();
+            WebSearchApiResponse webSearchApiResponse = client.webSearchProStreamingInvoke(chatCompletionRequest);
+            List<ChoiceDelta> choices = new ArrayList<>();
+            if (webSearchApiResponse.isSuccess()) {
+                AtomicBoolean isFirst = new AtomicBoolean(true);
+
+                AtomicReference<WebSearchPro> lastAccumulator = new AtomicReference<>();
+
+                webSearchApiResponse.getFlowable().map(result -> result)
+                        .doOnNext(accumulator -> {
+                            {
+                                if (isFirst.getAndSet(false)) {
+                                    log.info("Response: ");
+                                }
+                                ChoiceDelta delta = accumulator.getChoices().get(0).getDelta();
+                                if (delta != null && delta.getToolCalls() != null) {
+                                    log.info("tool_calls: {}", mapper.writeValueAsString(delta.getToolCalls()));
+                                }
+                                choices.add(delta);
+                            }
+                        })
+                        .doOnComplete(() -> System.out.println("Stream completed."))
+                        .doOnError(throwable -> System.err.println("Error: " + throwable))
+                        .blockingSubscribe();
+
+                WebSearchPro chatMessageAccumulator = lastAccumulator.get();
+                webSearchApiResponse.setFlowable(null);
+                webSearchApiResponse.setData(chatMessageAccumulator);
+            }
+            return  choices.get(1).getToolCalls().toString();
+        }
+    }
+
 }

--
Gitblit v1.9.3