|
@@ -0,0 +1,223 @@
|
|
|
+package com.backendsys.modules.ai.deepSeek.utils;
|
|
|
+
|
|
|
+import cn.hutool.core.convert.Convert;
|
|
|
+import com.backendsys.modules.ai.deepSeek.entity.DSContent;
|
|
|
+import com.backendsys.modules.ai.deepSeek.entity.DSRequest;
|
|
|
+import com.backendsys.modules.ai.deepSeek.entity.DSRequestMessage;
|
|
|
+import com.backendsys.modules.common.config.security.utils.SecurityUtil;
|
|
|
+import com.backendsys.modules.sse.entity.SseResponse;
|
|
|
+import com.backendsys.modules.sse.entity.SseResponseEnum;
|
|
|
+import com.backendsys.modules.sse.utils.SseUtil;
|
|
|
+import com.fasterxml.jackson.databind.JsonNode;
|
|
|
+import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
+import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
+import org.apache.http.client.methods.HttpPost;
|
|
|
+import org.apache.http.entity.StringEntity;
|
|
|
+import org.apache.http.impl.client.CloseableHttpClient;
|
|
|
+import org.apache.http.impl.client.HttpClients;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
+
|
|
|
+import java.io.*;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
+
|
|
|
+// 开放平台:https://platform.deepseek.com/
|
|
|
+// 官方文档:https://api-docs.deepseek.com/zh-cn/
|
|
|
+@Component
|
|
|
+public class DeepSeekClient {
|
|
|
+
|
|
|
+ @Value("${deepseek.url}")
|
|
|
+ private String API_URL;
|
|
|
+ @Value("${deepseek.api-key}")
|
|
|
+ private String API_KEY;
|
|
|
+ @Value("${spring.application.name}")
|
|
|
+ private String APPLICATION_NAME;
|
|
|
+ @Autowired
|
|
|
+ private SseUtil sseUtil;
|
|
|
+
|
|
|
+ private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对话 (文档:https://api-docs.deepseek.com/zh-cn/api/create-chat-completion)
|
|
|
+ * @param prompt
|
|
|
+ */
|
|
|
+ public void chatCompletion(String prompt, String model) {
|
|
|
+
|
|
|
+ Long userId = SecurityUtil.getUserId();
|
|
|
+ String emitterKey = APPLICATION_NAME + "-userid-" + Convert.toStr(userId);
|
|
|
+
|
|
|
+ // 创建一个 CompletableFuture 来执行异步任务
|
|
|
+ CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
|
|
+
|
|
|
+ try {
|
|
|
+ System.out.println("提问: " + prompt);
|
|
|
+
|
|
|
+ // 【多轮对话】
|
|
|
+ // https://api-docs.deepseek.com/zh-cn/guides/multi_round_chat
|
|
|
+
|
|
|
+
|
|
|
+// // 获取当前用户的对话历史
|
|
|
+// List<Map<String, String>> messages = sessionHistory.getOrDefault(userId, new ArrayList<>());
|
|
|
+
|
|
|
+// // 添加用户的新问题到对话历史
|
|
|
+// Map<String, String> userMessage = new HashMap<>();
|
|
|
+// userMessage.put("role", "user");
|
|
|
+// userMessage.put("content", prompt);
|
|
|
+//
|
|
|
+// Map<String, String> systemMessage = new HashMap<>();
|
|
|
+// systemMessage.put("role", "system");
|
|
|
+// systemMessage.put("content", "聚城网络科技有限公司的物业管理助手");
|
|
|
+// messages.add(userMessage);
|
|
|
+// messages.add(systemMessage);
|
|
|
+
|
|
|
+// // 提取 messages 为单独变量
|
|
|
+// List<Map<String, String>> messages = new ArrayList<>();
|
|
|
+// Map<String, String> userMessage = new HashMap<>();
|
|
|
+// userMessage.put("role", "user");
|
|
|
+// userMessage.put("content", prompt);
|
|
|
+// messages.add(userMessage);
|
|
|
+
|
|
|
+ List<DSRequestMessage> messages = new ArrayList<>();
|
|
|
+ DSRequestMessage messageItem = new DSRequestMessage();
|
|
|
+ messageItem.setRole("user");
|
|
|
+ messageItem.setContent(prompt);
|
|
|
+ messages.add(messageItem);
|
|
|
+
|
|
|
+ // 构建请求体
|
|
|
+ DSRequest dsRequest = new DSRequest();
|
|
|
+ dsRequest.setModel(model); // (deepseek-chat: 对话模型, deepseek-reasoner: 推理模型)
|
|
|
+ dsRequest.setMessages(messages);
|
|
|
+ dsRequest.setStream(true);
|
|
|
+
|
|
|
+ // 记录请求开始时间
|
|
|
+ long startTime = System.currentTimeMillis();
|
|
|
+
|
|
|
+ // 调用 Deepseek API
|
|
|
+ try (CloseableHttpClient client = HttpClients.createDefault()) {
|
|
|
+
|
|
|
+ HttpPost request = new HttpPost(API_URL + "/chat/completions");
|
|
|
+ request.setHeader("Content-Type", "application/json");
|
|
|
+ request.setHeader("Authorization", "Bearer " + API_KEY);
|
|
|
+
|
|
|
+ String requestBody = objectMapper.writeValueAsString(dsRequest);
|
|
|
+ request.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
|
|
|
+
|
|
|
+ try (
|
|
|
+ CloseableHttpResponse response = client.execute(request);
|
|
|
+ BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8))
|
|
|
+ ) {
|
|
|
+
|
|
|
+ long duration = System.currentTimeMillis() - startTime; // 计算耗时
|
|
|
+ System.out.println("API 调用耗时: " + duration + " 毫秒");
|
|
|
+ System.out.println("-------------------- 开始流式回答: --------------------");
|
|
|
+
|
|
|
+ // [SSE] 发送消息
|
|
|
+ sseUtil.send(emitterKey, (new SseResponse(SseResponseEnum.DEEPSEEK, "正在思考")).toJsonStr());
|
|
|
+
|
|
|
+ StringBuilder allContent = new StringBuilder();
|
|
|
+ StringBuilder allReasoning = new StringBuilder();
|
|
|
+
|
|
|
+ String line;
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
+
|
|
|
+ /*
|
|
|
+ ---------------------- line ----------------------
|
|
|
+ data: {
|
|
|
+ "id":"6fc7b0e0-6ba1-4050-b7f7-924dd3559102", "object":"chat.completion.chunk",
|
|
|
+ "created":1740550788, "model":"deepseek-chat", "system_fingerprint":"fp_3a5770e1b4_prod0225",
|
|
|
+ "choices":[
|
|
|
+ {
|
|
|
+ "index":0,
|
|
|
+ "delta":{ "content": "我是", "reasoning_content":null },
|
|
|
+ "logprobs":null, "finish_reason":null
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ --------------------------------------------------
|
|
|
+ */
|
|
|
+ // System.out.println(line);
|
|
|
+
|
|
|
+ if (line.startsWith("data: ")) {
|
|
|
+ String jsonData = line.substring(6);
|
|
|
+ if ("[DONE]".equals(jsonData)) break;
|
|
|
+ JsonNode node = objectMapper.readTree(jsonData);
|
|
|
+ JsonNode delta = node.path("choices").path(0).path("delta");
|
|
|
+
|
|
|
+ // [推理]
|
|
|
+ String reasoning_content = delta.path("reasoning_content").asText("");
|
|
|
+ if (!reasoning_content.isEmpty()) {
|
|
|
+
|
|
|
+ System.out.println("reasoning_content: " + reasoning_content);
|
|
|
+
|
|
|
+ // [SSE] 发送消息
|
|
|
+ DSContent dsContent = new DSContent();
|
|
|
+ dsContent.setModel(model);
|
|
|
+ dsContent.setContent(reasoning_content);
|
|
|
+ sseUtil.send(emitterKey, (new SseResponse(SseResponseEnum.DEEPSEEK, dsContent)).toJsonStr());
|
|
|
+
|
|
|
+ // 收集推理内容
|
|
|
+ allReasoning.append(reasoning_content);
|
|
|
+ }
|
|
|
+
|
|
|
+ // [回答]
|
|
|
+ String content = delta.path("content").asText("");
|
|
|
+ if (!content.isEmpty()) {
|
|
|
+ System.out.println("content: " + content);
|
|
|
+
|
|
|
+ // [SSE] 发送消息
|
|
|
+ DSContent dsContent = new DSContent();
|
|
|
+ dsContent.setModel(model);
|
|
|
+ dsContent.setContent(content);
|
|
|
+ sseUtil.send(emitterKey, (new SseResponse(SseResponseEnum.DEEPSEEK, dsContent)).toJsonStr());
|
|
|
+
|
|
|
+ // 收集回答内容
|
|
|
+ allContent.append(content);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+// // 将 AI 的回复添加到对话历史
|
|
|
+// Map<String, String> aiMessage = new HashMap<>();
|
|
|
+// aiMessage.put("role", "assistant");
|
|
|
+// aiMessage.put("content", aiResponse.toString());
|
|
|
+// messages.add(aiMessage);
|
|
|
+//
|
|
|
+// // 更新会话状态
|
|
|
+// sessionHistory.put(userId, messages);
|
|
|
+//
|
|
|
+// log.info("流式回答结束, 问题: {}", question);
|
|
|
+// System.out.println(que);
|
|
|
+// emitter.complete();
|
|
|
+
|
|
|
+ System.out.println("-------------------- 结束流式回答. --------------------");
|
|
|
+ long durationOutput = System.currentTimeMillis() - startTime;
|
|
|
+ System.out.println("输出耗时: " + durationOutput + " 毫秒");
|
|
|
+
|
|
|
+ System.out.println("全部推理: " + allReasoning);
|
|
|
+ System.out.println("全部回答: " + allContent);
|
|
|
+
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ String dataStr = (new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr();
|
|
|
+ sseUtil.send(emitterKey, dataStr);
|
|
|
+ System.out.println(e.getMessage());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ String dataStr = (new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr();
|
|
|
+ sseUtil.send(emitterKey, dataStr);
|
|
|
+ System.out.println(e.getMessage());
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|