From b67bc9faf0eaf6db3e54b99a3f20cd399ea628d4 Mon Sep 17 00:00:00 2001 From: PL <774412461@qq.com> Date: Thu, 3 Apr 2025 16:02:41 +0800 Subject: [PATCH] =?UTF-8?q?up:=E4=BF=AE=E6=94=B9=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=B9=B6=E5=8A=A0=E5=85=A5=E6=8E=A8=E7=90=86=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E6=B5=8B=E8=AF=95=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../teammodel/ai/deepseek/DeepSeekClient.java | 4 + .../frontend/AiDeepSeekController.java | 14 ++- .../model/dto/ai/ChatCompletionReqDto.java | 4 +- .../model/entity/ai/ChatSession.java | 14 +++ .../cn/teammodel/service/DeepSeekService.java | 7 ++ .../service/impl/ChatMessageServiceImpl.java | 14 ++- .../service/impl/DeepSeekServiceImpl.java | 116 +++++++++++++++++- src/main/resources/DeepSeekConfig.properties | 3 +- 8 files changed, 161 insertions(+), 15 deletions(-) diff --git a/src/main/java/cn/teammodel/ai/deepseek/DeepSeekClient.java b/src/main/java/cn/teammodel/ai/deepseek/DeepSeekClient.java index f976587..add6534 100644 --- a/src/main/java/cn/teammodel/ai/deepseek/DeepSeekClient.java +++ b/src/main/java/cn/teammodel/ai/deepseek/DeepSeekClient.java @@ -55,6 +55,8 @@ public class DeepSeekClient { public static final String API_Key; public static final String API_Url; public static String API_Model; + //public static String API_Model_Url; + //public static String API_Model_Reasoner; @Resource private static ChatSessionRepository chatSessionRepository; @@ -72,6 +74,8 @@ public class DeepSeekClient { API_Key = props.getProperty("key"); API_Url = props.getProperty("url"); API_Model = props.getProperty("model"); + //API_Model_Url = props.getProperty("modelReasonerUrl"); + //API_Model_Reasoner = props.getProperty("modelReasoner"); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/src/main/java/cn/teammodel/controller/frontend/AiDeepSeekController.java b/src/main/java/cn/teammodel/controller/frontend/AiDeepSeekController.java index cffc411..e36c08d 100644 --- a/src/main/java/cn/teammodel/controller/frontend/AiDeepSeekController.java +++ b/src/main/java/cn/teammodel/controller/frontend/AiDeepSeekController.java @@ -146,6 +146,16 @@ public class AiDeepSeekController { SseEmitter sseEmitter = new SseEmitter(-1L); return deepSeekChatService.ChatSeeEmitterAsk(chatCompletionReqDto); } - - + /** + * 与deepseek的对话 并保存到数据库中 + * @param chatCompletionReqDto + * @return + */ + @PostMapping("chat/deep_ponder") + @ApiOperation("与 spark 的流式对话") + public SseEmitter deepPonderChatCompletion(@RequestBody @Valid ChatCompletionReqDto chatCompletionReqDto) { + String userId = SecurityUtil.getLoginUser().getId(); + SseEmitter sseEmitter = new SseEmitter(-1L); + return deepSeekChatService.ReasonerChatCompletion(chatCompletionReqDto); + } } diff --git a/src/main/java/cn/teammodel/model/dto/ai/ChatCompletionReqDto.java b/src/main/java/cn/teammodel/model/dto/ai/ChatCompletionReqDto.java index c5038ce..1f9696a 100644 --- a/src/main/java/cn/teammodel/model/dto/ai/ChatCompletionReqDto.java +++ b/src/main/java/cn/teammodel/model/dto/ai/ChatCompletionReqDto.java @@ -14,8 +14,8 @@ public class ChatCompletionReqDto { @ApiModelProperty("会话id,没有则为空") private String appId; - @ApiModelProperty("模型类型: 科大讯飞:SparkMax;DeepSeek:DeepSeek_Chat") - private String model = "SparkMax"; + @ApiModelProperty("模型类型: 科大讯飞:sparkMax;DeepSeek:deepseek-chat ;DeepSeek深度思考:deepseek-reasoner") + private String model = "sparkMax"; @NotBlank(message = "请输入消息内容") private String text; diff --git a/src/main/java/cn/teammodel/model/entity/ai/ChatSession.java b/src/main/java/cn/teammodel/model/entity/ai/ChatSession.java index f78e55f..4a8fe2a 100644 --- a/src/main/java/cn/teammodel/model/entity/ai/ChatSession.java +++ b/src/main/java/cn/teammodel/model/entity/ai/ChatSession.java @@ -39,6 +39,7 @@ public class ChatSession extends BaseItem { private String id; private String userText; private String gptText; + private String reasoningText = ""; /** * 消耗的 point */ @@ -57,5 +58,18 @@ public class ChatSession extends BaseItem { message.setModel(model); return message; } + + + public static Message of(String userText, String gptText,String model,String reasoningText) { + Message message = new Message(); + message.setId(UUID.randomUUID().toString()); + message.setCost(0); + message.setUserText(userText); + message.setGptText(gptText); + message.setCreateTime(Instant.now().toEpochMilli()); + message.setModel(model); + message.setReasoningText(reasoningText); + return message; + } } } diff --git a/src/main/java/cn/teammodel/service/DeepSeekService.java b/src/main/java/cn/teammodel/service/DeepSeekService.java index 13b95bb..f9906fe 100644 --- a/src/main/java/cn/teammodel/service/DeepSeekService.java +++ b/src/main/java/cn/teammodel/service/DeepSeekService.java @@ -31,4 +31,11 @@ public interface DeepSeekService { */ SseEmitter ChatSeeEmitterAsk(ChatCompletionReqDto chatCompletionReqDto); + /** + * 深度思考获取AI的回答 + * @param chatCompletionReqDto + * @return + */ + SseEmitter ReasonerChatCompletion(ChatCompletionReqDto chatCompletionReqDto); + } diff --git a/src/main/java/cn/teammodel/service/impl/ChatMessageServiceImpl.java b/src/main/java/cn/teammodel/service/impl/ChatMessageServiceImpl.java index 416e295..c4614c2 100644 --- a/src/main/java/cn/teammodel/service/impl/ChatMessageServiceImpl.java +++ b/src/main/java/cn/teammodel/service/impl/ChatMessageServiceImpl.java @@ -64,7 +64,7 @@ public class ChatMessageServiceImpl implements ChatMessageService { String appId = chatCompletionReqDto.getAppId(); SseEmitter sseEmitter; // - if (StringUtils.isEmpty(appId) || chatCompletionReqDto.getModel().equals("DeepSeek_Chat")) { + if (chatCompletionReqDto.getModel().equals("deepseek_chat") || chatCompletionReqDto.getModel().equals("deepseek-reasoner")) { sseEmitter = completionBySession(chatCompletionReqDto, userId); } else { sseEmitter = completionByApp(chatCompletionReqDto, false); @@ -178,7 +178,7 @@ public class ChatMessageServiceImpl implements ChatMessageService { switch (chatCompletionReqDto.getModel()) { //星火大模型 - case "SparkMax": + case "sparkMax": { SparkGptStreamListener listener = new SparkGptStreamListener(sseEmitter); // open 回调 @@ -214,7 +214,8 @@ public class ChatMessageServiceImpl implements ChatMessageService { return finalSseEmitter; } // DeepSeek 模型 - case "DeepSeek_Chat": + case "deepseek_chat": + case "deepseek-reasoner": { // OKHttp 方式请求 sseEmitter = deepSeekChatService.ChatSeeEmitterAsk(chatCompletionReqDto); @@ -234,6 +235,8 @@ public class ChatMessageServiceImpl implements ChatMessageService { private SseEmitter completionBySession(ChatCompletionReqDto chatCompletionReqDto, String userId) { String userPrompt = chatCompletionReqDto.getText(); String sessionId = chatCompletionReqDto.getSessionId(); + String mode = chatCompletionReqDto.getModel(); + ChatSession session = RepositoryUtil.findOne(chatSessionRepository.findBySessionId(sessionId), "该会话不存在"); if (!session.getUserId().equals(userId)) { @@ -243,7 +246,7 @@ public class ChatMessageServiceImpl implements ChatMessageService { SseEmitter sseEmitter = new SseEmitter(-1L); switch (chatCompletionReqDto.getModel()){ // 星火大模型 - case "SparkMax":{ + case "sparkMax":{ SparkGptStreamListener listener = new SparkGptStreamListener(sseEmitter); // open 回调 listener.setOnOpen((s) -> { @@ -279,7 +282,8 @@ public class ChatMessageServiceImpl implements ChatMessageService { return finalSseEmitter; } // DeepSeek 模型 - case "DeepSeek_Chat": + case "deepseek-chat": + case "deepseek-reasoner": { sseEmitter = deepSeekChatService.ChatSeeEmitterAsk(chatCompletionReqDto ); return sseEmitter; diff --git a/src/main/java/cn/teammodel/service/impl/DeepSeekServiceImpl.java b/src/main/java/cn/teammodel/service/impl/DeepSeekServiceImpl.java index cb77f88..853196f 100644 --- a/src/main/java/cn/teammodel/service/impl/DeepSeekServiceImpl.java +++ b/src/main/java/cn/teammodel/service/impl/DeepSeekServiceImpl.java @@ -17,8 +17,11 @@ import cn.teammodel.service.DeepSeekService; import cn.teammodel.service.DeepSeekSessionService; import cn.teammodel.utils.RepositoryUtil; import com.azure.cosmos.models.CosmosPatchOperations; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import okio.BufferedSource; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; @@ -28,12 +31,15 @@ import org.springframework.stereotype.Service; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import javax.annotation.Resource; import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + import com.fasterxml.jackson.databind.ObjectMapper; @@ -136,6 +142,7 @@ public class DeepSeekServiceImpl implements DeepSeekService { public SseEmitter ChatSeeEmitterAsk(ChatCompletionReqDto chatCompletionReqDto) { SseEmitter sseEmitter = new SseEmitter(-1L); StringBuilder strContent = new StringBuilder(); + StringBuilder strReasoning = new StringBuilder(); executorService.execute(()-> { try { log.info("流式回答开始,问题:{}", chatCompletionReqDto.getText()); @@ -150,13 +157,13 @@ public class DeepSeekServiceImpl implements DeepSeekService { question.put("content", chatCompletionReqDto.getText()); Map requestMap = new HashMap<>(); - requestMap.put("model", DeepSeekClient.API_Model); + requestMap.put("model", chatCompletionReqDto.getModel()); requestMap.put("messages", Collections.singletonList(question)); requestMap.put("stream", true); + requestMap.put("max_tokens", 1024); String requestBody = objectMapper.writeValueAsString(requestMap); httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); - StringBuilder responseBody = new StringBuilder(); try (CloseableHttpResponse response = client.execute(httpPost); BufferedReader reader = new BufferedReader( new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8))) { @@ -167,7 +174,7 @@ public class DeepSeekServiceImpl implements DeepSeekService { if ("[DONE]".equals(jsonData)) { sseEmitter.send("[DONE]"); // 会话完成,更新历史会话记录 - ChatSession.Message message = ChatSession.Message.of(chatCompletionReqDto.getText(), strContent.toString(),chatCompletionReqDto.getModel()); + ChatSession.Message message = ChatSession.Message.of(chatCompletionReqDto.getText(), strContent.toString(),chatCompletionReqDto.getModel(),strReasoning.toString()); HistoryCache.updateContext(chatCompletionReqDto.getSessionId(), message); CosmosPatchOperations options = CosmosPatchOperations.create() .replace("/updateTime", Instant.now().toEpochMilli()) @@ -181,11 +188,20 @@ public class DeepSeekServiceImpl implements DeepSeekService { .path("delta") .path("content") .asText(""); + //推理过程 + String reasoning_content = node.path("choices") + .path(0) + .path("delta") + .path("reasoning_content") + .asText(""); if (!content.isEmpty()) { - responseBody.append(content); strContent.append(content); sseEmitter.send(content); } + if (!reasoning_content.isEmpty()) { + strReasoning.append(reasoning_content); + sseEmitter.send("reasoning:"+ reasoning_content); + } } } log.info("流式回答结束,{}",question); @@ -204,6 +220,98 @@ public class DeepSeekServiceImpl implements DeepSeekService { return sseEmitter; } + /** + * 深度思考 + * @param chatCompletionReqDto + * @return + */ + @Override + public SseEmitter ReasonerChatCompletion(ChatCompletionReqDto chatCompletionReqDto) { + SseEmitter sseEmitter = new SseEmitter(-1L); + OkHttpClient client = new OkHttpClient.Builder() + .readTimeout(30, TimeUnit.SECONDS) + .build(); + MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + // 构建请求体 +// String requestBody = "{" +// + "\"model\": \"deepseek-chat\"," +// + "\"messages\": [{\"role\": \"user\", \"content\": \"请介绍一下成都\"}]," +// + "\"stream\": true," +// + "\"temperature\": 0.7" +// + "}"; + + Map question = new HashMap<>(); + question.put("role", "user"); + question.put("content", chatCompletionReqDto.getText()); + + Map requestMap = new HashMap<>(); + requestMap.put("model", chatCompletionReqDto.getModel()); + requestMap.put("messages", Collections.singletonList(question)); + requestMap.put("stream", true); + String requestBody = null; + try { + requestBody = objectMapper.writeValueAsString(requestMap); + } catch (JsonProcessingException e) { + log.error("处理用户转换问题出错", e); + sseEmitter.completeWithError(e); + } + + Request request = new Request.Builder() + .url(DeepSeekClient.API_Url) + .post(RequestBody.create(JSON, requestBody)) + .addHeader("Authorization", "Bearer " + DeepSeekClient.API_Key) + .addHeader("Accept", "text/event-stream") + .build(); + try(Response response = client.newCall(request).execute()){ + if (response.isSuccessful() && response.body() != null) { + + try (ResponseBody body = response.body()) { + if (body != null) { + BufferedSource source = body.source(); + while (!source.exhausted()) { + String line = source.readUtf8Line(); + if (line != null && line.startsWith("data: ")) { + String json = line.substring(6).trim(); + if (json.equals("[DONE]")) { + sseEmitter.send("[DONE]"); + break; + } + JsonNode node = objectMapper.readTree(json); + String content = node.path("choices") + .path(0) + .path("delta") + .path("content") + .asText(""); + String reasoning_content = node.path("choices") + .path(0) + .path("delta") + .path("reasoning_content") + .asText(""); + if (!content.isEmpty()) { + sseEmitter.send(content); + } + if (!reasoning_content.isEmpty()) { + sseEmitter.send("reasoning:"+reasoning_content); + } + } + } + log.info("流式回答结束,{}",requestBody); + sseEmitter.complete(); + } + } catch (IOException e) { + log.error("处理 Deepseek 请求时发生错误", e); + sseEmitter.completeWithError(e); + } + } + }catch (IOException e) { + log.error("处理 Deepseek 请求时发生错误", e); + sseEmitter.completeWithError(e); + } + + return sseEmitter; + } + + //region 辅助方法 /** * 新增/更新会话 diff --git a/src/main/resources/DeepSeekConfig.properties b/src/main/resources/DeepSeekConfig.properties index e556c21..0ca02e2 100644 --- a/src/main/resources/DeepSeekConfig.properties +++ b/src/main/resources/DeepSeekConfig.properties @@ -1,4 +1,3 @@ key=sk-83b5b6dd85c745cbae2572ea01c54e44 url=https://api.deepseek.com/chat/completions -model=deepseek-chat -modelReasoner=deepseek-reasoner \ No newline at end of file +model=deepseek-chat \ No newline at end of file