|
@@ -1,9 +1,14 @@
|
|
-package com.backendsys.modules.ai.deepSeek.utils;
|
|
|
|
|
|
+package com.backendsys.modules.ai.deepSeek.service.impl;
|
|
|
|
|
|
import cn.hutool.core.convert.Convert;
|
|
import cn.hutool.core.convert.Convert;
|
|
|
|
+import cn.hutool.json.JSONArray;
|
|
|
|
+import cn.hutool.json.JSONObject;
|
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
|
+import com.backendsys.exception.CustException;
|
|
import com.backendsys.modules.ai.deepSeek.entity.DSContent;
|
|
import com.backendsys.modules.ai.deepSeek.entity.DSContent;
|
|
import com.backendsys.modules.ai.deepSeek.entity.DSRequest;
|
|
import com.backendsys.modules.ai.deepSeek.entity.DSRequest;
|
|
import com.backendsys.modules.ai.deepSeek.entity.DSRequestMessage;
|
|
import com.backendsys.modules.ai.deepSeek.entity.DSRequestMessage;
|
|
|
|
+import com.backendsys.modules.ai.deepSeek.service.DeepSeekClient;
|
|
import com.backendsys.modules.common.config.security.utils.SecurityUtil;
|
|
import com.backendsys.modules.common.config.security.utils.SecurityUtil;
|
|
import com.backendsys.modules.sse.entity.SseResponse;
|
|
import com.backendsys.modules.sse.entity.SseResponse;
|
|
import com.backendsys.modules.sse.entity.SseResponseEnum;
|
|
import com.backendsys.modules.sse.entity.SseResponseEnum;
|
|
@@ -11,26 +16,32 @@ import com.backendsys.modules.sse.utils.SseUtil;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
|
|
|
+import org.apache.http.client.methods.HttpGet;
|
|
import org.apache.http.client.methods.HttpPost;
|
|
import org.apache.http.client.methods.HttpPost;
|
|
import org.apache.http.entity.StringEntity;
|
|
import org.apache.http.entity.StringEntity;
|
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
import org.apache.http.impl.client.CloseableHttpClient;
|
|
import org.apache.http.impl.client.HttpClients;
|
|
import org.apache.http.impl.client.HttpClients;
|
|
|
|
+import org.apache.http.util.EntityUtils;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.stereotype.Component;
|
|
import org.springframework.stereotype.Component;
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.io.*;
|
|
import java.io.*;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.ArrayList;
|
|
import java.util.ArrayList;
|
|
-import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
|
|
|
-// 开放平台:https://platform.deepseek.com/
|
|
|
|
-// 官方文档:https://api-docs.deepseek.com/zh-cn/
|
|
|
|
-@Component
|
|
|
|
-public class DeepSeekClient {
|
|
|
|
|
|
+/**
|
|
|
|
+ * DeepseekAPI 工具类 (by Mure 2025/02/27)
|
|
|
|
+ *
|
|
|
|
+ * 开放平台:https://platform.deepseek.com/
|
|
|
|
+ * 官方文档:https://api-docs.deepseek.com/zh-cn/
|
|
|
|
+ */
|
|
|
|
+@Service
|
|
|
|
+public class DeepSeekClientImpl implements DeepSeekClient {
|
|
|
|
|
|
@Value("${deepseek.url}")
|
|
@Value("${deepseek.url}")
|
|
private String API_URL;
|
|
private String API_URL;
|
|
@@ -43,10 +54,13 @@ public class DeepSeekClient {
|
|
|
|
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
private final ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* 对话 (文档:https://api-docs.deepseek.com/zh-cn/api/create-chat-completion)
|
|
* 对话 (文档:https://api-docs.deepseek.com/zh-cn/api/create-chat-completion)
|
|
|
|
+ *
|
|
* @param prompt
|
|
* @param prompt
|
|
*/
|
|
*/
|
|
|
|
+ @Override
|
|
public void chatCompletion(String prompt, String model) {
|
|
public void chatCompletion(String prompt, String model) {
|
|
|
|
|
|
Long userId = SecurityUtil.getUserId();
|
|
Long userId = SecurityUtil.getUserId();
|
|
@@ -61,42 +75,24 @@ public class DeepSeekClient {
|
|
// 【多轮对话】
|
|
// 【多轮对话】
|
|
// https://api-docs.deepseek.com/zh-cn/guides/multi_round_chat
|
|
// 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);
|
|
|
|
|
|
+ // - 接口做最大问答限制 (1-字数限制, 2-次数限制)
|
|
|
|
|
|
List<DSRequestMessage> messages = new ArrayList<>();
|
|
List<DSRequestMessage> messages = new ArrayList<>();
|
|
- DSRequestMessage messageItem = new DSRequestMessage();
|
|
|
|
- messageItem.setRole("user");
|
|
|
|
- messageItem.setContent(prompt);
|
|
|
|
- messages.add(messageItem);
|
|
|
|
|
|
+// messages.add(new DSRequestMessage("user", "你是谁"));
|
|
|
|
+// messages.add(new DSRequestMessage("assistant", "xxx"));
|
|
|
|
+// messages.add(new DSRequestMessage("user", "你是什么大模型"));
|
|
|
|
+// messages.add(new DSRequestMessage("assistant", "xxx"));
|
|
|
|
+ messages.add(new DSRequestMessage("user", prompt));
|
|
|
|
|
|
// 构建请求体
|
|
// 构建请求体
|
|
- DSRequest dsRequest = new DSRequest();
|
|
|
|
- dsRequest.setModel(model); // (deepseek-chat: 对话模型, deepseek-reasoner: 推理模型)
|
|
|
|
- dsRequest.setMessages(messages);
|
|
|
|
- dsRequest.setStream(true);
|
|
|
|
|
|
+ DSRequest body = new DSRequest();
|
|
|
|
+ // model: (deepseek-chat: 对话模型, deepseek-reasoner: 推理模型)
|
|
|
|
+ body.setModel(model);
|
|
|
|
+ body.setMessages(messages);
|
|
|
|
+ body.setStream(true);
|
|
|
|
|
|
// 记录请求开始时间
|
|
// 记录请求开始时间
|
|
- long startTime = System.currentTimeMillis();
|
|
|
|
|
|
+ long allStartTime = System.currentTimeMillis();
|
|
|
|
|
|
// 调用 Deepseek API
|
|
// 调用 Deepseek API
|
|
try (CloseableHttpClient client = HttpClients.createDefault()) {
|
|
try (CloseableHttpClient client = HttpClients.createDefault()) {
|
|
@@ -105,16 +101,22 @@ public class DeepSeekClient {
|
|
request.setHeader("Content-Type", "application/json");
|
|
request.setHeader("Content-Type", "application/json");
|
|
request.setHeader("Authorization", "Bearer " + API_KEY);
|
|
request.setHeader("Authorization", "Bearer " + API_KEY);
|
|
|
|
|
|
- String requestBody = objectMapper.writeValueAsString(dsRequest);
|
|
|
|
|
|
+ String requestBody = objectMapper.writeValueAsString(body);
|
|
request.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
|
|
request.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
|
|
|
|
|
|
try (
|
|
try (
|
|
- CloseableHttpResponse response = client.execute(request);
|
|
|
|
- BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8))
|
|
|
|
|
|
+ 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 + " 毫秒");
|
|
|
|
|
|
+
|
|
|
|
+ long apiDuration = System.currentTimeMillis() - allStartTime; // 接口耗时
|
|
|
|
+
|
|
|
|
+ long thinkStartTime = 0L; // 开始思考
|
|
|
|
+ long thinkDuration = 0L; // 思考耗时
|
|
|
|
+ Boolean isThinking = false;
|
|
|
|
+
|
|
|
|
+ System.out.println("API 调用耗时: " + apiDuration + " 毫秒");
|
|
System.out.println("-------------------- 开始流式回答: --------------------");
|
|
System.out.println("-------------------- 开始流式回答: --------------------");
|
|
|
|
|
|
// [SSE] 发送消息
|
|
// [SSE] 发送消息
|
|
@@ -152,10 +154,16 @@ public class DeepSeekClient {
|
|
JsonNode node = objectMapper.readTree(jsonData);
|
|
JsonNode node = objectMapper.readTree(jsonData);
|
|
JsonNode delta = node.path("choices").path(0).path("delta");
|
|
JsonNode delta = node.path("choices").path(0).path("delta");
|
|
|
|
|
|
- // [推理] Think
|
|
|
|
|
|
+ // [推理] Think (仅 deepseek-reasoner: 推理模型 支持)
|
|
String reasoning_content = delta.path("reasoning_content").asText("");
|
|
String reasoning_content = delta.path("reasoning_content").asText("");
|
|
if (!reasoning_content.isEmpty()) {
|
|
if (!reasoning_content.isEmpty()) {
|
|
|
|
|
|
|
|
+ // 开始思考
|
|
|
|
+ if (!isThinking) {
|
|
|
|
+ isThinking = true;
|
|
|
|
+ thinkStartTime = System.currentTimeMillis();
|
|
|
|
+ }
|
|
|
|
+
|
|
System.out.println("reasoning_content: " + reasoning_content);
|
|
System.out.println("reasoning_content: " + reasoning_content);
|
|
|
|
|
|
// [SSE] 发送消息
|
|
// [SSE] 发送消息
|
|
@@ -171,6 +179,15 @@ public class DeepSeekClient {
|
|
// [回答] Reply
|
|
// [回答] Reply
|
|
String content = delta.path("content").asText("");
|
|
String content = delta.path("content").asText("");
|
|
if (!content.isEmpty()) {
|
|
if (!content.isEmpty()) {
|
|
|
|
+
|
|
|
|
+ // 停止思考,并计算思考耗时
|
|
|
|
+ if (isThinking) {
|
|
|
|
+ isThinking = false;
|
|
|
|
+ thinkDuration = thinkStartTime - allStartTime;
|
|
|
|
+ System.out.println("推理耗时: " + thinkDuration + "毫秒");
|
|
|
|
+ System.out.println("-----------------------------------------------");
|
|
|
|
+ }
|
|
|
|
+
|
|
System.out.println("content: " + content);
|
|
System.out.println("content: " + content);
|
|
|
|
|
|
// [SSE] 发送消息
|
|
// [SSE] 发送消息
|
|
@@ -201,11 +218,11 @@ public class DeepSeekClient {
|
|
// emitter.complete();
|
|
// emitter.complete();
|
|
|
|
|
|
System.out.println("-------------------- 结束流式回答. --------------------");
|
|
System.out.println("-------------------- 结束流式回答. --------------------");
|
|
- long durationOutput = System.currentTimeMillis() - startTime;
|
|
|
|
- System.out.println("输出耗时: " + durationOutput + " 毫秒");
|
|
|
|
|
|
+ long durationOutput = System.currentTimeMillis() - allStartTime;
|
|
|
|
|
|
System.out.println("全部推理: " + allReasoning);
|
|
System.out.println("全部推理: " + allReasoning);
|
|
System.out.println("全部回答: " + allContent);
|
|
System.out.println("全部回答: " + allContent);
|
|
|
|
+ System.out.println("总输出耗时: " + durationOutput + " 毫秒");
|
|
|
|
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
@@ -223,4 +240,45 @@ public class DeepSeekClient {
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-}
|
|
|
|
|
|
+ @Override
|
|
|
|
+ public JSONArray getModels() {
|
|
|
|
+
|
|
|
|
+ Long userId = SecurityUtil.getUserId();
|
|
|
|
+ String emitterKey = APPLICATION_NAME + "-userid-" + Convert.toStr(userId);
|
|
|
|
+
|
|
|
|
+ // 调用 Deepseek API
|
|
|
|
+ try (CloseableHttpClient client = HttpClients.createDefault()) {
|
|
|
|
+
|
|
|
|
+ HttpGet request = new HttpGet(API_URL + "/models");
|
|
|
|
+ request.setHeader("Content-Type", "application/json");
|
|
|
|
+ request.setHeader("Authorization", "Bearer " + API_KEY);
|
|
|
|
+
|
|
|
|
+ try (CloseableHttpResponse response = client.execute(request)) {
|
|
|
|
+
|
|
|
|
+ // 检查响应状态码
|
|
|
|
+ if (response.getStatusLine().getStatusCode() == 200) {
|
|
|
|
+
|
|
|
|
+ // 获取响应体
|
|
|
|
+ String responseBody = EntityUtils.toString(response.getEntity());
|
|
|
|
+ JSONObject jsonResponse = JSONUtil.parseObj(responseBody);
|
|
|
|
+ JSONArray models = jsonResponse.getJSONArray("data");
|
|
|
|
+ return models;
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ throw new CustException("Failed to retrieve models. Status Code: " + response.getStatusLine().getStatusCode());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ String dataStr = (new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr();
|
|
|
|
+ sseUtil.send(emitterKey, dataStr);
|
|
|
|
+ throw new CustException(e.getMessage());
|
|
|
|
+ }
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ String dataStr = (new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr();
|
|
|
|
+ sseUtil.send(emitterKey, dataStr);
|
|
|
|
+ throw new CustException(e.getMessage());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+}
|