refactor: redesign plugin system and enhance message handling
build / build (push) Has been cancelled

- Move plugin and tool-related classes to a dedicated `Plugin-API` module.
- Introduce a new plugin lifecycle (`onInit`, `onEnable`, `onDisable`) and a more robust `Tool` API.
- Refactor `Core` to use `PrintAdvanceMessageHandler` for improved handling of assistant messages, tool responses, and errors.
- Rename `PluginLoader` to `Loader` and migrate it to `me.zacharias.chat.plugin.loader`.
- Update build configurations and Java toolchains across modules.
- Clean up obsolete plugin annotations and interfaces in the `Core` module.
This commit is contained in:
2026-05-26 16:28:02 +02:00
parent 339b176480
commit f6610777ae
37 changed files with 510 additions and 132 deletions
+1
View File
@@ -8,6 +8,7 @@ build/
.idea/modules.xml .idea/modules.xml
.idea/jarRepositories.xml .idea/jarRepositories.xml
.idea/compiler.xml .idea/compiler.xml
.idea
.idea/libraries/ .idea/libraries/
*.iws *.iws
*.iml *.iml
Generated
+1 -1
View File
@@ -1 +1 @@
AI-test neurodock
+1 -1
View File
@@ -5,7 +5,6 @@
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
@@ -14,6 +13,7 @@
<option value="$PROJECT_DIR$/Display" /> <option value="$PROJECT_DIR$/Display" />
<option value="$PROJECT_DIR$/GeniusAPI" /> <option value="$PROJECT_DIR$/GeniusAPI" />
<option value="$PROJECT_DIR$/MALAPITool" /> <option value="$PROJECT_DIR$/MALAPITool" />
<option value="$PROJECT_DIR$/Plugin-API" />
<option value="$PROJECT_DIR$/WikipediaTool" /> <option value="$PROJECT_DIR$/WikipediaTool" />
<option value="$PROJECT_DIR$/launcher" /> <option value="$PROJECT_DIR$/launcher" />
</set> </set>
+1 -1
View File
@@ -7,7 +7,7 @@
<component name="FrameworkDetectionExcludesConfiguration"> <component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" /> <file type="web" url="file://$PROJECT_DIR$" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" project-jdk-name="26" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_26" default="true" project-jdk-name="26" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>
+6
View File
@@ -30,3 +30,9 @@ jar{
attributes 'Main-Class': 'me.zacharias.chat.api.APIApplication' attributes 'Main-Class': 'me.zacharias.chat.api.APIApplication'
} }
} }
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion)) // Set Java version
}
}
+18 -4
View File
@@ -2,15 +2,29 @@ plugins {
id 'java-library' id 'java-library'
} }
group = 'me.zacharias' group = 'me.zacharias.neurodock'
version = '1.3' version = '1.5'
dependencies { dependencies {
implementation project(":Plugin-API")
api "org.json:json:20250107"
} }
java { java {
toolchain { toolchain {
languageVersion.set(JavaLanguageVersion.of(21)) // Set Java version languageVersion.set(JavaLanguageVersion.of(javaVersion)) // Set Java version
}
}
// Tell shadow not to interfere with the main publication
shadowJar {
archiveClassifier = 'all' // keeps shadow as -all.jar, not the main artifact
}
configurations {
[apiElements, runtimeElements].each {
it.outgoing.artifacts.removeIf {
it.buildDependencies.getDependencies(null).contains(shadowJar)
}
} }
} }
@@ -4,7 +4,7 @@ import me.zacharias.chat.core.memory.*;
import me.zacharias.chat.ollama.*; import me.zacharias.chat.ollama.*;
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
import me.zacharias.chat.plugin.Plugin; import me.zacharias.chat.plugin.Plugin;
import me.zacharias.chat.plugin.PluginLoader; import me.zacharias.chat.plugin.loader.Loader;
import me.zacharias.chat.plugin.exceptions.PluginLoadingException; import me.zacharias.chat.plugin.exceptions.PluginLoadingException;
import org.intellij.lang.annotations.MagicConstant; import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -68,11 +68,7 @@ public class Core {
/** /**
* The PrintMessageHandler to use. * The PrintMessageHandler to use.
*/ */
private final PrintMessageHandler printMessageHandler; private final PrintAdvanceMessageHandler printMessageHandler;
/**
* If color is supported by the PrintMessageHandler.
*/
private boolean supportColor;
public static String DATA; public static String DATA;
public static File DATA_DIR; public static File DATA_DIR;
@@ -234,9 +230,8 @@ 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 * @param printMessageHandler The PrintMessageHandler to use as the default Output
*/ */
public Core(@NotNull PrintMessageHandler printMessageHandler) { public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler) {
this.printMessageHandler = printMessageHandler; this.printMessageHandler = printMessageHandler;
supportColor = printMessageHandler.color();
ollamaIP = "localhost"; ollamaIP = "localhost";
// TODO: This is a dirty way to not have a masisive init block, please check this methods comment. // TODO: This is a dirty way to not have a masisive init block, please check this methods comment.
@@ -248,10 +243,9 @@ public class Core {
* @param printMessageHandler The PrintMessageHandler to use as the default Output * @param printMessageHandler The PrintMessageHandler to use as the default Output
* @param ollamaIP The IP used for the Ollama backend * @param ollamaIP The IP used for the Ollama backend
*/ */
public Core(@NotNull PrintMessageHandler printMessageHandler, @NotNull String ollamaIP) public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler, @NotNull String ollamaIP)
{ {
this.printMessageHandler = printMessageHandler; this.printMessageHandler = printMessageHandler;
supportColor = printMessageHandler.color();
this.ollamaIP = ollamaIP; this.ollamaIP = ollamaIP;
// TODO: This is a dirty way to not have a masisive init block, please check this methods comment. // TODO: This is a dirty way to not have a masisive init block, please check this methods comment.
@@ -466,8 +460,9 @@ public class Core {
} }
if(functions.isEmpty()) { if(functions.isEmpty()) {
ollamaObject.addMessage(new OllamaToolError("Function '"+function.getString("name")+"' does not exist")); OllamaToolError ollamaToolError = new OllamaToolError("Function '" + function.getString("name") + "' does not exist");
printMessageHandler.printMessage((supportColor ?"\u001b[31m":"")+"Tried funtion call "+function.getString("name")+"("+deconstructOllamaFunctionArguments(argumentArrayList)+") but failed to find it."+(printMessageHandler.color()?"\u001b[0m":"")); ollamaObject.addMessage(ollamaToolError);
printMessageHandler.printErrorMessage(ollamaToolError);
writeLog("Failed function call to "+function.getString("name")); writeLog("Failed function call to "+function.getString("name"));
} }
else { else {
@@ -479,11 +474,12 @@ public class Core {
try { try {
OllamaToolRespnce function1 = func.function(argumentArrayList.toArray(new OllamaFunctionArgument[0])); OllamaToolRespnce function1 = func.function(argumentArrayList.toArray(new OllamaFunctionArgument[0]));
ollamaObject.addMessage(function1); ollamaObject.addMessage(function1);
printMessageHandler.printMessage((supportColor?"\u001b[34m":"")+"Call "+func.name() + "(" + deconstructOllamaFunctionArguments(argumentArrayList) + ")" + (supportColor?"\u001b[0m":"")); printMessageHandler.printMessage(function1);
writeLog("Successfully function call " + func.name() + " output: " + function1.getResponse()); writeLog("Successfully function call " + func.name() + " output: " + function1.getResponse());
} catch (OllamaToolErrorException e) { } catch (OllamaToolErrorException e) {
ollamaObject.addMessage(new OllamaToolError(e.getMessage())); OllamaToolError ollamaToolError = new OllamaToolError(e.getMessage());
printMessageHandler.printMessage((supportColor?"\u001b[31m":"")+"Tried funtion call " + func.name() + "("+deconstructOllamaFunctionArguments(argumentArrayList)+") but failed due to " + e.getError() + (supportColor?"\u001b[0m":"")); ollamaObject.addMessage(ollamaToolError);
printMessageHandler.printErrorMessage(ollamaToolError);
writeLog("ERROR: "+e.getMessage()); writeLog("ERROR: "+e.getMessage());
} }
} }
@@ -505,9 +501,10 @@ public class Core {
String message = responce.getJSONObject("message").getString("content"); String message = responce.getJSONObject("message").getString("content");
if(responce.getJSONObject("message").has("content") && !message.isBlank()) if(responce.getJSONObject("message").has("content") && !message.isBlank())
{ {
printMessageHandler.printMessage((supportColor?"\u001b[32m":"")+(LaunchOptions.getInstance().isShowFullMessage()? message : message.replaceAll("(?s)<think>.*?</think>", "")) +(supportColor?"\u001b[0m":"")); OllamaMessage ollamaMessage = new OllamaMessage(OllamaMessageRole.ASSISTANT, message);
printMessageHandler.printMessage(ollamaMessage);
writeLog("Response content: "+ message); writeLog("Response content: "+ message);
ollamaObject.addMessage(new OllamaMessage(OllamaMessageRole.ASSISTANT, message)); ollamaObject.addMessage(ollamaMessage);
} }
} }
@@ -541,7 +538,7 @@ public class Core {
if(files == null) { if(files == null) {
return; return;
} }
PluginLoader loader = new PluginLoader(); Loader loader = new Loader();
for(File file : files) { for(File file : files) {
try(FileSystem fs = FileSystems.newFileSystem(file.toPath())){ try(FileSystem fs = FileSystems.newFileSystem(file.toPath())){
if(!fs.getPath("/plugin.json").toFile().exists()) if(!fs.getPath("/plugin.json").toFile().exists())
@@ -0,0 +1,25 @@
package me.zacharias.chat.core;
import me.zacharias.chat.ollama.OllamaMessage;
import me.zacharias.chat.ollama.OllamaMessageRole;
/**
* Represents a {@link PrintAdvanceMessageHandler}.
* This is used by the {@link Core} to print messages for the user, LLM, Tools, or potentialy System, see {@link OllamaMessageRole}.
* For a simpler version see {@link PrintMessageHandler}
*/
public interface PrintAdvanceMessageHandler {
/**
* Expected to handle the printing of the provided {@link OllamaMessage}.
* @param message The {@link OllamaMessage} requested to be printed from a veriity of sources, see {@link OllamaMessageRole}
*/
void printMessage(OllamaMessage message);
/**
* Default method to print error messages to the user or API Client.
* This uses ANSI escape codes to color the output red if color is supported.
* @param errorMessage The error message to be printed.
* If color is not supported, it will print the message without color.
*/
void printErrorMessage(OllamaMessage errorMessage);
}
@@ -1,16 +1,35 @@
package me.zacharias.chat.core; package me.zacharias.chat.core;
import me.zacharias.chat.ollama.OllamaMessage;
import static me.zacharias.chat.ollama.OllamaFunctionArgument.deconstructOllamaFunctionArguments;
/** /**
* Represents a PrintMessageHandler. * Represents a PrintMessageHandler.
* This is used by the Core to print messages to the user or API Clients. * The Core uses this to print messages to the user or API Clients.
*/ */
public interface PrintMessageHandler { public interface PrintMessageHandler extends PrintAdvanceMessageHandler {
/** /**
* Handles the printing of a message. * Handles the printing of a message.
* This is meant to output the message to the user or API Client. * This is meant to output the message to the user or API Client.
* @param message The message to be printed. * @param message The message to be printed.
*/ */
void printMessage(String message); void printMessage(String message);
/**
* This is a default implementation when wrapping {@link PrintAdvanceMessageHandler} to this "simpler" {@link PrintMessageHandler}
*
*
* @param message The {@link OllamaMessage} requested to be printed from a veriity of sources, see {@link me.zacharias.chat.ollama.OllamaMessageRole}
*/
default void printMessage(OllamaMessage message) {
printMessage(switch (message.getRole()) {
case ASSISTANT -> (color()?"\u001b[32m":"")+(LaunchOptions.getInstance().isShowFullMessage()? message.getContent() : message.getContent().replaceAll("(?s)<think>.*?</think>", "")) +(color()?"\u001b[0m":"");
case TOOL -> (color() ?"\u001b[31m":"")+message.getContent()+(color()?"\u001b[0m":"");
case USER, SYSTEM -> (color() ?"\u001b[37m":"")+message.getContent()+(color()?"\u001b[0m":"");
default -> throw new IllegalArgumentException("Invalid message role");
});
}
/** /**
* Gets if color is supported by the PrintMessageHandler. * Gets if color is supported by the PrintMessageHandler.
@@ -32,4 +51,17 @@ public interface PrintMessageHandler {
printMessage(message); printMessage(message);
} }
} }
/**
* This is a default implementation when wrapping {@link PrintAdvanceMessageHandler} to this "simpler" {@link PrintMessageHandler}
*
*
* Default method to print error messages to the user or API Client.
* This uses ANSI escape codes to color the output red if color is supported.
* @param errorMessage The error message to be printed.
* If color is not supported, it will print the message without color.
*/
default void printErrorMessage(OllamaMessage errorMessage) {
printError(errorMessage.getContent());
}
} }
@@ -25,6 +25,20 @@ public class OllamaMessage {
this.content = content; this.content = content;
} }
/**
* @return The role of who sent this "message", represented as {@link OllamaMessageRole}
*/
public OllamaMessageRole getRole() {
return role;
}
/**
* @return The "message" or content sent by a source
*/
public String getContent() {
return content;
}
@Override @Override
public String toString() { public String toString() {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
@@ -1,6 +1,5 @@
package me.zacharias.chat.ollama; package me.zacharias.chat.ollama;
import com.sun.source.util.Plugin;
import me.zacharias.chat.core.Core; import me.zacharias.chat.core.Core;
import me.zacharias.chat.core.LaunchOptions; import me.zacharias.chat.core.LaunchOptions;
import me.zacharias.chat.core.Pair; import me.zacharias.chat.core.Pair;
@@ -15,10 +14,6 @@ import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -452,6 +447,17 @@ public class OllamaObject {
return this; return this;
} }
/**
* Initializes the {@link FileHandler} singleton with the given base directory and adds its tools.
* <p>
* The {@link FileHandler} is intentionally constructed here rather than eagerly,
* as file access tooling should only be initialized if explicitly requested during
* object construction. Calling this after {@link #build()} is technically possible
* but not intended.
* </p>
* @param baseDirectory the base directory for file access, see {@link FileHandlerLocation}
* @return The {@link OllamaObjectBuilder}
*/
public OllamaObjectBuilder addFileTools(@MagicConstant(valuesFromClass = FileHandlerLocation.class) String baseDirectory) public OllamaObjectBuilder addFileTools(@MagicConstant(valuesFromClass = FileHandlerLocation.class) String baseDirectory)
{ {
new FileHandler(baseDirectory); new FileHandler(baseDirectory);
@@ -1,5 +0,0 @@
package me.zacharias.chat.plugin;
public class Plugin {
private final PluginMetadata metadata = null;
}
@@ -1,62 +0,0 @@
package me.zacharias.chat.plugin;
import org.json.JSONObject;
import java.lang.reflect.Field;
import java.nio.file.FileSystem;
import java.nio.file.Path;
public class PluginLoader {
public PluginLoader() {
}
public Plugin loadPlugin(FileSystem pluginJar, JSONObject pluginMeta) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
String pluginName = pluginMeta.getString("name");
String pluginEntryPoint = pluginMeta.getString("entryPoint");
String pluginVersion = pluginMeta.getString("version");
String pluginDescription = pluginMeta.optString("description", "No description provided");
String[] pluginAuthors = pluginMeta.optJSONArray("author") != null ?
pluginMeta.getJSONArray("author").toList().toArray(new String[0]) : new String[0];
PluginMetadata pluginMetadata = new PluginMetadata() {
@Override
public String getName() {
return pluginName;
}
@Override
public String getVersion() {
return pluginVersion;
}
@Override
public String getDescription() {
return pluginDescription;
}
@Override
public String[] getAuthor() {
return pluginAuthors;
}
@Override
public String entryPoint() {
return pluginEntryPoint;
}
};
try {
Plugin plugin = (Plugin) Class.forName(pluginEntryPoint).newInstance();
Field metadataField = plugin.getClass().getDeclaredField("metadata");
metadataField.set(plugin, pluginMetadata);
if (metadataField.get(plugin) == null)
throw new IllegalStateException("Plugin metadata field is null for plugin: " + pluginName);
}catch (NoSuchFieldException e)
{
throw new IllegalStateException("Plugin class " + pluginEntryPoint + " does not have a 'metadata' field", e);
}
return null;
//ClassLoader classLoader = pluginJar.
}
}
@@ -1,9 +0,0 @@
package me.zacharias.chat.plugin;
public interface PluginMetadata {
public String getName();
public String getVersion();
public String getDescription();
public String[] getAuthor();
public String entryPoint();
}
@@ -1,14 +0,0 @@
package me.zacharias.chat.plugin.annotation.injectons;
import me.zacharias.chat.plugin.Plugin;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
public @interface InjectPlugin {
Class<? extends Plugin> classType() default Plugin.class;
}
@@ -0,0 +1,62 @@
package me.zacharias.chat.plugin.loader;
import jdk.jshell.spi.ExecutionControl;
import me.zacharias.chat.core.Pair;
import me.zacharias.chat.ollama.*;
import me.zacharias.chat.plugin.Plugin;
import me.zacharias.chat.plugin.PluginMetadata;
import me.zacharias.chat.plugin.exceptions.ToolRuntimeException;
import me.zacharias.chat.plugin.tool.Tool;
import me.zacharias.chat.plugin.tool.ToolResponse;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class Loader {
private final ArrayList<Pair<Plugin, PluginMetadata>> plugins = new ArrayList<>();
/**
* Mock example!
* @return
*/
public OllamaTool getTool() {
// Mock example!
Tool tool = null;
return new OllamaFunctionTool() {
@Override
public @NotNull String name() {
return tool.name();
}
@Override
public String description() {
return tool.description();
}
@Override
public @NotNull OllamaPerameter parameters() {
// TODO: make a wrapper around ToolPerameters to OllamaPeramer
throw new RuntimeException(new ExecutionControl.NotImplementedException("To be Implemented"));
}
@Override
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
// Wrap OllamaFunctionArguments[] to ether ToolArgument[] or ToolArguments(current implementation)
try {
ToolResponse toolResponse = tool.callTool(null);
} catch (ToolRuntimeException e) {
throw new RuntimeException(e);
}
// Wrap ToolResponce to an OllamaToolRespnce(Well I see a typo here now)
return null;
}
@Override
public String toString() {
// Replaces the toString which is used thru out the Core and OllamaObject to get the JSON representation of a Function Tool
return tool.getToolJSON();
}
};
}
}
+3 -2
View File
@@ -6,6 +6,7 @@ import me.zacharias.chat.ollama.OllamaPerameter;
import me.zacharias.chat.ollama.OllamaToolRespnce; import me.zacharias.chat.ollama.OllamaToolRespnce;
import me.zacharias.chat.plugin.annotation.OllamaTool; import me.zacharias.chat.plugin.annotation.OllamaTool;
import me.zacharias.chat.plugin.annotation.injectons.InjectPlugin; import me.zacharias.chat.plugin.annotation.injectons.InjectPlugin;
import org.jetbrains.annotations.NotNull;
@OllamaTool @OllamaTool
public class Tool extends OllamaFunctionTool { public class Tool extends OllamaFunctionTool {
@@ -13,7 +14,7 @@ public class Tool extends OllamaFunctionTool {
Test core; Test core;
@Override @Override
public String name() { public @NotNull String name() {
return ""; return "";
} }
@@ -23,7 +24,7 @@ public class Tool extends OllamaFunctionTool {
} }
@Override @Override
public OllamaPerameter parameters() { public @NotNull OllamaPerameter parameters() {
return null; return null;
} }
+6
View File
@@ -24,4 +24,10 @@ jar{
manifest { manifest {
attributes 'Main-Class': 'me.zacharias.chat.display.Main' attributes 'Main-Class': 'me.zacharias.chat.display.Main'
} }
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion)) // Set Java version
}
} }
+6
View File
@@ -21,4 +21,10 @@ dependencies {
test { test {
useJUnitPlatform() useJUnitPlatform()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion)) // Set Java version
}
} }
+6
View File
@@ -20,4 +20,10 @@ dependencies {
test { test {
useJUnitPlatform() useJUnitPlatform()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion)) // Set Java version
}
} }
@@ -0,0 +1,9 @@
package me.zacharias.chat.plugin;
import java.io.File;
public abstract class Data {
public abstract File getDataDictionary();
public abstract LoadedPlugins getLoadedPlugins();
public abstract File getCacheDirectory();
}
@@ -0,0 +1,56 @@
package me.zacharias.chat.plugin;
import me.zacharias.chat.plugin.annotations.InjectPluginMetadata;
import me.zacharias.chat.plugin.annotations.PluginEntryPoint;
import me.zacharias.chat.plugin.tool.Tool;
import org.jetbrains.annotations.NotNull;
/**
* Entry point for the plugin, this is required for the plugin to work, and you MUST annotate it with {@link PluginEntryPoint}
*/
public abstract class Plugin {
/**
* This shuld be used if {@link PluginEntryPoint#requireData()} is false (default)
* @apiNote While in this constructor, it is not safe to assume any plugin is loaded other than potentially defined plugins in {@link PluginEntryPoint#dependencies()}. See {@link this#onInit}
*/
public Plugin() {}
/**
* This shuld be used if {@link PluginEntryPoint#requireData()} is true
* @apiNote While in this constructor, it is not safe to assume any plugin is loaded other than potentially defined plugins in {@link PluginEntryPoint#dependencies()}. See {@link this#onInit}
* @param data the {@link Data} generated by the plugin loader
*/
public Plugin(Data data) {}
/**
* This will be called once every plugin has been loaded
*/
public void onInit(){}
/**
* This will be called if the core gets a "fetal" exception or fails to fetch certain info
*/
public void onDisable(){}
/**
* This will be called after all plugins have been initialised; here it is safe to perform potential stuff where you load data from optional dependencies, or similar things
*/
public void onEnable(){}
/**
* The "main" method of a plugin, it will be the one returning a list of all tools you define.
* @return An array of all {@link Tool}s you provide in this plugin, see {@link Tool} for definitions or <a href="https://git.server.4zellen.se/Chat_things/NeuroDock/src/branch/master/MALAPITool">MAL API tool</a> for ideas or examples
*/
public Tool[] getTools(){
return null;
}
/**
* This represents your plugin's Metadata
* @return A {@link PluginMetadata} object representing the metadata for your plugin
*/
@NotNull
public abstract PluginMetadata getMetadata();
}
@@ -0,0 +1,40 @@
package me.zacharias.chat.plugin;
import org.jetbrains.annotations.NotNull;
/**
* Interface defining the core properties/information about the plugin
*/
public interface PluginMetadata {
/**
* Returns the name of the plugin
* @return String containing the name
*/
@NotNull
public String getName();
/**
* Version of the project
* @return String returning the version, expected format Major.Minor.Path see <a href="https://semver.org/">semver.org</a>
*/
@NotNull
public String getVersion();
/**
* Description for the tool, shuld be an explanatory, yet simple description
* @return String containing the description
*/
default String getDescription() {
return null;
}
/**
* A String containing a list of all authors for this<br>
* May be empty, but may not be null
* @return
*/
@NotNull
default String[] getAuthor() {
return new String[0];
}
}
@@ -1,4 +1,4 @@
package me.zacharias.chat.plugin.annotation; package me.zacharias.chat.plugin.annotations;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
@@ -6,7 +6,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) @Target(ElementType.FIELD)
public @interface OllamaTool { public @interface InjectPluginMetadata {
} }
@@ -0,0 +1,13 @@
package me.zacharias.chat.plugin.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PluginEntryPoint {
boolean requireData() default true;
String[] dependencies() default {};
}
@@ -0,0 +1,41 @@
package me.zacharias.chat.plugin.exceptions;
import me.zacharias.chat.plugin.tool.Tool;
public class ToolRuntimeException extends Exception {
String toolName;
public String getToolName() {
return toolName;
}
public ToolRuntimeException(Tool tool, String message) {
super(message);
this.toolName = tool.name();
}
public ToolRuntimeException(String toolName, String message) {
super(message);
this.toolName = toolName;
}
public ToolRuntimeException(String toolName, String message, Throwable cause) {
super(message, cause);
this.toolName = toolName;
}
public ToolRuntimeException(Tool tool, String message, Throwable cause) {
super(message, cause);
this.toolName = tool.name();
}
public ToolRuntimeException(Tool tool, Throwable cause) {
super(cause);
this.toolName = tool.name();
}
public ToolRuntimeException(String toolName, Throwable cause) {
super(cause);
this.toolName = toolName;
}
}
@@ -0,0 +1,75 @@
package me.zacharias.chat.plugin.tool;
import me.zacharias.chat.plugin.Plugin;
import me.zacharias.chat.plugin.exceptions.ToolRuntimeException;
import org.jetbrains.annotations.NotNull;
import org.json.JSONObject;
public abstract class Tool {
/**
* The parent plugin of this tool, used by {@link Tool#getToolJSON()}.
*/
protected final Plugin PLUGIN;
/**
* A base contructor, as the {@link Plugin} is used within this class
* @param plugin The plugin that owns this tool
*/
public Tool(Plugin plugin) {
this.PLUGIN = plugin;
}
/**
* The core uses this method when generating the list of available functions to the LLM
* @return a string formated JSON containing the representation of the tool definitions
*/
public final String getToolJSON() {
JSONObject ret = new JSONObject();
ret.put("type", "function");
JSONObject function = new JSONObject();
function.put("name", String.format("%s_%s_RPCP", name(), PLUGIN.getMetadata().getName()).replaceAll(" ", "_"));
if(description() != null) {
function.put("description", description());
}
function.put("parameters", (parameters() == null? new JSONObject() : new JSONObject(parameters().toString())));
ret.put("function", function);
return ret.toString();
}
/**
* <p>Name of the tool.</p>
* @apiNote This is a lower snake_case formated string.
* <p>Example: {@code "get_clock"}</p>
* @return returns the name of this tool
*/
@NotNull
abstract public String name();
/**
* Description for the LLM on what this tool does
* @return Short description defining the scope of this tool
*/
public String description() {
return null;
}
/**
* The parameters this tool allows
* @apiNote Can return {@link ToolParameters#empty()} if no arguments are needed
* @return A {@link ToolParameters} object defining what this tool expects when being called
*/
@NotNull
abstract public ToolParameters parameters();
/**
* This is the main course of the tool. The LLM will call this on request of using your tool
* @param arguments A collective of your {@link ToolParameters} from {@link Tool#parameters()}
* @return The expected results, or {@link ToolResponse#empty(Tool)} if this is a "void" function
* @throws ToolRuntimeException This indicates that an {@link Exception} is not fatal, any other exceptions thrown from this tool will be treated as fatal, and will result in this tool, or it's plugin being disabled
*/
abstract public ToolResponse callTool(ToolArguments arguments) throws ToolRuntimeException;
}
@@ -0,0 +1,4 @@
package me.zacharias.chat.plugin.tool;
public class ToolArguments {
}
@@ -0,0 +1,12 @@
package me.zacharias.chat.plugin.tool;
public class ToolParameters {
/**
* An empty {@link ToolParameters} for when a tool dosent need any perameters
* @return the default {@link ToolParameters}
*/
public static ToolParameters empty() {
return new ToolParameters();
}
}
@@ -0,0 +1,12 @@
package me.zacharias.chat.plugin.tool;
public class ToolResponse {
public static ToolResponse empty(Tool tool) {
return new ToolResponse();
}
public static ToolResponse empty(String toolName)
{
return new ToolResponse();
}
}
+6
View File
@@ -20,4 +20,10 @@ dependencies {
test { test {
useJUnitPlatform() useJUnitPlatform()
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion)) // Set Java version
}
} }
+18 -1
View File
@@ -21,7 +21,6 @@ subprojects {
apply plugin: 'com.gradleup.shadow' apply plugin: 'com.gradleup.shadow'
dependencies { dependencies {
implementation("org.json:json:20250107")
implementation("org.jetbrains:annotations:23.1.0") implementation("org.jetbrains:annotations:23.1.0")
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0' testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
@@ -73,4 +72,22 @@ subprojects {
} }
} }
} }
publishing {
publications {
maven(MavenPublication) {
from components.java
}
}
repositories {
maven {
name = "gitea"
url = "https://git.server.4zellen.se/api/packages/Chat_things/maven"
credentials {
username = project.findProperty("giteaUser")
password = project.findProperty("giteaToken")
}
}
}
}
} }
+2 -1
View File
@@ -1,2 +1,3 @@
# This is used in sub-project that dosent explicitly require the API sub-project to only include if thay shuld be included project wide. # This is used in sub-project that dosent explicitly require the API sub-project to only include if thay shuld be included project wide.
useAPI = true useAPI = true
javaVersion = 26
Vendored Regular → Executable
View File
+6
View File
@@ -15,3 +15,9 @@ jar{
attributes 'Main-Class': 'me.zacharias.chat.launcher.Launcher' attributes 'Main-Class': 'me.zacharias.chat.launcher.Launcher'
} }
} }
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion)) // Set Java version
}
}
+3 -2
View File
@@ -1,4 +1,4 @@
rootProject.name = 'AI-test' rootProject.name = 'neurodock'
include 'API', 'Core', 'Display', 'launcher' include 'API', 'Core', 'Display', 'launcher'
@@ -12,4 +12,5 @@ include 'MALAPITool'
*/ */
include 'GeniusAPI' include 'GeniusAPI'
include 'WikipediaTool' include 'WikipediaTool'
include 'Plugin-API'