From 3c6a75a5873a276ed4347ec1310115260a488f67 Mon Sep 17 00:00:00 2001 From: Zacharias Date: Sun, 28 Jun 2026 15:46:57 +0200 Subject: [PATCH] Some small poking at things :) dont midn the version bump, it's easier to bump versions then to get Maven to bahave --- .idea/misc.xml | 41 +-- Core/build.gradle | 2 +- .../src/main/java/me/neurodock/core/Core.java | 48 +++- .../me/neurodock/core/memory/CoreMemory.java | 9 +- .../ollama/OllamaFunctionArgument.java | 7 + .../neurodock/ollama/OllamaFunctionTool.java | 2 +- .../me/neurodock/ollama/OllamaPerameter.java | 102 ++++++-- .../me/neurodock/plugin/loader/Loader.java | 9 +- .../me/neurodock/display/PythonRunner.java | 3 + Plugin-API/build.gradle | 2 +- .../java/me/neurodock/plugin/tool/Tool.java | 2 +- .../neurodock/plugin/tool/ToolParameters.java | 238 +++++++++++++++++- build.gradle | 4 +- 13 files changed, 377 insertions(+), 92 deletions(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index ac6e8ea..cfebfa0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -7,46 +7,7 @@ - - - - - + \ No newline at end of file diff --git a/Core/build.gradle b/Core/build.gradle index f0396fe..041f977 100644 --- a/Core/build.gradle +++ b/Core/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -version = '1.6' +version = '1.6.6' dependencies { implementation project(":Plugin-API") diff --git a/Core/src/main/java/me/neurodock/core/Core.java b/Core/src/main/java/me/neurodock/core/Core.java index bd5f04a..452523e 100644 --- a/Core/src/main/java/me/neurodock/core/Core.java +++ b/Core/src/main/java/me/neurodock/core/Core.java @@ -40,6 +40,7 @@ public class Core { * The file to write the logs to. */ private static File logFile = new File("./logs/latest.log"); + private static File logDir = new File("./logs"); /** * The writer to write the logs to. */ @@ -114,19 +115,33 @@ public class Core { 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"); + // After looking at things, just run {@link Core.setDataDirectory(String)} somewhere else before initilising the Core object and seems to be fine + setDataDirectory("AI-Chat", false); + } + + public static void setLogDirectory(String logDirectory) + { + logDir = new File(logDirectory); + if (!logDir.exists()) + { + logDir.mkdir(); + } + logFile = new File(logDirectory, "latest.log"); } /** * 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, boolean fullDirectory) { String data; if(System.getenv("AI_CHAT_DEBUG") != null) { data = "./data"; } + if(fullDirectory) { + data = dataDirectory; + } else if(System.getProperty("os.name").toLowerCase().contains("windows")) { String localappdata = System.getenv("LOCALAPPDATA"); if(localappdata == null) { @@ -170,9 +185,8 @@ public class Core { * rather than being relative to the working directory. */ private void initDirectories() { - ensureDir("./logs/"); - ensureDir("./pythonFiles/"); - ensureDir("./messages/"); + ensureDir(logDir.getAbsolutePath()); + ensureDir(DATA_DIR.getAbsolutePath() + "/messages"); } /** @@ -180,7 +194,7 @@ public class Core { * * @param path The path of the directory to create */ - private static void ensureDir(String path) { + public static void ensureDir(String path) { File dir = new File(path); if (!dir.exists()) dir.mkdir(); } @@ -206,6 +220,9 @@ public class Core { */ private void initLogWriter() { try { + if(!logDir.exists()) { + logDir.mkdir(); + } if (logFile.exists()) { rotateLogFile(); } @@ -228,8 +245,8 @@ public class Core { 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"); + logFile.renameTo(new File(logDir, date + ".log")); + logFile = new File(logDir,"/latest.log"); } else { System.out.println("Existing log file is empty, overwriting it!"); logFile.delete(); @@ -296,7 +313,7 @@ public class Core { 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.getAbsolutePath()+"/messages/" + timestamp + ".json"), messages); writeMessagesTo(new File(DATA_DIR, "messages.json"), messages); } @@ -712,7 +729,7 @@ public class Core { */ private OllamaFunctionTool findTool(JSONObject function) { return funtionTools.stream() - .filter(f -> (f.getKey().name() + f.getValue()) + .filter(f -> (f.getKey().name() + "_" + f.getValue()) .equalsIgnoreCase(function.getString("name"))) .map(Pair::getKey) .findFirst() @@ -834,12 +851,12 @@ public class Core { Loader loader = new Loader(); for(File file : files) { try(JarFile jar = new JarFile(file)){ - JarEntry pluginJsonFile = jar.getJarEntry("/plugin.json"); + JarEntry pluginJsonFile = jar.getJarEntry("plugin.json"); if(pluginJsonFile == null) { throw new PluginLoadingException("Plugin does not contain a plugin.json file", file.getName()); } - if(jar.getJarEntry("/me/neurodock/plugin/Plugin") != null) + if(jar.getJarEntry("me/neurodock/plugin/Plugin") != null) throw new PluginLoadingException("Plugin bundles NeuroDock API classes. Consider using compileOnly", file.getName()); StringBuilder pluginJsonData = new StringBuilder(); String tmp = null; @@ -857,6 +874,13 @@ public class Core { throw new RuntimeException(e); } } + + data.plugins.forEach(loadedPlugin -> { + for(OllamaFunctionTool tool : loader.getTools(loadedPlugin.plugin())) + { + addTool(tool, Source.RPCP); + } + }); } private static class LoaderPluginData extends Data { diff --git a/Core/src/main/java/me/neurodock/core/memory/CoreMemory.java b/Core/src/main/java/me/neurodock/core/memory/CoreMemory.java index ed7a8b5..8997923 100644 --- a/Core/src/main/java/me/neurodock/core/memory/CoreMemory.java +++ b/Core/src/main/java/me/neurodock/core/memory/CoreMemory.java @@ -181,16 +181,17 @@ public class CoreMemory { */ public String getMappedMemories() { ArrayList memories = new ArrayList<>(); - for (String key : memory.keySet()) { - memories.add(key + ": " + memory.getString(key)); + for (String key : memory.getJSONObject(MAPPED_MEMORY).keySet()) { + memories.add(key + ": " + memory.getJSONObject(MAPPED_MEMORY).getString(key)); } return new JSONArray(memories).toString(); } public ArrayList getMemoriesArray() { ArrayList memories = new ArrayList<>(); - for (String key : memory.keySet()) { - memories.add(key + ": " + memory.getString(key)); + int length = memory.getJSONArray(ARRAYED_MEMORY).length(); + for (int i = 0; i < length; i++) { + memories.add(i + ": " + memory.getJSONArray(ARRAYED_MEMORY).getString(i)); } return memories; } diff --git a/Core/src/main/java/me/neurodock/ollama/OllamaFunctionArgument.java b/Core/src/main/java/me/neurodock/ollama/OllamaFunctionArgument.java index 82e11e7..b579b8a 100644 --- a/Core/src/main/java/me/neurodock/ollama/OllamaFunctionArgument.java +++ b/Core/src/main/java/me/neurodock/ollama/OllamaFunctionArgument.java @@ -40,6 +40,13 @@ public record OllamaFunctionArgument(String argument, Object value) { return value; } + public > T getValue(Class enumClass) { + if(value instanceof String str) { + return Enum.valueOf(enumClass, str); + } + throw new IllegalArgumentException(String.format("%s is not a valid enum value of type %s", value.toString(), enumClass.getName())); + } + public static String deconstructOllamaFunctionArgument(OllamaFunctionArgument argument) { return argument.argument() + ": " + argument.value(); } diff --git a/Core/src/main/java/me/neurodock/ollama/OllamaFunctionTool.java b/Core/src/main/java/me/neurodock/ollama/OllamaFunctionTool.java index 776e0bc..2027740 100644 --- a/Core/src/main/java/me/neurodock/ollama/OllamaFunctionTool.java +++ b/Core/src/main/java/me/neurodock/ollama/OllamaFunctionTool.java @@ -25,7 +25,7 @@ public abstract class OllamaFunctionTool implements OllamaTool { ret.put("type", "function"); JSONObject function = new JSONObject(); - function.put("name", name()+(source != null ? source : "")); + function.put("name", name()+"_"+(source != null ? source : "")); if(description() != null) { function.put("description", description()); } diff --git a/Core/src/main/java/me/neurodock/ollama/OllamaPerameter.java b/Core/src/main/java/me/neurodock/ollama/OllamaPerameter.java index 0d491b0..c5a9874 100644 --- a/Core/src/main/java/me/neurodock/ollama/OllamaPerameter.java +++ b/Core/src/main/java/me/neurodock/ollama/OllamaPerameter.java @@ -1,17 +1,17 @@ package me.neurodock.ollama; +import me.neurodock.plugin.tool.ToolParameters; import org.json.JSONObject; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; +import java.util.*; +import java.util.stream.Stream; /** * Represents the parameters of a tool. * This is used by Ollama to determine the parameters of a tool. */ public class OllamaPerameter { - + /** * Creates a new instance of {@link OllamaPerameter}. * @param properties The properties of the parameters @@ -21,7 +21,7 @@ public class OllamaPerameter { this.properties = properties; this.required = required; }; - + @Override public String toString() { JSONObject json = new JSONObject(); @@ -32,16 +32,16 @@ public class OllamaPerameter { return json.toString(); } - + /** * the properties of the parameters */ - JSONObject properties; + private final JSONObject properties; /** * the required parameters */ - String[] required; - + private final String[] required; + /** * Gets the properties of the {@link OllamaPerameter} * @return The properties of the {@link OllamaPerameter} @@ -49,7 +49,7 @@ public class OllamaPerameter { public JSONObject getProperties() { return properties; } - + /** * Gets the required parameters of the {@link OllamaPerameter} * @return The required parameters of the {@link OllamaPerameter} @@ -57,7 +57,7 @@ public class OllamaPerameter { public String[] getRequired() { return required; } - + /** * Creates a new instance of {@link OllamaPerameterBuilder}. * @return The {@link OllamaPerameterBuilder} @@ -77,7 +77,7 @@ public class OllamaPerameter { public static OllamaPerameter empty() { return builder().build(); } - + /** * Represents a builder for {@link OllamaPerameter}. */ @@ -90,7 +90,7 @@ public class OllamaPerameter { * The required parameters. */ ArrayList required = new ArrayList<>(); - + /** * Add an optinal perameter to this {@link OllamaPerameterBuilder} * @param name The name of the parameter @@ -102,7 +102,7 @@ public class OllamaPerameter { public OllamaPerameterBuilder addProperty(String name, Type type, String description) { return OllamaPerameterBuilder.this.addProperty(name, type, description, false); } - + /** * Add a potentialy required peremeter to this {@link OllamaPerameterBuilder}. * @param name The name of the parameter @@ -121,7 +121,36 @@ public class OllamaPerameter { } return this; } - + + public > OllamaPerameterBuilder addEnumProperty(String name, Class enumClass, String description, boolean required) { + StringBuilder newDescription = new StringBuilder(description); + if(newDescription.charAt(newDescription.length() - 1) != '.') { + newDescription.append(". "); + } + else + { + newDescription.append(' '); + } + + newDescription.append("Enum values: "); + + Iterator it = Arrays.stream(enumClass.getEnumConstants()).iterator(); + + while(it.hasNext()) { + T item = it.next(); + newDescription.append(item.name()); + if(it.hasNext()) { + newDescription.append(", "); + } + } + + return addProperty(name, Type.ENUM, newDescription.toString(), required); + } + + public > OllamaPerameterBuilder addEnumProperty(Class enumClass, String description) { + return addEnumProperty(enumClass.getSimpleName(), enumClass, description, false); + } + /** * Makes a previusly optinal perameter required for this {@link OllamaPerameterBuilder} * @param name The name of the parameter @@ -131,7 +160,7 @@ public class OllamaPerameter { required.add(name); return this; } - + /** * Removes a property from the parameters. * @param name The name of the property to remove @@ -142,7 +171,23 @@ public class OllamaPerameter { required.remove(name); return this; } - + + /** + * Coverts a RPCP Tool Perameter to a CTP Ollama Peameter + * @param parameters + * @return + */ + public OllamaPerameterBuilder of(ToolParameters parameters) { + required.addAll(Arrays.asList(parameters.getRequired())); + for(String key : parameters.getProperties().keySet()) + { + Property property = new Property(parameters.getProperties().getJSONObject(key).getString("type"), + parameters.getProperties().getJSONObject(key).getString("description")); + propertyMap.put(key, property); + } + return this; + } + /** * Builds the {@link OllamaPerameter} * @return The {@link OllamaPerameter} @@ -154,7 +199,7 @@ public class OllamaPerameter { } return new OllamaPerameter(properties, required.toArray(new String[0])); } - + /** * Represents a property of a parameter. */ @@ -167,7 +212,7 @@ public class OllamaPerameter { * The description of the property. */ String description; - + /** * Creates a new instance of {@link Property}. * @param type The type of the property @@ -188,7 +233,7 @@ public class OllamaPerameter { return json.toString(); } } - + /** * Represents the type of parameter. */ @@ -222,7 +267,7 @@ public class OllamaPerameter { * The type of the parameter. */ private final String type; - + /** * Gets the type of the parameter. * @return The type of the parameter @@ -230,7 +275,7 @@ public class OllamaPerameter { public String getType() { return type; } - + /** * Creates a new instance of {@link Type}. * @param type The type of the parameter @@ -238,6 +283,19 @@ public class OllamaPerameter { Type(String type) { this.type = type; } + + Type of(String type) + { + return switch (type) { + case "string" -> STRING; + case "int" -> INT; + case "boolean" -> BOOLEAN; + case "enum" -> ENUM; + case "array" -> ARRAY; + case "object" -> OBJECT; + default -> throw new IllegalArgumentException("Unknown type " + type); + }; + } } } } diff --git a/Core/src/main/java/me/neurodock/plugin/loader/Loader.java b/Core/src/main/java/me/neurodock/plugin/loader/Loader.java index e5c5eb1..8e58271 100644 --- a/Core/src/main/java/me/neurodock/plugin/loader/Loader.java +++ b/Core/src/main/java/me/neurodock/plugin/loader/Loader.java @@ -48,7 +48,7 @@ public class Loader { tools.add(new OllamaFunctionTool() { @Override public @NotNull String name() { - return tool.name(); + return tool.name() + "_" + plugin.getMetadata().getName(); } @Override @@ -58,8 +58,7 @@ public class Loader { @Override public @NotNull OllamaPerameter parameters() { - // TODO: make a wrapper around ToolPerameters to OllamaPeramer - throw new RuntimeException(new ExecutionControl.NotImplementedException("To be Implemented")); + return OllamaPerameter.builder().of(tool.parameters()).build(); } @Override @@ -98,7 +97,7 @@ public class Loader { throw new PluginLoadingException("Malformed plugin json", UNKNOWN_PLUGIN); if(!pluginJson.has("entryPoint") || !(pluginJson.get("entryPoint") instanceof String pluginEntryPoint)) throw new PluginLoadingException("Malformed plugin json", pluginName); - JarEntry entry = jar.getJarEntry(pluginEntryPoint.replaceAll("\\.", "/")); + JarEntry entry = jar.getJarEntry(pluginEntryPoint.replaceAll("\\.", "/")+".class"); if(entry == null) throw new PluginLoadingException("Missing plugin entrypoint", pluginName); Plugin plugin = null; @@ -146,6 +145,6 @@ public class Loader { ); } - return new LoadedPlugin(plugin, classLoader, null/*TODO: MUST BE REPLACED WITH ACTUAL PLUGIN METADATA! this is read from the PluginEntyPoint*/); + return new LoadedPlugin(plugin, classLoader, plugin.getMetadata()/*TODO: MUST BE REPLACED WITH ACTUAL PLUGIN METADATA! this is read from the PluginEntyPoint*/); } } diff --git a/Display/src/main/java/me/neurodock/display/PythonRunner.java b/Display/src/main/java/me/neurodock/display/PythonRunner.java index 8fb6ed3..7c5f214 100644 --- a/Display/src/main/java/me/neurodock/display/PythonRunner.java +++ b/Display/src/main/java/me/neurodock/display/PythonRunner.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; +import static me.neurodock.core.Core.ensureDir; import static me.neurodock.core.Core.writeLog; /** @@ -71,6 +72,8 @@ public class PythonRunner extends OllamaFunctionTool { public PythonRunner(Core core) { this.core = core; + ensureDir("./pythonFiles/"); + try { serverSocket = new ServerSocket(6050); Thread thread = new Thread(() -> { diff --git a/Plugin-API/build.gradle b/Plugin-API/build.gradle index 6d3239f..aadf409 100644 --- a/Plugin-API/build.gradle +++ b/Plugin-API/build.gradle @@ -2,7 +2,7 @@ plugins { id 'java-library' } -version = '0.1.2' +version = '0.1.4.1' repositories { mavenCentral() diff --git a/Plugin-API/src/main/java/me/neurodock/plugin/tool/Tool.java b/Plugin-API/src/main/java/me/neurodock/plugin/tool/Tool.java index e93b200..108aa10 100644 --- a/Plugin-API/src/main/java/me/neurodock/plugin/tool/Tool.java +++ b/Plugin-API/src/main/java/me/neurodock/plugin/tool/Tool.java @@ -38,7 +38,7 @@ public abstract class Tool { if(description() != null) { function.put("description", description()); } - function.put("parameters", (parameters() == null? new JSONObject() : new JSONObject(parameters().toString()))); + function.put("parameters", (parameters() == null? new JSONObject() : new JSONObject(parameters().getJSON()))); ret.put("function", function); diff --git a/Plugin-API/src/main/java/me/neurodock/plugin/tool/ToolParameters.java b/Plugin-API/src/main/java/me/neurodock/plugin/tool/ToolParameters.java index 9a1fed6..c9f315a 100644 --- a/Plugin-API/src/main/java/me/neurodock/plugin/tool/ToolParameters.java +++ b/Plugin-API/src/main/java/me/neurodock/plugin/tool/ToolParameters.java @@ -1,12 +1,242 @@ package me.neurodock.plugin.tool; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Represents the parameters of a tool. + * This is used by Ollama to determine the parameters of a tool. + */ public class ToolParameters { + /** - * An empty {@link ToolParameters} for when a tool dosent need any perameters - * @return the default {@link ToolParameters} + * Creates a new instance of {@link ToolParameters}. + * @param properties The properties of the parameters + * @param required The required parameters */ - public static ToolParameters empty() { - return new ToolParameters(); + private ToolParameters(JSONObject properties, String[] required) { + this.properties = properties; + this.required = required; + }; + + public JSONObject getJSON() { + JSONObject json = new JSONObject(); + + json.put("type", "object"); + json.put("properties", properties); + json.put("required", required); + + return json; } + /** + * the properties of the parameters + */ + JSONObject properties; + /** + * the required parameters + */ + String[] required; + + /** + * Gets the properties of the {@link ToolParameters} + * @return The properties of the {@link ToolParameters} + */ + public JSONObject getProperties() { + return properties; + } + + /** + * Gets the required parameters of the {@link ToolParameters} + * @return The required parameters of the {@link ToolParameters} + */ + public String[] getRequired() { + return required; + } + + /** + * Creates a new instance of {@link ToolParametersBuilder}. + * @return The {@link ToolParametersBuilder} + */ + public static ToolParametersBuilder builder() { + return new ToolParametersBuilder(); + } + + /** + * Creates an empty {@link ToolParameters} + * @return an empty {@link ToolParameters} + * @apiNote This is equvalent to + *
{@code
+     *    ToolParameters.builder().build();
+     * }
+ */ + public static ToolParameters empty() { + return builder().build(); + } + + /** + * Represents a builder for {@link ToolParameters}. + */ + public static class ToolParametersBuilder { + /** + * The properties of the parameters. + */ + Map propertyMap = new HashMap<>(); + /** + * The required parameters. + */ + ArrayList required = new ArrayList<>(); + + /** + * Add an optinal perameter to this {@link ToolParametersBuilder} + * @param name The name of the parameter + * @param type The type of the parameter + * @param description The description of the parameter + * @return The {@link ToolParametersBuilder} + * @apiNote Prefer {@link #addProperty(String, Type, String, boolean)} to be explicit about required state + */ + public ToolParametersBuilder addProperty(String name, Type type, String description) { + return ToolParametersBuilder.this.addProperty(name, type, description, false); + } + + /** + * Add a potentialy required peremeter to this {@link ToolParametersBuilder}. + * @param name The name of the parameter + * @param type The type of the parameter + * @param description The description of the parameter + * @param required The required state of the parameter + * @return The {@link ToolParametersBuilder} + */ + public ToolParametersBuilder 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)); + if(required) { + this.required.add(name); + } + return this; + } + + /** + * Makes a previusly optinal perameter required for this {@link ToolParametersBuilder} + * @param name The name of the parameter + * @return The {@link ToolParametersBuilder} + */ + public ToolParametersBuilder required(String name) { + required.add(name); + return this; + } + + /** + * Removes a property from the parameters. + * @param name The name of the property to remove + * @return The {@link ToolParametersBuilder} + */ + public ToolParametersBuilder removeProperty(String name) { + propertyMap.remove(name); + required.remove(name); + return this; + } + + /** + * Builds the {@link ToolParameters} + * @return The {@link ToolParameters} + */ + public ToolParameters build() { + JSONObject properties = new JSONObject(); + for(String name : propertyMap.keySet()) { + properties.put(name, new JSONObject(propertyMap.get(name).toString())); + } + return new ToolParameters(properties, required.toArray(new String[0])); + } + + /** + * Represents a property of a parameter. + */ + private static class Property { + /** + * The type of the property. + */ + String type; + /** + * The description of the property. + */ + String description; + + /** + * Creates a new instance of {@link Property}. + * @param type The type of the property + * @param description The description of the property + */ + 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(); + } + } + + /** + * Represents the type of parameter. + */ + public enum Type { + /** + * Represents a string parameter. + */ + STRING("string"), + /** + * Represents an integer parameter. + */ + INT("int"), + /** + * Represents a boolean parameter. + */ + BOOLEAN("boolean"), + /** + * Represents a enum parameter. + */ + ENUM("enum"), + /** + * Represents a array parameter. + */ + ARRAY("array"), + /** + * Represents a object parameter. + */ + OBJECT("object"); + + /** + * The type of the parameter. + */ + private final String type; + + /** + * Gets the type of the parameter. + * @return The type of the parameter + */ + public String getType() { + return type; + } + + /** + * Creates a new instance of {@link Type}. + * @param type The type of the parameter + */ + Type(String type) { + this.type = type; + } + } + } } diff --git a/build.gradle b/build.gradle index 5e723ba..1237f2e 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,7 @@ subprojects { if (tasks.findByName('javadocJar')) artifact tasks.javadocJar pom { name = proj.name - description = "${proj.name} module of AI-test" + description = "${proj.name} module of NeuroDock" } } } @@ -98,4 +98,6 @@ subprojects { } } } + + java.toolchain.languageVersion = JavaLanguageVersion.of(25) } \ No newline at end of file