added a global cache directory, not fully properly implemented, and dose not respect t.ex the FreeDesktop specifications for cache directories and files OPS this is a partial commit! things might not add up on this commit.
This commit is contained in:
@@ -2,12 +2,11 @@ package me.zacharias.chat.display;
|
||||
|
||||
import com.github.dockerjava.api.DockerClient;
|
||||
import com.github.dockerjava.api.async.ResultCallback;
|
||||
import com.github.dockerjava.api.async.ResultCallbackTemplate;
|
||||
import com.github.dockerjava.api.command.*;
|
||||
import com.github.dockerjava.api.model.BuildResponseItem;
|
||||
import com.github.dockerjava.api.model.Frame;
|
||||
import com.github.dockerjava.api.model.Statistics;
|
||||
import com.github.dockerjava.api.model.HostConfig;
|
||||
import com.github.dockerjava.core.DefaultDockerClientConfig;
|
||||
import com.github.dockerjava.core.DockerClientImpl;
|
||||
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
||||
import com.github.dockerjava.transport.DockerHttpClient;
|
||||
import me.zacharias.chat.core.Core;
|
||||
@@ -28,6 +27,7 @@ import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static me.zacharias.chat.core.Core.writeLog;
|
||||
@@ -52,6 +52,16 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
*/
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
/**
|
||||
* A hash map of all python runners, this is to make it thread safe in a theoretical case of multiple python runners running at once
|
||||
*/
|
||||
private ConcurrentHashMap<String, StringBuilder> outputBuffers = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* The docker client to be used throughout this tool
|
||||
*/
|
||||
DockerHttpClient dockerHttpClient;
|
||||
|
||||
/**
|
||||
* Creates a new instance of PythonRunner.
|
||||
* @param core The Core instance
|
||||
@@ -59,8 +69,6 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
public PythonRunner(Core core) {
|
||||
this.core = core;
|
||||
|
||||
generateExternalTools();
|
||||
|
||||
try {
|
||||
serverSocket = new ServerSocket(6050);
|
||||
Thread thread = new Thread(() -> {
|
||||
@@ -76,7 +84,26 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
|
||||
try {
|
||||
JSONObject data = new JSONObject(inputLine);
|
||||
List<Pair<OllamaFunctionTool, String>> list = core.getFuntionTools().stream().filter(funtionTool -> funtionTool.getKey().name().equalsIgnoreCase(data.optString("function", ""))).toList();
|
||||
|
||||
String toolName = data.optString("function", "");
|
||||
|
||||
if(toolName.equals("print_output")) {
|
||||
// This is a "special" "tool" that is used to hijack the output of the python program so logs can be put else where since the LLM dose'nt need the entirety of the log from pacman and what not.
|
||||
String containerId = data.optString("container_id", "");
|
||||
if(containerId.isEmpty()) {
|
||||
out.write(new JSONObject().put("error", "Missing container id").toString());
|
||||
out.newLine();
|
||||
out.flush();
|
||||
out.close();
|
||||
in.close();
|
||||
socket.close();
|
||||
continue;
|
||||
}
|
||||
|
||||
outputBuffers.getOrDefault(containerId, new StringBuilder()).append(data.optString("text", ""));
|
||||
}
|
||||
|
||||
List<Pair<OllamaFunctionTool, String>> list = core.getFuntionTools().stream().filter(funtionTool -> funtionTool.getKey().name().equalsIgnoreCase(toolName)).toList();
|
||||
|
||||
if (list.isEmpty()) {
|
||||
out.write(new JSONObject().put("error", "Function don't exist").toString());
|
||||
@@ -120,12 +147,11 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
|
||||
DefaultDockerClientConfig config
|
||||
= DefaultDockerClientConfig.createDefaultConfigBuilder()
|
||||
.withDockerHost("tcp://localhost:2375")
|
||||
.withDockerTlsVerify(false)
|
||||
.withDockerCertPath("~/.docker")
|
||||
.build();
|
||||
DockerHttpClient dockerHttpClient = new ApacheDockerHttpClient.Builder()
|
||||
|
||||
dockerHttpClient = new ApacheDockerHttpClient.Builder()
|
||||
.dockerHost(config.getDockerHost())
|
||||
.sslConfig(config.getSSLConfig())
|
||||
.maxConnections(10)
|
||||
.connectionTimeout(Duration.ofSeconds(100))
|
||||
.responseTimeout(Duration.ofSeconds(100))
|
||||
@@ -141,14 +167,14 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
{
|
||||
if(!(response.getStatusCode() == 200))
|
||||
{
|
||||
writeLog("Failed to ping docker");
|
||||
System.out.println("Failed to ping docker. Docker components is disabled.");
|
||||
writeLog("Failed to ping docker: " + response.getStatusCode());
|
||||
System.out.println("Failed to ping docker (" + response.getStatusCode() + "). Docker components is disabled.");
|
||||
dockerClient = null;
|
||||
return;
|
||||
}
|
||||
if(!(IOUtils.toString(response.getBody(), Charset.defaultCharset()).equals("OK")))
|
||||
{
|
||||
writeLog("Failed to ping docker");
|
||||
writeLog("Failed to ping docker: body not OK");
|
||||
System.out.println("Failed to ping docker. Docker components is disabled.");
|
||||
dockerClient = null;
|
||||
return;
|
||||
@@ -156,9 +182,73 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
writeLog("Failed to ping docker");
|
||||
writeLog("Failed to ping docker: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to ping docker. Docker components is disabled.");
|
||||
dockerClient = null;
|
||||
return;
|
||||
}
|
||||
|
||||
dockerClient = DockerClientImpl.getInstance(config, dockerHttpClient);
|
||||
|
||||
initializingImage();
|
||||
}
|
||||
|
||||
/**
|
||||
* A small function to pull the archlinux:latest image from Docker Hub. See <a href="https://hub.docker.com/_/archlinux/">ArchLinux Docker Hub</a>
|
||||
* <br>
|
||||
* I want to note here that the reason for using an ArchLinux image and not somthing like python or Ubuntu. Is because I'm more
|
||||
* comfortable with how to do this on an Arch host, and I could not manage to pull libraries for the python image. So those who despise Arch you're welcome to re-write the arch specific parts.
|
||||
*/
|
||||
private void initializingImage() {
|
||||
// Check if archlinux:latest is pulled already
|
||||
DockerHttpClient.Request checkArchlinux_latest = DockerHttpClient.Request.builder()
|
||||
.method(DockerHttpClient.Request.Method.GET)
|
||||
.path("/images/archlinux:latest/json")
|
||||
.build();
|
||||
|
||||
boolean archlinux_latestAvailable = false;
|
||||
|
||||
try(DockerHttpClient.Response response = dockerHttpClient.execute(checkArchlinux_latest))
|
||||
{
|
||||
if(response.getStatusCode() == 200)
|
||||
{
|
||||
archlinux_latestAvailable = true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
writeLog("Failed to fetch archlinux:latest: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to fetch archlinux:latest.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!archlinux_latestAvailable) {
|
||||
|
||||
// If it's not already downloaded, we will download it.
|
||||
DockerHttpClient.Request downloadArchlinux_latest = DockerHttpClient.Request.builder()
|
||||
.method(DockerHttpClient.Request.Method.POST)
|
||||
.path("/images/create?fromImage=archlinux&tag=latest")
|
||||
.build();
|
||||
|
||||
try (DockerHttpClient.Response response = dockerHttpClient.execute(downloadArchlinux_latest)) {
|
||||
if (response.getStatusCode() == 200) {
|
||||
writeLog("Successfully downloaded archlinux:latest");
|
||||
}
|
||||
else {
|
||||
writeLog("Failed to download archlinux:latest: " + response.getStatusCode() + "\n" + response.getBody());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
writeLog("Failed to fetch archlinux:latest: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
System.out.println("Failed to fetch archlinux:latest.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
writeLog("archlinux:latest already exists");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,13 +259,13 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "Runs python code";
|
||||
return "Runs python code.\nuse python's print method for output";
|
||||
}
|
||||
|
||||
@Override
|
||||
public OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
.addProperty("code", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The code to be executed", true)
|
||||
.addProperty("code", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The code to be executed, optional if a file with the same name exists.")
|
||||
.addProperty("name", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The name of the python code")
|
||||
.addProperty("libs", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "A space separated list of pip packages needed")
|
||||
.build();
|
||||
@@ -214,6 +304,11 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
}
|
||||
}
|
||||
|
||||
if(name == null && code == null)
|
||||
{
|
||||
throw new OllamaToolErrorException(name(), "Missing code or name");
|
||||
}
|
||||
|
||||
if(name == null)
|
||||
{
|
||||
try {
|
||||
@@ -247,8 +342,14 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
|
||||
File f = new File("./pythonFiles", "external_tools.py");
|
||||
|
||||
String containerId = dockerClient.createContainerCmd("archlinux")
|
||||
.withCmd("/bin/bash","./cmd.sh")
|
||||
.withHostConfig(HostConfig.newHostConfig()
|
||||
.withExtraHosts("host.docker.internal:host-gateway"))
|
||||
.exec().getId();
|
||||
|
||||
try {
|
||||
String external_tools = generateExternalTools();
|
||||
String external_tools = generateExternalTools(containerId);
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(f));
|
||||
bw.write(external_tools.replace("\\n", "\n"));
|
||||
bw.flush();
|
||||
@@ -258,10 +359,23 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
try {
|
||||
ArrayList<String> pythonArgs = new ArrayList<>();
|
||||
|
||||
pythonArgs.add("/bin/bash");
|
||||
pythonArgs.add("#!/bin/env bash\n");
|
||||
|
||||
StringBuilder cmd = new StringBuilder();
|
||||
cmd.append("set -e\n\npacman --noconfirm -Sy > /dev/null\npacman --noconfirm -S python python-pip > /dev/null\nmkdir pythonRun > /dev/null\npython -m venv ./pythonRun > /dev/null\ncp external_tools.py ./pythonRun > /dev/null\ncp ").append(name).append(" ./pythonRun > /dev/null\ncd pythonRun > /dev/null\nsource ./bin/activate > /dev/null\n");
|
||||
cmd.append("""
|
||||
set -e
|
||||
|
||||
pacman --noconfirm -Sy > /dev/null
|
||||
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""")
|
||||
.append(name).append("""
|
||||
\s./pythonRun > /dev/null
|
||||
cd pythonRun > /dev/null
|
||||
source ./bin/activate > /dev/null
|
||||
""");
|
||||
|
||||
if(libs != null && !libs.isEmpty())
|
||||
{
|
||||
@@ -285,20 +399,17 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
fullCmd.append(arg).append(" ");
|
||||
}
|
||||
|
||||
File program = new File("./cache", "cmd.sh");
|
||||
File program = new File(Core.CACHE_DIRECTORY, "cmd.sh");
|
||||
if(program.exists()){
|
||||
program.delete();
|
||||
}
|
||||
program.createNewFile();
|
||||
|
||||
BufferedWriter bw = new BufferedWriter(new FileWriter(program));
|
||||
bw.write(cmd.toString());
|
||||
bw.write(fullCmd.toString());
|
||||
bw.flush();
|
||||
bw.close();
|
||||
|
||||
|
||||
String containerId = dockerClient.createContainerCmd("archlinux").withCmd("/bin/bash","./cmd.sh").exec().getId();
|
||||
|
||||
dockerClient.copyArchiveToContainerCmd(containerId)
|
||||
.withHostResource(pythonFile.getPath())
|
||||
.exec();
|
||||
@@ -311,14 +422,8 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
.withHostResource(program.getPath())
|
||||
.exec();
|
||||
|
||||
//InputStream stdin = new ByteArrayInputStream(fullCmd.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
dockerClient.startContainerCmd(containerId).exec();
|
||||
|
||||
//dockerClient.attachContainerCmd(containerId).withStdIn(stdin).exec(null);
|
||||
|
||||
//dockerClient.execCreateCmd(containerId).withCmd(fullCmd.toString()).exec();
|
||||
|
||||
GetContainerLog log = new GetContainerLog(dockerClient, containerId);
|
||||
|
||||
List<String> logs = new ArrayList<>();
|
||||
@@ -354,9 +459,11 @@ public class PythonRunner extends OllamaFunctionTool {
|
||||
* @return The generated external_tools.py file
|
||||
* @throws IllegalStateException This is thrown when the external_tool_base.py can not be read, this should be handled by disabling the python runner.
|
||||
*/
|
||||
private String generateExternalTools() throws IllegalStateException{
|
||||
private String generateExternalTools(String containerId) throws IllegalStateException{
|
||||
StringBuilder code = new StringBuilder();
|
||||
|
||||
code.append("CONTAINER_ID = \"").append(containerId).append("\"\n\n");
|
||||
|
||||
try (InputStream in = getClass().getResourceAsStream("/external_tool_base.py");
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
|
||||
String tmp = null;
|
||||
|
||||
@@ -14,11 +14,13 @@ def connect(data):
|
||||
return responce.decode("utf-8")
|
||||
|
||||
def print_output(text):
|
||||
data = {"function": "print_output", "arguments": [{"name": "text", "value": text}]}
|
||||
data = {"function": "print_output", "container_id": CONTAINER_ID, "text": text}
|
||||
connect(json.dumps(data))
|
||||
|
||||
def _neurodock_print(*args, **kwargs):
|
||||
text = " ".join(str(a) for a in args)
|
||||
print_output(text)
|
||||
|
||||
builtins.print = _neurodock_print
|
||||
builtins.print = _neurodock_print
|
||||
|
||||
## Following is the generated
|
||||
Reference in New Issue
Block a user