diff --git a/.idea/misc.xml b/.idea/misc.xml index 3a83431..f3c17f3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/API/src/main/java/me/zacharias/chat/api/controllers/Tool.java b/API/src/main/java/me/zacharias/chat/api/controllers/Tool.java index d025833..e890f6b 100644 --- a/API/src/main/java/me/zacharias/chat/api/controllers/Tool.java +++ b/API/src/main/java/me/zacharias/chat/api/controllers/Tool.java @@ -34,7 +34,7 @@ public class Tool { } return ResponseEntity.ok(new NewToolResponse(request.getName(), request.getDescription(), arguments)); } else { - return ResponseEntity.status(400).body("Tool already exists"); + return ResponseEntity.badRequest().body("Tool already exists"); } } } diff --git a/Core/src/main/java/me/zacharias/chat/core/files/FileHandler.java b/Core/src/main/java/me/zacharias/chat/core/files/FileHandler.java index 54deed2..f9cd9df 100644 --- a/Core/src/main/java/me/zacharias/chat/core/files/FileHandler.java +++ b/Core/src/main/java/me/zacharias/chat/core/files/FileHandler.java @@ -1,11 +1,16 @@ package me.zacharias.chat.core.files; import me.zacharias.chat.core.Core; +import me.zacharias.chat.core.Pair; +import me.zacharias.chat.core.files.tools.ReadFileTool; +import me.zacharias.chat.ollama.OllamaTool; import org.intellij.lang.annotations.MagicConstant; import java.io.*; import java.nio.file.FileSystem; import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; /** @@ -20,7 +25,7 @@ public class FileHandler { /** * The directory used as base for this instance of {@link FileHandler}. This is where all files that can be read or writen will be located */ - private final File/*System*/ directory; + private final Path/*System*/ root; /** * Creates a new instance as well as setting the {@link #instance} to this new one @@ -28,19 +33,41 @@ public class FileHandler { */ public FileHandler(@MagicConstant(valuesFromClass = FileHandlerLocation.class) String baseDirectory) { try { - FileSystem fs = FileSystems.newFileSystem(new File(baseDirectory).toPath()); - //fs.getPath() - directory = new File(baseDirectory); - if (!directory.exists()) - directory.mkdirs(); + root = Path.of(baseDirectory).toAbsolutePath().normalize(); + if (!root.toFile().exists()) { + root.toFile().mkdirs(); + } instance = this; }catch (Exception ex) { + ex.printStackTrace(); throw new FileHandlerException("Failed to create FileHandler instance with base directory \"" + baseDirectory + "\""); } } + public static FileHandler getInstance() { + return instance; + } + + /** + * Returns a list of all {@link me.zacharias.chat.ollama.OllamaTool}'s this module adds + * @return + */ + public static ArrayList> getTools() { + ArrayList> tools = new ArrayList<>(); + + tools.add(new Pair<>(new ReadFileTool(), Core.Source.INTERNAL)); + + return tools; + } + + public Path resolve(String relative) throws IOException{ + Path p = root.resolve(relative).normalize(); + if (!p.startsWith(root)) throw new IOException("Access denied"); + return p; + } + /** * * @param filename @@ -48,11 +75,12 @@ public class FileHandler { * @throws FileHandlerException */ public String readFile(String filename) throws FileHandlerException { - if(filename.contains("..")) - { - throw new FileHandlerException("File \"" + filename + "\" tries to retrace path"); + File file = null; + try { + file = resolve(filename).toFile(); + } catch (IOException e) { + throw new FileHandlerException("Illegal restricted path"); } - File file = new File(directory, filename); if(file.exists()) { try{ @@ -82,4 +110,13 @@ public class FileHandler { } throw new FileHandlerException("Cant find file \""+filename+"\""); } + + /** + * Provides a relative path from {@link FileHandler#root} to file + * @param file the file to be relativised + * @return the relative path from {@link FileHandler#root} to file + */ + public String path(File file) { + return root.relativize(file.toPath()).toString(); + } } diff --git a/Core/src/main/java/me/zacharias/chat/core/files/tools/ReadFileTool.java b/Core/src/main/java/me/zacharias/chat/core/files/tools/ReadFileTool.java new file mode 100644 index 0000000..9b46c37 --- /dev/null +++ b/Core/src/main/java/me/zacharias/chat/core/files/tools/ReadFileTool.java @@ -0,0 +1,77 @@ +package me.zacharias.chat.core.files.tools; + +import me.zacharias.chat.core.files.FileHandler; +import me.zacharias.chat.ollama.OllamaFunctionArgument; +import me.zacharias.chat.ollama.OllamaFunctionTool; +import me.zacharias.chat.ollama.OllamaPerameter; +import me.zacharias.chat.ollama.OllamaToolRespnce; +import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException; + +import java.io.*; +import java.nio.file.Path; + +public class ReadFileTool extends OllamaFunctionTool { + FileHandler fs = FileHandler.getInstance(); + @Override + public String name() { + return "read_file"; + } + + @Override + public String description() { + return "Reads the file of path or null if that file dosent exists"; + } + + @Override + public OllamaPerameter parameters() { + return OllamaPerameter.builder() + .addProperty("file_path", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The path to the file to be read") + .build(); + } + + @Override + public OllamaToolRespnce function(OllamaFunctionArgument... args) { + String orgPath = null; + for (OllamaFunctionArgument arg : args) { + if(arg.argument().equals("file_path")) { + orgPath = (String) arg.value(); + } + } + + if(orgPath == null) { + throw new OllamaToolErrorException(this.name(), "Missing required argument 'file_path'"); + } + + Path filePath = null; + + try{ + filePath = fs.resolve(orgPath); + } + catch (IOException ex) { + throw new OllamaToolErrorException(this.name(), ex); + } + + File file = filePath.toFile(); + if(!file.exists()) { + throw new OllamaToolErrorException(this.name(), "File does not exist: " + fs.path(file)); + } + + BufferedReader br = null; + try{ + br = new BufferedReader(new FileReader(file)); + }catch (FileNotFoundException ex) { + throw new OllamaToolErrorException(this.name(), "File not found: " + fs.path(file)); + } + StringBuilder sb = new StringBuilder(); + String tmp = null; + try { + while ((tmp = br.readLine()) != null) { + sb.append(tmp).append("\n"); + } + } + catch (IOException e) { + throw new OllamaToolErrorException(this.name(), "Error reading file: " + fs.path(file)); + } + return new OllamaToolRespnce(this.name(), sb.toString()); + } +} diff --git a/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java b/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java index 6b99b2b..a375741 100644 --- a/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java +++ b/Core/src/main/java/me/zacharias/chat/ollama/OllamaObject.java @@ -416,7 +416,8 @@ public class OllamaObject { * @param tools The tools to add * @return The {@link OllamaObjectBuilder} */ - public OllamaObjectBuilder addTools(Pair... tools) { + @SafeVarargs + public final OllamaObjectBuilder addTools(Pair... tools) { this.tools.addAll(List.of(tools)); return this; } @@ -453,11 +454,10 @@ public class OllamaObject { public OllamaObjectBuilder addFileTools(@MagicConstant(valuesFromClass = FileHandlerLocation.class) String baseDirectory) { - FileHandler fileHandler = new FileHandler(baseDirectory); + new FileHandler(baseDirectory); - if(false); - - throw new IllegalArgumentException("FileHandler is not supported yet!"); + //throw new IllegalArgumentException("FileHandler is not supported yet!"); + return addTools(FileHandler.getTools()); } /** diff --git a/Core/src/main/java/me/zacharias/chat/ollama/exceptions/OllamaToolErrorException.java b/Core/src/main/java/me/zacharias/chat/ollama/exceptions/OllamaToolErrorException.java index d90e470..abfb846 100644 --- a/Core/src/main/java/me/zacharias/chat/ollama/exceptions/OllamaToolErrorException.java +++ b/Core/src/main/java/me/zacharias/chat/ollama/exceptions/OllamaToolErrorException.java @@ -27,6 +27,10 @@ public class OllamaToolErrorException extends RuntimeException { this.tool = tool; this.error = error; } + + public OllamaToolErrorException(String tool, Exception ex) { + this(tool, ex.getMessage()); + } /** * Gets the tool that caused the error. diff --git a/Display/src/main/java/me/zacharias/chat/display/Display.java b/Display/src/main/java/me/zacharias/chat/display/Display.java index a153c56..081c3d0 100644 --- a/Display/src/main/java/me/zacharias/chat/display/Display.java +++ b/Display/src/main/java/me/zacharias/chat/display/Display.java @@ -50,10 +50,10 @@ public class Display { { core.setOllamaObject/*NoMemory*/(OllamaObject.builder() - .setModel("llama3.2") + .setModel("llama3.1:8b") .keep_alive(10) //.stream(false) - //.addFileTools(FileHandlerLocation.DATA_FILES) + .addFileTools(FileHandlerLocation.DATA_FILES) .addMessages(new SystemMessage("You need to emulate feelings and emotions.")) .build()); @@ -61,13 +61,12 @@ public class Display { core.addTool(new TimeTool(), Core.Source.INTERNAL); // TODO: Well Docker failes when luanched.... Fuck - PythonRunner pythonRunner = new PythonRunner(core); - core.addTool(pythonRunner, Core.Source.INTERNAL); - //core.addTools(new MALAPITool().getOllamaTools()); - //core.addTools(new GeniusTools().getGeniusTools()); - //core.addTools(new WikipediaTool().getWikipediaToolsInstance()); + core.addTool(new PythonRunner(core), Core.Source.INTERNAL); + core.addTools(new MALAPITool().getOllamaTools()); + core.addTools(new GeniusTools().getGeniusTools()); + core.addTools(new WikipediaTool().getWikipediaToolsInstance()); - APIApplication.start(); + //APIApplication.start(); //core.getOllamaObject().addMessage(new OllamaMessage(OllamaMessageRole.SYSTEM, "Have a nice tone and use formal wording")); diff --git a/Display/src/main/java/me/zacharias/chat/display/PythonRunner.java b/Display/src/main/java/me/zacharias/chat/display/PythonRunner.java index 7b42026..cbeef69 100644 --- a/Display/src/main/java/me/zacharias/chat/display/PythonRunner.java +++ b/Display/src/main/java/me/zacharias/chat/display/PythonRunner.java @@ -100,7 +100,21 @@ public class PythonRunner extends OllamaFunctionTool { continue; } - outputBuffers.getOrDefault(containerId, new StringBuilder()).append(data.optString("text", "")); + outputBuffers.compute(containerId, (k, buf) -> { + if(buf == null) { + buf = new StringBuilder(); + } + buf.append(data.optString("text", "")); + buf.append(System.lineSeparator()); + return buf; + }); + out.write(new JSONObject().put("container_id", containerId).toString()); + out.newLine(); + out.flush(); + out.close(); + in.close(); + socket.close(); + continue; } List> list = core.getFuntionTools().stream().filter(funtionTool -> funtionTool.getKey().name().equalsIgnoreCase(toolName)).toList(); @@ -155,7 +169,6 @@ public class PythonRunner extends OllamaFunctionTool { .maxConnections(10) .connectionTimeout(Duration.ofSeconds(100)) .responseTimeout(Duration.ofSeconds(100)) - .sslConfig(config.getSSLConfig()) .build(); DockerHttpClient.Request ping = DockerHttpClient.Request.builder() @@ -343,15 +356,17 @@ public class PythonRunner extends OllamaFunctionTool { File f = new File("./pythonFiles", "external_tools.py"); String containerId = dockerClient.createContainerCmd("archlinux") - .withCmd("/bin/bash","./cmd.sh") + .withCmd("/bin/bash","/cmd.sh") .withHostConfig(HostConfig.newHostConfig() .withExtraHosts("host.docker.internal:host-gateway")) .exec().getId(); + outputBuffers.put(containerId, new StringBuilder()); + try { String external_tools = generateExternalTools(containerId); BufferedWriter bw = new BufferedWriter(new FileWriter(f)); - bw.write(external_tools.replace("\\n", "\n")); + bw.write(external_tools/*.replace("\\n", "\n").replace("\\!n", "\\n")*/); bw.flush(); bw.close(); }catch(IOException e) {} @@ -369,8 +384,8 @@ public class PythonRunner extends OllamaFunctionTool { pacman --noconfirm -S python python-pip > /dev/null mkdir pythonRun > /dev/null python -m venv ./pythonRun > /dev/null - cp external_tools.py ./pythonRun > /dev/null - cp\s""") + cp /external_tools.py ./pythonRun > /dev/null + cp\s/""") .append(name).append(""" \s./pythonRun > /dev/null cd pythonRun > /dev/null @@ -389,7 +404,6 @@ public class PythonRunner extends OllamaFunctionTool { cmd.append("python ").append(name);//.append(" exit"); - pythonArgs.add("-c"); pythonArgs.add(cmd.toString()); StringBuilder fullCmd = new StringBuilder(); @@ -424,32 +438,16 @@ public class PythonRunner extends OllamaFunctionTool { dockerClient.startContainerCmd(containerId).exec(); - GetContainerLog log = new GetContainerLog(dockerClient, containerId); + dockerClient.waitContainerCmd(containerId).start().awaitCompletion(); - List logs = new ArrayList<>(); + String output = outputBuffers.remove(containerId).toString(); - do { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - logs.addAll(log.getDockerLogs()); - } - while (isRunning(containerId)); + dockerClient.removeContainerCmd(containerId).exec(); - StringBuilder output = new StringBuilder(); - - for (String s : logs) { - output.append(s).append("\n"); - } - - //writeLog("Result from python: " + output.toString()); - - return new OllamaToolRespnce(name(), output.toString()); + return new OllamaToolRespnce(name(), output.isEmpty() ? "Code ran successfully with no output" : output); } catch (Exception e) { - throw new OllamaToolErrorException(name(), "Docker unavalible"); + throw new OllamaToolErrorException(name(), "Docker unavailable"); } } @@ -488,15 +486,24 @@ public class PythonRunner extends OllamaFunctionTool { boolean first = true; try { - for (String argName : tool.parameters().getProperties().keySet()) { + for(String argName : tool.parameters().getRequired()) + { args.add(argName); if (!first) { code.append(", "); } code.append(argName); - if (Arrays.stream(tool.parameters().getRequired()).noneMatch(required -> required.equals(argName))) { - code.append("=None"); + first = false; + } + + for (String argName : tool.parameters().getProperties().keySet()) { + if(Arrays.asList(tool.parameters().getRequired()).contains(argName)) continue; + + args.add(argName); + if (!first) { + code.append(", "); } + code.append(argName).append("=None"); first = false; } }catch (Exception e) {} @@ -526,69 +533,4 @@ public class PythonRunner extends OllamaFunctionTool { return code.toString(); } - - /** - * Checks if a Docker Container is running - * @param containerId The ID of the Container - * @return a boolean weather it's running or not - */ - public boolean isRunning(String containerId) - { - InspectContainerResponse cmd = dockerClient.inspectContainerCmd(containerId).exec(); - return Boolean.TRUE.equals(cmd.getState().getRunning()); - } - - /** - * A Helper class to get the logs from a docker container. - */ - public class GetContainerLog { - private DockerClient dockerClient; - private String containerId; - private int lastLogTime; - - private static String nameOfLogger = "dockertest.PrintContainerLog"; - private static Logger myLogger = Logger.getLogger(nameOfLogger); - - /** - * Creates a new instance of {@link GetContainerLog} - * @param dockerClient The DockerClient instance - * @param containerId The container id - */ - public GetContainerLog(DockerClient dockerClient, String containerId) { - this.dockerClient = dockerClient; - this.containerId = containerId; - this.lastLogTime = (int) (System.currentTimeMillis() / 1000); - } - - /** - * Gets the logs of the container. - * @return The logs of the container - */ - public List getDockerLogs() { - - final List logs = new ArrayList<>(); - - LogContainerCmd logContainerCmd = dockerClient.logContainerCmd(containerId); - logContainerCmd.withStdOut(true).withStdErr(true); - logContainerCmd.withSince(lastLogTime); // UNIX timestamp (integer) to filter logs. Specifying a timestamp will only output log-entries since that timestamp. - // logContainerCmd.withTail(4); // get only the last 4 log entries - - logContainerCmd.withTimestamps(true); - - try { - logContainerCmd.exec(new ResultCallback.Adapter() { - @Override - public void onNext(Frame item) { - logs.add(new String(item.getPayload()).trim()); - } - }).awaitCompletion(); - } catch (InterruptedException e) { - myLogger.severe("Interrupted Exception!" + e.getMessage()); - } - - lastLogTime = (int) (System.currentTimeMillis() / 1000) + 5; // assumes at least a 5 second wait between calls to getDockerLogs - - return logs; - } - } } diff --git a/GeniusAPI/build.gradle b/GeniusAPI/build.gradle index 3250df5..e4d6d31 100644 --- a/GeniusAPI/build.gradle +++ b/GeniusAPI/build.gradle @@ -14,7 +14,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter' implementation "org.jsoup:jsoup:1.20.1" - implementation 'io.github.classgraph:classgraph:4.8.158' + implementation 'io.github.classgraph:classgraph:4.8.184' implementation project(":Core") } diff --git a/MALAPITool/build.gradle b/MALAPITool/build.gradle index d93b3a7..cb0882f 100644 --- a/MALAPITool/build.gradle +++ b/MALAPITool/build.gradle @@ -15,7 +15,7 @@ dependencies { implementation project(":Core") - implementation 'io.github.classgraph:classgraph:4.8.158' + implementation 'io.github.classgraph:classgraph:4.8.184' } test { diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..02f59c4 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +# 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 \ No newline at end of file diff --git a/launcher/build.gradle b/launcher/build.gradle index 4d73288..b799b87 100644 --- a/launcher/build.gradle +++ b/launcher/build.gradle @@ -7,7 +7,6 @@ version = '1.0-SNAPSHOT' dependencies { implementation project(":Display") - implementation project(":API") implementation project(":Core") } diff --git a/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java b/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java index df5ee42..1269157 100644 --- a/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java +++ b/launcher/src/main/java/me/zacharias/chat/launcher/Launcher.java @@ -1,7 +1,6 @@ package me.zacharias.chat.launcher; //import me.zacharias.chat.api.APIApplication; -import me.zacharias.chat.api.APIApplication; import me.zacharias.chat.core.LaunchOptions; import me.zacharias.chat.display.Display; @@ -93,7 +92,8 @@ public class Launcher { if (options.isServerMode()) { System.out.println("Starting in API mode..."); try { - APIApplication.start(); + Class clazz = Class.forName("me.zacharias.chat.api.APIApplication"); + clazz.getMethod("start").invoke(null); }catch (Exception e) { System.out.println("Failed to start API server: " + e.getMessage());