From da2cb69b1b107c0d83ea1080899076993f274835 Mon Sep 17 00:00:00 2001 From: Zacharias Date: Fri, 21 Feb 2025 13:31:13 +0100 Subject: [PATCH] Started work on API and remembrance fuction --- .gitignore | 1 + .../java/me/zacharias/chat/api/APIServer.java | 116 +++++++++++++++++- .../java/me/zacharias/chat/api/Client.java | 22 ++++ .../java/me/zacharias/chat/core/Core.java | 17 ++- .../me/zacharias/chat/core/LaunchOptions.java | 59 +++++++++ .../chat/ollama/OllamaMessageRole.java | 8 ++ .../chat/ollama/OllamaMessageToolCall.java | 25 ++++ .../zacharias/chat/ollama/OllamaObject.java | 33 +++++ .../zacharias/chat/display/PythonRunner.java | 61 ++++----- launcher/build.gradle | 1 + .../me/zacharias/chat/launcher/Launcher.java | 30 +++-- 11 files changed, 331 insertions(+), 42 deletions(-) create mode 100644 Core/src/main/java/me/zacharias/chat/core/LaunchOptions.java create mode 100644 Core/src/main/java/me/zacharias/chat/ollama/OllamaMessageToolCall.java diff --git a/.gitignore b/.gitignore index 88233f1..728cbdc 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ bin/ /pythonFiles/ /messages/ /logs/ +/cache/ diff --git a/API/src/main/java/me/zacharias/chat/api/APIServer.java b/API/src/main/java/me/zacharias/chat/api/APIServer.java index e6ef274..5e8b91e 100644 --- a/API/src/main/java/me/zacharias/chat/api/APIServer.java +++ b/API/src/main/java/me/zacharias/chat/api/APIServer.java @@ -1,13 +1,127 @@ package me.zacharias.chat.api; +import me.zacharias.chat.core.Core; +import me.zacharias.chat.core.LaunchOptions; +import me.zacharias.chat.core.PrintMessageHandler; +import me.zacharias.chat.ollama.OllamaObject; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; import java.net.ServerSocket; +import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Date; +import java.util.Scanner; public class APIServer { ArrayList clientsList = new ArrayList<>(); ServerSocket serverSocket; + PrintStream dataOut; - public APIServer(int port, String redirectedOutput) { + PrintMessageHandler printMessageHandler = new PrintMessageHandler() { + @Override + public void printMessage(String message) { + synchronized (clientsList) { + for (Client client : clientsList) { + boolean success = client.sendMessage(message); + if (!success) { + clientsList.remove(client); + continue; + } + } + } + } + + @Override + public boolean color() { + return false; + } + }; + + Core core = new Core(printMessageHandler); + + /** + * Options foir this is expected to be passed thru the {@link LaunchOptions#instance} object.
+ * Used objects:
+ * - {@link LaunchOptions#autoAccept}
+ * - {@link LaunchOptions#redirectOutput}
+ * - {@link LaunchOptions#port}
+ */ + public APIServer() { + LaunchOptions options = LaunchOptions.getInstance(); + String redirectedOutput = options.getRedirectOutput(); + int port = options.getPort(); + + core.setOllamaObject(OllamaObject.builder() + .setModel("llama3-AI") + .keep_alive(10) + .stream(false) + .build()); + + if (!Paths.get(redirectedOutput).toFile().getParentFile().exists()) { + System.out.println("Failed to be able to open the redirected output file due to missing directory"); + } + + File f = new File(redirectedOutput); + + try { + if (f.exists()) { + System.out.println("Output already exists"); + System.out.print("Overwrite the existing output file? [y/N]: "); + Scanner sc = new Scanner(System.in); + char c; + + if (options.isAutoAccept()) { + c = 'y'; + } else { + String s = sc.nextLine(); + c = s.isBlank() ? 'n' : s.charAt(0); + } + + if (Character.toLowerCase(c) == 'y') { + f.delete(); + f.createNewFile(); + dataOut = new PrintStream(new FileOutputStream(f), true); + } else { + System.out.print("Rename existing output file? [y/N]: "); + String s = sc.nextLine(); + c = s.isBlank() ? 'n' : s.charAt(0); + if (c == 'y') { + System.out.println("New file name for [" + f.getName() + "]: "); + File newFile = new File(f.getParentFile(), sc.nextLine().trim()); + if (f.renameTo(newFile)) { + System.out.println("Old file placed in [" + newFile.getPath() + "]"); + f = new File(redirectedOutput); + } else { + System.out.println("Failed to rename file. Proceeding with appending data."); + } + if (f.exists()) { + f.delete(); + } + f.createNewFile(); + dataOut = new PrintStream(new FileOutputStream(f), true); + } else { + System.out.print("Appending new data to [" + f.getPath() + "]"); + dataOut = new PrintStream(new FileOutputStream(f, true), true); + Date date = new Date(); + dataOut.println("\n\nNew instance started at: " + date.toString() + "\n"); + } + } + sc.close(); + } else { + f.createNewFile(); + dataOut = new PrintStream(new FileOutputStream(f, true), true); + } + } + catch (Exception e) { + e.printStackTrace(); + System.exit(1); + return; + } + + //serverSocket = new ServerSocket(port); } } diff --git a/API/src/main/java/me/zacharias/chat/api/Client.java b/API/src/main/java/me/zacharias/chat/api/Client.java index c6d6005..878bb0e 100644 --- a/API/src/main/java/me/zacharias/chat/api/Client.java +++ b/API/src/main/java/me/zacharias/chat/api/Client.java @@ -1,4 +1,26 @@ package me.zacharias.chat.api; +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.net.Socket; + public class Client { + + Socket socket; + + public Client(Socket socket) { + this.socket = socket; + } + + /** + * Returnes true unless if the method failes + */ + public boolean sendMessage(String message) { + try(BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) { + out.write(message+"\r\n"); + return true; + }catch (Exception e) { + return false; + } + } } diff --git a/Core/src/main/java/me/zacharias/chat/core/Core.java b/Core/src/main/java/me/zacharias/chat/core/Core.java index b72c058..825e773 100644 --- a/Core/src/main/java/me/zacharias/chat/core/Core.java +++ b/Core/src/main/java/me/zacharias/chat/core/Core.java @@ -92,7 +92,7 @@ public class Core { LocalDateTime now = LocalDateTime.now(); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH-mm-ss"); - File messagesFile = new File("./messages/"+now.format(formatter)+".txt"); + File messagesFile = new File("./messages/"+now.format(formatter)+".json"); BufferedWriter messagesWriter = new BufferedWriter(new FileWriter(messagesFile)); @@ -105,6 +105,15 @@ public class Core { messagesWriter.write(messages.toString()); messagesWriter.close(); + File f = new File("./cache/messages.json"); + messagesWriter = new BufferedWriter(new FileWriter(messagesFile)); + for(OllamaMessage message : ollamaObject.getMessages()) { + messages.put(new JSONObject(message.toString())); + } + + messagesWriter.write(messages.toString()); + messagesWriter.close(); + } catch (IOException e) { throw new RuntimeException(e); } @@ -184,9 +193,11 @@ public class Core { //System.out.println("Responce: "+responce); if(responce != null) { writeLog("Raw responce: "+responce.toString()); - if(responce.getJSONObject("message").has("tool_calls")) + JSONObject message = responce.getJSONObject("message"); + if(message.has("tool_calls")) { - JSONArray calls = responce.getJSONObject("message").getJSONArray("tool_calls"); + ollamaObject.addMessage(new OllamaMessageToolCall(OllamaMessageRole.fromRole(message.optString("role")), message.getString("content"), message.getJSONArray("tool_calls"))); + JSONArray calls = message.getJSONArray("tool_calls"); for(Object call : calls) { if(call instanceof JSONObject jsonObject) diff --git a/Core/src/main/java/me/zacharias/chat/core/LaunchOptions.java b/Core/src/main/java/me/zacharias/chat/core/LaunchOptions.java new file mode 100644 index 0000000..e4ca5a7 --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/core/LaunchOptions.java @@ -0,0 +1,59 @@ +package me.zacharias.chat.core; + +public class LaunchOptions { + private static LaunchOptions instance = new LaunchOptions(); + + public static LaunchOptions getInstance() { + return instance; + } + + private boolean loadOld; + private boolean autoAccept; + private boolean serverMode; + private int port = 39075; + private String redirectOutput; + + public static void setInstance(LaunchOptions instance) { + LaunchOptions.instance = instance; + } + + public boolean isLoadOld() { + return loadOld; + } + + public void setLoadOld(boolean loadOld) { + this.loadOld = loadOld; + } + + public boolean isAutoAccept() { + return autoAccept; + } + + public void setAutoAccept(boolean autoAccept) { + this.autoAccept = autoAccept; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getRedirectOutput() { + return redirectOutput; + } + + public void setRedirectOutput(String redirectOutput) { + this.redirectOutput = redirectOutput; + } + + public boolean isServerMode() { + return serverMode; + } + + public void setServerMode(boolean serverMode) { + this.serverMode = serverMode; + } +} diff --git a/Core/src/main/java/me/zacharias/chat/ollama/OllamaMessageRole.java b/Core/src/main/java/me/zacharias/chat/ollama/OllamaMessageRole.java index 4fd9e34..115e6a3 100644 --- a/Core/src/main/java/me/zacharias/chat/ollama/OllamaMessageRole.java +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaMessageRole.java @@ -15,4 +15,12 @@ public enum OllamaMessageRole { public String getRole() { return role; } + + public static OllamaMessageRole fromRole(String role) { + for(OllamaMessageRole roleRole : values()) { + if(roleRole.role.equals(role)) + return roleRole; + } + throw new IllegalArgumentException("Invalid role: " + role); + } } diff --git a/Core/src/main/java/me/zacharias/chat/ollama/OllamaMessageToolCall.java b/Core/src/main/java/me/zacharias/chat/ollama/OllamaMessageToolCall.java new file mode 100644 index 0000000..6165dac --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaMessageToolCall.java @@ -0,0 +1,25 @@ +package me.zacharias.chat.ollama; + +import org.json.JSONArray; +import org.json.JSONObject; + +public class OllamaMessageToolCall extends OllamaMessage{ + + private JSONArray tool_calls; + + public OllamaMessageToolCall(OllamaMessageRole role, String content, JSONArray tool_calls) { + super(role, content); + this.tool_calls = tool_calls; + } + + @Override + public String toString() { + JSONObject json = new JSONObject(); + + json.put("role", role); + json.put("content", content); + json.put("tool_calls", tool_calls); + + return json.toString(); + } +} diff --git a/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java b/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java index 22a2e5a..69e0e0f 100644 --- a/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java @@ -1,8 +1,12 @@ package me.zacharias.chat.ollama; +import me.zacharias.chat.core.LaunchOptions; import org.json.JSONArray; import org.json.JSONObject; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -25,6 +29,35 @@ public class OllamaObject { this.options = options; this.stream = stream; this.keep_alive = keep_alive; + LaunchOptions launchOptions = new LaunchOptions(); + if(launchOptions.isLoadOld()) { + System.out.println("Loading old data..."); + File f = new File("./cache/messages.json"); + if(f.exists()) { + try { + BufferedReader br = new BufferedReader(new FileReader(f)); + StringBuilder data = new StringBuilder(); + String buffer = null; + while ((buffer = br.readLine()) != null) { + data.append(buffer).append("\n"); + } + JSONArray jsonArray = new JSONArray(data.toString()); + for (int i = 0; i < jsonArray.length(); i++) { + JSONObject obj = jsonArray.getJSONObject(i); + OllamaMessage message; + if (!obj.has("tool_calls")) { + message = new OllamaMessage(obj.getEnum(OllamaMessageRole.class, "role"), obj.getString("content")); + } else { + message = new OllamaMessageToolCall(obj.getEnum(OllamaMessageRole.class, "role"), obj.getString("content"), obj.getJSONArray("tool_calls")); + } + messages.add(message); + } + }catch (Exception e) { + System.out.println("Error loading old data"); + e.printStackTrace(); + } + } + } } public String getModel() { diff --git a/Display/src/main/java/me/zacharias/chat/display/PythonRunner.java b/Display/src/main/java/me/zacharias/chat/display/PythonRunner.java index 65cb877..faf805e 100644 --- a/Display/src/main/java/me/zacharias/chat/display/PythonRunner.java +++ b/Display/src/main/java/me/zacharias/chat/display/PythonRunner.java @@ -6,11 +6,9 @@ import com.github.dockerjava.api.command.LogContainerCmd; import com.github.dockerjava.api.model.Frame; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; -import me.zacharias.chat.ollama.OllamaFunctionArgument; -import me.zacharias.chat.ollama.OllamaFuntionTool; -import me.zacharias.chat.ollama.OllamaPerameter; -import me.zacharias.chat.ollama.OllamaToolRespnce; +import me.zacharias.chat.ollama.*; import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; +import org.apache.http.conn.HttpHostConnectException; import java.io.*; import java.nio.charset.StandardCharsets; @@ -105,38 +103,43 @@ public class PythonRunner extends OllamaFuntionTool { }catch(IOException e) {} } - String containerId = dockerClient.createContainerCmd("python").withCmd("python", name).exec().getId(); - dockerClient.copyArchiveToContainerCmd(containerId) - .withHostResource(pythonFile.getPath()) - //.withRemotePath("~/") - .exec(); - dockerClient.startContainerCmd(containerId).exec(); + try { + String containerId = dockerClient.createContainerCmd("python").withCmd("python", name).exec().getId(); + dockerClient.copyArchiveToContainerCmd(containerId) + .withHostResource(pythonFile.getPath()) + //.withRemotePath("~/") + .exec(); - GetContainerLog log = new GetContainerLog(dockerClient, containerId); + dockerClient.startContainerCmd(containerId).exec(); - List logs = new ArrayList<>(); + GetContainerLog log = new GetContainerLog(dockerClient, containerId); - do { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - throw new RuntimeException(e); + List logs = new ArrayList<>(); + + do { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + logs.addAll(log.getDockerLogs()); } - logs.addAll(log.getDockerLogs()); + while (logs.isEmpty()); + + StringBuilder output = new StringBuilder(); + + for (String s : logs) { + output.append(s).append("\n"); + } + + //writeLog("Result from python: " + output.toString()); + + return new OllamaToolRespnce(name(), output.toString()); } - while (logs.isEmpty()); - - StringBuilder output = new StringBuilder(); - - for(String s : logs) - { - output.append(s).append("\n"); + catch (Exception e) { + throw new OllamaToolErrorException(name(), "Docker unavalible"); } - - //writeLog("Result from python: " + output.toString()); - - return new OllamaToolRespnce(name(), output.toString()); } public class GetContainerLog { diff --git a/launcher/build.gradle b/launcher/build.gradle index 426c970..caa27eb 100644 --- a/launcher/build.gradle +++ b/launcher/build.gradle @@ -8,6 +8,7 @@ version = '1.0-SNAPSHOT' dependencies { implementation project(":Display") implementation project(":API") + implementation project(":Core") } jar{ diff --git a/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java b/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java index b354d69..4aa2d0c 100644 --- a/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java +++ b/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java @@ -1,23 +1,24 @@ package me.zacharias.chat.launcher; import me.zacharias.chat.api.APIServer; +import me.zacharias.chat.core.LaunchOptions; import me.zacharias.chat.display.Display; public class Launcher { public static void main(String[] args) { - boolean serverMode = false; - int port = 39075; - String redirectedOutput = null; + + LaunchOptions options = LaunchOptions.getInstance(); for (int i = 0; i < args.length; i++) { String arg = args[i].toLowerCase(); String argValue = (i + 1 < args.length) ? args[i + 1] : null; switch (arg) { - case "-s", "--server" -> serverMode = true; + case "-s", "--server" -> options.setServerMode(true); case "-p", "--port" -> { if (argValue != null) { - port = Integer.parseInt(argValue); + options.setPort(Integer.parseInt(argValue)); + i++; } else { System.out.println("Missing argument for -p or --port option"); System.exit(1); @@ -25,7 +26,8 @@ public class Launcher { } case "-o", "--output" -> { if (argValue != null) { - redirectedOutput = argValue; + options.setRedirectOutput(argValue); + i++; } else { System.out.println("Missing argument for -o or --output option"); System.exit(1); @@ -42,10 +44,20 @@ public class Launcher { -s --server Starts the application as API server -p --port Provides the port number that the API server shuld use, defaults to 39075 -o --output Redirects the API Server output to another file + -y Auto accepts to prompts, used for a seamless run. Not recomended when running as Display + -l --loadOld Loads the old message history. [Default] --api Provides API docs """); return; } + case "-y" -> + { + options.setAutoAccept(true); + } + case "-l", "--loadold" -> + { + options.setLoadOld(true); + } default -> { System.out.println("Unknown option: " + arg+"\nUse --help for help"); @@ -55,14 +67,14 @@ public class Launcher { } } - if (redirectedOutput != null && !serverMode) { + if (options.getRedirectOutput() != null && !options.isServerMode()) { System.out.println("Cannot run with a redirected output without running in server mode"); return; } - if (serverMode) { + if (options.isServerMode()) { System.out.println("Starting in API mode..."); - new APIServer(port, redirectedOutput); + new APIServer(); } else { System.out.println("Starting in Display mode..."); new Display();