refactor(Core): decompose constructCore() into focused init methods
build / build (push) Has been cancelled
build / build (push) Has been cancelled
- Extracted Core#constructCore into: initDirectories(), ensureDir(String), initOllamaUrl(), initLogWriter(), rotateLogFile(), initScheduler(), initShutdownHook(), closeLogWriter(), saveMessages(), buildMessagesArray(), writeMessagesTo(File, JSONArray) - Overloaded constructors for Core - Cleaned up some Javadocs Fixed error(AddArrayMemory) - Line 51 used the wrong value Updated minor version for(::Core) Dev note: Long overdue — that generic init block was ugly and is finally laid to rest.
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
group = 'me.zacharias.neurodock'
|
||||
version = '1.5'
|
||||
version = '1.6'
|
||||
|
||||
dependencies {
|
||||
implementation project(":Plugin-API")
|
||||
|
||||
@@ -75,9 +75,15 @@ public class Core {
|
||||
public static File CACHE_DIRECTORY;
|
||||
|
||||
static {
|
||||
// This should not be enforced like this, instead this should read a data field, or wait for an init to be called....
|
||||
// Unsure of how to properly do this at this time, however.
|
||||
setDataDirectory("AI-Chat");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the data directory in appropriate locations depending on the host OS, falling back to $WORKING_DIR/data
|
||||
* @param dataDirectory the data directory to use
|
||||
*/
|
||||
public static void setDataDirectory(String dataDirectory) {
|
||||
String data;
|
||||
|
||||
@@ -120,11 +126,181 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the base directories required by the application.
|
||||
* <p>
|
||||
* TODO: These paths should be resolved to OS-appropriate locations
|
||||
* rather than being relative to the working directory.
|
||||
*/
|
||||
private void initDirectories() {
|
||||
ensureDir("./logs/");
|
||||
ensureDir("./pythonFiles/");
|
||||
ensureDir("./messages/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that a directory exists at the given path, creating it if it does not.
|
||||
*
|
||||
* @param path The path of the directory to create
|
||||
*/
|
||||
private static void ensureDir(String path) {
|
||||
File dir = new File(path);
|
||||
if (!dir.exists()) dir.mkdir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the Ollama API {@link #url} from {@link #ollamaIP} and {@link #ollamaPort}.
|
||||
*
|
||||
* @throws RuntimeException If the resulting URL is malformed or the URI syntax is invalid
|
||||
*/
|
||||
private void initOllamaUrl() {
|
||||
try {
|
||||
url = new URI("http://" + ollamaIP + ":" + ollamaPort + "/api/chat").toURL();
|
||||
} catch (MalformedURLException | URISyntaxException e) {
|
||||
throw new RuntimeException("Failed to construct Ollama URL", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the {@link #logWriter}, rotating any pre-existing log file beforehand.
|
||||
*
|
||||
* @throws RuntimeException If the log file cannot be created or opened for writing
|
||||
* @see #rotateLogFile()
|
||||
*/
|
||||
private void initLogWriter() {
|
||||
try {
|
||||
if (logFile.exists()) {
|
||||
rotateLogFile();
|
||||
}
|
||||
logWriter = new BufferedWriter(new FileWriter(logFile));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to initialize log writer", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the existing log file by renaming it to a timestamped filename derived from
|
||||
* its first line, then recreates {@link #logFile} as a fresh {@code latest.log}.
|
||||
* <p>
|
||||
* If the existing log file is empty, it is simply deleted and recreated.
|
||||
*
|
||||
* @throws IOException If the log file cannot be read, renamed, deleted, or recreated
|
||||
*/
|
||||
private void rotateLogFile() throws IOException {
|
||||
try (BufferedReader br = new BufferedReader(new FileReader(logFile))) {
|
||||
String line = br.readLine();
|
||||
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("Existing log file is empty, overwriting it!");
|
||||
logFile.delete();
|
||||
}
|
||||
}
|
||||
logFile.createNewFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the {@link #scheduler} and schedules periodic flushing of {@link #logWriter}
|
||||
* to disk every 3 minutes.
|
||||
*/
|
||||
private void initScheduler() {
|
||||
this.scheduler = Executors.newScheduledThreadPool(1);
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
logWriter.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, 0, 3, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a JVM shutdown hook that shuts down the {@link #scheduler},
|
||||
* flushes and closes the {@link #logWriter}, and persists the current
|
||||
* session's messages to disk.
|
||||
*
|
||||
* @see #closeLogWriter()
|
||||
* @see #saveMessages()
|
||||
*/
|
||||
private void initShutdownHook() {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
scheduler.shutdownNow();
|
||||
closeLogWriter();
|
||||
saveMessages();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes and closes the {@link #logWriter}.
|
||||
* <p>
|
||||
* Any {@link IOException} thrown during this process is suppressed, as the writer
|
||||
* may already be closed by the time the shutdown hook runs.
|
||||
*/
|
||||
private void closeLogWriter() {
|
||||
try {
|
||||
logWriter.flush();
|
||||
logWriter.close();
|
||||
} catch (IOException ignore) {
|
||||
System.out.println("Failed to flush log file, but that is not a problem.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the current session's messages from the {@link OllamaObject} to two locations:
|
||||
* a timestamped archive file under {@code ./messages/}, and a rolling {@code messages.json}
|
||||
* under {@link #DATA_DIR} for resuming the session later.
|
||||
*
|
||||
* @see #buildMessagesArray()
|
||||
* @see #writeMessagesTo(File, JSONArray)
|
||||
*/
|
||||
private void saveMessages() {
|
||||
JSONArray messages = buildMessagesArray();
|
||||
|
||||
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd_HH-mm-ss"));
|
||||
writeMessagesTo(new File("./messages/" + timestamp + ".json"), messages);
|
||||
writeMessagesTo(new File(DATA_DIR, "messages.json"), messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all messages from the {@link OllamaObject} and serializes them into a {@link JSONArray}.
|
||||
*
|
||||
* @return A {@link JSONArray} containing all current messages
|
||||
*/
|
||||
private JSONArray buildMessagesArray() {
|
||||
JSONArray messages = new JSONArray();
|
||||
for (OllamaMessage message : ollamaObject.getMessages()) {
|
||||
messages.put(new JSONObject(message.toString()));
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@link JSONArray} of messages to the given file, overwriting it if it already exists.
|
||||
*
|
||||
* @param file The target file to write to
|
||||
* @param messages The message content to write
|
||||
* @throws RuntimeException If the file cannot be created or written to
|
||||
*/
|
||||
private void writeMessagesTo(File file, JSONArray messages) {
|
||||
try {
|
||||
if (file.exists()) file.delete();
|
||||
file.createNewFile();
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
|
||||
writer.write(messages.toString());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to write messages to " + file.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is pending a better name, as this was introduced from being a generic block. This was ugly, and ideally things here should be refactored more.
|
||||
*
|
||||
* TODO: Fix a better name and refactor this method.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
private void constructCore(){
|
||||
File dir = new File("./logs/");
|
||||
if (!dir.exists()) {
|
||||
@@ -226,34 +402,37 @@ public class Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of Core with the provided PrintMessageHandler
|
||||
* @param printMessageHandler The PrintMessageHandler to use as the default Output
|
||||
* Creates a new instance of Core with the provided PrintMessageHandler,
|
||||
* defaulting the Ollama backend to {@code localhost}.
|
||||
*
|
||||
* @param printMessageHandler The PrintMessageHandler to use as the default output
|
||||
*/
|
||||
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler) {
|
||||
this.printMessageHandler = printMessageHandler;
|
||||
ollamaIP = "localhost";
|
||||
|
||||
// TODO: This is a dirty way to not have a masisive init block, please check this methods comment.
|
||||
constructCore();
|
||||
this(printMessageHandler, "localhost");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of Core with the provided PrintMessageHandler, with a specific IP used for Ollama
|
||||
* @param printMessageHandler The PrintMessageHandler to use as the default Output
|
||||
* @param ollamaIP The IP used for the Ollama backend
|
||||
* Creates a new instance of Core with the provided PrintMessageHandler
|
||||
* and a specific Ollama backend address.
|
||||
*
|
||||
* @param printMessageHandler The PrintMessageHandler to use as the default output
|
||||
* @param ollamaIP The IP or hostname of the Ollama backend
|
||||
*/
|
||||
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler, @NotNull String ollamaIP)
|
||||
{
|
||||
this.printMessageHandler = printMessageHandler;
|
||||
this.ollamaIP = ollamaIP;
|
||||
|
||||
// TODO: This is a dirty way to not have a masisive init block, please check this methods comment.
|
||||
constructCore();
|
||||
initDirectories();
|
||||
initOllamaUrl();
|
||||
initLogWriter();
|
||||
initScheduler();
|
||||
initShutdownHook();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #ollamaObject} object to the provided argument,
|
||||
* Also adds the memory base system. see {@link Core#setOllamaObjectNoMemory} if you don't want to add memory functions
|
||||
* Also adds the memory base system. See {@link Core#setOllamaObjectNoMemory} if you don't want to add memory functions
|
||||
* @param ollamaObject The OllamaObject to use
|
||||
*/
|
||||
public void setOllamaObject(OllamaObject ollamaObject) {
|
||||
@@ -317,7 +496,6 @@ public class Core {
|
||||
{
|
||||
for(Pair<OllamaFunctionTool, String> tool : tools)
|
||||
{
|
||||
|
||||
addTool(tool.getKey(), tool.getValue());
|
||||
}
|
||||
}
|
||||
@@ -338,16 +516,24 @@ public class Core {
|
||||
return ollamaObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a tool with a given name
|
||||
* @param name The tool to remove
|
||||
*/
|
||||
public void removeTool(String name) {
|
||||
Pair<OllamaFunctionTool, String> funtionTool = funtionTools.stream().filter(tool -> tool.getKey().name().equalsIgnoreCase(name)).findFirst().orElse(null);
|
||||
funtionTools.remove(funtionTool);
|
||||
if(funtionTool.getKey() == null) {
|
||||
// This should never happens... So if it does, Shit hit the fan
|
||||
Exception e = new IllegalArgumentException("Function tool with name '"+name+"' does not exist");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
ollamaObject.removeTool(funtionTool.getKey());
|
||||
funtionTools.stream()
|
||||
.filter(tool -> tool.getKey().name().equalsIgnoreCase(name))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(tool -> {
|
||||
funtionTools.remove(tool);
|
||||
ollamaObject.removeTool(tool.getKey());
|
||||
}, () -> {
|
||||
|
||||
new IllegalArgumentException("Function tool with name '"+name+"' does not exist")
|
||||
.printStackTrace();
|
||||
System.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -461,8 +647,7 @@ public class Core {
|
||||
if(functions.isEmpty()) {
|
||||
OllamaToolError ollamaToolError = new OllamaToolError("Function '" + function.getString("name") + "' does not exist");
|
||||
ollamaObject.addMessage(ollamaToolError);
|
||||
printMessageHandler.printErrorMessage(ollamaToolError);
|
||||
writeLog("Failed function call to "+function.getString("name"));
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public class AddArrayMemory extends OllamaFunctionTool {
|
||||
if(!args[0].argument().equals("memory")) {
|
||||
throw new OllamaToolErrorException(name(), "no memory provided");
|
||||
}
|
||||
this.memory.addMemory(name(), args[0].argument());
|
||||
this.memory.addMemory(name(), (String) args[0].value());
|
||||
}
|
||||
return new OllamaToolResponse(name(), "Added arrayed memory");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user