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