|
@@ -5,9 +5,12 @@ import cn.hutool.json.JSONArray;
|
|
import cn.hutool.json.JSONObject;
|
|
import cn.hutool.json.JSONObject;
|
|
import cn.hutool.json.JSONUtil;
|
|
import cn.hutool.json.JSONUtil;
|
|
import com.backendsys.exception.CustException;
|
|
import com.backendsys.exception.CustException;
|
|
|
|
+import com.backendsys.modules.ai.chat.dao.ChatDao;
|
|
|
|
+import com.backendsys.modules.ai.chat.entity.Chat;
|
|
import com.backendsys.modules.sdk.deepseek.entity.DSContent;
|
|
import com.backendsys.modules.sdk.deepseek.entity.DSContent;
|
|
import com.backendsys.modules.sdk.deepseek.entity.DSRequest;
|
|
import com.backendsys.modules.sdk.deepseek.entity.DSRequest;
|
|
import com.backendsys.modules.sdk.deepseek.entity.DSRequestMessage;
|
|
import com.backendsys.modules.sdk.deepseek.entity.DSRequestMessage;
|
|
|
|
+import com.backendsys.modules.sdk.deepseek.entity.DSResponse;
|
|
import com.backendsys.modules.sdk.deepseek.service.DeepSeekClient;
|
|
import com.backendsys.modules.sdk.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;
|
|
@@ -29,6 +32,7 @@ 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.Collections;
|
|
import java.util.List;
|
|
import java.util.List;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
|
|
|
@@ -45,205 +49,203 @@ public class DeepSeekClientImpl implements DeepSeekClient {
|
|
private String API_URL;
|
|
private String API_URL;
|
|
@Value("${deepseek.api-key}")
|
|
@Value("${deepseek.api-key}")
|
|
private String API_KEY;
|
|
private String API_KEY;
|
|
- @Value("${spring.application.name}")
|
|
|
|
- private String APPLICATION_NAME;
|
|
|
|
@Autowired
|
|
@Autowired
|
|
private SseUtil sseUtil;
|
|
private SseUtil sseUtil;
|
|
|
|
|
|
- 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
|
|
@Override
|
|
- public void chatCompletion(String model, String prompt, Long user_id) {
|
|
|
|
|
|
+ public DSResponse chatCompletion(String model, String prompt, List<Chat> chatList) {
|
|
|
|
|
|
- String emitterKey = APPLICATION_NAME + "-userid-" + Convert.toStr(user_id);
|
|
|
|
|
|
+ DSResponse dsResponse = new DSResponse();
|
|
|
|
+ try {
|
|
|
|
+ System.out.println("向模型: " + model + " 提问: " + prompt);
|
|
|
|
|
|
- // 创建一个 CompletableFuture 来执行异步任务
|
|
|
|
- CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
|
|
|
|
|
|
+ // 【多轮对话】
|
|
|
|
+ // https://api-docs.deepseek.com/zh-cn/guides/multi_round_chat
|
|
|
|
|
|
- try {
|
|
|
|
- System.out.println("向模型: " + model + " 提问: " + prompt);
|
|
|
|
|
|
+ // - 接口做最大问答限制 (1-字数限制, 2-次数限制)
|
|
|
|
|
|
- // 【多轮对话】
|
|
|
|
- // https://api-docs.deepseek.com/zh-cn/guides/multi_round_chat
|
|
|
|
|
|
|
|
- // - 接口做最大问答限制 (1-字数限制, 2-次数限制)
|
|
|
|
|
|
+ List<DSRequestMessage> messages = new ArrayList<>();
|
|
|
|
|
|
- List<DSRequestMessage> messages = new ArrayList<>();
|
|
|
|
-// 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));
|
|
|
|
|
|
+ // 加入上下文历史对话
|
|
|
|
+ if (chatList != null && !chatList.isEmpty()) {
|
|
|
|
+ chatList.stream().forEach(chat -> {
|
|
|
|
+ if (!"THINK".equals(chat.getContent_type())) {
|
|
|
|
+ messages.add(new DSRequestMessage(chat.getRole(), chat.getContent()));
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ // 反转列表
|
|
|
|
+ Collections.reverse(messages);
|
|
|
|
+ }
|
|
|
|
|
|
- // 构建请求体
|
|
|
|
- DSRequest body = new DSRequest();
|
|
|
|
- // model: (deepseek-chat: 对话模型, deepseek-reasoner: 推理模型)
|
|
|
|
- body.setModel(model);
|
|
|
|
- body.setMessages(messages);
|
|
|
|
- body.setStream(true);
|
|
|
|
|
|
+ messages.add(0, new DSRequestMessage("user", prompt));
|
|
|
|
|
|
- // 记录请求开始时间
|
|
|
|
- long allStartTime = System.currentTimeMillis();
|
|
|
|
|
|
|
|
- // 调用 Deepseek API
|
|
|
|
- try (CloseableHttpClient client = HttpClients.createDefault()) {
|
|
|
|
|
|
+ System.out.println("------------------");
|
|
|
|
+ System.out.println("messages:");
|
|
|
|
+ System.out.println(messages);
|
|
|
|
+ System.out.println("------------------");
|
|
|
|
|
|
- HttpPost request = new HttpPost(API_URL + "/chat/completions");
|
|
|
|
- request.setHeader("Content-Type", "application/json");
|
|
|
|
- request.setHeader("Authorization", "Bearer " + API_KEY);
|
|
|
|
|
|
+ // 构建请求体
|
|
|
|
+ DSRequest body = new DSRequest();
|
|
|
|
|
|
- String requestBody = objectMapper.writeValueAsString(body);
|
|
|
|
- request.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
|
|
|
|
|
|
+ // model: (deepseek-chat: 对话模型, deepseek-reasoner: 推理模型)
|
|
|
|
+ body.setModel(model);
|
|
|
|
+ body.setMessages(messages);
|
|
|
|
+ body.setStream(true);
|
|
|
|
|
|
- try (
|
|
|
|
- CloseableHttpResponse response = client.execute(request);
|
|
|
|
- BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8))
|
|
|
|
- ) {
|
|
|
|
|
|
+ // 记录请求开始时间
|
|
|
|
+ long allStartTime = System.currentTimeMillis();
|
|
|
|
|
|
|
|
+ // 调用 Deepseek API
|
|
|
|
+ try (CloseableHttpClient client = HttpClients.createDefault()) {
|
|
|
|
|
|
- long apiDuration = System.currentTimeMillis() - allStartTime; // 接口耗时
|
|
|
|
|
|
+ HttpPost request = new HttpPost(API_URL + "/chat/completions");
|
|
|
|
+ request.setHeader("Content-Type", "application/json");
|
|
|
|
+ request.setHeader("Authorization", "Bearer " + API_KEY);
|
|
|
|
|
|
- long thinkStartTime = 0L; // 开始思考
|
|
|
|
- long thinkDuration = 0L; // 思考耗时
|
|
|
|
- Boolean isThinking = false;
|
|
|
|
|
|
+ ObjectMapper objectMapper = new ObjectMapper();
|
|
|
|
+ String requestBody = objectMapper.writeValueAsString(body);
|
|
|
|
+ request.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
|
|
|
|
|
|
- System.out.println("API 调用耗时: " + apiDuration + " 毫秒");
|
|
|
|
- System.out.println("-------------------- 开始流式回答: --------------------");
|
|
|
|
|
|
+ try (
|
|
|
|
+ CloseableHttpResponse response = client.execute(request);
|
|
|
|
+ BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8))
|
|
|
|
+ ) {
|
|
|
|
|
|
- // [SSE] 发送消息
|
|
|
|
- DSContent initContent = new DSContent();
|
|
|
|
- initContent.setContent_type("loading");
|
|
|
|
- initContent.setContent("正在思考");
|
|
|
|
- sseUtil.send(emitterKey, (new SseResponse(SseResponseEnum.DEEPSEEK, initContent)).toJsonStr());
|
|
|
|
|
|
+ long apiDuration = System.currentTimeMillis() - allStartTime; // 接口耗时
|
|
|
|
|
|
- StringBuilder allContent = new StringBuilder();
|
|
|
|
- StringBuilder allReasoning = new StringBuilder();
|
|
|
|
|
|
+ long thinkStartTime = 0L; // 开始思考
|
|
|
|
+ long reasoningContentDuration = 0L; // 思考耗时
|
|
|
|
+ Boolean isThinking = false;
|
|
|
|
|
|
- String line;
|
|
|
|
- while ((line = reader.readLine()) != null) {
|
|
|
|
|
|
+ System.out.println("API 调用耗时: " + apiDuration + " 毫秒");
|
|
|
|
+ System.out.println("-------------------- 开始流式回答: --------------------");
|
|
|
|
|
|
- // System.out.println(line);
|
|
|
|
- /*
|
|
|
|
- ---------------------- 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");
|
|
|
|
-
|
|
|
|
- // [推理] Think (仅 deepseek-reasoner: 推理模型 支持)
|
|
|
|
- String reasoning_content = delta.path("reasoning_content").asText("");
|
|
|
|
- if (!reasoning_content.isEmpty()) {
|
|
|
|
-
|
|
|
|
- // 开始思考
|
|
|
|
- if (!isThinking) {
|
|
|
|
- isThinking = true;
|
|
|
|
- thinkStartTime = System.currentTimeMillis();
|
|
|
|
- }
|
|
|
|
|
|
+ // [SSE] 发送消息
|
|
|
|
+ DSContent initContent = new DSContent();
|
|
|
|
+ initContent.setContent_type("loading");
|
|
|
|
+ initContent.setContent("正在思考");
|
|
|
|
+ sseUtil.send(new SseResponse(SseResponseEnum.DEEPSEEK, initContent).toJsonStr());
|
|
|
|
|
|
- System.out.println("reasoning_content: " + reasoning_content);
|
|
|
|
|
|
+ StringBuilder allContent = new StringBuilder();
|
|
|
|
+ StringBuilder allReasoningContent = new StringBuilder();
|
|
|
|
|
|
- // [SSE] 发送消息
|
|
|
|
- DSContent dsContent = new DSContent();
|
|
|
|
- dsContent.setContent_type("think");
|
|
|
|
- dsContent.setContent(reasoning_content);
|
|
|
|
- sseUtil.send(emitterKey, (new SseResponse(SseResponseEnum.DEEPSEEK, dsContent)).toJsonStr());
|
|
|
|
|
|
+ String line;
|
|
|
|
+ while ((line = reader.readLine()) != null) {
|
|
|
|
|
|
- // 收集推理内容
|
|
|
|
- allReasoning.append(reasoning_content);
|
|
|
|
|
|
+ // System.out.println(line);
|
|
|
|
+ /*
|
|
|
|
+ ---------------------- 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
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ }
|
|
|
|
+ --------------------------------------------------
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ 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");
|
|
|
|
+
|
|
|
|
+ // [推理] Think (仅 deepseek-reasoner: 推理模型 支持)
|
|
|
|
+ String reasoning_content = delta.path("reasoning_content").asText("");
|
|
|
|
+ if (!reasoning_content.isEmpty()) {
|
|
|
|
+
|
|
|
|
+ // 开始思考
|
|
|
|
+ if (!isThinking) {
|
|
|
|
+ isThinking = true;
|
|
|
|
+ thinkStartTime = System.currentTimeMillis();
|
|
}
|
|
}
|
|
|
|
|
|
- // [回答] Reply
|
|
|
|
- String content = delta.path("content").asText("");
|
|
|
|
- if (!content.isEmpty()) {
|
|
|
|
|
|
+ System.out.println("reasoning_content: " + reasoning_content);
|
|
|
|
|
|
- // 停止思考,并计算思考耗时
|
|
|
|
- if (isThinking) {
|
|
|
|
- isThinking = false;
|
|
|
|
- thinkDuration = thinkStartTime - allStartTime;
|
|
|
|
- System.out.println("推理耗时: " + thinkDuration + "毫秒");
|
|
|
|
- System.out.println("-----------------------------------------------");
|
|
|
|
- }
|
|
|
|
|
|
+ // [SSE] 发送消息
|
|
|
|
+ DSContent dsContent = new DSContent();
|
|
|
|
+ dsContent.setContent_type("think");
|
|
|
|
+ dsContent.setContent(reasoning_content);
|
|
|
|
+ sseUtil.send(new SseResponse(SseResponseEnum.DEEPSEEK, dsContent).toJsonStr());
|
|
|
|
|
|
- System.out.println("content: " + content);
|
|
|
|
|
|
+ // 收集推理内容
|
|
|
|
+ allReasoningContent.append(reasoning_content);
|
|
|
|
+ }
|
|
|
|
|
|
- // [SSE] 发送消息
|
|
|
|
- DSContent dsContent = new DSContent();
|
|
|
|
- dsContent.setContent_type("reply");
|
|
|
|
- dsContent.setContent(content);
|
|
|
|
- sseUtil.send(emitterKey, (new SseResponse(SseResponseEnum.DEEPSEEK, dsContent)).toJsonStr());
|
|
|
|
|
|
+ // [回答] Reply
|
|
|
|
+ String content = delta.path("content").asText("");
|
|
|
|
+ if (!content.isEmpty()) {
|
|
|
|
|
|
- // 收集回答内容
|
|
|
|
- allContent.append(content);
|
|
|
|
|
|
+ // 停止思考,并计算思考耗时
|
|
|
|
+ if (isThinking) {
|
|
|
|
+ isThinking = false;
|
|
|
|
+ reasoningContentDuration = thinkStartTime - allStartTime;
|
|
|
|
+ System.out.println("推理耗时: " + reasoningContentDuration + "毫秒");
|
|
|
|
+ System.out.println("-----------------------------------------------");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ System.out.println("content: " + content);
|
|
|
|
+
|
|
|
|
+ // [SSE] 发送消息
|
|
|
|
+ DSContent dsContent = new DSContent();
|
|
|
|
+ dsContent.setContent_type("reply");
|
|
|
|
+ dsContent.setContent(content);
|
|
|
|
+ sseUtil.send(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() - allStartTime;
|
|
|
|
-
|
|
|
|
- System.out.println("全部推理: " + allReasoning);
|
|
|
|
- System.out.println("全部回答: " + allContent);
|
|
|
|
- System.out.println("总输出耗时: " + durationOutput + " 毫秒");
|
|
|
|
|
|
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- } catch (Exception e) {
|
|
|
|
- String dataStr = (new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr();
|
|
|
|
- sseUtil.send(emitterKey, dataStr);
|
|
|
|
- System.out.println(e.getMessage());
|
|
|
|
|
|
+
|
|
|
|
+ System.out.println("-------------------- 结束流式回答. --------------------");
|
|
|
|
+ long contentDuration = System.currentTimeMillis() - allStartTime;
|
|
|
|
+
|
|
|
|
+ System.out.println("全部推理: " + allReasoningContent);
|
|
|
|
+ System.out.println("全部回答: " + allContent);
|
|
|
|
+ System.out.println("总输出耗时: " + contentDuration + " 毫秒");
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ dsResponse.setReasoning_content(allReasoningContent.toString());
|
|
|
|
+ dsResponse.setReasoning_duration(reasoningContentDuration);
|
|
|
|
+ dsResponse.setContent(allContent.toString());
|
|
|
|
+ dsResponse.setContent_duration(contentDuration);
|
|
|
|
+ return dsResponse;
|
|
|
|
+
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
- String dataStr = (new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr();
|
|
|
|
- sseUtil.send(emitterKey, dataStr);
|
|
|
|
|
|
+ sseUtil.send((new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr());
|
|
System.out.println(e.getMessage());
|
|
System.out.println(e.getMessage());
|
|
|
|
+ dsResponse.setContent(e.getMessage());
|
|
|
|
+ return dsResponse;
|
|
}
|
|
}
|
|
-
|
|
|
|
- });
|
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ sseUtil.send((new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr());
|
|
|
|
+ System.out.println(e.getMessage());
|
|
|
|
+ dsResponse.setContent(e.getMessage());
|
|
|
|
+ return dsResponse;
|
|
|
|
+ }
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
@Override
|
|
public JSONArray getModels() {
|
|
public JSONArray getModels() {
|
|
|
|
|
|
- Long userId = SecurityUtil.getUserId();
|
|
|
|
- String emitterKey = APPLICATION_NAME + "-userid-" + Convert.toStr(userId);
|
|
|
|
-
|
|
|
|
// 调用 Deepseek API
|
|
// 调用 Deepseek API
|
|
try (CloseableHttpClient client = HttpClients.createDefault()) {
|
|
try (CloseableHttpClient client = HttpClients.createDefault()) {
|
|
|
|
|
|
@@ -267,13 +269,11 @@ public class DeepSeekClientImpl implements DeepSeekClient {
|
|
}
|
|
}
|
|
|
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
- String dataStr = (new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr();
|
|
|
|
- sseUtil.send(emitterKey, dataStr);
|
|
|
|
|
|
+ sseUtil.send((new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr());
|
|
throw new CustException(e.getMessage());
|
|
throw new CustException(e.getMessage());
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
- String dataStr = (new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr();
|
|
|
|
- sseUtil.send(emitterKey, dataStr);
|
|
|
|
|
|
+ sseUtil.send((new SseResponse(SseResponseEnum.DEEPSEEK, e.getMessage())).toJsonStr());
|
|
throw new CustException(e.getMessage());
|
|
throw new CustException(e.getMessage());
|
|
}
|
|
}
|
|
|
|
|