Browse Source

对接Deepseek api

tsurumure 6 months ago
parent
commit
27e5a67bd2

+ 5 - 5
pom.xml

@@ -389,11 +389,11 @@
         </dependency>
 
         <!-- Deepseek -->
-        <dependency>
-            <groupId>io.github.pig-mesh.ai</groupId>
-            <artifactId>deepseek-spring-boot-starter</artifactId>
-            <version>1.4.3</version>
-        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>io.github.pig-mesh.ai</groupId>-->
+<!--            <artifactId>deepseek-spring-boot-starter</artifactId>-->
+<!--            <version>1.4.3</version>-->
+<!--        </dependency>-->
 
     </dependencies>
 

+ 3 - 3
src/main/java/com/backendsys/modules/TestController.java

@@ -17,9 +17,9 @@ import cn.afterturn.easypoi.word.WordExportUtil;
 import com.backendsys.utils.MD5Util;
 
 import com.tencentcloudapi.common.Credential;
-import io.github.pigmesh.ai.deepseek.core.DeepSeekClient;
-import io.github.pigmesh.ai.deepseek.core.OpenAiHttpException;
-import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse;
+//import io.github.pigmesh.ai.deepseek.core.DeepSeekClient;
+//import io.github.pigmesh.ai.deepseek.core.OpenAiHttpException;
+//import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse;
 import jakarta.servlet.ServletContext;
 import org.apache.poi.xwpf.usermodel.XWPFDocument;
 import org.redisson.api.*;

+ 15 - 63
src/main/java/com/backendsys/modules/ai/deepSeek/controller/DeepSeekController.java

@@ -1,30 +1,18 @@
 package com.backendsys.modules.ai.deepSeek.controller;
 
-import cn.hutool.core.convert.Convert;
-import com.backendsys.modules.ai.deepSeek.entity.DeepSeekParam;
+import com.backendsys.modules.ai.deepSeek.entity.DSParam;
+import com.backendsys.modules.ai.deepSeek.utils.DeepSeekClient;
 import com.backendsys.modules.ai.deepSeek.utils.OllamaUtil;
 import com.backendsys.modules.common.config.security.utils.SecurityUtil;
 import com.backendsys.modules.common.utils.Result;
-import com.backendsys.modules.sse.entity.SseResponse;
-import com.backendsys.modules.sse.entity.SseResponseEnum;
-import com.backendsys.modules.sse.utils.SseUtil;
-import io.github.pigmesh.ai.deepseek.core.DeepSeekClient;
-import io.github.pigmesh.ai.deepseek.core.chat.AssistantMessage;
-import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionChoice;
-import io.github.pigmesh.ai.deepseek.core.chat.ChatCompletionResponse;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
-import reactor.core.publisher.Flux;
-
-import java.util.List;
-import java.util.concurrent.CompletableFuture;
 
 @Validated
 @RestController
@@ -34,65 +22,29 @@ public class DeepSeekController {
     @Autowired
     private OllamaUtil ollamaUtil;
 
-    @Value("${spring.application.name}")
-    private String APPLICATION_NAME;
-    @Autowired
-    private SseUtil sseUtil;
     @Autowired
     private DeepSeekClient deepSeekClient;
 
-
     /**
-     * deepseek4j
-     * https://javaai.pig4cloud.com/deepseek/quickstart
-     *
      * deepseek开放平台
      * https://platform.deepseek.com/
      */
 
-    @Operation(summary = "提问")
+    @Operation(summary = "Deepseek 提问 (API)")
     @PreAuthorize("@sr.hasPermission('101')")
-    @PostMapping("/api/deepSeek/ask")
-    public Result ask(@Validated @RequestBody DeepSeekParam param) {
-
-        Long userId = SecurityUtil.getUserId();
-        String emitterKey = APPLICATION_NAME + "-userid-" + Convert.toStr(userId);
-
-
-        // https://api-docs.deepseek.com/zh-cn/
-
-        // 还是手搓api 吧,不要用 SDK
-
-
-//        // 创建一个 CompletableFuture 来执行异步任务
-//        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
-//
-//            System.out.println("question: " + param.getQuestion());
-//
-//            System.out.println(flux);
-//
-//            flux.doOnNext(i -> {
-//                System.out.println("i.");
-//                List<ChatCompletionChoice> contentList = i.choices();
-//                contentList.stream().forEach(content -> {
-//                    AssistantMessage message = content.message();
-//                    System.out.println("message: " + message.content());
-//
-//                    // [SSE] 发送消息
-//                    String dataStr = (new SseResponse(SseResponseEnum.DEEPSEEK, message.content())).toJsonStr();
-//                    sseUtil.send(emitterKey, dataStr);
-//
-//                });
-//                // 其他ELT流程
-//            }).doOnError(e -> {
-//                System.out.println(e.getMessage());
-//            });
-//
-//        });
-
-//        ollamaUtil.chatDeepSeek("deepseek-r1:1.5b", param.getQuestion(), userId);
-//        ollamaUtil.chatDeepSeek("deepseek-r1:7b", param.getQuestion(), userId);
+    @PostMapping("/api/deepSeek/chat")
+    public Result chat(@Validated @RequestBody DSParam param) {
+//        deepSeekClient.chatCompletion(param.getPrompt(), "deepseek-chat");  // 对话模型
+        deepSeekClient.chatCompletion(param.getPrompt(), "deepseek-reasoner");  // 推理模型
+        return Result.success();
+    }
 
+    @Operation(summary = "Deepseek 提问 (本地部署)")
+    @PreAuthorize("@sr.hasPermission('101')")
+    @PostMapping("/api/deepSeek/chatLocal")
+    public Result chatLocal(@Validated @RequestBody DSParam param) {
+        ollamaUtil.chatDeepSeek("deepseek-r1:1.5b", param.getPrompt(), SecurityUtil.getUserId());
+        // ollamaUtil.chatDeepSeek("deepseek-r1:7b", param.getPrompt(), SecurityUtil.getUserId());
         return Result.success();
     }
 

+ 11 - 0
src/main/java/com/backendsys/modules/ai/deepSeek/entity/DSContent.java

@@ -0,0 +1,11 @@
+package com.backendsys.modules.ai.deepSeek.entity;
+
+import lombok.Data;
+
+@Data
+public class DSContent {
+
+    private String model;
+    private String content;
+
+}

+ 3 - 3
src/main/java/com/backendsys/modules/ai/deepSeek/entity/DeepSeekParam.java → src/main/java/com/backendsys/modules/ai/deepSeek/entity/DSParam.java

@@ -4,9 +4,9 @@ import jakarta.validation.constraints.NotEmpty;
 import lombok.Data;
 
 @Data
-public class DeepSeekParam {
+public class DSParam {
 
-    @NotEmpty(message="问题不能为空")
-    private String question;
+    @NotEmpty(message="提示词不能为空")
+    private String prompt;
 
 }

+ 15 - 0
src/main/java/com/backendsys/modules/ai/deepSeek/entity/DSRequest.java

@@ -0,0 +1,15 @@
+package com.backendsys.modules.ai.deepSeek.entity;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class DSRequest {
+
+    private String model;
+    private List<DSRequestMessage> messages;
+    private Boolean stream;
+
+}

+ 11 - 0
src/main/java/com/backendsys/modules/ai/deepSeek/entity/DSRequestMessage.java

@@ -0,0 +1,11 @@
+package com.backendsys.modules.ai.deepSeek.entity;
+
+import lombok.Data;
+
+@Data
+public class DSRequestMessage {
+
+    private String role;
+    private String content;
+
+}

+ 223 - 0
src/main/java/com/backendsys/modules/ai/deepSeek/utils/DeepSeekClient.java

@@ -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());
+            }
+
+        });
+
+    }
+
+}

+ 3 - 0
src/main/java/com/backendsys/modules/ai/deepSeek/utils/OllamaUtil.java

@@ -81,6 +81,9 @@ public class OllamaUtil {
                 {
                     String line;
                     while ((line = reader.readLine()) != null) {
+
+                        System.out.println(line);
+
                         // 每行数据可以是一个JSON对象,根据实际情况处理
                         JSONObject resJson = JSONObject.parseObject(line);
                         String responseContent = resJson.getString("response");

+ 2 - 1
src/main/resources/application-local.yml

@@ -167,4 +167,5 @@ ai:
       domain: http://localhost:11434
 
 deepseek:
-  api-key: sk-7614064f7e3343d786a2240672dd4d39
+  url: https://api.deepseek.com
+  api-key: sk-6078a4ceee5443128ce74a23538dd54e

+ 2 - 1
src/main/resources/application-prod.yml

@@ -159,4 +159,5 @@ ai:
       domain: http://localhost:11434
 
 deepseek:
-  api-key: sk-7614064f7e3343d786a2240672dd4d39
+  url: https://api.deepseek.com
+  api-key: sk-6078a4ceee5443128ce74a23538dd54e