Enhanced tool management in OllamaObject and added Maven publishing configuration.

- Updated `OllamaObject` to support tool registration with sources.
- Improved error handling and debug logging in multiple classes.
- Added Maven plugin and publication setup in `build.gradle`.
- Updated version to `1.3`.

Signed-off-by: Zacharias <zacharias@4zellen.se>
This commit is contained in:
2025-08-18 20:01:42 +02:00
parent 2cf0428d2a
commit 4262dd68c6
12 changed files with 205 additions and 36 deletions

View File

@@ -3,7 +3,7 @@ plugins {
} }
group = 'me.zacharias' group = 'me.zacharias'
version = '1.0-SNAPSHOT' version = '1.3'
dependencies { dependencies {

View File

@@ -23,6 +23,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static me.zacharias.chat.ollama.OllamaFunctionArgument.deconstructOllamaFunctionArguments;
/** /**
* The Main class for the System, responsible for managing the OllamaObject, tools, and the Ollama API. * The Main class for the System, responsible for managing the OllamaObject, tools, and the Ollama API.
*/ */
@@ -52,7 +54,7 @@ public class Core {
/** /**
* The IP of the Ollama API. * The IP of the Ollama API.
*/ */
private String ollamaIP = "192.168.5.178";//"192.168.5.184"; private String ollamaIP = "10.0.1.101";//"192.168.5.184";
/** /**
* The port of the Ollama API. * The port of the Ollama API.
*/ */
@@ -71,11 +73,15 @@ public class Core {
*/ */
private boolean supportColor; private boolean supportColor;
public static final String DATA; public static String DATA;
public static final File DATA_DIR; public static File DATA_DIR;
public static final File PLUGIN_DIRECTORY; public static File PLUGIN_DIRECTORY;
static { static {
setDataDirectory("AI-Chat");
}
public static void setDataDirectory(String dataDirectory) {
String data; String data;
if(System.getenv("AI_CHAT_DEBUG") != null) { if(System.getenv("AI_CHAT_DEBUG") != null) {
@@ -86,13 +92,13 @@ public class Core {
if(localappdata == null) { if(localappdata == null) {
localappdata = System.getenv("APPDATA"); localappdata = System.getenv("APPDATA");
} }
data = localappdata + "/AI-CHAT"; data = localappdata + "/"+ dataDirectory;
} }
else if (System.getProperty("os.name").toLowerCase().contains("linux")) { else if (System.getProperty("os.name").toLowerCase().contains("linux")) {
data = System.getenv("HOME") + "/.local/share/AI-CHAT"; data = System.getenv("HOME") + "/.local/share/" + dataDirectory;
} }
else if (System.getProperty("os.name").toLowerCase().contains("mac")) { else if (System.getProperty("os.name").toLowerCase().contains("mac")) {
data = System.getProperty("user.home") + "/Library/Application Support/AI-CHAT"; data = System.getProperty("user.home") + "/Library/Application Support/"+ dataDirectory;
} }
else { else {
data = "./data"; data = "./data";
@@ -193,7 +199,7 @@ public class Core {
messagesWriter.write(messages.toString()); messagesWriter.write(messages.toString());
messagesWriter.close(); messagesWriter.close();
File f = new File("./data/messages.json"); File f = new File(DATA_DIR,"messages.json");
if(f.exists()) if(f.exists())
{ {
f.delete(); f.delete();
@@ -261,7 +267,7 @@ public class Core {
*/ */
public void addTool(OllamaFunctionTool functionTool, @MagicConstant(valuesFromClass = Source.class) String source) { public void addTool(OllamaFunctionTool functionTool, @MagicConstant(valuesFromClass = Source.class) String source) {
funtionTools.add(new Pair<>(functionTool, source)); funtionTools.add(new Pair<>(functionTool, source));
ollamaObject.addTool(functionTool); ollamaObject.addTool(functionTool, source);
} }
/** /**
@@ -404,32 +410,34 @@ public class Core {
JSONObject function = jsonObject.getJSONObject("function"); JSONObject function = jsonObject.getJSONObject("function");
List<Pair<OllamaFunctionTool, String>> functions = funtionTools.stream().filter(func -> (func.getKey().name()+func.getValue()).equalsIgnoreCase(function.getString("name"))).toList(); List<Pair<OllamaFunctionTool, String>> functions = funtionTools.stream().filter(func -> (func.getKey().name()+func.getValue()).equalsIgnoreCase(function.getString("name"))).toList();
ArrayList<OllamaFunctionArgument> argumentArrayList = new ArrayList<>();
JSONObject arguments = function.getJSONObject("arguments");
for (String key : arguments.keySet()) {
argumentArrayList.add(new OllamaFunctionArgument(key, arguments.get(key)));
}
if(functions.isEmpty()) { if(functions.isEmpty()) {
ollamaObject.addMessage(new OllamaToolError("Function '"+function.getString("name")+"' does not exist")); 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":"")); printMessageHandler.printMessage((supportColor ?"\u001b[31m":"")+"Tried funtion call "+function.getString("name")+"("+deconstructOllamaFunctionArguments(argumentArrayList)+") but failed to find it."+(printMessageHandler.color()?"\u001b[0m":""));
writeLog("Failed function call to "+function.getString("name")); writeLog("Failed function call to "+function.getString("name"));
} }
else { else {
OllamaFunctionTool func = functions.getFirst().getKey(); OllamaFunctionTool func = functions.getFirst().getKey();
ArrayList<OllamaFunctionArgument> argumentArrayList = new ArrayList<>(); // TODO: Check so all arguments is the correct type, else error out in a safe mannet.
JSONObject arguments = function.getJSONObject("arguments");
for (String key : arguments.keySet()) {
argumentArrayList.add(new OllamaFunctionArgument(key, arguments.get(key)));
}
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() + (supportColor?"\u001b[0m":"")); printMessageHandler.printMessage((supportColor?"\u001b[34m":"")+"Call "+func.name() + "(" + deconstructOllamaFunctionArguments(argumentArrayList) + ")" + (supportColor?"\u001b[0m":""));
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())); 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":"")); printMessageHandler.printMessage((supportColor?"\u001b[31m":"")+"Tried funtion call " + func.name() + "("+deconstructOllamaFunctionArguments(argumentArrayList)+") but failed due to " + e.getError() + (supportColor?"\u001b[0m":""));
writeLog(e.getMessage()); writeLog("ERROR: "+e.getMessage());
} }
} }
} }

View File

@@ -15,6 +15,8 @@ public class LaunchOptions {
return instance; return instance;
} }
private LaunchOptions() {}
private boolean loadOld = true; private boolean loadOld = true;
private boolean autoAccept; private boolean autoAccept;
private boolean serverMode; private boolean serverMode;

View File

@@ -18,4 +18,18 @@ public interface PrintMessageHandler {
* @return a boolean indicating if color is supported by the PrintMessageHandler. * @return a boolean indicating if color is supported by the PrintMessageHandler.
*/ */
boolean color(); boolean color();
/**
* 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 message The error message to be printed.
* If color is not supported, it will print the message without color.
*/
default void printError(String message) {
if (color()) {
printMessage("\u001B[31m" + message + "\u001B[0m"); // Red color for errors
} else {
printMessage(message);
}
}
} }

View File

@@ -1,5 +1,7 @@
package me.zacharias.chat.ollama; package me.zacharias.chat.ollama;
import java.util.ArrayList;
/** /**
* Represents an argument passed to a tool. * Represents an argument passed to a tool.
* *
@@ -37,4 +39,24 @@ public record OllamaFunctionArgument(String argument, Object value) {
public Object value() { public Object value() {
return value; return value;
} }
public static String deconstructOllamaFunctionArgument(OllamaFunctionArgument argument) {
return argument.argument() + ": " + argument.value();
}
public static String deconstructOllamaFunctionArguments(OllamaFunctionArgument... arguments) {
StringBuilder sb = new StringBuilder();
for (OllamaFunctionArgument argument : arguments) {
sb.append(deconstructOllamaFunctionArgument(argument)).append(", ");
}
return sb.toString().replaceAll(", $", "");
}
public static String deconstructOllamaFunctionArguments(ArrayList<OllamaFunctionArgument> arguments) {
StringBuilder sb = new StringBuilder();
for (OllamaFunctionArgument argument : arguments) {
sb.append(deconstructOllamaFunctionArgument(argument)).append(", ");
}
return sb.toString().replaceAll(", $", "");
}
} }

View File

@@ -9,13 +9,15 @@ import org.json.JSONObject;
*/ */
public abstract class OllamaFunctionTool implements OllamaTool { public abstract class OllamaFunctionTool implements OllamaTool {
protected String source = "";
@Override @Override
public String toString() { public String toString() {
JSONObject ret = new JSONObject(); JSONObject ret = new JSONObject();
ret.put("tool", "function"); ret.put("tool", "function");
JSONObject function = new JSONObject(); JSONObject function = new JSONObject();
function.put("name", name()); function.put("name", name()+(source != null ? source : ""));
function.put("description", description()); function.put("description", description());
function.put("parameters", (parameters() == null? function.put("parameters", (parameters() == null?
new JSONObject() : new JSONObject(parameters().toString()))); new JSONObject() : new JSONObject(parameters().toString())));

View File

@@ -3,6 +3,7 @@ package me.zacharias.chat.ollama;
import com.sun.source.util.Plugin; 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.files.FileHandlerLocation; import me.zacharias.chat.core.files.FileHandlerLocation;
import me.zacharias.chat.core.files.FileHandler; import me.zacharias.chat.core.files.FileHandler;
@@ -13,6 +14,7 @@ import org.json.JSONObject;
import java.io.BufferedReader; 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.nio.file.FileSystem; import java.nio.file.FileSystem;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.nio.file.Files; import java.nio.file.Files;
@@ -41,7 +43,7 @@ public class OllamaObject {
/** /**
* The tools of the Ollama Object. * The tools of the Ollama Object.
*/ */
ArrayList<OllamaTool> tools; ArrayList<Pair<OllamaTool, String>> tools;
/** /**
* The format of the Ollama Object. * The format of the Ollama Object.
*/ */
@@ -69,7 +71,7 @@ public class OllamaObject {
* @param stream If the Ollama Object is streamed. see {@link OllamaObject#stream} * @param stream If the Ollama Object is streamed. see {@link OllamaObject#stream}
* @param keep_alive The keep alive of the Ollama Object. see {@link OllamaObject#keep_alive} * @param keep_alive The keep alive of the Ollama Object. see {@link OllamaObject#keep_alive}
*/ */
private OllamaObject(String model, ArrayList<OllamaMessage> messages, ArrayList<OllamaTool> tools, JSONObject format, Map<String, Object> options, boolean stream, String keep_alive) { private OllamaObject(String model, ArrayList<OllamaMessage> messages, ArrayList<Pair<OllamaTool, String>> tools, JSONObject format, Map<String, Object> options, boolean stream, String keep_alive) {
this.model = model; this.model = model;
this.messages = messages; this.messages = messages;
this.tools = tools; this.tools = tools;
@@ -77,7 +79,7 @@ public class OllamaObject {
this.options = options; this.options = options;
this.stream = stream; this.stream = stream;
this.keep_alive = keep_alive; this.keep_alive = keep_alive;
LaunchOptions launchOptions = new LaunchOptions(); LaunchOptions launchOptions = LaunchOptions.getInstance();
if(launchOptions.isLoadOld()) { if(launchOptions.isLoadOld()) {
System.out.println("Loading old data..."); System.out.println("Loading old data...");
File f = new File(Core.DATA_DIR+"/messages.json"); File f = new File(Core.DATA_DIR+"/messages.json");
@@ -105,6 +107,10 @@ public class OllamaObject {
e.printStackTrace(); e.printStackTrace();
} }
} }
else
{
System.out.println("No old data found, skipping loading old data.");
}
} }
} }
@@ -128,7 +134,7 @@ public class OllamaObject {
* Gets the tools * Gets the tools
* @return The tools * @return The tools
*/ */
public ArrayList<OllamaTool> getTools() { public ArrayList<Pair<OllamaTool, String>> getTools() {
return tools; return tools;
} }
@@ -136,8 +142,29 @@ public class OllamaObject {
* Adds a tool to the Ollama Object * Adds a tool to the Ollama Object
* @param tool The tool to add * @param tool The tool to add
*/ */
public void addTool(OllamaTool tool) { public void addTool(OllamaTool tool, @MagicConstant(valuesFromClass = Core.Source.class) String source) {
tools.add(tool); // We inject the source into the tool's source field if it exists, This is to not cause issues with duplicate tools
Class<? extends Object> clazz = tool.getClass();
Field field = null;
while(!clazz.equals(Object.class)) {
try {
field = clazz.getDeclaredField("source");
if (field != null) {
break;
}
}
catch (NoSuchFieldException ignore){}
clazz = clazz.getSuperclass();
}
if (field != null) {
field.setAccessible(true);
try {
field.set(tool, source);
} catch (IllegalAccessException e) {
Core.writeLog("ERROR: "+e.getMessage());
}
}
tools.add(new Pair<>(tool, source));
} }
public void removeTool(OllamaTool tool) { public void removeTool(OllamaTool tool) {
@@ -189,8 +216,11 @@ public class OllamaObject {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
JSONArray tools = new JSONArray(); JSONArray tools = new JSONArray();
for (OllamaTool tool : this.tools) { for (Pair<OllamaTool, String> tool : this.tools) {
tools.put(new JSONObject(tool.toString())); if(tool.getKey().getClass().isInterface()) continue;
JSONObject obj = new JSONObject(tool.getKey().toString());
//obj.put("name", obj.getString("name") + tool.getValue()); // Injects the source of the tool into the name
tools.put(obj);
} }
JSONArray messages = new JSONArray(); JSONArray messages = new JSONArray();
@@ -207,7 +237,7 @@ public class OllamaObject {
json.put("keep_alive", keep_alive); json.put("keep_alive", keep_alive);
return json.toString(); return json.toString();
} }
/** /**
* Creates a new instance of OllamaObjectBuilder. * Creates a new instance of OllamaObjectBuilder.
* @return The {@link OllamaObjectBuilder} * @return The {@link OllamaObjectBuilder}
@@ -232,7 +262,7 @@ public class OllamaObject {
/** /**
* The tools of the Ollama Object. * The tools of the Ollama Object.
*/ */
ArrayList<OllamaTool> tools = new ArrayList<>(); ArrayList<Pair<OllamaTool, String>> tools = new ArrayList<>();
/** /**
* The format of the Ollama Object. * The format of the Ollama Object.
*/ */
@@ -321,30 +351,72 @@ public class OllamaObject {
/** /**
* Adds a tool to the Ollama Object * Adds a tool to the Ollama Object
* Assumes the tool is from an external source, see {@link OllamaObject.OllamaObjectBuilder#addTool(OllamaTool, String)} to specify the source
* @param tool The tool to add * @param tool The tool to add
* @return The {@link OllamaObjectBuilder} * @return The {@link OllamaObjectBuilder}
*/ */
public OllamaObjectBuilder addTool(OllamaTool tool) { public OllamaObjectBuilder addTool(OllamaTool tool) {
this.tools.add(tool); this.tools.add(new Pair<>(tool, Core.Source.EXTERNAL));
return this;
}
/**
* Adds a tool to the Ollama Object
* This allows you to specify the source of the tool, see {@link OllamaObject.OllamaObjectBuilder#addTool(OllamaTool)} to add a tool from an external source
* @param tool The tool to add
* @return The {@link OllamaObjectBuilder}
*/
public OllamaObjectBuilder addTool(OllamaTool tool, @MagicConstant(valuesFromClass = Core.Source.class) String source) {
this.tools.add(new Pair<>(tool, source));
return this; return this;
} }
/** /**
* Adds tools to the Ollama Object * Adds tools to the Ollama Object
* Assumes the tools are from an external source, see {@link OllamaObject.OllamaObjectBuilder#addTools(ArrayList)}} to specify the source
* @param tools The tools to add * @param tools The tools to add
* @return The {@link OllamaObjectBuilder} * @return The {@link OllamaObjectBuilder}
*/ */
public OllamaObjectBuilder addTools(ArrayList<? extends OllamaTool> tools) { public OllamaObjectBuilder addToolsExternal(ArrayList<? extends OllamaTool> tools) {
this.tools.addAll(tools); for (OllamaTool tool : tools) {
this.tools.add(new Pair<>(tool, Core.Source.EXTERNAL));
}
return this;
}
/**
* Adds tools to the Ollama Object
* This allows you to specify the source of the tools, see {@link OllamaObject.OllamaObjectBuilder#addToolsExternal(ArrayList)}} to add tools from an external source
* @param tools The tools to add
* @return The {@link OllamaObjectBuilder}
*/
public OllamaObjectBuilder addTools(ArrayList<Pair<? extends OllamaTool, String>> tools) {
for(Pair<? extends OllamaTool, String> tool : tools) {
this.tools.add(new Pair<>(tool.getKey(), tool.getValue()));
}
return this; return this;
} }
/** /**
* Adds tools to the Ollama Object * Adds tools to the Ollama Object
* Assumes the tools are from an external source, see {@link OllamaObject.OllamaObjectBuilder#addTools(Pair[])}} to specify the source
* @param tools The tools to add * @param tools The tools to add
* @return The {@link OllamaObjectBuilder} * @return The {@link OllamaObjectBuilder}
*/ */
public OllamaObjectBuilder addTools(OllamaTool... tools) { public OllamaObjectBuilder addTools(OllamaTool... tools) {
for(OllamaTool tool : tools) {
this.tools.add(new Pair<>(tool, Core.Source.EXTERNAL));
}
return this;
}
/**
* Adds tools to the Ollama Object
* This allows you to specify the source of the tools, see {@link OllamaObject.OllamaObjectBuilder#addTools(OllamaTool[])} to add tools from an external source
* @param tools The tools to add
* @return The {@link OllamaObjectBuilder}
*/
public OllamaObjectBuilder addTools(Pair<OllamaTool, String>... tools) {
this.tools.addAll(List.of(tools)); this.tools.addAll(List.of(tools));
return this; return this;
} }

View File

@@ -200,7 +200,11 @@ public class OllamaPerameter {
/** /**
* Represents a array parameter. * Represents a array parameter.
*/ */
ARRAY("array"); ARRAY("array"),
/**
* Represents a object parameter.
*/
OBJECT("object");
/** /**
* The type of the parameter. * The type of the parameter.

View File

@@ -109,6 +109,7 @@ public class Display {
/bye Exits the program. /bye Exits the program.
/write Flushes the current log stream to file. /write Flushes the current log stream to file.
/list Lists all available tools. /list Lists all available tools.
/corelist Lists all tools according to the OllamaObject.
/working Prints the current working directories. /working Prints the current working directories.
/peek Peeks the current memory. /peek Peeks the current memory.
"""); """);
@@ -153,6 +154,14 @@ public class Display {
writeLog("Function: " + funtion.getKey().name() + "(" + args + ") [" + funtion.getValue() + "]"); writeLog("Function: " + funtion.getKey().name() + "(" + args + ") [" + funtion.getValue() + "]");
} }
break; break;
case "corelist":
writeLog("Tools installed in this instance acording to the coire OllamaObject");
for(Pair<OllamaTool, String> funtion : core.getOllamaObject().getTools()) {
System.out.println("> Function: " + funtion.getKey().toString());
writeLog("Function: " + funtion.getKey().toString());
}
break;
case "working": case "working":
System.out.println("Working directories:\n" + System.out.println("Working directories:\n" +
" Data: " + Core.DATA_DIR.getAbsolutePath() + "\n" + " Data: " + Core.DATA_DIR.getAbsolutePath() + "\n" +

View File

@@ -59,6 +59,10 @@ public class GetLyrics extends GeniusEndpointTool {
@Override @Override
public OllamaToolRespnce function(OllamaFunctionArgument... args) { public OllamaToolRespnce function(OllamaFunctionArgument... args) {
if(!( args[0].value() instanceof Integer)) {
throw new OllamaToolErrorException(this.name(), "The song_id must be an integer.");
}
String lyricsStr = geniusToolsInstance.hasCache((int) args[0].value()); String lyricsStr = geniusToolsInstance.hasCache((int) args[0].value());
if(lyricsStr != null) if(lyricsStr != null)

View File

@@ -7,6 +7,9 @@ group = 'me.zacharias'
version = '1.0-SNAPSHOT' version = '1.0-SNAPSHOT'
allprojects { allprojects {
apply plugin: 'maven-publish'
repositories { repositories {
mavenCentral() mavenCentral()
} }
@@ -43,4 +46,32 @@ subprojects {
archives sourcesJar archives sourcesJar
archives javadocJar archives javadocJar
} }
// Make Javadoc generation non-fatal to avoid publish failures on bad Javadoc
tasks.withType(Javadoc).configureEach {
options.addBooleanOption('Xdoclint:none', true)
failOnError = false
}
// Configure local Maven publishing for modules that have a Java component
afterEvaluate { proj ->
if (components.findByName("java") != null) {
publishing {
publications {
mavenJava(MavenPublication) {
from components.java
if (tasks.findByName('sourcesJar')) artifact tasks.sourcesJar
if (tasks.findByName('javadocJar')) artifact tasks.javadocJar
pom {
name = proj.name
description = "${proj.name} module of AI-test"
}
}
}
repositories {
mavenLocal()
}
}
}
}
} }

View File

@@ -11,4 +11,5 @@ include 'MALAPITool'
* The core NeuroDock system will still function without this module. * The core NeuroDock system will still function without this module.
*/ */
include 'GeniusAPI' include 'GeniusAPI'
include 'WikipediaTool' include 'WikipediaTool'