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'
|
group = 'me.zacharias.neurodock'
|
||||||
version = '1.5'
|
version = '1.6'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":Plugin-API")
|
implementation project(":Plugin-API")
|
||||||
|
|||||||
@@ -75,9 +75,15 @@ public class Core {
|
|||||||
public static File CACHE_DIRECTORY;
|
public static File CACHE_DIRECTORY;
|
||||||
|
|
||||||
static {
|
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");
|
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) {
|
public static void setDataDirectory(String dataDirectory) {
|
||||||
String data;
|
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.
|
* 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.
|
* TODO: Fix a better name and refactor this method.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = true)
|
||||||
private void constructCore(){
|
private void constructCore(){
|
||||||
File dir = new File("./logs/");
|
File dir = new File("./logs/");
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
@@ -226,34 +402,37 @@ public class Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of Core with the provided PrintMessageHandler
|
* Creates a new instance of Core with the provided PrintMessageHandler,
|
||||||
* @param printMessageHandler The PrintMessageHandler to use as the default Output
|
* defaulting the Ollama backend to {@code localhost}.
|
||||||
|
*
|
||||||
|
* @param printMessageHandler The PrintMessageHandler to use as the default output
|
||||||
*/
|
*/
|
||||||
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler) {
|
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler) {
|
||||||
this.printMessageHandler = printMessageHandler;
|
this(printMessageHandler, "localhost");
|
||||||
ollamaIP = "localhost";
|
|
||||||
|
|
||||||
// TODO: This is a dirty way to not have a masisive init block, please check this methods comment.
|
|
||||||
constructCore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of Core with the provided PrintMessageHandler, with a specific IP used for Ollama
|
* Creates a new instance of Core with the provided PrintMessageHandler
|
||||||
* @param printMessageHandler The PrintMessageHandler to use as the default Output
|
* and a specific Ollama backend address.
|
||||||
* @param ollamaIP The IP used for the Ollama backend
|
*
|
||||||
|
* @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)
|
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler, @NotNull String ollamaIP)
|
||||||
{
|
{
|
||||||
this.printMessageHandler = printMessageHandler;
|
this.printMessageHandler = printMessageHandler;
|
||||||
this.ollamaIP = ollamaIP;
|
this.ollamaIP = ollamaIP;
|
||||||
|
|
||||||
// TODO: This is a dirty way to not have a masisive init block, please check this methods comment.
|
initDirectories();
|
||||||
constructCore();
|
initOllamaUrl();
|
||||||
|
initLogWriter();
|
||||||
|
initScheduler();
|
||||||
|
initShutdownHook();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link #ollamaObject} object to the provided argument,
|
* 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
|
* @param ollamaObject The OllamaObject to use
|
||||||
*/
|
*/
|
||||||
public void setOllamaObject(OllamaObject ollamaObject) {
|
public void setOllamaObject(OllamaObject ollamaObject) {
|
||||||
@@ -317,7 +496,6 @@ public class Core {
|
|||||||
{
|
{
|
||||||
for(Pair<OllamaFunctionTool, String> tool : tools)
|
for(Pair<OllamaFunctionTool, String> tool : tools)
|
||||||
{
|
{
|
||||||
|
|
||||||
addTool(tool.getKey(), tool.getValue());
|
addTool(tool.getKey(), tool.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -338,16 +516,24 @@ public class Core {
|
|||||||
return ollamaObject;
|
return ollamaObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a tool with a given name
|
||||||
|
* @param name The tool to remove
|
||||||
|
*/
|
||||||
public void removeTool(String name) {
|
public void removeTool(String name) {
|
||||||
Pair<OllamaFunctionTool, String> funtionTool = funtionTools.stream().filter(tool -> tool.getKey().name().equalsIgnoreCase(name)).findFirst().orElse(null);
|
Pair<OllamaFunctionTool, String> funtionTool = funtionTools.stream().filter(tool -> tool.getKey().name().equalsIgnoreCase(name)).findFirst().orElse(null);
|
||||||
funtionTools.remove(funtionTool);
|
funtionTools.stream()
|
||||||
if(funtionTool.getKey() == null) {
|
.filter(tool -> tool.getKey().name().equalsIgnoreCase(name))
|
||||||
// This should never happens... So if it does, Shit hit the fan
|
.findFirst()
|
||||||
Exception e = new IllegalArgumentException("Function tool with name '"+name+"' does not exist");
|
.ifPresentOrElse(tool -> {
|
||||||
e.printStackTrace();
|
funtionTools.remove(tool);
|
||||||
|
ollamaObject.removeTool(tool.getKey());
|
||||||
|
}, () -> {
|
||||||
|
|
||||||
|
new IllegalArgumentException("Function tool with name '"+name+"' does not exist")
|
||||||
|
.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
});
|
||||||
ollamaObject.removeTool(funtionTool.getKey());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -461,8 +647,7 @@ public class Core {
|
|||||||
if(functions.isEmpty()) {
|
if(functions.isEmpty()) {
|
||||||
OllamaToolError ollamaToolError = new OllamaToolError("Function '" + function.getString("name") + "' does not exist");
|
OllamaToolError ollamaToolError = new OllamaToolError("Function '" + function.getString("name") + "' does not exist");
|
||||||
ollamaObject.addMessage(ollamaToolError);
|
ollamaObject.addMessage(ollamaToolError);
|
||||||
printMessageHandler.printErrorMessage(ollamaToolError);
|
|
||||||
writeLog("Failed function call to "+function.getString("name"));
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class AddArrayMemory extends OllamaFunctionTool {
|
|||||||
if(!args[0].argument().equals("memory")) {
|
if(!args[0].argument().equals("memory")) {
|
||||||
throw new OllamaToolErrorException(name(), "no memory provided");
|
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");
|
return new OllamaToolResponse(name(), "Added arrayed memory");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user