Basicly refactord the entire project into a modular nature

This commit is contained in:
2025-02-20 23:28:24 +01:00
parent 6935e938a3
commit b892306c09
26 changed files with 340 additions and 145 deletions

16
Core/build.gradle Normal file
View File

@@ -0,0 +1,16 @@
plugins {
id 'java-library'
}
group = 'me.zacharias'
version = '1.0-SNAPSHOT'
dependencies {
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21)) // Set Java version
}
}

View File

@@ -0,0 +1,256 @@
package me.zacharias.chat.core;
import me.zacharias.chat.ollama.*;
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.*;
import java.net.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Core {
private static File logFile = new File("./logs/latest.log");
private static BufferedWriter logWriter;
private ScheduledExecutorService scheduler;
private OllamaObject ollamaObject;
private ArrayList<OllamaFuntionTool> funtionTools = new ArrayList<>();
private String ollamaIP = "localhost";//"192.168.5.184";
private int ollamaPort = 11434;
private URL url;
private final PrintMessageHandler printMessageHandler;
private boolean supportColor;
{
File dir = new File("./logs/");
if (!dir.exists()) {
dir.mkdir();
}
dir = new File("./pythonFiles/");
if (!dir.exists()) {
dir.mkdir();
}
dir = new File("./messages");
if (!dir.exists()) {
dir.mkdir();
}
try {
url = new URI("http://"+ollamaIP+":"+ollamaPort+"/api/chat").toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
try {
if (logFile.exists()) {
BufferedReader br = new BufferedReader(new FileReader(logFile));
String line = br.readLine();
br.close();
if (line != null) {
String date = line.substring(0, line.indexOf(">")).replaceAll("[/:]", "-");
logFile.renameTo(new File(logFile.getParentFile(), date + ".log"));
logFile = new File("./logs/latest.log");
}
else {
System.out.println("Exisitng log file is empty, overwriting it!");
logFile.delete();
}
logFile.createNewFile();
}
logWriter = new BufferedWriter(new FileWriter(logFile));
}catch (IOException e) {
throw new RuntimeException(e);
}
this.scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
try {
logWriter.flush();
//System.out.println("Buffer flushed to file.");
} catch (IOException e) {
e.printStackTrace();
}
}, 0, 3, TimeUnit.MINUTES);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
scheduler.shutdownNow();
try {
logWriter.flush();
logWriter.close();
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH-mm-ss");
File messagesFile = new File("./messages/"+now.format(formatter)+".txt");
BufferedWriter messagesWriter = new BufferedWriter(new FileWriter(messagesFile));
JSONArray messages = new JSONArray();
for(OllamaMessage message : ollamaObject.getMessages()) {
messages.put(new JSONObject(message.toString()));
}
messagesWriter.write(messages.toString());
messagesWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
}
public Core(PrintMessageHandler printMessageHandler) {
this.printMessageHandler = printMessageHandler;
supportColor = printMessageHandler.color();
}
public void setOllamaObject(OllamaObject ollamaObject) {
this.ollamaObject = ollamaObject;
}
public OllamaObject getOllamaObject() {
return ollamaObject;
}
public void addFuntionTool(OllamaFuntionTool funtionTool) {
funtionTools.add(funtionTool);
}
public static void flushLog() {
try {
logWriter.flush();
}catch (IOException e) {}
}
public JSONObject qurryOllama()
{
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
connection.setConnectTimeout(80*1000);
try(DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
wr.writeBytes(ollamaObject.toString());
wr.flush();
}
int responseCode = connection.getResponseCode();
// HTTP_OK or 200 response code generally means that the server ran successfully without any errors
StringBuilder response = new StringBuilder();
// Read response content
// connection.getInputStream() purpose is to obtain an input stream for reading the server's response.
try (
BufferedReader reader = new BufferedReader( new InputStreamReader( connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
response.append(line); // Adds every line to response till the end of file.
}
}
if (responseCode == HttpURLConnection.HTTP_OK) {
connection.disconnect();
return new JSONObject(response.toString());
}
else {
connection.disconnect();
System.err.println("Error: HTTP Response code - " + responseCode + "\n"+response.toString());
throw new RuntimeException("HTTP Response code - " + responseCode);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void handleResponce(JSONObject responce)
{
//System.out.println("Responce: "+responce);
if(responce != null) {
writeLog("Raw responce: "+responce.toString());
if(responce.getJSONObject("message").has("tool_calls"))
{
JSONArray calls = responce.getJSONObject("message").getJSONArray("tool_calls");
for(Object call : calls)
{
if(call instanceof JSONObject jsonObject)
{
if(jsonObject.has("function"))
{
JSONObject function = jsonObject.getJSONObject("function");
List<OllamaFuntionTool> functions = funtionTools.stream().filter(func -> func.name().equalsIgnoreCase(function.getString("name"))).toList();
if(functions.isEmpty()) {
ollamaObject.addMessage(new OllamaToolError("Function '"+function.getString("name")+"' does not exist"));
printMessageHandler.printMessage((supportColor ?"\u001b[31m":"")+"Tried funtion call "+function.getString("name")+" but failed to find it."+(printMessageHandler.color()?"\u001b[0m":""));
writeLog("Failed function call to "+function.getString("name"));
}
else {
OllamaFuntionTool func = functions.getFirst();
ArrayList<OllamaFunctionArgument> argumentArrayList = new ArrayList<>();
JSONObject arguments = function.getJSONObject("arguments");
for (String key : arguments.keySet()) {
argumentArrayList.add(new OllamaFunctionArgument(key, arguments.get(key)));
}
try {
OllamaToolRespnce function1 = func.function(argumentArrayList.toArray(new OllamaFunctionArgument[0]));
ollamaObject.addMessage(function1);
printMessageHandler.printMessage((supportColor?"\u001b[34m":"")+"Call "+func.name() + (printMessageHandler.color()?"\u001b[0m":""));
writeLog("Successfully function call " + func.name() + " output: " + function1.getResponse());
} catch (OllamaToolErrorException e) {
ollamaObject.addMessage(new OllamaToolError(e.getMessage()));
printMessageHandler.printMessage((supportColor?"\u001b[31m":"")+"Tried funtion call " + func.name() + " but failed due to " + e.getError() + (supportColor?"\u001b[0m":""));
writeLog(e.getMessage());
}
}
}
}
}
checkIfResponceMessage(responce);
handleResponce(qurryOllama());
}
else checkIfResponceMessage(responce);
}
}
private void checkIfResponceMessage(JSONObject responce) {
if(responce.getJSONObject("message").has("content") && !responce.getJSONObject("message").getString("content").isBlank())
{
printMessageHandler.printMessage((supportColor?"\u001b[32m":"")+responce.getJSONObject("message").getString("content")+(supportColor?"\u001b[0m":""));
writeLog("Response content: "+responce.getJSONObject("message").getString("content"));
ollamaObject.addMessage(new OllamaMessage(OllamaMessageRole.ASSISTANT, responce.getJSONObject("message").getString("content")));
}
}
public static void writeLog(String message)
{
try {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd%EEEE HH:mm:ss'#'SSS");
logWriter.write(now.format(formatter) + "> " + message + "\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,6 @@
package me.zacharias.chat.core;
public interface PrintMessageHandler {
void printMessage(String message);
boolean color();
}

View File

@@ -0,0 +1,19 @@
package me.zacharias.chat.ollama;
public class OllamaFunctionArgument {
private final String argument;
private final Object value;
public OllamaFunctionArgument(String argument, Object value) {
this.argument = argument;
this.value = value;
}
public String getArgument() {
return argument;
}
public Object getValue() {
return value;
}
}

View File

@@ -0,0 +1,28 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
public abstract class OllamaFuntionTool implements OllamaTool {
@Override
public String toString() {
JSONObject ret = new JSONObject();
ret.put("tool", "function");
JSONObject function = new JSONObject();
function.put("name", name());
function.put("description", description());
function.put("parameters", (parameters() == null?
new JSONObject() : new JSONObject(parameters().toString())));
ret.put("function", function);
return ret.toString();
}
abstract public String name();
abstract public String description();
abstract public OllamaPerameter parameters();
abstract public OllamaToolRespnce function(OllamaFunctionArgument... args);
}

View File

@@ -0,0 +1,21 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
public class OllamaMessage {
OllamaMessageRole role;
String content;
public OllamaMessage(OllamaMessageRole role, String content) {
this.role = role;
this.content = content;
}
@Override
public String toString() {
JSONObject json = new JSONObject();
json.put("role", role.getRole());
json.put("content", content);
return json.toString();
}
}

View File

@@ -0,0 +1,18 @@
package me.zacharias.chat.ollama;
public enum OllamaMessageRole {
USER("user"),
ASSISTANT("assistant"),
TOOL("tool"),
SYSTEM("system");
private String role;
OllamaMessageRole(String role) {
this.role = role;
}
public String getRole() {
return role;
}
}

View File

@@ -0,0 +1,166 @@
package me.zacharias.chat.ollama;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OllamaObject {
String model;
ArrayList<OllamaMessage> messages;
OllamaTool[] tools;
JSONObject format;
Map<String, Object> options;
boolean stream;
String keep_alive;
private OllamaObject(String model, ArrayList<OllamaMessage> messages, OllamaTool[] tools, JSONObject format, Map<String, Object> options, boolean stream, String keep_alive) {
this.model = model;
this.messages = messages;
this.tools = tools;
this.format = format;
this.options = options;
this.stream = stream;
this.keep_alive = keep_alive;
}
public String getModel() {
return model;
}
public ArrayList<OllamaMessage> getMessages() {
return messages;
}
public OllamaTool[] getTools() {
return tools;
}
public JSONObject getFormat() {
return format;
}
public Map<String, Object> getOptions() {
return options;
}
public boolean isStream() {
return stream;
}
public String getKeep_alive() {
return keep_alive;
}
public void addMessage(OllamaMessage message) {
messages.add(message);
}
@Override
public String toString() {
JSONObject json = new JSONObject();
JSONArray tools = new JSONArray();
for (OllamaTool tool : this.tools) {
tools.put(new JSONObject(tool.toString()));
}
JSONArray messages = new JSONArray();
for (OllamaMessage message : this.messages) {
messages.put(new JSONObject(message.toString()));
}
json.put("model", model);
json.put("messages", messages);
json.put("tools", tools);
json.put("format", format);
json.put("options", options);
json.put("stream", stream);
json.put("keep_alive", keep_alive);
return json.toString();
}
public static OllamaObjectBuilder builder()
{
return new OllamaObjectBuilder();
}
public static class OllamaObjectBuilder {
String model;
ArrayList<OllamaMessage> messages = new ArrayList<>();
ArrayList<OllamaTool> tools = new ArrayList<>();
JSONObject format;
Map<String, Object> options = new HashMap<>();
boolean stream;
String keep_alive;
public OllamaObjectBuilder() {}
public OllamaObjectBuilder format(String format) {
this.format = new JSONObject(format);
return this;
}
public OllamaObjectBuilder options(Map<String, Object> options) {
this.options.putAll(options);
return this;
}
public OllamaObjectBuilder option(String key, String value) {
this.options.put(key, value);
return this;
}
public OllamaObjectBuilder stream(boolean stream) {
this.stream = stream;
return this;
}
public OllamaObjectBuilder keep_alive(String keep_alive) {
this.keep_alive = keep_alive;
return this;
}
public OllamaObjectBuilder keep_alive(int minutes) {
this.keep_alive = minutes+"m";
return this;
}
public OllamaObjectBuilder addTool(OllamaTool tools) {
this.tools.add(tools);
return this;
}
public OllamaObjectBuilder addTools(ArrayList<? extends OllamaTool> tools) {
this.tools.addAll(tools);
return this;
}
public OllamaObjectBuilder addTools(OllamaTool... tools) {
this.tools.addAll(List.of(tools));
return this;
}
public OllamaObjectBuilder addMessages(OllamaMessage... messages) {
this.messages.addAll(List.of(messages));
return this;
}
public OllamaObjectBuilder addMessage(OllamaMessage messages) {
this.messages.add(messages);
return this;
}
public OllamaObjectBuilder setModel(String model) {
this.model = model;
return this;
}
public OllamaObject build() {
return new OllamaObject(model, messages, tools.toArray(new OllamaTool[0]), format, options, stream, keep_alive);
}
}
}

View File

@@ -0,0 +1,116 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class OllamaPerameter {
private OllamaPerameter(JSONObject properties, String[] required) {
this.properties = properties;
this.required = required;
};
@Override
public String toString() {
JSONObject json = new JSONObject();
json.put("type", "object");
json.put("properties", properties);
json.put("required", required);
return json.toString();
}
JSONObject properties;
String[] required;
public JSONObject getProperties() {
return properties;
}
public String[] getRequired() {
return required;
}
public static OllamaPerameterBuilder builder() {
return new OllamaPerameterBuilder();
}
public static class OllamaPerameterBuilder {
Map<String, Property> propertyMap = new HashMap<>();
ArrayList<String> required = new ArrayList<>();
public OllamaPerameterBuilder addProperty(String name, Type type, String description) {
if(name == null || type == null || description == null) {
return this;
}
propertyMap.put(name, new Property(type.getType(), description));
return this;
}
public OllamaPerameterBuilder addProperty(String name, Type type, String description, boolean required) {
if(name == null || type == null || description == null) {
return this;
}
propertyMap.put(name, new Property(type.getType(), description));
this.required.add(name);
return this;
}
public OllamaPerameterBuilder required(String name) {
required.add(name);
return this;
}
public OllamaPerameterBuilder removeProperty(String name) {
propertyMap.remove(name);
return this;
}
public OllamaPerameter build() {
JSONObject properties = new JSONObject();
for(String name : propertyMap.keySet()) {
properties.put(name, new JSONObject(propertyMap.get(name).toString()));
}
return new OllamaPerameter(properties, required.toArray(new String[0]));
}
private class Property {
String type;
String description;
public Property(String type, String description) {
this.type = type;
this.description = description;
}
@Override
public String toString() {
JSONObject json = new JSONObject();
json.put("type", type);
json.put("description", description);
return json.toString();
}
}
public enum Type {
STRING("string"),
INT("int"),
BOOLEAN("boolean");
private String type;
public String getType() {
return type;
}
Type(String type) {
this.type = type;
}
}
}
}

View File

@@ -0,0 +1,5 @@
package me.zacharias.chat.ollama;
public interface OllamaTool {
}

View File

@@ -0,0 +1,15 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
public class OllamaToolError extends OllamaMessage {
String error;
public OllamaToolError(String error) {
super(OllamaMessageRole.TOOL, new JSONObject().put("error", error).toString());
this.error = error;
}
public String getError() {
return error;
}
}

View File

@@ -0,0 +1,21 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
public class OllamaToolRespnce extends OllamaMessage {
private final String tool;
private final String response;
public OllamaToolRespnce(String tool, String response) {
super(OllamaMessageRole.TOOL, new JSONObject().put("tool", tool).put("result", response).toString());
this.tool = tool;
this.response = response;
}
public String getTool() {
return tool;
}
public String getResponse() {
return response;
}
}

View File

@@ -0,0 +1,19 @@
package me.zacharias.chat.ollama.exceptions;
public class OllamaToolErrorException extends RuntimeException {
private final String tool;
private final String error;
public OllamaToolErrorException(String tool, String error) {
super(tool + ": " + error);
this.tool = tool;
this.error = error;
}
public String getTool() {
return tool;
}
public String getError() {
return error;
}
}