فهرست منبع

feat:ai模块的搭建和流程梳理

yangyi 5 ماه پیش
والد
کامیت
380efd1b4e
19فایلهای تغییر یافته به همراه1185 افزوده شده و 0 حذف شده
  1. 5 0
      ruoyi-admin/pom.xml
  2. 1 0
      ruoyi-modules/pom.xml
  3. 117 0
      ruoyi-modules/ruoyi-ai/pom.xml
  4. 19 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/TestController.java
  5. 80 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekChoice.java
  6. 46 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekFunction.java
  7. 103 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekHttpRequestData.java
  8. 108 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekHttpRequestMessage.java
  9. 91 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekHttpResponseData.java
  10. 41 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekHttpResponseFormat.java
  11. 73 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekLogprob.java
  12. 34 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekLogprobs.java
  13. 30 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekPromptTokensDetails.java
  14. 59 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekTool.java
  15. 60 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekTopLogprob.java
  16. 80 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekUsage.java
  17. 54 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/handler/AIHandler.java
  18. 129 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/handler/AIHandlerImpl.java
  19. 55 0
      ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/util/DeepSeekAIUtil.java

+ 5 - 0
ruoyi-admin/pom.xml

@@ -129,6 +129,11 @@
             <artifactId>ruoyi-customerReconciliation</artifactId>
             <version>${revision}</version>
         </dependency>
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-ai</artifactId>
+            <version>${revision}</version>
+        </dependency>
 
         <!--  工作流模块  -->
         <dependency>

+ 1 - 0
ruoyi-modules/pom.xml

@@ -23,6 +23,7 @@
         <module>ruoyi-dataManagement</module>
         <module>ruoyi-InventoryManagement</module>
         <module>ruoyi-customerReconciliation</module>
+        <module>ruoyi-ai</module>
     </modules>
 
     <artifactId>ruoyi-modules</artifactId>

+ 117 - 0
ruoyi-modules/ruoyi-ai/pom.xml

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.dromara</groupId>
+        <artifactId>ruoyi-modules</artifactId>
+        <version>5.3.0</version>
+    </parent>
+
+    <artifactId>ruoyi-ai</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+
+        <!-- 通用工具-->
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-core</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-doc</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-sms</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-mail</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-idempotent</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-log</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-excel</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-security</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-ratelimiter</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-translation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-sensitive</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-encrypt</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-tenant</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-websocket</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 19 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/controller/TestController.java

@@ -0,0 +1,19 @@
+package org.dromara.ai.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.ai.handler.AIHandler;
+import org.dromara.common.core.domain.R;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/ai")
+public class TestController {
+    private final AIHandler aiHandler;
+    @GetMapping("/get")
+    public R test(String message) {
+        return R.ok(aiHandler.handle(message));
+    }
+}

+ 80 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekChoice.java

@@ -0,0 +1,80 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+
+/**
+ * @ProjectName: serve
+ * @ClassName Choice
+ * @date 2025/2/6 13:27
+ * @Description
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekChoice implements Serializable {
+    /**
+     * 模型生成的 completion 消息。
+     */
+    private DeepSeekHttpRequestMessage message;
+    /**
+     * 该 completion 在模型生成的 completion 的选择列表中的索引。
+     */
+    private Integer index;
+    /**
+     * 模型停止生成 token 的原因。
+     *
+     * stop:模型自然停止生成,或遇到 stop 序列中列出的字符串。
+     *
+     * length :输出长度达到了模型上下文长度限制,或达到了 max_tokens 的限制。
+     *
+     * content_filter:输出内容因触发过滤策略而被过滤。
+     *
+     * insufficient_system_resource:系统推理资源不足,生成被打断。
+     */
+    private String finish_reason;
+    /**
+     * 该 choice 的对数概率信息。
+     */
+    private DeepSeekLogprobs logprobs;
+
+    public DeepSeekHttpRequestMessage getMessage() {
+        return message;
+    }
+
+    public void setMessage(DeepSeekHttpRequestMessage message) {
+        this.message = message;
+    }
+
+    public Integer getIndex() {
+        return index;
+    }
+
+    public void setIndex(Integer index) {
+        this.index = index;
+    }
+
+    public String getFinish_reason() {
+        return finish_reason;
+    }
+
+    public void setFinish_reason(String finish_reason) {
+        this.finish_reason = finish_reason;
+    }
+
+    public DeepSeekLogprobs getLogprobs() {
+        return logprobs;
+    }
+
+    public void setLogprobs(DeepSeekLogprobs logprobs) {
+        this.logprobs = logprobs;
+    }
+
+    @Override
+    public String toString() {
+        return "DeepSeekChoice{" +
+                "message=" + message +
+                ", index=" + index +
+                ", finish_reason='" + finish_reason + '\'' +
+                ", logprobs=" + logprobs +
+                '}';
+    }
+}

+ 46 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekFunction.java

@@ -0,0 +1,46 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+
+/**
+ * @ProjectName: serve
+ * @ClassName DeepSeekFunction
+ * @date 2025/2/7 12:56
+ * @Description DeepSeek的函数对象
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekFunction implements Serializable {
+    /**
+     * 模型调用的 function 名。
+     */
+    private String name;
+    /**
+     * 要调用的 function 的参数,由模型生成,格式为 JSON。请注意,模型并不总是生成有效的 JSON,并且可能会臆造出你函数模式中未定义的参数。在调用函数之前,请在代码中验证这些参数。
+     */
+    private String arguments;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getArguments() {
+        return arguments;
+    }
+
+    public void setArguments(String arguments) {
+        this.arguments = arguments;
+    }
+
+    @Override
+    public String toString() {
+        return "DeepSeekFunction{" +
+                "name='" + name + '\'' +
+                ", arguments='" + arguments + '\'' +
+                '}';
+    }
+}

+ 103 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekHttpRequestData.java

@@ -0,0 +1,103 @@
+package org.dromara.ai.domain.deepseek;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @ProjectName: serve
+* @ClassName HttpRequestData
+* @date 2025/2/6 13:04
+* @Description 调用DeepSeek模型的请求参数对象
+* @Version 1.0
+* @Author: 杨逸
+*/
+public class DeepSeekHttpRequestData {
+    /**
+     * deekseek V3模型
+     */
+    public static final String DEEPSEEK_CHAT = "deepseek-chat";
+    /**
+     * deekseek R1模型
+     */
+    public static final String DEEPSEEK_REASONER = "deepseek-reasoner";
+    private List<DeepSeekHttpRequestMessage> messages = new ArrayList<>();
+    private String model = DEEPSEEK_CHAT;
+    private Integer frequency_penalty = 0;
+    private Integer max_tokens = 1024*8;
+    private Integer presence_penalty = 0;
+    private DeepSeekHttpResponseFormat response_format = DeepSeekHttpResponseFormat.json();
+    private Boolean stream = false;
+
+    public void addMessage(DeepSeekHttpRequestMessage message) {
+        messages.add(message);
+    }
+    public List<DeepSeekHttpRequestMessage> getMessages() {
+        return messages;
+    }
+
+    public void setMessages(List<DeepSeekHttpRequestMessage> messages) {
+        this.messages = messages;
+    }
+
+    public String getModel() {
+        return model;
+    }
+
+    public void setModel(String model) {
+        this.model = model;
+    }
+
+    public Integer getFrequency_penalty() {
+        return frequency_penalty;
+    }
+
+    public void setFrequency_penalty(Integer frequency_penalty) {
+        this.frequency_penalty = frequency_penalty;
+    }
+
+    public Integer getMax_tokens() {
+        return max_tokens;
+    }
+
+    public void setMax_tokens(Integer max_tokens) {
+        this.max_tokens = max_tokens;
+    }
+
+    public Integer getPresence_penalty() {
+        return presence_penalty;
+    }
+
+    public void setPresence_penalty(Integer presence_penalty) {
+        this.presence_penalty = presence_penalty;
+    }
+
+    public DeepSeekHttpResponseFormat getResponse_format() {
+        return response_format;
+    }
+
+    public void setResponse_format(DeepSeekHttpResponseFormat response_format) {
+        this.response_format = response_format;
+    }
+
+    public Boolean getStream() {
+        return stream;
+    }
+
+    public void setStream(Boolean stream) {
+        this.stream = stream;
+    }
+
+    @Override
+    public String toString() {
+        return "HttpRequestData{" +
+                "messages=" + messages +
+                ", model='" + model + '\'' +
+                ", frequency_penalty=" + frequency_penalty +
+                ", max_tokens=" + max_tokens +
+                ", presence_penalty=" + presence_penalty +
+                ", response_format=" + response_format +
+                ", stream=" + stream +
+                '}';
+    }
+}

+ 108 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekHttpRequestMessage.java

@@ -0,0 +1,108 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @ProjectName: serve
+ * @ClassName HttpRequestMessage
+ * @date 2025/2/6 13:24
+ * @Description DeepSeek的信息对象
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekHttpRequestMessage implements Serializable {
+
+    /**
+     * 系统命令
+     * @see String
+     */
+    public static final String SYSTEM_ROLE = "system";
+    /**
+     * 用户
+     * @see String
+     */
+    public static final String USER_ROLE = "user";
+    /**
+     * AI模型
+     * @see String
+     */
+    public static final String ASSISTANT_ROLE = "assistant";
+    /**
+     * function call执行结果
+     * @see String
+     */
+    public static final String TOOL_ROLE = "tool";
+    /**
+     * 生成这条消息的角色。
+     */
+    private String role = USER_ROLE;
+    /**
+     * 该 completion 的内容。
+     */
+    private String content = "";
+    /**
+     * 仅适用于 deepseek-reasoner 模型。内容为 assistant 消息中在最终答案之前的推理内容。
+     */
+    private String reasoning_content = "";
+    /**
+     * 模型生成的 tool 调用,例如 function 调用。
+     */
+    private List<DeepSeekTool> tool_calls;
+
+
+    public DeepSeekHttpRequestMessage(String role, String content) {
+        this.role = role;
+        this.content = content;
+    }
+
+    public DeepSeekHttpRequestMessage(String content) {
+        this.content = content;
+    }
+
+    public DeepSeekHttpRequestMessage() {
+    }
+
+
+    public String getRole() {
+        return role;
+    }
+
+    public void setRole(String role) {
+        this.role = role;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public String getReasoning_content() {
+        return reasoning_content;
+    }
+
+    public void setReasoning_content(String reasoning_content) {
+        this.reasoning_content = reasoning_content;
+    }
+
+    public List<DeepSeekTool> getTool_calls() {
+        return tool_calls;
+    }
+
+    public void setTool_calls(List<DeepSeekTool> tool_calls) {
+        this.tool_calls = tool_calls;
+    }
+
+    @Override
+    public String toString() {
+        return "HttpRequestMessage{" +
+                "role='" + role + '\'' +
+                ", content='" + content + '\'' +
+                ", reasoning_content='" + reasoning_content + '\'' +
+                ", tool_calls=" + tool_calls +
+                '}';
+    }
+}

+ 91 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekHttpResponseData.java

@@ -0,0 +1,91 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @ProjectName: serve
+ * @ClassName HttpResponseData
+ * @date 2025/2/6 13:31
+ * @Description DeepSeek响应数据的对象
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekHttpResponseData implements Serializable {
+    private String id;
+    private List<DeepSeekChoice> choices;
+    private Long created;
+    private String model;
+    private String system_fingerprint;
+    private String object;
+    private DeepSeekUsage usage;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public List<DeepSeekChoice> getChoices() {
+        return choices;
+    }
+
+    public void setChoices(List<DeepSeekChoice> choices) {
+        this.choices = choices;
+    }
+
+    public Long getCreated() {
+        return created;
+    }
+
+    public void setCreated(Long created) {
+        this.created = created;
+    }
+
+    public String getModel() {
+        return model;
+    }
+
+    public void setModel(String model) {
+        this.model = model;
+    }
+
+    public String getObject() {
+        return object;
+    }
+
+    public void setObject(String object) {
+        this.object = object;
+    }
+
+    public DeepSeekUsage getUsage() {
+        return usage;
+    }
+
+    public void setUsage(DeepSeekUsage usage) {
+        this.usage = usage;
+    }
+
+    public String getSystem_fingerprint() {
+        return system_fingerprint;
+    }
+
+    public void setSystem_fingerprint(String system_fingerprint) {
+        this.system_fingerprint = system_fingerprint;
+    }
+
+    @Override
+    public String toString() {
+        return "DeepSeekHttpResponseData{" +
+                "id='" + id + '\'' +
+                ", choices=" + choices +
+                ", created=" + created +
+                ", model='" + model + '\'' +
+                ", system_fingerprint='" + system_fingerprint + '\'' +
+                ", object='" + object + '\'' +
+                ", usage=" + usage +
+                '}';
+    }
+}

+ 41 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekHttpResponseFormat.java

@@ -0,0 +1,41 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+
+/**
+ * @ProjectName: serve
+ * @ClassName HttpResponseFormat
+ * @date 2025/2/6 13:26
+ * @Description 用于指定返回数据格式的对象
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekHttpResponseFormat implements Serializable {
+    private String type;
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public static DeepSeekHttpResponseFormat json(){
+        DeepSeekHttpResponseFormat format = new DeepSeekHttpResponseFormat();
+        format.type = "json_object";
+        return format;
+    }
+    public static DeepSeekHttpResponseFormat text(){
+        DeepSeekHttpResponseFormat format = new DeepSeekHttpResponseFormat();
+        format.type = "text";
+        return format;
+    }
+
+    @Override
+    public String toString() {
+        return "HttpResponseFormat{" +
+                "type='" + type + '\'' +
+                '}';
+    }
+}

+ 73 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekLogprob.java

@@ -0,0 +1,73 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @ProjectName: serve
+ * @ClassName DeepSeekLogprob
+ * @date 2025/2/7 13:01
+ * @Description
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekLogprob implements Serializable {
+    /**
+     * 输出的 token。
+     */
+    private String token;
+    /**
+     * 该 token 的对数概率。-9999.0 代表该 token 的输出概率极小,不在 top 20 最可能输出的 token 中。
+     */
+    private Double logprob;
+    /**
+     * 一个包含该 token UTF-8 字节表示的整数列表。一般在一个 UTF-8 字符被拆分成多个 token 来表示时有用。如果 token 没有对应的字节表示,则该值为 null。
+     */
+    private List<Integer> bytes;
+    /**
+     * 一个包含在该输出位置上,输出概率 top N 的 token 的列表,以及它们的对数概率。在罕见情况下,返回的 token 数量可能少于请求参数中指定的 top_logprobs 值。
+     */
+    private List<DeepSeekTopLogprob> top_logprobs;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public Double getLogprob() {
+        return logprob;
+    }
+
+    public void setLogprob(Double logprob) {
+        this.logprob = logprob;
+    }
+
+    public List<Integer> getBytes() {
+        return bytes;
+    }
+
+    public void setBytes(List<Integer> bytes) {
+        this.bytes = bytes;
+    }
+
+    public List<DeepSeekTopLogprob> getTop_logprobs() {
+        return top_logprobs;
+    }
+
+    public void setTop_logprobs(List<DeepSeekTopLogprob> top_logprobs) {
+        this.top_logprobs = top_logprobs;
+    }
+
+    @Override
+    public String toString() {
+        return "DeepSeekLogprob{" +
+                "token='" + token + '\'' +
+                ", logprob=" + logprob +
+                ", bytes=" + bytes +
+                ", top_logprobs=" + top_logprobs +
+                '}';
+    }
+}

+ 34 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekLogprobs.java

@@ -0,0 +1,34 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @ProjectName: serve
+ * @ClassName DeepSeekLogprobs
+ * @date 2025/2/7 12:59
+ * @Description DeepSeek的logprobs对象
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekLogprobs implements Serializable {
+    /**
+     * 一个包含输出 token 对数概率信息的列表。
+     */
+    private List<DeepSeekLogprob> content;
+
+    public List<DeepSeekLogprob> getContent() {
+        return content;
+    }
+
+    public void setContent(List<DeepSeekLogprob> content) {
+        this.content = content;
+    }
+
+    @Override
+    public String toString() {
+        return "DeepSeekDeepSeekLogprobs{" +
+                "content=" + content +
+                '}';
+    }
+}

+ 30 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekPromptTokensDetails.java

@@ -0,0 +1,30 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+
+/**
+ * @ProjectName: serve
+ * @ClassName PromptTokensDetails
+ * @date 2025/2/6 14:48
+ * @Description
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekPromptTokensDetails implements Serializable {
+    private Integer cached_tokens;
+
+    public Integer getCached_tokens() {
+        return cached_tokens;
+    }
+
+    public void setCached_tokens(Integer cached_tokens) {
+        this.cached_tokens = cached_tokens;
+    }
+
+    @Override
+    public String toString() {
+        return "PromptTokensDetails{" +
+                "cached_tokens=" + cached_tokens +
+                '}';
+    }
+}

+ 59 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekTool.java

@@ -0,0 +1,59 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+
+/**
+ * @ProjectName: serve
+ * @ClassName DeepSeekTool
+ * @date 2025/2/7 12:54
+ * @Description DeepSeek的工具对象
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekTool implements Serializable {
+    /**
+     * tool 调用的 ID。
+     */
+    private String id;
+    /**
+     * tool 的类型。目前仅支持 function。
+     */
+    private String type;
+    /**
+     * 模型调用的 function。
+     */
+    private DeepSeekFunction function;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public DeepSeekFunction getFunction() {
+        return function;
+    }
+
+    public void setFunction(DeepSeekFunction function) {
+        this.function = function;
+    }
+
+    @Override
+    public String toString() {
+        return "DeepSeekTool{" +
+                "id='" + id + '\'' +
+                ", type='" + type + '\'' +
+                ", function=" + function +
+                '}';
+    }
+}

+ 60 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekTopLogprob.java

@@ -0,0 +1,60 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @ProjectName: serve
+ * @ClassName DeepSeekTopLogprob
+ * @date 2025/2/7 13:04
+ * @Description
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekTopLogprob implements Serializable {
+    /**
+     * 输出的 token。
+     */
+    private String token;
+    /**
+     * 该 token 的对数概率。-9999.0 代表该 token 的输出概率极小,不在 top 20 最可能输出的 token 中。
+     */
+    private Double logprob;
+    /**
+     * 一个包含该 token UTF-8 字节表示的整数列表。一般在一个 UTF-8 字符被拆分成多个 token 来表示时有用。如果 token 没有对应的字节表示,则该值为 null。
+     */
+    private List<Integer> bytes;
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public Double getLogprob() {
+        return logprob;
+    }
+
+    public void setLogprob(Double logprob) {
+        this.logprob = logprob;
+    }
+
+    public List<Integer> getBytes() {
+        return bytes;
+    }
+
+    public void setBytes(List<Integer> bytes) {
+        this.bytes = bytes;
+    }
+
+    @Override
+    public String toString() {
+        return "DeepSeekTopLogprob{" +
+                "token='" + token + '\'' +
+                ", logprob=" + logprob +
+                ", bytes=" + bytes +
+                '}';
+    }
+}

+ 80 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/domain/deepseek/DeepSeekUsage.java

@@ -0,0 +1,80 @@
+package org.dromara.ai.domain.deepseek;
+
+import java.io.Serializable;
+
+/**
+ * @ProjectName: serve
+ * @ClassName Usage
+ * @date 2025/2/6 13:30
+ * @Description DeepSeek token使用的表示对象
+ * @Version 1.0
+ * @Author: 杨逸
+ */
+public class DeepSeekUsage implements Serializable {
+    private Integer total_tokens;
+    private Integer prompt_tokens;
+    private Integer completion_tokens;
+    private DeepSeekPromptTokensDetails prompt_tokens_details;
+    private Integer prompt_cache_hit_tokens;
+    private Integer prompt_cache_miss_tokens;
+
+    public Integer getTotal_tokens() {
+        return total_tokens;
+    }
+
+    public void setTotal_tokens(Integer total_tokens) {
+        this.total_tokens = total_tokens;
+    }
+
+    public Integer getPrompt_tokens() {
+        return prompt_tokens;
+    }
+
+    public void setPrompt_tokens(Integer prompt_tokens) {
+        this.prompt_tokens = prompt_tokens;
+    }
+
+    public Integer getCompletion_tokens() {
+        return completion_tokens;
+    }
+
+    public void setCompletion_tokens(Integer completion_tokens) {
+        this.completion_tokens = completion_tokens;
+    }
+
+    public DeepSeekPromptTokensDetails getPrompt_tokens_details() {
+        return prompt_tokens_details;
+    }
+
+    public void setPrompt_tokens_details(DeepSeekPromptTokensDetails prompt_tokens_details) {
+        this.prompt_tokens_details = prompt_tokens_details;
+    }
+
+    public Integer getPrompt_cache_hit_tokens() {
+        return prompt_cache_hit_tokens;
+    }
+
+    public void setPrompt_cache_hit_tokens(Integer prompt_cache_hit_tokens) {
+        this.prompt_cache_hit_tokens = prompt_cache_hit_tokens;
+    }
+
+    public Integer getPrompt_cache_miss_tokens() {
+        return prompt_cache_miss_tokens;
+    }
+
+    public void setPrompt_cache_miss_tokens(Integer prompt_cache_miss_tokens) {
+        this.prompt_cache_miss_tokens = prompt_cache_miss_tokens;
+    }
+
+    @Override
+    public String toString() {
+        return "Usage{" +
+                "total_tokens=" + total_tokens +
+                ", prompt_tokens=" + prompt_tokens +
+                ", completion_tokens=" + completion_tokens +
+                ", prompt_tokens_details=" + prompt_tokens_details +
+                ", prompt_cache_hit_tokens=" + prompt_cache_hit_tokens +
+                ", prompt_cache_miss_tokens=" + prompt_cache_miss_tokens +
+                '}';
+    }
+}

+ 54 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/handler/AIHandler.java

@@ -0,0 +1,54 @@
+package org.dromara.ai.handler;
+
+import java.util.List;
+
+public interface AIHandler {
+    default Object handle(String content) {
+        //获取涉及的表名,通过LLM
+        List<String> tableNames = getTableNames(content);
+        //获取sql,通过LLM
+        String sql = getSQL(content,tableNames);
+        //查数据,通过JDBCTemplate
+        String data = getData(content, sql);
+        //处理数据,通过LLM
+        return dataHandler(content, data);
+    };
+
+    /**
+     * 获取涉及的表名
+     * @param context 用户需求上下文
+     * @return 返回涉及表名的列表
+     */
+    List<String> getTableNames(String context);
+
+    /**
+     * 获取sql
+     * @param context 用户需求上下文
+     * @param tableNames 涉及的表名
+     * @return 返回可执行的查询sql
+     */
+    String getSQL(String context, List<String> tableNames);
+
+    /**
+     * 查数据,
+     * @param context 用户需求上下文
+     * @param sql sql
+     * @return 返回csv格式
+     */
+    String getData(String context,String sql);
+
+    /**
+     * 处理数据
+     * @param context
+     * @param data
+     * @return
+     */
+    Object dataHandler(String context,String data);
+
+    /**
+     * 发送消息,调用ai
+     * @param prompt 提示词
+     * @return 返回ai生成的结果
+     */
+    String sendMessage(String prompt);
+}

+ 129 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/handler/AIHandlerImpl.java

@@ -0,0 +1,129 @@
+package org.dromara.ai.handler;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.RequiredArgsConstructor;
+import org.dromara.ai.domain.deepseek.DeepSeekHttpResponseData;
+import org.dromara.ai.util.DeepSeekAIUtil;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@RequiredArgsConstructor
+@Component
+public class AIHandlerImpl implements AIHandler {
+    private final ObjectMapper objectMapper;
+    private final JdbcTemplate jdbcTemplate;
+    @Override
+    public List<String> getTableNames(String context) {
+        return List.of();
+    }
+
+    @Override
+    public String getSQL(String context, List<String> tableNames) {
+        //todo:获取表的结构
+        String struct = "CREATE TABLE `fa_kuyou_user_order`  (\n" +
+            "  `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,\n" +
+            "  `user_id` int(11) NULL DEFAULT NULL COMMENT '用户ID',\n" +
+            "  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',\n" +
+            "  `phone` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系方式',\n" +
+            "  `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '详细地址',\n" +
+            "  `order_num` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '订单号',\n" +
+            "  `order_price` float(11, 2) NOT NULL DEFAULT 0.00 COMMENT '订单价格',\n" +
+            "  `status` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '状态:0=待审核,1=已审核,2=待发货,3=待收货,4=已收货,5=退款中,6=退款完成,7=交易取消,8=交易完成',\n" +
+            "  `remark` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',\n" +
+            "  `pay_status` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '支付状态:0=支付中,1=支付成功,2=支付失败',\n" +
+            "  `pay_time` datetime(0) NULL DEFAULT NULL COMMENT '付款时间',\n" +
+            "  `diver_type` enum('1','2') CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '配送类型:1=专业配送,2=到店自取',\n" +
+            "  `store_id` int(11) NULL DEFAULT NULL COMMENT '门店ID',\n" +
+            "  `diver_id` int(11) NULL DEFAULT NULL COMMENT '司机ID',\n" +
+            "  `diver_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '司机姓名',\n" +
+            "  `diver_phone` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '司机电话',\n" +
+            "  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',\n" +
+            "  `update_time` datetime(0) NULL DEFAULT NULL COMMENT '更新时间',\n" +
+            "  `delete_time` datetime(0) NULL DEFAULT NULL COMMENT '删除时间',\n" +
+            "  `hide` int(11) NOT NULL DEFAULT 0,\n" +
+            "  `note` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '备注',\n" +
+            "  `confirm_delivery_time` datetime(0) NULL DEFAULT NULL COMMENT '确认收货时间',\n" +
+            "  `factory_user_id` int(10) NOT NULL DEFAULT 0,\n" +
+            "  `order_status` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,\n" +
+            "  `tenant_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,\n" +
+            "  `create_dept` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,\n" +
+            "  `create_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,\n" +
+            "  `update_by` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,\n" +
+            "  PRIMARY KEY (`id`) USING BTREE,\n" +
+            "  INDEX `order_num`(`order_num`) USING BTREE\n" +
+            ") ENGINE = InnoDB AUTO_INCREMENT = 1909434245425213445 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '订单表' ROW_FORMAT = Dynamic;";
+        String prompt = new StringBuilder("用户需求:\n")
+            .append(context)
+            .append("涉及的表和结构如下:\n")
+            .append(struct)
+            .append("\n")
+            .append("当前的时间是:")
+            .append(LocalDateTime.now())
+            .append("\n")
+            .append("请根据用户需求,生成对应的完整的可用的查询数据SQL语句,最大数据量限制为一千条,返回json格式的字符串{“sql”:“”}").toString();
+        String response = sendMessage(prompt);
+        String sql = null;
+        try {
+            Map map = objectMapper.readValue(response, Map.class);
+            sql = (String) map.get("sql");
+            System.out.println("sql = " + sql);
+        } catch (JsonProcessingException e) {
+            System.err.println("response = " + response);
+            throw new RuntimeException(e);
+        }
+        return sql;
+    }
+
+    @Override
+    public String getData(String content, String sql) {
+        System.out.println("sql = \n" + sql);
+        List<Map<String,Object>> maps = jdbcTemplate.queryForList(sql);
+        StringBuilder dataStringBuilder = new StringBuilder();
+        StringBuilder titleStringBuilder = new StringBuilder();
+        for (int i = 0; i < maps.size(); i++) {
+            Map<String, Object> map = maps.get(i);
+            Set<String> set = map.keySet();
+            for (String k : set) {
+                Object v = map.get(k);
+                dataStringBuilder.append(v).append(",");
+                if (1 == i){
+                    titleStringBuilder.append(k).append(",");
+                }
+            }
+            dataStringBuilder.append("\n");
+        }
+        return titleStringBuilder + "\n" + dataStringBuilder;
+    }
+
+    @Override
+    public Object dataHandler(String context, String data) {
+        System.out.println("data = \n" + data);
+        String prompt = new StringBuilder("用户需求:\n").append(context)
+            .append("涉及的数据如下:\n").append(data)
+            .append("\n")
+            .append("请根据用户需求和数据给出结论(中文的),并选择表格或者图表(折线图、柱状图、饼图、热力图)将数据展示出来,表格状态为1;图标状态为2,表格或者图表使用svg格式的xml,以json的格式返回.{'conclusion':'','data':'','status':0}").toString();
+//            .append("请根据用户需求和数据给出结论(中文的),并选择表格或者图表(折线图、柱状图、饼图、热力图)将数据展示出来,表格状态为1,markdown格式的字符串;图标状态为2,echarts的配置对象json格式的字符串,以json的格式返回.{'conclusion':'','data':'','status':0}").toString();
+        String mes = sendMessage(prompt);
+        System.out.println("mes = " + mes);
+        Map map = null;
+        try {
+            map = objectMapper.readValue(mes, Map.class);
+        } catch (JsonProcessingException e) {
+            System.err.println("LLM处理数据返回的数据格式错误:");
+            throw new RuntimeException(e);
+        }
+        return map;
+    }
+
+    @Override
+    public String sendMessage(String prompt) {
+        DeepSeekHttpResponseData deepSeekHttpResponseData = DeepSeekAIUtil.doChat(DeepSeekAIUtil.createDefaultRequestData(prompt));
+        return  deepSeekHttpResponseData.getChoices().get(0).getMessage().getContent();
+    }
+}

+ 55 - 0
ruoyi-modules/ruoyi-ai/src/main/java/org/dromara/ai/util/DeepSeekAIUtil.java

@@ -0,0 +1,55 @@
+package org.dromara.ai.util;
+
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.dromara.ai.domain.deepseek.DeepSeekHttpRequestData;
+import org.dromara.ai.domain.deepseek.DeepSeekHttpRequestMessage;
+import org.dromara.ai.domain.deepseek.DeepSeekHttpResponseData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class DeepSeekAIUtil {
+    private static final String url = "https://api.deepseek.com/chat/completions";
+    public static ObjectMapper objectMapper = new ObjectMapper();
+    public static DeepSeekHttpResponseData doChat(DeepSeekHttpRequestData requestData){
+        String json = null;
+        try {
+            json = objectMapper.writeValueAsString(requestData);
+        } catch (JsonProcessingException e) {
+            e.printStackTrace();
+        }
+        HttpResponse httpResponse = HttpUtil.createPost(url)
+            .header("Content-Type", "application/json")
+            .header("Authorization", "Bearer sk-9260e6b06ae94516878e1e6fff403e65")
+            .body(json)
+            .execute();
+        String body = httpResponse.body();
+        int index = body.indexOf('i');
+        DeepSeekHttpResponseData httpResponseData = null;
+        if (index == 2){
+            //调用成功
+            try {
+                httpResponseData = objectMapper.readValue(body, DeepSeekHttpResponseData.class);
+            } catch (JsonProcessingException e) {
+                e.printStackTrace();
+            }
+        }else{
+            System.err.println(body);
+            throw new RuntimeException("调用DeepSeek失败");
+        }
+        return httpResponseData;
+    }
+    public static DeepSeekHttpRequestData createDefaultRequestData(String message){
+        DeepSeekHttpRequestData httpRequestData = new DeepSeekHttpRequestData();
+        List<DeepSeekHttpRequestMessage> messages = new ArrayList<>();
+        //系统预设
+//        messages.add(DeepSeekHttpRequestMessage.getPromptMessage());
+        messages.add(new DeepSeekHttpRequestMessage(DeepSeekHttpRequestMessage.USER_ROLE, message));
+        httpRequestData.setMessages(messages);
+        return httpRequestData;
+    }
+}