Initial commit

This commit is contained in:
2025-02-20 18:00:16 +01:00
commit 6935e938a3
24 changed files with 1413 additions and 0 deletions

46
.gitignore vendored Normal file
View File

@@ -0,0 +1,46 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
/pythonFiles/
/messages/
/logs/

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

17
.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

10
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

26
build.gradle Normal file
View File

@@ -0,0 +1,26 @@
plugins {
id 'java'
id 'com.gradleup.shadow' version '9.0.0-beta7'
}
group = 'me.zacharias'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation("org.json:json:20250107")
implementation("com.github.docker-java:docker-java:3.4.1")
}
test {
useJUnitPlatform()
}
jar{
manifest {
attributes 'Main-Class': 'me.zacharias.chat.Main'
}
}

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Thu Feb 20 00:10:50 CET 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored Normal file
View File

@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle Normal file
View File

@@ -0,0 +1,2 @@
rootProject.name = 'AI-test'

View File

@@ -0,0 +1,320 @@
package me.zacharias.chat;
import me.zacharias.chat.ollama.*;
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.*;
import java.net.*;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Main {
private String ollamaIP = "localhost";//"192.168.5.184";
private int ollamaPort = 11434;
private URL url;
private static File logFile = new File("./logs/latest.log");
private static BufferedWriter logWriter;
private ScheduledExecutorService scheduler;
private OllamaObject ollamaObject;
private ArrayList<OllamaFuntionTool> funtionTools;
{
File dir = new File("./logs/");
if (!dir.exists()) {
dir.mkdir();
}
dir = new File("./pythonFiles/");
if (!dir.exists()) {
dir.mkdir();
}
dir = new File("./messages");
if (!dir.exists()) {
dir.mkdir();
}
try {
url = new URI("http://"+ollamaIP+":"+ollamaPort+"/api/chat").toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
try {
if (logFile.exists()) {
BufferedReader br = new BufferedReader(new FileReader(logFile));
String line = br.readLine();
br.close();
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");
}
else {
System.out.println("Exisitng log file is empty, overwriting it!");
logFile.delete();
}
logFile.createNewFile();
}
logWriter = new BufferedWriter(new FileWriter(logFile));
}catch (IOException e) {
throw new RuntimeException(e);
}
this.scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
try {
logWriter.flush();
//System.out.println("Buffer flushed to file.");
} catch (IOException e) {
e.printStackTrace();
}
}, 0, 3, TimeUnit.MINUTES);
}
public static void main(String[] args) {
new Main();
}
public Main()
{
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
scheduler.shutdownNow();
try {
logWriter.flush();
logWriter.close();
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH-mm-ss");
File messagesFile = new File("./messages/"+now.format(formatter)+".txt");
BufferedWriter messagesWriter = new BufferedWriter(new FileWriter(messagesFile));
JSONArray messages = new JSONArray();
for(OllamaMessage message : ollamaObject.getMessages()) {
messages.put(new JSONObject(message.toString()));
}
messagesWriter.write(messages.toString());
messagesWriter.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}));
ollamaObject = OllamaObject.builder()
.setModel("llama3-AI")
.keep_alive(10)
.addTool(new TimeTool())
.addTool(new PythonRunner())
.stream(false)
.build();
writeLog("Creating base OllamaObject with model: "+ollamaObject.getModel());
funtionTools = new ArrayList<>();
System.out.println("Installed tools");
writeLog("Tools installed in this instance");
for(OllamaTool tool : ollamaObject.getTools())
{
if(tool instanceof OllamaFuntionTool funtion)
{
StringBuilder args = new StringBuilder();
OllamaPerameter perameter = funtion.parameters();
if(perameter != null) {
JSONObject obj = perameter.getProperties();
for (String name : obj.keySet()) {
args.append(args.toString().isBlank() ? "" : ", ").append(obj.getJSONObject(name).getString("type")).append(Arrays.stream(perameter.getRequired()).anyMatch(str -> str.equalsIgnoreCase(name)) ? "" : "?").append(" ").append(name);
}
}
System.out.println("> Function: "+funtion.name()+"("+args+")");
writeLog("Function: "+funtion.name()+"("+args+")");
funtionTools.add(funtion);
}
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Message Trsnscription:");
try {
while (true) {
System.out.print("> ");
StringBuilder message = new StringBuilder(br.readLine());
while(br.ready())
{
message.append("\n").append(br.readLine());
}
if(message.toString().startsWith("/"))
{
switch (message.substring(1)) {
case "help":
System.out.print("""
Available commands:
/help Prints this help message.
/bye Exits the program.
/write Flushes the current log stream to file.
""");
break;
case "bye":
writeLog("Exiting program...");
System.out.println("Bye!");
System.exit(0);
return;
case "write":
logWriter.flush();
break;
default:
System.out.println("Unknown command: "+message);
}
}
else
{
writeLog("User: "+message);
ollamaObject.addMessage(new OllamaMessage(OllamaMessageRole.USER, message.toString()));
//System.out.println(ollamaObject.toString());
handleResponce(qurryOllama(ollamaObject));
}
}
}catch (Exception e) {
e.printStackTrace();
System.out.println("Exiting due to exception");
System.exit(-1);
}
}
public JSONObject qurryOllama(OllamaObject request)
{
try {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
connection.setConnectTimeout(80*1000);
try(DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
wr.writeBytes(request.toString());
wr.flush();
}
int responseCode = connection.getResponseCode();
// HTTP_OK or 200 response code generally means that the server ran successfully without any errors
StringBuilder response = new StringBuilder();
// Read response content
// connection.getInputStream() purpose is to obtain an input stream for reading the server's response.
try (
BufferedReader reader = new BufferedReader( new InputStreamReader( connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
response.append(line); // Adds every line to response till the end of file.
}
}
if (responseCode == HttpURLConnection.HTTP_OK) {
connection.disconnect();
return new JSONObject(response.toString());
}
else {
connection.disconnect();
System.out.println("Error: HTTP Response code - " + responseCode + "\n"+response.toString());
throw new RuntimeException("HTTP Response code - " + responseCode);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void handleResponce(JSONObject responce)
{
//System.out.println("Responce: "+responce);
if(responce != null) {
writeLog("Raw responce: "+responce.toString());
if(responce.getJSONObject("message").has("tool_calls"))
{
JSONArray calls = responce.getJSONObject("message").getJSONArray("tool_calls");
for(Object call : calls)
{
if(call instanceof JSONObject jsonObject)
{
if(jsonObject.has("function"))
{
JSONObject function = jsonObject.getJSONObject("function");
List<OllamaFuntionTool> functions = funtionTools.stream().filter(func -> func.name().equalsIgnoreCase(function.getString("name"))).toList();
if(functions.isEmpty()) {
ollamaObject.addMessage(new OllamaToolError("Function '"+function.getString("name")+"' does not exist"));
System.out.println(">> \u001b[31mTried funtion call "+function.getString("name")+" but failed to find it.\u001b[0m");
writeLog("Failed function call to "+function.getString("name"));
}
else {
OllamaFuntionTool func = functions.getFirst();
ArrayList<OllamaFunctionArgument> argumentArrayList = new ArrayList<>();
JSONObject arguments = function.getJSONObject("arguments");
for (String key : arguments.keySet()) {
argumentArrayList.add(new OllamaFunctionArgument(key, arguments.get(key)));
}
try {
OllamaToolRespnce function1 = func.function(argumentArrayList.toArray(new OllamaFunctionArgument[0]));
ollamaObject.addMessage(function1);
System.out.println(">> \u001b[34mCall " + func.name() + "\u001b[0m");
writeLog("Successfully function call " + func.name() + " output: " + function1.getResponse());
} catch (OllamaToolErrorException e) {
ollamaObject.addMessage(new OllamaToolError(e.getMessage()));
System.out.println(">> \u001b[31mTried funtion call " + func.name() + " but failed due to " + e.getError() + "\u001b[0m");
writeLog(e.getMessage());
}
}
}
}
}
if(responce.getJSONObject("message").has("content") && !responce.getJSONObject("message").getString("content").isBlank())
{
System.out.println(">> \u001b[32m"+responce.getJSONObject("message").getString("content")+"\u001b[0m");
writeLog("Response content: "+responce.getJSONObject("message").getString("content"));
ollamaObject.addMessage(new OllamaMessage(OllamaMessageRole.ASSISTANT, responce.getJSONObject("message").getString("content")));
}
handleResponce(qurryOllama(ollamaObject));
}
else if(responce.getJSONObject("message").has("content") && !responce.getJSONObject("message").getString("content").isBlank())
{
System.out.println(">> \u001b[32m"+responce.getJSONObject("message").getString("content")+"\u001b[0m");
writeLog("Response content: "+responce.getJSONObject("message").getString("content"));
ollamaObject.addMessage(new OllamaMessage(OllamaMessageRole.ASSISTANT, responce.getJSONObject("message").getString("content")));
}
}
}
public static void writeLog(String message)
{
try {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd%EEEE HH:mm:ss'#'SSS");
logWriter.write(now.format(formatter) + "> " + message + "\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,187 @@
package me.zacharias.chat;
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.AttachContainerCmd;
import com.github.dockerjava.api.command.LogContainerCmd;
import com.github.dockerjava.api.command.StartContainerCmd;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.command.LogContainerResultCallback;
import me.zacharias.chat.ollama.*;
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
import org.json.JSONObject;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.logging.Logger;
import static me.zacharias.chat.Main.writeLog;
public class PythonRunner extends OllamaFuntionTool {
DockerClient dockerClient;
public PythonRunner() {
DefaultDockerClientConfig.Builder config
= DefaultDockerClientConfig.createDefaultConfigBuilder()
.withDockerHost("tcp://localhost:2375")
.withDockerTlsVerify(false);
dockerClient = DockerClientBuilder
.getInstance(config)
.build();
}
@Override
public String name() {
return "python_runner";
}
@Override
public String description() {
return "Runs python code";
}
@Override
public OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("code", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The code to be executed", true)
.addProperty("name", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The name of the python code")
.build();
}
@Override
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
if(args.length == 0)
{
throw new OllamaToolErrorException(name(), "Missing code argument");
}
String name = null;
String code = null;
for(OllamaFunctionArgument arg : args)
{
if(arg.getArgument().equals("name"))
{
name = (String) arg.getValue();
if(!name.endsWith(".py"))
{
name += ".py";
}
} else if (arg.getArgument().equals("code")) {
code = (String) arg.getValue();
}
}
if(name == null)
{
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] encodedhash = digest.digest(String.valueOf(args[0].getValue()).getBytes(StandardCharsets.UTF_8));
StringBuffer hexString = new StringBuffer();
for(byte b : encodedhash)
{
hexString.append(String.format("%02x", b));
}
name = hexString.toString()+".py";
}catch (Exception e) {}
}
name = name.replace(" ", "_");
writeLog("Running python code `" + name + "`");
File pythonFile = new File("./pythonFiles", name);
if(!pythonFile.exists())
{
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(pythonFile));
writer.write(code);
writer.close();
}catch(IOException e) {}
}
String containerId = dockerClient.createContainerCmd("python").withCmd("python", name).exec().getId();
dockerClient.copyArchiveToContainerCmd(containerId)
.withHostResource(pythonFile.getPath())
//.withRemotePath("~/")
.exec();
dockerClient.startContainerCmd(containerId).exec();
GetContainerLog log = new GetContainerLog(dockerClient, containerId);
List<String> logs = new ArrayList<>();
do {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
logs.addAll(log.getDockerLogs());
}
while (logs.isEmpty());
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());
}
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);
public GetContainerLog(DockerClient dockerClient, String containerId) {
this.dockerClient = dockerClient;
this.containerId = containerId;
this.lastLogTime = (int) (System.currentTimeMillis() / 1000);
}
public List<String> getDockerLogs() {
final List<String> 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<Frame>() {
@Override
public void onNext(Frame item) {
logs.add(item.toString());
}
}).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;
}
}
}

View File

@@ -0,0 +1,32 @@
package me.zacharias.chat;
import me.zacharias.chat.ollama.OllamaFunctionArgument;
import me.zacharias.chat.ollama.OllamaFuntionTool;
import me.zacharias.chat.ollama.OllamaPerameter;
import me.zacharias.chat.ollama.OllamaToolRespnce;
import java.util.Date;
public class TimeTool extends OllamaFuntionTool {
@Override
public OllamaToolRespnce function(OllamaFunctionArgument... arguments) {
Date date = new Date();
return new OllamaToolRespnce(name(), date.toString());
}
@Override
public String name() {
return "get_current_date";
}
@Override
public OllamaPerameter parameters() {
return null;
}
@Override
public String description() {
return "Get the current date";
}
}

View File

@@ -0,0 +1,19 @@
package me.zacharias.chat.ollama;
public class OllamaFunctionArgument {
private final String argument;
private final Object value;
public OllamaFunctionArgument(String argument, Object value) {
this.argument = argument;
this.value = value;
}
public String getArgument() {
return argument;
}
public Object getValue() {
return value;
}
}

View File

@@ -0,0 +1,28 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
public abstract class OllamaFuntionTool implements OllamaTool {
@Override
public String toString() {
JSONObject ret = new JSONObject();
ret.put("tool", "function");
JSONObject function = new JSONObject();
function.put("name", name());
function.put("description", description());
function.put("parameters", (parameters() == null?
new JSONObject() : new JSONObject(parameters().toString())));
ret.put("function", function);
return ret.toString();
}
abstract public String name();
abstract public String description();
abstract public OllamaPerameter parameters();
abstract public OllamaToolRespnce function(OllamaFunctionArgument... args);
}

View File

@@ -0,0 +1,21 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
public class OllamaMessage {
OllamaMessageRole role;
String content;
public OllamaMessage(OllamaMessageRole role, String content) {
this.role = role;
this.content = content;
}
@Override
public String toString() {
JSONObject json = new JSONObject();
json.put("role", role);
json.put("content", content);
return json.toString();
}
}

View File

@@ -0,0 +1,18 @@
package me.zacharias.chat.ollama;
public enum OllamaMessageRole {
USER("user"),
ASSISTANT("assistant"),
TOOL("tool"),
SYSTEM("system");
private String role;
OllamaMessageRole(String role) {
this.role = role;
}
public String getRole() {
return role;
}
}

View File

@@ -0,0 +1,166 @@
package me.zacharias.chat.ollama;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OllamaObject {
String model;
ArrayList<OllamaMessage> messages;
OllamaTool[] tools;
JSONObject format;
Map<String, Object> options;
boolean stream;
String keep_alive;
private OllamaObject(String model, ArrayList<OllamaMessage> messages, OllamaTool[] tools, JSONObject format, Map<String, Object> options, boolean stream, String keep_alive) {
this.model = model;
this.messages = messages;
this.tools = tools;
this.format = format;
this.options = options;
this.stream = stream;
this.keep_alive = keep_alive;
}
public String getModel() {
return model;
}
public ArrayList<OllamaMessage> getMessages() {
return messages;
}
public OllamaTool[] getTools() {
return tools;
}
public JSONObject getFormat() {
return format;
}
public Map<String, Object> getOptions() {
return options;
}
public boolean isStream() {
return stream;
}
public String getKeep_alive() {
return keep_alive;
}
public void addMessage(OllamaMessage message) {
messages.add(message);
}
@Override
public String toString() {
JSONObject json = new JSONObject();
JSONArray tools = new JSONArray();
for (OllamaTool tool : this.tools) {
tools.put(new JSONObject(tool.toString()));
}
JSONArray messages = new JSONArray();
for (OllamaMessage message : this.messages) {
messages.put(new JSONObject(message.toString()));
}
json.put("model", model);
json.put("messages", messages);
json.put("tools", tools);
json.put("format", format);
json.put("options", options);
json.put("stream", stream);
json.put("keep_alive", keep_alive);
return json.toString();
}
public static OllamaObjectBuilder builder()
{
return new OllamaObjectBuilder();
}
public static class OllamaObjectBuilder {
String model;
ArrayList<OllamaMessage> messages = new ArrayList<>();
ArrayList<OllamaTool> tools = new ArrayList<>();
JSONObject format;
Map<String, Object> options = new HashMap<>();
boolean stream;
String keep_alive;
public OllamaObjectBuilder() {}
public OllamaObjectBuilder format(String format) {
this.format = new JSONObject(format);
return this;
}
public OllamaObjectBuilder options(Map<String, Object> options) {
this.options.putAll(options);
return this;
}
public OllamaObjectBuilder option(String key, String value) {
this.options.put(key, value);
return this;
}
public OllamaObjectBuilder stream(boolean stream) {
this.stream = stream;
return this;
}
public OllamaObjectBuilder keep_alive(String keep_alive) {
this.keep_alive = keep_alive;
return this;
}
public OllamaObjectBuilder keep_alive(int minutes) {
this.keep_alive = minutes+"m";
return this;
}
public OllamaObjectBuilder addTool(OllamaTool tools) {
this.tools.add(tools);
return this;
}
public OllamaObjectBuilder addTools(ArrayList<? extends OllamaTool> tools) {
this.tools.addAll(tools);
return this;
}
public OllamaObjectBuilder addTools(OllamaTool... tools) {
this.tools.addAll(List.of(tools));
return this;
}
public OllamaObjectBuilder addMessages(OllamaMessage... messages) {
this.messages.addAll(List.of(messages));
return this;
}
public OllamaObjectBuilder addMessage(OllamaMessage messages) {
this.messages.add(messages);
return this;
}
public OllamaObjectBuilder setModel(String model) {
this.model = model;
return this;
}
public OllamaObject build() {
return new OllamaObject(model, messages, tools.toArray(new OllamaTool[0]), format, options, stream, keep_alive);
}
}
}

View File

@@ -0,0 +1,116 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class OllamaPerameter {
private OllamaPerameter(JSONObject properties, String[] required) {
this.properties = properties;
this.required = required;
};
@Override
public String toString() {
JSONObject json = new JSONObject();
json.put("type", "object");
json.put("properties", properties);
json.put("required", required);
return json.toString();
}
JSONObject properties;
String[] required;
public JSONObject getProperties() {
return properties;
}
public String[] getRequired() {
return required;
}
public static OllamaPerameterBuilder builder() {
return new OllamaPerameterBuilder();
}
public static class OllamaPerameterBuilder {
Map<String, Property> propertyMap = new HashMap<>();
ArrayList<String> required = new ArrayList<>();
public OllamaPerameterBuilder addProperty(String name, Type type, String description) {
if(name == null || type == null || description == null) {
return this;
}
propertyMap.put(name, new Property(type.getType(), description));
return this;
}
public OllamaPerameterBuilder 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));
this.required.add(name);
return this;
}
public OllamaPerameterBuilder required(String name) {
required.add(name);
return this;
}
public OllamaPerameterBuilder removeProperty(String name) {
propertyMap.remove(name);
return this;
}
public OllamaPerameter build() {
JSONObject properties = new JSONObject();
for(String name : propertyMap.keySet()) {
properties.put(name, new JSONObject(propertyMap.get(name).toString()));
}
return new OllamaPerameter(properties, required.toArray(new String[0]));
}
private class Property {
String type;
String description;
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();
}
}
public enum Type {
STRING("string"),
INT("int"),
BOOLEAN("boolean");
private String type;
public String getType() {
return type;
}
Type(String type) {
this.type = type;
}
}
}
}

View File

@@ -0,0 +1,5 @@
package me.zacharias.chat.ollama;
public interface OllamaTool {
}

View File

@@ -0,0 +1,15 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
public class OllamaToolError extends OllamaMessage {
String error;
public OllamaToolError(String error) {
super(OllamaMessageRole.TOOL, new JSONObject().put("error", error).toString());
this.error = error;
}
public String getError() {
return error;
}
}

View File

@@ -0,0 +1,21 @@
package me.zacharias.chat.ollama;
import org.json.JSONObject;
public class OllamaToolRespnce extends OllamaMessage {
private final String tool;
private final String response;
public OllamaToolRespnce(String tool, String response) {
super(OllamaMessageRole.TOOL, new JSONObject().put("tool", tool).put("result", response).toString());
this.tool = tool;
this.response = response;
}
public String getTool() {
return tool;
}
public String getResponse() {
return response;
}
}

View File

@@ -0,0 +1,21 @@
package me.zacharias.chat.ollama.exceptions;
import me.zacharias.chat.ollama.OllamaToolRespnce;
public class OllamaToolErrorException extends RuntimeException {
private final String tool;
private final String error;
public OllamaToolErrorException(String tool, String error) {
super(tool + ": " + error);
this.tool = tool;
this.error = error;
}
public String getTool() {
return tool;
}
public String getError() {
return error;
}
}