commit 9e52a93120cf8fc9c15afea554fef23e11edc37d
Author: lisang <1186733841@qq.com>
Date: Thu May 23 21:49:23 2024 +0800
init project
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() {
+ }
+
+}