This commit is contained in:
2025-10-17 10:11:04 +08:00
commit 9618d5cfa1
2012 changed files with 163764 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
<?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">
<parent>
<artifactId>jeelowcode-framework</artifactId>
<groupId>com.jeelowcode</groupId>
<version>${jeelowcode.version}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jeelowcode-ai</artifactId>
<name>${project.artifactId}</name>
<version>${jeelowcode.version}</version>
<packaging>jar</packaging>
<description> JeeLowCode低代码平台 - ai模块 </description>
<dependencies>
<dependency>
<groupId>com.jeelowcode</groupId>
<artifactId>jeelowcode-utils</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,97 @@
package com.jeelowcode.framework.ai;
import com.jeelowcode.framework.ai.config.DeepSeekConfig;
import com.jeelowcode.framework.ai.config.KimiConfig;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseRequest;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseResponse;
import com.jeelowcode.framework.ai.platform.chat.tool.ChatMessage;
import com.jeelowcode.framework.ai.platform.chat.tool.Choice;
import com.jeelowcode.framework.ai.service.Configuration;
import com.jeelowcode.framework.ai.service.IChatService;
import com.jeelowcode.framework.ai.service.PlatformType;
import com.jeelowcode.framework.ai.service.factor.AiService;
import com.jeelowcode.framework.ai.template.CreateTableTemplate;
import com.jeelowcode.framework.utils.utils.FuncBase;
import lombok.Data;
import okhttp3.OkHttpClient;
import org.junit.Before;
import org.junit.Test;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class TestUtils {
private IChatService chatService;
@Before
public void test_init() throws NoSuchAlgorithmException, KeyManagementException {
KimiConfig kimiConfig = new KimiConfig();
kimiConfig.setApiKey("sk-EE98wvDeTFvxRcBxDQmWhKQlewkGFIrfyCxvyNxDj8Vaf701");
DeepSeekConfig deepseekConfig = new DeepSeekConfig();
deepseekConfig.setApiKey("sk-b31fcf4e27bf45fbb65ace5bbc627d5d");
Configuration configuration = new Configuration();
configuration.setKimiConfig(kimiConfig);
configuration.setDeepSeekConfig(deepseekConfig);
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
.connectTimeout(300, TimeUnit.SECONDS)
.writeTimeout(300, TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS)
.build();
configuration.setOkHttpClient(okHttpClient);
AiService aiService = new AiService(configuration);
chatService = aiService.getChatService(PlatformType.KIMI);
//chatService = aiService.getChatService(PlatformType.DEEPSEEK);
}
@Test
public void test_chatCompletions_common() throws Exception {
long time1 = System.currentTimeMillis();
ChatBaseRequest chatBaseRequest = ChatBaseRequest.builder()
//.model("deepseek-chat")
.messages(CreateTableTemplate.getMessageTemplateList())
.message(ChatMessage.withUser("物流信息,其中要有,物流单号,收件人,寄件人"))
.build();
ChatBaseResponse chatBaseResponse = chatService.chatCompletion(chatBaseRequest);
List<Choice> choices = chatBaseResponse.getChoices();
if(FuncBase.isEmpty(choices)){
return;
}
Choice choice = choices.get(0);
ChatMessage message = choice.getMessage();
if(FuncBase.isEmpty(message)){
return;
}
String content = message.getContent();
System.out.println("content===="+content);
CreateTableTemplate.RspModel rspModel = FuncBase.json2Bean(content, CreateTableTemplate.RspModel.class);
System.out.println(rspModel);
long time2 = System.currentTimeMillis();
System.out.println("time2-time1="+(time2-time1));
}
}

View File

@@ -0,0 +1,19 @@
package com.jeelowcode.framework.ai.config;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeepSeekConfig {
private String apiHost = "https://api.deepseek.com/";
private String apiKey = "";
private String model="deepseek-chat";
private String chatCompletionUrl = "chat/completions";
}

View File

@@ -0,0 +1,15 @@
package com.jeelowcode.framework.ai.config;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class KimiConfig {
private String apiHost = "https://api.moonshot.cn/";
private String apiKey = "";
private String model="moonshot-v1-8k";
private String chatCompletionUrl = "v1/chat/completions";
}

View File

@@ -0,0 +1,9 @@
package com.jeelowcode.framework.ai.convert;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseRequest;
public interface ParameterConvert<T> {
T convertParam(ChatBaseRequest chatBaseRequest);
}

View File

@@ -0,0 +1,10 @@
package com.jeelowcode.framework.ai.convert;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseResponse;
public interface ResultConvert<T> {
//统一格式输出
ChatBaseResponse convertResponse(T t);
}

View File

@@ -0,0 +1,118 @@
package com.jeelowcode.framework.ai.platform.chat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jeelowcode.framework.ai.platform.chat.tool.ChatMessage;
import lombok.*;
import lombok.experimental.SuperBuilder;
import java.util.List;
import java.util.Map;
@Data
@SuperBuilder
@NoArgsConstructor
public class ChatBaseRequest {
private String model;
/**
* 消息内容
*/
@Singular
private List<ChatMessage> messages;
/**
* 介于 -2.0 和 2.0 之间的数字。如果该值为正,那么新 token 会根据其在已有文本中的出现频率受到相应的惩罚,降低模型重复相同内容的可能性。
*/
@Builder.Default
@JsonProperty("frequency_penalty")
private Float frequencyPenalty = 0f;
/**
* 采样温度,介于 0 和 2 之间。更高的值,如 0.8,会使输出更随机,而更低的值,如 0.2,会使其更加集中和确定。
* 我们通常建议可以更改这个值或者更改 top_p但不建议同时对两者进行修改。
*/
@Builder.Default
private Float temperature = 1f;
/**
* 作为调节采样温度的替代方案,模型会考虑前 top_p 概率的 token 的结果。所以 0.1 就意味着只有包括在最高 10% 概率中的 token 会被考虑。
* 我们通常建议修改这个值或者更改 temperature但不建议同时对两者进行修改。
*/
@Builder.Default
@JsonProperty("top_p")
private Float topP = 1f;
/**
* 限制一次请求中模型生成 completion 的最大 token 数。输入 token 和输出 token 的总长度受模型的上下文长度的限制。
*/
@JsonProperty("max_tokens")
private Integer maxTokens;
/**
* 辅助属性
*/
@JsonIgnore
private List<String> functions;
/**
* 控制模型调用 tool 的行为。
* none 意味着模型不会调用任何 tool而是生成一条消息。
* auto 意味着模型可以选择生成一条消息或调用一个或多个 tool。
* 当没有 tool 时,默认值为 none。如果有 tool 存在,默认值为 auto。
*/
@JsonProperty("tool_choice")
private String toolChoice;
@Builder.Default
@JsonProperty("parallel_tool_calls")
private Boolean parallelToolCalls = true;
/**
* 一个 object指定模型必须输出的格式。
*
* 设置为 { "type": "json_object" } 以启用 JSON 模式,该模式保证模型生成的消息是有效的 JSON。
*
* 注意: 使用 JSON 模式时,你还必须通过系统或用户消息指示模型生成 JSON。
* 否则,模型可能会生成不断的空白字符,直到生成达到令牌限制,从而导致请求长时间运行并显得“卡住”。
* 此外,如果 finish_reason="length",这表示生成超过了 max_tokens 或对话超过了最大上下文长度,消息内容可能会被部分截断。
*/
@JsonProperty("response_format")
private Object responseFormat;
private String user;
@Builder.Default
private Integer n = 1;
/**
* 在遇到这些词时API 将停止生成更多的 token。
*/
private List<String> stop;
/**
* 介于 -2.0 和 2.0 之间的数字。如果该值为正,那么新 token 会根据其是否已在已有文本中出现受到相应的惩罚,从而增加模型谈论新主题的可能性。
*/
@Builder.Default
@JsonProperty("presence_penalty")
private Float presencePenalty = 0f;
@JsonProperty("logit_bias")
private Map logitBias;
/**
* 是否返回所输出 token 的对数概率。如果为 true则在 message 的 content 中返回每个输出 token 的对数概率。
*/
@Builder.Default
private Boolean logprobs = false;
/**
* 一个介于 0 到 20 之间的整数 N指定每个输出位置返回输出概率 top N 的 token且返回这些 token 的对数概率。指定此参数时logprobs 必须为 true。
*/
@JsonProperty("top_logprobs")
private Integer topLogprobs;
}

View File

@@ -0,0 +1,46 @@
package com.jeelowcode.framework.ai.platform.chat;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jeelowcode.framework.ai.platform.chat.tool.Choice;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
public class ChatBaseResponse {
/**
* 该对话的唯一标识符。
*/
private String id;
/**
* 对象的类型, 其值为 chat.completion 或 chat.completion.chunk
*/
private String object;
/**
* 创建聊天完成时的 Unix 时间戳(以秒为单位)。
*/
private Long created;
/**
* 生成该 completion 的模型名。
*/
private String model;
/**
* 该指纹代表模型运行时使用的后端配置。
*/
@JsonProperty("system_fingerprint")
private String systemFingerprint;
/**
* 模型生成的 completion 的选择列表。
*/
private List<Choice> choices;
}

View File

@@ -0,0 +1,34 @@
package com.jeelowcode.framework.ai.platform.chat;
import com.jeelowcode.framework.utils.utils.FuncBase;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Optional;
@Getter
@AllArgsConstructor
public enum ChatMessageType {
SYSTEM("system"),
USER("user"),
ASSISTANT("assistant"),
TOOL("tool"),
;
private final String role;
public static ChatMessageType getByRole(String role) {
// 使用流来查找匹配的
Optional<ChatMessageType> matchingEnum = Arrays.stream(ChatMessageType.values())
.filter(chatMessageType -> FuncBase.equals(chatMessageType.getRole(), role))
.findFirst(); // findFirst()会返回第一个匹配的元素或者如果找不到则返回一个空的Optional
// 检查是否找到了匹配的枚举项
if (matchingEnum.isPresent()) {
return matchingEnum.get();
}
return null;
}
}

View File

@@ -0,0 +1,99 @@
package com.jeelowcode.framework.ai.platform.chat.deepseek;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jeelowcode.framework.ai.config.DeepSeekConfig;
import com.jeelowcode.framework.ai.convert.ParameterConvert;
import com.jeelowcode.framework.ai.convert.ResultConvert;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseRequest;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseResponse;
import com.jeelowcode.framework.ai.platform.chat.deepseek.entity.DeepSeekChatRequest;
import com.jeelowcode.framework.ai.platform.chat.deepseek.entity.DeepSeekChatResponse;
import com.jeelowcode.framework.ai.platform.chat.kimi.entity.KimiChatRequest;
import com.jeelowcode.framework.ai.service.Configuration;
import com.jeelowcode.framework.ai.service.IChatService;
import com.jeelowcode.framework.utils.utils.FuncBase;
import okhttp3.*;
public class DeepSeekChatService implements IChatService, ParameterConvert<DeepSeekChatRequest>, ResultConvert<DeepSeekChatResponse> {
private final DeepSeekConfig deepSeekConfig;
private final OkHttpClient okHttpClient;
public DeepSeekChatService(Configuration configuration) {
this.deepSeekConfig = configuration.getDeepSeekConfig();
this.okHttpClient = configuration.getOkHttpClient();
}
@Override
public DeepSeekChatRequest convertParam(ChatBaseRequest chatBaseRequest) {
DeepSeekChatRequest deepSeekChatRequest = DeepSeekChatRequest.builder()
.messages(chatBaseRequest.getMessages())
.frequencyPenalty(chatBaseRequest.getFrequencyPenalty())
.maxTokens(chatBaseRequest.getMaxTokens())
.presencePenalty(chatBaseRequest.getPresencePenalty())
.responseFormat(chatBaseRequest.getResponseFormat())
.stop(chatBaseRequest.getStop())
.temperature(chatBaseRequest.getTemperature() / 2)
.topP(chatBaseRequest.getTopP())
.functions(chatBaseRequest.getFunctions())
.toolChoice(chatBaseRequest.getToolChoice())
.build();
return deepSeekChatRequest;
}
@Override
public ChatBaseResponse convertResponse(DeepSeekChatResponse deepSeekChatResponse) {
ChatBaseResponse chatBaseResponse = new ChatBaseResponse();
chatBaseResponse.setId(deepSeekChatResponse.getId());
chatBaseResponse.setObject(deepSeekChatResponse.getObject());
chatBaseResponse.setCreated(deepSeekChatResponse.getCreated());
chatBaseResponse.setModel(deepSeekChatResponse.getModel());
chatBaseResponse.setSystemFingerprint(deepSeekChatResponse.getSystemFingerprint());
chatBaseResponse.setChoices(deepSeekChatResponse.getChoices());
return chatBaseResponse;
}
@Override
public ChatBaseResponse chatCompletion(String baseUrl, String apiKey, ChatBaseRequest chatBaseRequest) throws Exception {
baseUrl = FuncBase.isEmpty(baseUrl) ? deepSeekConfig.getApiHost() : baseUrl;
apiKey = FuncBase.isEmpty(apiKey) ? deepSeekConfig.getApiKey() : apiKey;
// 转换 请求参数
DeepSeekChatRequest deepSeekChatRequest = this.convertParam(chatBaseRequest);
deepSeekChatRequest.setModel(deepSeekConfig.getModel());
// 构造请求
ObjectMapper mapper = new ObjectMapper();
String requestString = mapper.writeValueAsString(deepSeekChatRequest);
Request request = new Request.Builder()
.header("Authorization", "Bearer " + apiKey)
.url(baseUrl + deepSeekConfig.getChatCompletionUrl())
.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), requestString))
.build();
Response execute = okHttpClient.newCall(request).execute();
if (FuncBase.isEmpty(execute)) {
return null;
}
if (FuncBase.isEmpty(execute.body())) {
return null;
}
//转为统一格式
String rsp = execute.body().string();
DeepSeekChatResponse moonshotChatCompletionResponse = FuncBase.json2Bean(rsp, DeepSeekChatResponse.class);
return this.convertResponse(moonshotChatCompletionResponse);
}
@Override
public ChatBaseResponse chatCompletion(ChatBaseRequest chatBaseRequest) throws Exception {
return this.chatCompletion(null, null, chatBaseRequest);
}
}

View File

@@ -0,0 +1,23 @@
package com.jeelowcode.framework.ai.platform.chat.deepseek.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseRequest;
import com.jeelowcode.framework.ai.platform.chat.tool.ChatMessage;
import lombok.*;
import lombok.experimental.SuperBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Data
@SuperBuilder
@NoArgsConstructor
public class DeepSeekChatRequest extends ChatBaseRequest {
}

View File

@@ -0,0 +1,16 @@
package com.jeelowcode.framework.ai.platform.chat.deepseek.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseResponse;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 聊天返回结果
*/
@Data
public class DeepSeekChatResponse extends ChatBaseResponse {
}

View File

@@ -0,0 +1,97 @@
package com.jeelowcode.framework.ai.platform.chat.kimi;
import com.jeelowcode.framework.ai.config.KimiConfig;
import com.jeelowcode.framework.ai.convert.ParameterConvert;
import com.jeelowcode.framework.ai.convert.ResultConvert;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseRequest;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseResponse;
import com.jeelowcode.framework.ai.platform.chat.kimi.entity.KimiChatRequest;
import com.jeelowcode.framework.ai.platform.chat.kimi.entity.KimiChatResponse;
import com.jeelowcode.framework.ai.service.Configuration;
import com.jeelowcode.framework.ai.service.IChatService;
import com.jeelowcode.framework.utils.utils.FuncBase;
import okhttp3.*;
public class KimiChatService implements IChatService, ParameterConvert<KimiChatRequest>, ResultConvert<KimiChatResponse> {
private final KimiConfig kimiConfig;
private final OkHttpClient okHttpClient;
public KimiChatService(Configuration configuration) {
this.kimiConfig = configuration.getKimiConfig();
this.okHttpClient = configuration.getOkHttpClient();
}
@Override
public KimiChatRequest convertParam(ChatBaseRequest chatBaseRequest) {
KimiChatRequest kimiChatRequest = KimiChatRequest.builder()
.messages(chatBaseRequest.getMessages())
.frequencyPenalty(chatBaseRequest.getFrequencyPenalty())
.maxTokens(chatBaseRequest.getMaxTokens())
.presencePenalty(chatBaseRequest.getPresencePenalty())
.responseFormat(chatBaseRequest.getResponseFormat())
.stop(chatBaseRequest.getStop())
.temperature(chatBaseRequest.getTemperature() / 2)
.topP(chatBaseRequest.getTopP())
.functions(chatBaseRequest.getFunctions())
.toolChoice(chatBaseRequest.getToolChoice())
.build();
return kimiChatRequest;
}
/**
* 回复内容参数转换
*/
@Override
public ChatBaseResponse convertResponse(KimiChatResponse kimiChatResponse) {
ChatBaseResponse chatBaseResponse = new ChatBaseResponse();
chatBaseResponse.setId(kimiChatResponse.getId());
chatBaseResponse.setObject(kimiChatResponse.getObject());
chatBaseResponse.setCreated(kimiChatResponse.getCreated());
chatBaseResponse.setModel(kimiChatResponse.getModel());
chatBaseResponse.setChoices(kimiChatResponse.getChoices());
return chatBaseResponse;
}
/**
* 同步请求
*/
@Override
public ChatBaseResponse chatCompletion(String baseUrl, String apiKey, ChatBaseRequest chatBaseRequest) throws Exception {
baseUrl = FuncBase.isEmpty(baseUrl) ? kimiConfig.getApiHost() : baseUrl;
apiKey = FuncBase.isEmpty(apiKey) ? kimiConfig.getApiKey() : apiKey;
// 转换 请求参数
KimiChatRequest kimiChatRequest = this.convertParam(chatBaseRequest);
kimiChatRequest.setModel(kimiConfig.getModel());
// 构造请求
String requestString = FuncBase.json2Str(kimiChatRequest);
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), requestString);
Request request = new Request.Builder()
.header("Authorization", "Bearer " + apiKey)
.url(baseUrl+ kimiConfig.getChatCompletionUrl())
.post(requestBody)
.build();
Response execute = okHttpClient.newCall(request).execute();
if (FuncBase.isEmpty(execute)) {
return null;
}
if (FuncBase.isEmpty(execute.body())) {
return null;
}
//转为统一格式
String rsp = execute.body().string();
KimiChatResponse kimiChatResponse = FuncBase.json2Bean(rsp, KimiChatResponse.class);
return this.convertResponse(kimiChatResponse);
}
@Override
public ChatBaseResponse chatCompletion(ChatBaseRequest chatBaseRequest) throws Exception {
return this.chatCompletion(null, null, chatBaseRequest);
}
}

View File

@@ -0,0 +1,17 @@
package com.jeelowcode.framework.ai.platform.chat.kimi.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseRequest;
import lombok.*;
import lombok.experimental.SuperBuilder;
/**
*
*/
@Data
@SuperBuilder
public class KimiChatRequest extends ChatBaseRequest {
}

View File

@@ -0,0 +1,18 @@
package com.jeelowcode.framework.ai.platform.chat.kimi.entity;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseResponse;
import com.jeelowcode.framework.ai.platform.chat.tool.Choice;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
public class KimiChatResponse extends ChatBaseResponse {
}

View File

@@ -0,0 +1,61 @@
package com.jeelowcode.framework.ai.platform.chat.tool;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jeelowcode.framework.ai.platform.chat.ChatMessageType;
import lombok.*;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.jeelowcode.framework.ai.platform.chat.ChatMessageType;
import lombok.*;
@Data
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ChatMessage {
private String content;
private String role;
private String name;
private String refusal;
@JsonProperty("reasoning_content")
private String reasoningContent;
@JsonProperty("tool_call_id")
private String toolCallId;
public ChatMessage(String userMessage) {
this.role = ChatMessageType.USER.getRole();
this.content =userMessage;
}
public ChatMessage(ChatMessageType role, String message) {
this.role = role.getRole();
this.content = message;
}
public ChatMessage(String role, String message) {
this.role = role;
this.content = message;
}
public static ChatMessage withSystem(String content) {
return new ChatMessage(ChatMessageType.SYSTEM, content);
}
public static ChatMessage withUser(String content) {
return new ChatMessage(ChatMessageType.USER, content);
}
public static ChatMessage withAssistant(String content) {
return new ChatMessage(ChatMessageType.ASSISTANT, content);
}
}

View File

@@ -0,0 +1,34 @@
package com.jeelowcode.framework.ai.platform.chat.tool;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Choice {
private Integer index;
private ChatMessage delta;
private ChatMessage message;
private Object logprobs;
/**
* 模型停止生成 token 的原因。
*
* [stop, length, content_filter, tool_calls, insufficient_system_resource]
*
* stop模型自然停止生成或遇到 stop 序列中列出的字符串。
* length输出长度达到了模型上下文长度限制或达到了 max_tokens 的限制。
* content_filter输出内容因触发过滤策略而被过滤。
* tool_calls函数调用。
* insufficient_system_resource系统推理资源不足生成被打断。
*
*/
@JsonProperty("finish_reason")
private String finishReason;
}

View File

@@ -0,0 +1,87 @@
package com.jeelowcode.framework.ai.platform.chat.tool;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.*;
import java.util.ArrayList;
import java.util.List;
@ToString
public class Content {
private String text; // 纯文本时使用
private List<MultiModal> multiModals; // 多模态时使用
// 纯文本构造方法
public static Content ofText(String text) {
Content instance = new Content();
instance.text = text;
return instance;
}
// 多模态构造方法
public static Content ofMultiModals(List<MultiModal> parts) {
Content instance = new Content();
instance.multiModals = parts;
return instance;
}
// 序列化逻辑
@JsonValue
public Object toJson() {
if (text != null) {
return text; // 直接返回
} else if (multiModals != null) {
return multiModals;
}
throw new IllegalStateException("Invalid content state");
}
public String getText() { return text; }
public List<MultiModal> getMultiModals() { return multiModals; }
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class MultiModal {
private String type = Type.TEXT.type;
private String text;
@JsonProperty("image_url")
private ImageUrl imageUrl;
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class ImageUrl {
private String url;
}
@Getter
@AllArgsConstructor
public enum Type {
TEXT("text", "文本类型"),
IMAGE_URL("image_url", "图片类型可以为url或者base64"),
;
private final String type;
private final String info;
}
public static List<MultiModal> withMultiModal(String text, String... imageUrl) {
List<MultiModal> messages = new ArrayList<>();
messages.add(new MultiModal(Type.TEXT.getType(), text, null));
for (String url : imageUrl) {
messages.add(new MultiModal(Type.IMAGE_URL.getType(), null, new ImageUrl(url)));
}
return messages;
}
}
}

View File

@@ -0,0 +1,89 @@
package com.jeelowcode.framework.ai.properties;
import com.jeelowcode.framework.ai.config.DeepSeekConfig;
import com.jeelowcode.framework.ai.config.KimiConfig;
import com.jeelowcode.framework.ai.service.factor.AiService;
import okhttp3.OkHttpClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableConfigurationProperties({
DeepSeekConfigProperties.class,
KimiConfigProperties.class})
public class AiConfigAutoConfiguration {
private final DeepSeekConfigProperties deepSeekConfigProperties;
private final KimiConfigProperties kimiConfigProperties;
private com.jeelowcode.framework.ai.service.Configuration configuration = new com.jeelowcode.framework.ai.service.Configuration();
public AiConfigAutoConfiguration(DeepSeekConfigProperties deepSeekConfigProperties, KimiConfigProperties kimiConfigProperties) {
this.deepSeekConfigProperties = deepSeekConfigProperties;
this.kimiConfigProperties = kimiConfigProperties;
}
@Bean
public AiService aiService() {
return new AiService(configuration);
}
@PostConstruct
private void init() {
initOkHttp();
initDeepSeekConfig();
initMoonshotConfig();
}
private void initOkHttp() {
// 开启 Http 客户端
OkHttpClient.Builder okHttpBuilder = new OkHttpClient
.Builder()
.connectTimeout(300, TimeUnit.SECONDS)
.writeTimeout(300,TimeUnit.SECONDS)
.readTimeout(300, TimeUnit.SECONDS);
OkHttpClient okHttpClient = okHttpBuilder.build();
configuration.setOkHttpClient(okHttpClient);
}
/**
* 初始化DeepSeek 配置信息
*/
private void initDeepSeekConfig(){
DeepSeekConfig deepSeekConfig = new DeepSeekConfig();
deepSeekConfig.setApiHost(deepSeekConfigProperties.getApiHost());
deepSeekConfig.setApiKey(deepSeekConfigProperties.getApiKey());
deepSeekConfig.setModel(deepSeekConfigProperties.getModel());
deepSeekConfig.setChatCompletionUrl(deepSeekConfigProperties.getChatCompletionUrl());
configuration.setDeepSeekConfig(deepSeekConfig);
}
/**
* 初始化Moonshot 配置信息
*/
private void initMoonshotConfig() {
KimiConfig kimiConfig = new KimiConfig();
kimiConfig.setApiHost(kimiConfigProperties.getApiHost());
kimiConfig.setApiKey(kimiConfigProperties.getApiKey());
kimiConfig.setModel(kimiConfigProperties.getModel());
kimiConfig.setChatCompletionUrl(kimiConfigProperties.getChatCompletionUrl());
configuration.setKimiConfig(kimiConfig);
}
}

View File

@@ -0,0 +1,14 @@
package com.jeelowcode.framework.ai.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "jeelowcode.ai.deepseek")
public class DeepSeekConfigProperties {
private String apiHost = "https://api.deepseek.com/";
private String apiKey = "";
private String model="deepseek-chat";
private String chatCompletionUrl = "chat/completions";
}

View File

@@ -0,0 +1,19 @@
package com.jeelowcode.framework.ai.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @Author cly
* @Description Moonshot配置文件
* @Date 2024/8/30 15:56
*/
@Data
@ConfigurationProperties(prefix = "jeelowcode.ai.kimi")
public class KimiConfigProperties {
private String apiHost = "https://api.moonshot.cn/";
private String apiKey = "";
private String model = "moonshot-v1-8k";
private String chatCompletionUrl = "v1/chat/completions";
}

View File

@@ -0,0 +1,22 @@
package com.jeelowcode.framework.ai.service;
import com.jeelowcode.framework.ai.config.DeepSeekConfig;
import com.jeelowcode.framework.ai.config.KimiConfig;
import com.jeelowcode.framework.ai.websearch.searxng.SearXNGConfig;
import lombok.Data;
import okhttp3.OkHttpClient;
@Data
public class Configuration {
private OkHttpClient okHttpClient;
private DeepSeekConfig deepSeekConfig;
private KimiConfig kimiConfig;
private SearXNGConfig searXNGConfig;
}

View File

@@ -0,0 +1,14 @@
package com.jeelowcode.framework.ai.service;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseRequest;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseResponse;
public interface IChatService {
//同步消息
ChatBaseResponse chatCompletion(String baseUrl, String apiKey, ChatBaseRequest chatBaseRequest) throws Exception;
ChatBaseResponse chatCompletion(ChatBaseRequest chatBaseRequest) throws Exception;
}

View File

@@ -0,0 +1,15 @@
package com.jeelowcode.framework.ai.service;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public enum PlatformType {
DEEPSEEK("deepseek"),
KIMI("kimi"),
;
private final String platform;
}

View File

@@ -0,0 +1,44 @@
package com.jeelowcode.framework.ai.service.factor;
import com.jeelowcode.framework.ai.platform.chat.deepseek.DeepSeekChatService;
import com.jeelowcode.framework.ai.platform.chat.kimi.KimiChatService;
import com.jeelowcode.framework.ai.service.Configuration;
import com.jeelowcode.framework.ai.service.IChatService;
import com.jeelowcode.framework.ai.service.PlatformType;
import com.jeelowcode.framework.ai.websearch.ChatWithWebSearchEnhance;
public class AiService {
private final Configuration configuration;
public AiService(Configuration configuration) {
this.configuration = configuration;
}
public IChatService getChatService(PlatformType platform) {
return createChatService(platform);
}
public IChatService webSearchEnhance(IChatService chatService) {
return new ChatWithWebSearchEnhance(chatService, configuration);
}
private IChatService createChatService(PlatformType platform) {
switch (platform) {
case KIMI:
return new KimiChatService(configuration);
case DEEPSEEK:
return new DeepSeekChatService(configuration);
default:
throw new IllegalArgumentException("Unknown platform: " + platform);
}
}
}

View File

@@ -0,0 +1,42 @@
package com.jeelowcode.framework.ai.template;
import com.jeelowcode.framework.ai.platform.chat.tool.ChatMessage;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
public class CreateTableTemplate {
//获取模板
public static List<ChatMessage> getMessageTemplateList(){
List<ChatMessage> sysMessageList=new ArrayList<>();
sysMessageList.add(ChatMessage.withSystem("你是一个mysql数据库设计专家你需要根据用户的需求理解设计出数据表然后按特定格式返回"));
sysMessageList.add(ChatMessage.withSystem("表信息解析tableName表名称其中必须是tbl_ 开头。 tableDescribe表中文描述"));
sysMessageList.add(ChatMessage.withSystem("字段解析fieldCode字段编码fieldName字段名称fieldLen字段长度fieldDefaultVal默认值fieldType字段类型"));
sysMessageList.add(ChatMessage.withSystem("其中fieldType字段类型的值有String=字符串类型Integer=整型Date=日期yyyy-MM-ddDateTime=日期时间yyyy-MM-dd HH:mm:ssTime=时间HH:mm:ssBigInt=长整型long,BigDecimal=小数LongText=长文本Blob=二进制"));
sysMessageList.add(ChatMessage.withSystem("其中必须有的字段未id,tenant_id,create_user,create_time,create_dept,update_user,update_time,is_deleted"));
sysMessageList.add(ChatMessage.withSystem("这个是返回格式一定要以json格式返回其他不必要的内容不要返回{\"tableName\":\"tbl_name\",\"tableDescribe\":\"学生表\",\"fieldModelList\":[{\"fieldCode\":\"name\",\"fieldName\":\"姓名\",\"fieldLen\":10,\"fieldPointLen\":0,\"fieldDefaultVal\":\"\",\"fieldType\":\"String\"},{\"fieldCode\":\"age\",\"fieldName\":\"年龄\",\"fieldLen\":10,\"fieldPointLen\":0,\"fieldDefaultVal\":\"\",\"fieldType\":\"String\"}]}\n"));
sysMessageList.add(ChatMessage.withSystem("返回格式[重要]只需要返回json格式即可 ,其他不必要的内容不要返回"));
return sysMessageList;
}
@Data
public static class RspModel{
private String tableName;//表名称
private String tableDescribe;//表描述
List<FieldModel> fieldModelList;
}
@Data
public static class FieldModel{
private String fieldCode;//字段编号 例如name
private String fieldName;//字段名称 例如:姓名
private Integer fieldLen;//字段长度
private Integer fieldPointLen;//字段小数位
private String fieldDefaultVal;//默认值
private String fieldType;//字段类型
}
}

View File

@@ -0,0 +1,98 @@
package com.jeelowcode.framework.ai.websearch;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseRequest;
import com.jeelowcode.framework.ai.platform.chat.ChatBaseResponse;
import com.jeelowcode.framework.ai.service.Configuration;
import com.jeelowcode.framework.ai.service.IChatService;
import com.jeelowcode.framework.ai.websearch.searxng.SearXNGConfig;
import com.jeelowcode.framework.ai.websearch.searxng.SearXNGRequest;
import com.jeelowcode.framework.ai.websearch.searxng.SearXNGResponse;
import com.jeelowcode.framework.exception.JeeLowCodeException;
import com.jeelowcode.framework.utils.utils.FuncBase;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class ChatWithWebSearchEnhance implements IChatService {
private final IChatService chatService;
private final SearXNGConfig searXNGConfig;
private final OkHttpClient okHttpClient;
public ChatWithWebSearchEnhance(IChatService chatService, Configuration configuration) {
this.chatService = chatService;
this.searXNGConfig = configuration.getSearXNGConfig();
this.okHttpClient = configuration.getOkHttpClient();
}
@Override
public ChatBaseResponse chatCompletion(String baseUrl, String apiKey, ChatBaseRequest chatBaseRequest) throws Exception {
return chatService.chatCompletion(baseUrl, apiKey, addWebSearchResults(chatBaseRequest));
}
@Override
public ChatBaseResponse chatCompletion(ChatBaseRequest chatBaseRequest) throws Exception {
return chatService.chatCompletion(addWebSearchResults(chatBaseRequest));
}
private ChatBaseRequest addWebSearchResults(ChatBaseRequest chatBaseRequest) {
int chatLen = chatBaseRequest.getMessages().size();
String prompt = chatBaseRequest.getMessages().get(chatLen - 1).getContent();
// 执行联网搜索并将结果附加到提示词中
String searchResults = performWebSearch(prompt);
chatBaseRequest.getMessages().get(chatLen - 1).setContent("我将提供一段来自互联网的资料信息, 请根据这段资料以及用户提出的问题来给出回答。请确保在回答中使用Markdown格式并在回答末尾列出参考资料。如果资料中的信息不足以回答用户的问题可以根据自身知识库进行补充或者说明无法提供确切的答案。\n" +
"网络资料:\n"
+ "============\n"
+ searchResults
+ "============\n"
+ "用户问题:\n"
+ "============\n"
+ prompt
+ "============\n");
return chatBaseRequest;
}
private String performWebSearch(String query) {
SearXNGRequest searXNGRequest = SearXNGRequest.builder()
.q(query)
.engines(searXNGConfig.getEngines())
.build();
if(StringUtils.isBlank(searXNGConfig.getUrl())){
throw new JeeLowCodeException("SearXNG url is not configured");
}
Request request = new Request.Builder()
.url(searXNGConfig.getUrl()+ "?format=json&q=" + query + "&engines=" + searXNGConfig.getEngines())
.get()
.build();
try(Response execute = okHttpClient.newCall(request).execute()) {
if (execute.isSuccessful() && execute.body() != null){
SearXNGResponse searXNGResponse = FuncBase.json2Bean(execute.body().string(), SearXNGResponse.class);
if(searXNGResponse.getResults().size() > searXNGConfig.getNums()) {
return FuncBase.json2Str(searXNGResponse.getResults().subList(0, searXNGConfig.getNums()));
}
return FuncBase.json2Str(searXNGResponse.getResults());
}else{
throw new Exception("SearXNG request failed");
}
} catch (Exception e) {
throw new JeeLowCodeException("SearXNG request failed");
}
}
}

View File

@@ -0,0 +1,11 @@
package com.jeelowcode.framework.ai.websearch.searxng;
import lombok.Data;
@Data
public class SearXNGConfig {
private String url;
private String engines = "duckduckgo,google,bing,brave,mojeek,presearch,qwant,startpage,yahoo,arxiv,crossref,google_scholar,internetarchivescholar,semantic_scholar";
private int nums = 20;
}

View File

@@ -0,0 +1,18 @@
package com.jeelowcode.framework.ai.websearch.searxng;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
public class SearXNGRequest {
@Builder.Default
private final String format = "json";
private String q;
@Builder.Default
private String engines = "duckduckgo,google,bing,brave,mojeek,presearch,qwant,startpage,yahoo,arxiv,crossref,google_scholar,internetarchivescholar,semantic_scholar";
}

View File

@@ -0,0 +1,29 @@
package com.jeelowcode.framework.ai.websearch.searxng;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
public class SearXNGResponse {
private String query;
@JsonProperty("number_of_results")
private String numberOfResults;
private List<Result> results;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Result {
private String url;
private String title;
private String content;
}
}

View File

@@ -0,0 +1 @@
com.jeelowcode.framework.ai.properties.AiConfigAutoConfiguration