From 9e52a93120cf8fc9c15afea554fef23e11edc37d Mon Sep 17 00:00:00 2001 From: lisang <1186733841@qq.com> Date: Thu, 23 May 2024 21:49:23 +0800 Subject: [PATCH] init project --- .gitignore | 33 +++ pom.xml | 99 +++++++++ .../shockkid/aicoder/AiCoderApplication.java | 13 ++ .../aicoder/config/OkHttpClientSingleton.java | 40 ++++ .../aicoder/constant/HtmlConstant.java | 22 ++ .../aicoder/constant/ModelConstant.java | 18 ++ .../aicoder/controller/AiController.java | 32 +++ .../shockkid/aicoder/executor/AiExecutor.java | 192 ++++++++++++++++++ .../com/shockkid/aicoder/model/ChatModel.java | 14 ++ .../shockkid/aicoder/model/ChatRoleModel.java | 16 ++ .../shockkid/aicoder/model/InputModel.java | 14 ++ .../aicoder/model/RoleContentModel.java | 13 ++ .../shockkid/aicoder/model/TongYiModel.java | 15 ++ .../storage/MessageHistoryStorage.java | 99 +++++++++ .../shockkid/aicoder/util/MarkdownUtils.java | 23 +++ src/main/resources/application.properties | 1 + .../aicoder/AiCoderApplicationTests.java | 13 ++ 17 files changed, 657 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/com/shockkid/aicoder/AiCoderApplication.java create mode 100644 src/main/java/com/shockkid/aicoder/config/OkHttpClientSingleton.java create mode 100644 src/main/java/com/shockkid/aicoder/constant/HtmlConstant.java create mode 100644 src/main/java/com/shockkid/aicoder/constant/ModelConstant.java create mode 100644 src/main/java/com/shockkid/aicoder/controller/AiController.java create mode 100644 src/main/java/com/shockkid/aicoder/executor/AiExecutor.java create mode 100644 src/main/java/com/shockkid/aicoder/model/ChatModel.java create mode 100644 src/main/java/com/shockkid/aicoder/model/ChatRoleModel.java create mode 100644 src/main/java/com/shockkid/aicoder/model/InputModel.java create mode 100644 src/main/java/com/shockkid/aicoder/model/RoleContentModel.java create mode 100644 src/main/java/com/shockkid/aicoder/model/TongYiModel.java create mode 100644 src/main/java/com/shockkid/aicoder/storage/MessageHistoryStorage.java create mode 100644 src/main/java/com/shockkid/aicoder/util/MarkdownUtils.java create mode 100644 src/main/resources/application.properties create mode 100644 src/test/java/com/shockkid/aicoder/AiCoderApplicationTests.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e00a --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a551955 --- /dev/null +++ b/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + com.shockkid + AiCoder + 0.0.1-SNAPSHOT + jar + + AiCoder + AiCoder + + + 1.8 + UTF-8 + UTF-8 + 2.6.13 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.projectlombok + lombok + true + + + + com.squareup.okhttp3 + okhttp + 4.11.0 + + + com.alibaba + fastjson + 2.0.19 + + + + org.commonmark + commonmark + 0.19.0 + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + UTF-8 + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + com.shockkid.aicoder.AiCoderApplication + true + + + + repackage + + repackage + + + + + + + + diff --git a/src/main/java/com/shockkid/aicoder/AiCoderApplication.java b/src/main/java/com/shockkid/aicoder/AiCoderApplication.java new file mode 100644 index 0000000..b088290 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/AiCoderApplication.java @@ -0,0 +1,13 @@ +package com.shockkid.aicoder; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AiCoderApplication { + + public static void main(String[] args) { + SpringApplication.run(AiCoderApplication.class, args); + } + +} diff --git a/src/main/java/com/shockkid/aicoder/config/OkHttpClientSingleton.java b/src/main/java/com/shockkid/aicoder/config/OkHttpClientSingleton.java new file mode 100644 index 0000000..9503c98 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/config/OkHttpClientSingleton.java @@ -0,0 +1,40 @@ +package com.shockkid.aicoder.config; + +import okhttp3.OkHttpClient; + +import java.util.concurrent.TimeUnit; + +/** + * OkHttp模块的单例实现 + * + * @auther lisang + * @date 2024/5/22 23:23 + **/ +public class OkHttpClientSingleton { + private static OkHttpClient instance; + + private OkHttpClientSingleton() { + // 私有构造函数,防止外部实例化 + } + + public static OkHttpClient getInstance() { + if (instance == null) { + synchronized (OkHttpClientSingleton.class) { + if (instance == null) { + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + + // 设置连接超时 + builder.connectTimeout(60, TimeUnit.SECONDS); + // 设置读取超时 + builder.readTimeout(60, TimeUnit.SECONDS); + // 设置写入超时 + builder.writeTimeout(60, TimeUnit.SECONDS); + + // 构建 OkHttpClient 实例 + instance = builder.build(); + } + } + } + return instance; + } +} diff --git a/src/main/java/com/shockkid/aicoder/constant/HtmlConstant.java b/src/main/java/com/shockkid/aicoder/constant/HtmlConstant.java new file mode 100644 index 0000000..596cf9b --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/constant/HtmlConstant.java @@ -0,0 +1,22 @@ +package com.shockkid.aicoder.constant; + +/** + * 定义Html相关常量 + * + * @auther lisang + * @date 2024/5/22 23:30 + **/ +public class HtmlConstant { + public static String htmlTitle = ""; + public static String htmlEnding = ""; + public static String userHtmlFormat = "
" + + "
" + + "You: %s" + + "
" + + "
"; + public static String assistantFormat = "
" + + "
" + + "Assistant: %s" + + "
" + + "
" ; +} diff --git a/src/main/java/com/shockkid/aicoder/constant/ModelConstant.java b/src/main/java/com/shockkid/aicoder/constant/ModelConstant.java new file mode 100644 index 0000000..aa9cb8c --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/constant/ModelConstant.java @@ -0,0 +1,18 @@ +package com.shockkid.aicoder.constant; + +/** + * @auther lisang + * @date 2024/5/22 23:28 + **/ +public class ModelConstant { + public static final String LLAMA = "llama3"; + public static final String QWEN_MAX = "qwen-max"; + public static final String QWEN_LONG = "qwen-long"; + public static final String RESPONSE = "response"; + public static final String OUTPUT = "output"; + public static final String TEXT = "text"; + public static final String USER_ROLE = "user"; + public static final String ASSISTANT_ROLE = "assistant"; + public static final String MESSAGE = "message"; + public static final String CONTENT = "content"; +} diff --git a/src/main/java/com/shockkid/aicoder/controller/AiController.java b/src/main/java/com/shockkid/aicoder/controller/AiController.java new file mode 100644 index 0000000..85562d8 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/controller/AiController.java @@ -0,0 +1,32 @@ +package com.shockkid.aicoder.controller; + +import com.shockkid.aicoder.executor.AiExecutor; +import com.shockkid.aicoder.storage.MessageHistoryStorage; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @auther lisang + * @date 2024/5/22 23:43 + **/ +@RestController +public class AiController { + + @PostMapping("/aiChat") + public String aiChat(String text) { + AiExecutor aiExecutor = new AiExecutor(); + String result = aiExecutor.getChatMessageByTongYi(text); + return result; + } + + @PostMapping("/aiChatHistory") + public String aiChatHistory(String text) { + AiExecutor aiExecutor = new AiExecutor(); + String result = aiExecutor.getChatHistoryByTongYi(text); + MessageHistoryStorage.putUserContent(text); + MessageHistoryStorage.putAssistantContent(result); + String html = MessageHistoryStorage.buildHtmlString(); + return html; + } + +} diff --git a/src/main/java/com/shockkid/aicoder/executor/AiExecutor.java b/src/main/java/com/shockkid/aicoder/executor/AiExecutor.java new file mode 100644 index 0000000..99fa6fa --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/executor/AiExecutor.java @@ -0,0 +1,192 @@ +package com.shockkid.aicoder.executor; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.shockkid.aicoder.config.OkHttpClientSingleton; +import com.shockkid.aicoder.constant.ModelConstant; +import com.shockkid.aicoder.model.*; +import com.shockkid.aicoder.storage.MessageHistoryStorage; +import okhttp3.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static com.shockkid.aicoder.constant.ModelConstant.*; + +/** + * AI执行器 + * + * @auther lisang + * @date 2024/5/22 23:30 + **/ +public class AiExecutor { + private String generateUrl = "http://127.0.0.1:11434/api/generate"; + private String chatUrl = "http://127.0.0.1:11434/api/chat"; + private String ApiKey = "Bearer 你的TOKEN"; + /** + * 通义大模型模型链接 + */ + private String tongYiUrl = "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation"; + + /** + * 基于通义大模型API获取返回结果 + * @param prompt + * @return + */ + public String getChatMessageByTongYi(String prompt){ + OkHttpClient client = OkHttpClientSingleton.getInstance(); + // 构建入参 + TongYiModel model = new TongYiModel(); + model.setModel(QWEN_MAX); + InputModel inputModel = new InputModel(); + List messages = new ArrayList<>(); + RoleContentModel roleContentModel = new RoleContentModel(); + roleContentModel.setRole(USER_ROLE); + roleContentModel.setContent(prompt); + messages.add(roleContentModel); + inputModel.setMessages(messages); + model.setInput(inputModel); + // 发起请求 + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(model)); + Request request = new Request.Builder() + .header("Authorization",ApiKey) + .url(tongYiUrl) + .post(requestBody) + .build(); + try (Response response = client.newCall(request).execute()) { + // 处理响应 + if (response.isSuccessful()) { + // 请求成功 + ResponseBody responseBody = response.body(); + String string = responseBody.string(); + JSONObject jsonObject = JSON.parseObject(string); + // 处理响应体 + JSONObject outputJson = jsonObject.getJSONObject(OUTPUT); + return outputJson.getString(TEXT); + } else { + // 请求失败 + return "请求失败,响应码: " + response.code(); + } + } catch (IOException e) { + // 发生异常 + e.printStackTrace(); + } + return "请求失败"; + } + + /** + * 基于通义大模型API获取上下文对象 + * @param prompt + * @return + */ + public String getChatHistoryByTongYi(String prompt){ + OkHttpClient client = OkHttpClientSingleton.getInstance(); + // 构建入参 + TongYiModel model = new TongYiModel(); + model.setModel(QWEN_MAX); + InputModel inputModel = new InputModel(); + // 构建上下文入参 + inputModel.setMessages(MessageHistoryStorage.buildRoleContentModel(prompt)); + model.setInput(inputModel); + // 发起请求 + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(model)); + Request request = new Request.Builder() + .header("Authorization",ApiKey) + .url(tongYiUrl) + .post(requestBody) + .build(); + try (Response response = client.newCall(request).execute()) { + // 处理响应 + if (response.isSuccessful()) { + // 请求成功 + ResponseBody responseBody = response.body(); + String string = responseBody.string(); + JSONObject jsonObject = JSON.parseObject(string); + // 处理响应体 + JSONObject outputJson = jsonObject.getJSONObject(OUTPUT); + return outputJson.getString(TEXT); + } else { + // 请求失败 + return "请求失败,响应码: " + response.code(); + } + } catch (IOException e) { + // 发生异常 + e.printStackTrace(); + } + return "请求失败"; + } + + + /** + * ollama开源框架简单的聊天获取结果 + * @param prompt + * @return + */ + public String getChatMessage(String prompt){ + OkHttpClient client = OkHttpClientSingleton.getInstance(); + ChatModel chatModel = new ChatModel(); + chatModel.setModel(ModelConstant.LLAMA); + chatModel.setPrompt(prompt); + chatModel.setStream(false); + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(chatModel)); + Request request = new Request.Builder() + .url(generateUrl) + .post(requestBody) + .build(); + try (Response response = client.newCall(request).execute()) { + // 处理响应 + if (response.isSuccessful()) { + // 请求成功 + ResponseBody responseBody = response.body(); + String string = responseBody.string(); + JSONObject jsonObject = JSON.parseObject(string); + // 处理响应体 + return jsonObject.getString(RESPONSE); + } else { + // 请求失败 + return "请求失败,响应码: " + response.code(); + } + } catch (IOException e) { + // 发生异常 + e.printStackTrace(); + } + return "请求失败"; + } + + /** + * ollama开源框架带历史上下文的回答 + * @param prompt + * @return + */ + public String getRoleContentResult(String prompt){ + OkHttpClient client = OkHttpClientSingleton.getInstance(); + ChatRoleModel chatRoleModel = new ChatRoleModel(); + chatRoleModel.setModel(LLAMA); + chatRoleModel.setStream(false); + chatRoleModel.setMessages(MessageHistoryStorage.buildRoleContentModel(prompt)); + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), JSON.toJSONString(chatRoleModel)); + Request request = new Request.Builder() + .url(chatUrl) + .post(requestBody) + .build(); + try (Response response = client.newCall(request).execute()) { + // 处理响应 + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + String string = responseBody.string(); + JSONObject jsonObject = JSON.parseObject(string); + JSONObject messageJson = jsonObject.getJSONObject(MESSAGE); + return messageJson.getString(CONTENT); + } else { + // 请求失败 + System.out.println("请求失败,响应码: " + response.code()); + return "请求失败,响应码: " + response.code(); + } + } catch (IOException e) { + // 发生异常 + e.printStackTrace(); + } + return "请求失败"; + } +} diff --git a/src/main/java/com/shockkid/aicoder/model/ChatModel.java b/src/main/java/com/shockkid/aicoder/model/ChatModel.java new file mode 100644 index 0000000..a8cb22c --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/model/ChatModel.java @@ -0,0 +1,14 @@ +package com.shockkid.aicoder.model; + +import lombok.Data; + +/** + * @auther lisang + * @date 2024/5/22 23:33 + **/ +@Data +public class ChatModel { + private String model; + private String prompt; + private Boolean stream; +} diff --git a/src/main/java/com/shockkid/aicoder/model/ChatRoleModel.java b/src/main/java/com/shockkid/aicoder/model/ChatRoleModel.java new file mode 100644 index 0000000..1553814 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/model/ChatRoleModel.java @@ -0,0 +1,16 @@ +package com.shockkid.aicoder.model; + +import lombok.Data; + +import java.util.List; + +/** + * @auther lisang + * @date 2024/5/22 23:33 + **/ +@Data +public class ChatRoleModel { + private String model; + private List messages; + private Boolean stream; +} diff --git a/src/main/java/com/shockkid/aicoder/model/InputModel.java b/src/main/java/com/shockkid/aicoder/model/InputModel.java new file mode 100644 index 0000000..b308e5d --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/model/InputModel.java @@ -0,0 +1,14 @@ +package com.shockkid.aicoder.model; + +import lombok.Data; + +import java.util.List; + +/** + * @auther lisang + * @date 2024/5/22 23:34 + **/ +@Data +public class InputModel { + private List messages; +} diff --git a/src/main/java/com/shockkid/aicoder/model/RoleContentModel.java b/src/main/java/com/shockkid/aicoder/model/RoleContentModel.java new file mode 100644 index 0000000..d46dd2e --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/model/RoleContentModel.java @@ -0,0 +1,13 @@ +package com.shockkid.aicoder.model; + +import lombok.Data; + +/** + * @auther lisang + * @date 2024/5/22 23:34 + **/ +@Data +public class RoleContentModel { + private String role; + private String content; +} \ No newline at end of file diff --git a/src/main/java/com/shockkid/aicoder/model/TongYiModel.java b/src/main/java/com/shockkid/aicoder/model/TongYiModel.java new file mode 100644 index 0000000..6594920 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/model/TongYiModel.java @@ -0,0 +1,15 @@ +package com.shockkid.aicoder.model; + +import lombok.Data; + +/** + * 通义模型参数 + * + * @auther lisang + * @date 2024/5/22 23:32 + **/ +@Data +public class TongYiModel { + private String model; + private InputModel input; +} diff --git a/src/main/java/com/shockkid/aicoder/storage/MessageHistoryStorage.java b/src/main/java/com/shockkid/aicoder/storage/MessageHistoryStorage.java new file mode 100644 index 0000000..b1c875a --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/storage/MessageHistoryStorage.java @@ -0,0 +1,99 @@ +package com.shockkid.aicoder.storage; + +import com.shockkid.aicoder.constant.HtmlConstant; +import com.shockkid.aicoder.constant.ModelConstant; +import com.shockkid.aicoder.model.RoleContentModel; +import com.shockkid.aicoder.util.MarkdownUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * 本地历史记录 + * + * @auther lisang + * @date 2024/5/22 23:37 + **/ +public class MessageHistoryStorage { + /** + * 存储用户的聊天记录 + */ + public static List userContentList; + /** + * 存储AI的回答记录 + */ + public static List assistantContentList; + + /** + * 截断历史数量 + */ + public static final Integer LIMIT_NUMBER = 5; + + static { + userContentList = new ArrayList<>(); + assistantContentList = new ArrayList<>(); + // 后期接入历史数据重新加载时可以在这里加载 + } + + /** + * 初始化一个角色信息 + */ + public static void initRole(){ + + } + + public static void putUserContent(String content){ + RoleContentModel roleContentModel = new RoleContentModel(); + roleContentModel.setRole(ModelConstant.USER_ROLE); + roleContentModel.setContent(content); + userContentList.add(roleContentModel); + } + + public static void putAssistantContent(String content){ + RoleContentModel roleContentModel = new RoleContentModel(); + roleContentModel.setRole(ModelConstant.ASSISTANT_ROLE); + roleContentModel.setContent(content); + assistantContentList.add(roleContentModel); + } + + /** + * 构建展示的Html代码 + * @return + */ + public static String buildHtmlString(){ + StringBuilder builder = new StringBuilder(); + builder.append(HtmlConstant.htmlTitle); + + for (int i = 0; i < assistantContentList.size(); i++) { + builder.append(String.format(HtmlConstant.userHtmlFormat, MarkdownUtils.markdownToHtml(userContentList.get(i).getContent()))); + builder.append(String.format(HtmlConstant.assistantFormat, MarkdownUtils.markdownToHtml(assistantContentList.get(i).getContent()))); + } + builder.append(HtmlConstant.htmlEnding); + return builder.toString(); + } + + /** + * 构建包含历史记录的请求集合 + * @param prompt + * @return + */ + public static List buildRoleContentModel(String prompt){ + List modelList = new ArrayList<>(); + if (assistantContentList.isEmpty()){ + RoleContentModel model = new RoleContentModel(); + model.setRole(ModelConstant.USER_ROLE); + model.setContent(prompt); + modelList.add(model); + }else { + for (int i = 0; i < assistantContentList.size(); i++) { + modelList.add(userContentList.get(i)); + modelList.add(assistantContentList.get(i)); + } + RoleContentModel model = new RoleContentModel(); + model.setRole(ModelConstant.USER_ROLE); + model.setContent(prompt); + modelList.add(model); + } + return modelList; + } +} diff --git a/src/main/java/com/shockkid/aicoder/util/MarkdownUtils.java b/src/main/java/com/shockkid/aicoder/util/MarkdownUtils.java new file mode 100644 index 0000000..0be9b31 --- /dev/null +++ b/src/main/java/com/shockkid/aicoder/util/MarkdownUtils.java @@ -0,0 +1,23 @@ +package com.shockkid.aicoder.util; + +import org.commonmark.node.Node; +import org.commonmark.parser.Parser; +import org.commonmark.renderer.html.HtmlRenderer; + +/** + * MD文档转换为HTML格式 + * + * @auther lisang + * @date 2024/5/22 23:38 + **/ +public class MarkdownUtils { + public static String markdownToHtml(String htmlString){ + // 使用 CommonMark 解析器解析 Markdown + Parser parser = Parser.builder().build(); + Node document = parser.parse(htmlString); + // 使用 CommonMark HTML 渲染器将 Markdown 转换为 HTML + HtmlRenderer renderer = HtmlRenderer.builder().build(); + String htmlContent = renderer.render(document); + return htmlContent; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..11152c6 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8520 \ No newline at end of file diff --git a/src/test/java/com/shockkid/aicoder/AiCoderApplicationTests.java b/src/test/java/com/shockkid/aicoder/AiCoderApplicationTests.java new file mode 100644 index 0000000..2552b79 --- /dev/null +++ b/src/test/java/com/shockkid/aicoder/AiCoderApplicationTests.java @@ -0,0 +1,13 @@ +package com.shockkid.aicoder; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class AiCoderApplicationTests { + + @Test + void contextLoads() { + } + +}