Compare commits

...

17 Commits

Author SHA1 Message Date
943c8470a5 Honestly speaking... No clue what the updates are :/
Some checks failed
build / build (push) Has been cancelled
2026-01-17 14:30:26 +01:00
4262dd68c6 Enhanced tool management in OllamaObject and added Maven publishing configuration.
Some checks failed
build / build (push) Has been cancelled
- 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>
2025-08-18 20:01:42 +02:00
2cf0428d2a Added legal disclaimers about scraping.. mostly to make sure i dont get into too mutch trouble about things
Some checks failed
build / build (push) Has been cancelled
2025-08-05 01:33:48 +02:00
bbf3c645b0 Added README.md to GeniusAPI
Some checks failed
build / build (push) Has been cancelled
2025-08-05 00:51:35 +02:00
4015d37657 Merge remote-tracking branch 'origin/master'
Some checks failed
build / build (push) Has been cancelled
2025-08-05 00:43:19 +02:00
5886d164b4 Core.java
- Added a safe handle for if Core#logWriter is not initialized to prevent craching

GetLyrics.java
- Slowly modifying the http headers to avoid package due to catching this as a Scrapper
- Added so we skip extra calls if the song is cached
2025-08-05 00:38:58 +02:00
ab72e1ff78 Update README.md
All checks were successful
build / build (push) Successful in 3s
2025-07-28 22:15:37 +02:00
db53028d1e Fixed an issue in the config
All checks were successful
build / build (push) Successful in 9s
2025-07-16 01:03:02 +02:00
bf5ae6dd3c And again.... 2025-07-16 00:31:04 +02:00
fbf7c504b1 Testing again... *sigh* 2025-07-16 00:29:46 +02:00
8426bcac37 CI is confusing 2025-07-16 00:28:37 +02:00
5724a2342d Merge remote-tracking branch 'origin/master' 2025-07-16 00:12:55 +02:00
95ac78b2d2 Testing gitea act_runner 2025-07-16 00:11:56 +02:00
70e7e24aaa Update README.md
Fixed misspelling
2025-06-08 22:32:52 +02:00
57fc7f2533 Quick and dirty commit, due to heaste of this needing 2025-06-08 22:01:17 +02:00
840ad468e3 Added Genius API wrapper
for fetching song lyrics from Genius Database

# Disclaimer. This commit contains parts that are for educational purposes only!

FindSong:
- Uses Genius API to find the song queried

GetLyrics:
# Educational purposes only
- Scrapes Genius lyrics page for the Lyrics
2025-06-08 21:54:56 +02:00
796271443c Added Wikipedia API wrapper
GetWikiPegeText:
- Fetches a Wikipedia wiki page

WikipediaTool:
- The base for this module, Initialise a Wiki instance and add the Wikipedia API endpoints
2025-06-08 21:44:04 +02:00
45 changed files with 1354 additions and 109 deletions

View File

@@ -0,0 +1,22 @@
name: build
on:
push:
branches:
- master
jobs:
build:
runs-on: arch
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ job.status }}."

3
.idea/gradle.xml generated
View File

@@ -12,8 +12,9 @@
<option value="$PROJECT_DIR$/API" />
<option value="$PROJECT_DIR$/Core" />
<option value="$PROJECT_DIR$/Display" />
<option value="$PROJECT_DIR$/GeniusAPI" />
<option value="$PROJECT_DIR$/MALAPITool" />
<option value="$PROJECT_DIR$/Plugin" />
<option value="$PROJECT_DIR$/WikipediaTool" />
<option value="$PROJECT_DIR$/launcher" />
</set>
</option>

View File

@@ -0,0 +1,8 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavadocReference" enabled="true" level="ERROR" enabled_by_default="true">
<option name="REPORT_INACCESSIBLE" value="false" />
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@@ -14,7 +14,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.0-M1'
//implementation 'org.springframework.boot:spring-boot-starter-actuator'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

View File

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

View File

@@ -1,8 +1,6 @@
package me.zacharias.chat.core;
import me.zacharias.chat.core.memory.AddMemoryFunction;
import me.zacharias.chat.core.memory.GetMemoryFunction;
import me.zacharias.chat.core.memory.RemoveMemoryFunction;
import me.zacharias.chat.core.memory.*;
import me.zacharias.chat.ollama.*;
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
import me.zacharias.chat.plugin.Plugin;
@@ -14,6 +12,7 @@ import org.json.JSONObject;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.time.LocalDateTime;
@@ -24,6 +23,8 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
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.
*/
@@ -53,7 +54,7 @@ public class Core {
/**
* The IP of the Ollama API.
*/
private String ollamaIP = "localhost";//"192.168.5.184";
private String ollamaIP = "10.0.1.101";//"192.168.5.184";
/**
* The port of the Ollama API.
*/
@@ -72,14 +73,18 @@ public class Core {
*/
private boolean supportColor;
public static final String DATA;
public static final File DATA_DIR;
public static final File PLUGIN_DIRECTORY;
public static String DATA;
public static File DATA_DIR;
public static File PLUGIN_DIRECTORY;
static {
setDataDirectory("AI-Chat");
}
public static void setDataDirectory(String dataDirectory) {
String data;
if(System.getenv("AI-CHAT-DEBUG") != null) {
if(System.getenv("AI_CHAT_DEBUG") != null) {
data = "./data";
}
else if(System.getProperty("os.name").toLowerCase().contains("windows")) {
@@ -87,13 +92,13 @@ public class Core {
if(localappdata == null) {
localappdata = System.getenv("APPDATA");
}
data = localappdata + "/AI-CHAT";
data = localappdata + "/"+ dataDirectory;
}
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")) {
data = System.getProperty("user.home") + "/Library/Application Support/AI-CHAT";
data = System.getProperty("user.home") + "/Library/Application Support/"+ dataDirectory;
}
else {
data = "./data";
@@ -169,8 +174,14 @@ public class Core {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
scheduler.shutdownNow();
try {
logWriter.flush();
logWriter.close();
try {
logWriter.flush();
logWriter.close();
}catch (IOException ignore)
{
// This exception is kinda expected. Since it can often occur that the logWriter is already closed
System.out.println("Failed to flush log file, but that is not a problem.");
}
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd_HH-mm-ss");
@@ -188,7 +199,7 @@ public class Core {
messagesWriter.write(messages.toString());
messagesWriter.close();
File f = new File("./data/messages.json");
File f = new File(DATA_DIR,"messages.json");
if(f.exists())
{
f.delete();
@@ -227,6 +238,8 @@ public class Core {
addTool(new AddMemoryFunction(), Source.CORE);
addTool(new RemoveMemoryFunction(), Source.CORE);
addTool(new GetMemoryFunction(), Source.CORE);
addTool(new GetMemoriesFunction(), Source.CORE);
addTool(new GetMemoryIdentitiesFunction(), Source.CORE);
}
else {
throw new IllegalArgumentException("Ollama object is already set");
@@ -254,7 +267,7 @@ public class Core {
*/
public void addTool(OllamaFunctionTool functionTool, @MagicConstant(valuesFromClass = Source.class) String source) {
funtionTools.add(new Pair<>(functionTool, source));
ollamaObject.addTool(functionTool);
ollamaObject.addTool(functionTool, source);
}
/**
@@ -323,8 +336,10 @@ public class Core {
String ollamaObjectString = ollamaObject.toString();
ollamaObjectString = ollamaObjectString.replace("\n", "\\n");
try(DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
wr.writeBytes(ollamaObjectString);
wr.write(ollamaObjectString.getBytes(StandardCharsets.UTF_8));
wr.flush();
}
@@ -341,6 +356,17 @@ public class Core {
while ((line = reader.readLine()) != null) {
response.append(line); // Adds every line to response till the end of file.
}
}catch (Exception ex)
{
// If the server returns an error, we read the error stream instead
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
} catch (Exception e) {
System.err.println("Error reading error stream: " + e.getMessage());
}
}
if (responseCode == HttpURLConnection.HTTP_OK) {
@@ -382,34 +408,36 @@ public class Core {
if(jsonObject.has("function"))
{
JSONObject function = jsonObject.getJSONObject("function");
List<Pair<OllamaFunctionTool, String>> functions = funtionTools.stream().filter(func -> func.getKey().name().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()) {
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"));
}
else {
OllamaFunctionTool func = functions.getFirst().getKey();
ArrayList<OllamaFunctionArgument> argumentArrayList = new ArrayList<>();
JSONObject arguments = function.getJSONObject("arguments");
for (String key : arguments.keySet()) {
argumentArrayList.add(new OllamaFunctionArgument(key, arguments.get(key)));
}
// TODO: Check so all arguments is the correct type, else error out in a safe mannet.
try {
OllamaToolRespnce function1 = func.function(argumentArrayList.toArray(new OllamaFunctionArgument[0]));
ollamaObject.addMessage(function1);
printMessageHandler.printMessage((supportColor?"\u001b[34m":"")+"Call "+func.name() + (printMessageHandler.color()?"\u001b[0m":""));
printMessageHandler.printMessage((supportColor?"\u001b[34m":"")+"Call "+func.name() + "(" + deconstructOllamaFunctionArguments(argumentArrayList) + ")" + (supportColor?"\u001b[0m":""));
writeLog("Successfully function call " + func.name() + " output: " + function1.getResponse());
} catch (OllamaToolErrorException e) {
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":""));
writeLog(e.getMessage());
printMessageHandler.printMessage((supportColor?"\u001b[31m":"")+"Tried funtion call " + func.name() + "("+deconstructOllamaFunctionArguments(argumentArrayList)+") but failed due to " + e.getError() + (supportColor?"\u001b[0m":""));
writeLog("ERROR: "+e.getMessage());
}
}
}
@@ -427,11 +455,12 @@ public class Core {
* @param responce the Ollama response
*/
private void checkIfResponceMessage(JSONObject responce) {
if(responce.getJSONObject("message").has("content") && !responce.getJSONObject("message").getString("content").isBlank())
String message = responce.getJSONObject("message").getString("content");
if(responce.getJSONObject("message").has("content") && !message.isBlank())
{
printMessageHandler.printMessage((supportColor?"\u001b[32m":"")+responce.getJSONObject("message").getString("content")+(supportColor?"\u001b[0m":""));
writeLog("Response content: "+responce.getJSONObject("message").getString("content"));
ollamaObject.addMessage(new OllamaMessage(OllamaMessageRole.ASSISTANT, responce.getJSONObject("message").getString("content")));
printMessageHandler.printMessage((supportColor?"\u001b[32m":"")+(LaunchOptions.getInstance().isShowFullMessage()? message : message.replaceAll("(?s)<think>.*?</think>", "")) +(supportColor?"\u001b[0m":""));
writeLog("Response content: "+ message);
ollamaObject.addMessage(new OllamaMessage(OllamaMessageRole.ASSISTANT, message));
}
}
@@ -441,6 +470,10 @@ public class Core {
*/
public static void writeLog(String message)
{
if(logWriter == null) {
System.err.println("!! Log writer is not initialized !!");
return;
}
try {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd%EEEE HH:mm:ss'#'SSS");
@@ -468,8 +501,8 @@ public class Core {
{
throw new PluginLoadingException("Plugin does not contain a plugin.json file", file.getName());
}
JSONObject pluginJson = new JSONObject(new String(fs.getPath("/plugin.json").toFile().readAllBytes()));
Plugin plugin = loader.loadPlugin(pluginJson, fs);
//JSONObject pluginJson = new JSONObject(new String(fs.getPath("/plugin.json").toFile().readAllBytes()));
//Plugin plugin = loader.loadPlugin(pluginJson, fs);
} catch (IOException e) {
throw new RuntimeException(e);
}

View File

@@ -5,7 +5,7 @@ package me.zacharias.chat.core;
*/
public class LaunchOptions {
private static LaunchOptions instance = new LaunchOptions();
/**
* Gets the singleton instance of the LaunchOptions class.
* Meant to be used to get or set ant option.
@@ -15,11 +15,14 @@ public class LaunchOptions {
return instance;
}
private LaunchOptions() {}
private boolean loadOld = true;
private boolean autoAccept;
private boolean serverMode;
private boolean serverCredentialsEnabled;
private boolean notDisplay = true;
private boolean showFullMessage = false;
private int port = 39075;
private String redirectOutput;
private String serverCredentials;
@@ -160,4 +163,20 @@ public class LaunchOptions {
public void setNotDisplay(boolean notDisplay) {
this.notDisplay = notDisplay;
}
/**
* Gets if the full message should be shown in the display.
* @return a boolean indicating if the full message should be shown.
*/
public boolean isShowFullMessage() {
return showFullMessage;
}
/**
* Sets if the full message should be shown in the display.
* @param showFullMessage a boolean indicating if the full message should be shown.
*/
public void setShowFullMessage(boolean showFullMessage) {
this.showFullMessage = showFullMessage;
}
}

View File

@@ -18,4 +18,18 @@ public interface PrintMessageHandler {
* @return a boolean indicating if color is supported by the PrintMessageHandler.
*/
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

@@ -4,6 +4,8 @@ import me.zacharias.chat.core.Core;
import org.intellij.lang.annotations.MagicConstant;
import java.io.*;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.util.Arrays;
/**
@@ -18,19 +20,25 @@ 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 directory;
private final File/*System*/ directory;
/**
* Creates a new instance as well as setting the {@link #instance} to this new one
* @param baseDirectory the directory to be used as base directory
*/
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();
directory = new File(baseDirectory);
if(!directory.exists())
directory.mkdirs();
instance = this;
instance = this;
}catch (Exception ex)
{
throw new FileHandlerException("Failed to create FileHandler instance with base directory \"" + baseDirectory + "\"");
}
}
/**

View File

@@ -30,6 +30,7 @@ public class AddMemoryFunction extends OllamaFunctionTool {
public OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("memory", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The memory to remember", true)
.addProperty("identity", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The identity of the memory to remember", true)
.build();
}
@@ -38,8 +39,25 @@ public class AddMemoryFunction extends OllamaFunctionTool {
if (args.length == 0) {
throw new OllamaToolErrorException(name(), "Missing memory argument");
}
String value = (String) args[0].value();
memory.addMemory(value);
return new OllamaToolRespnce(name(), "Added "+value+" to the memory");
String memory = null;
String identity = null;
for(OllamaFunctionArgument arg : args) {
if (arg.argument().equals("memory")) {
memory = (String) arg.value();
} else if (arg.argument().equals("identity")) {
identity = (String) arg.value();
} else {
throw new OllamaToolErrorException(name(), "Unknown argument: " + arg.argument());
}
}
if (memory == null || identity == null) {
throw new OllamaToolErrorException(name(), "Missing memory or identity argument");
}
this.memory.addMemory(identity, memory);
return new OllamaToolRespnce(name(), "Added "+identity+" to the memory");
}
}

View File

@@ -1,5 +1,6 @@
package me.zacharias.chat.core.memory;
import me.zacharias.chat.core.Core;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -14,7 +15,7 @@ public class CoreMemory {
/**
* The singleton instance of CoreMemory.
*/
private static CoreMemory instance = new CoreMemory("./cache/CoreMemory.json");
private static final CoreMemory instance = new CoreMemory(Core.DATA + "/CoreMemory.json");
/**
* Gets the singleton instance of CoreMemory.
@@ -38,16 +39,7 @@ public class CoreMemory {
while ((buffer = br.readLine()) != null) {
data.append(buffer);
}
JSONArray jsonArray = new JSONArray(data.toString());
for(Object obj : jsonArray) {
if(obj instanceof String str) {
memory.add(str);
}
else
{
memory.add(obj.toString());
}
}
memory = new JSONObject(data.toString());
}catch (Exception e) {
e.printStackTrace();
}
@@ -63,18 +55,14 @@ public class CoreMemory {
f.delete();
}
f.createNewFile();
BufferedWriter bw = new BufferedWriter(new FileWriter(f));
JSONArray jsonArray = new JSONArray();
for(String str : memory) {
jsonArray.put(str);
}
bw.write(jsonArray.toString());
bw.write(memory.toString());
bw.close();
}catch (Exception e) {
e.printStackTrace();
}
}
});
@@ -83,7 +71,7 @@ public class CoreMemory {
/**
* The memory.
*/
private ArrayList<String> memory = new ArrayList<>();
private JSONObject memory = new JSONObject();
/**
* The file to store the memory in.
*/
@@ -91,25 +79,50 @@ public class CoreMemory {
/**
* Gets the memory.
* @return The memory
* @return A list of memory identifies/names
*/
public ArrayList<String> getMemory() {
return memory;
public String[] getMemoriesIdentity() {
return memory.keySet().toArray(new String[0]);
}
public String getMemory(String name) {
return memory.optString(name, null);
}
/**
* Sets the memory.
* @param name The name/identity of the memory
* @param memory The memory
*/
public void addMemory(String memory) {
this.memory.add(memory);
public void addMemory(String name, String memory) {
this.memory.put(name, memory);
}
/**
* Removes the memory.
* @param memory The memory to remove
* @param name The memory to remove
*/
public void removeMemory(String memory) {
this.memory.remove(memory);
public void removeMemory(String name) {
this.memory.remove(name);
}
/**
* Gets all memories as a JSON string.
* @return A JSON string of all memories
*/
public String getMemories() {
ArrayList<String> memories = new ArrayList<>();
for (String key : memory.keySet()) {
memories.add(key + ": " + memory.getString(key));
}
return new JSONArray(memories).toString();
}
public ArrayList<String> getMemoriesArray() {
ArrayList<String> memories = new ArrayList<>();
for (String key : memory.keySet()) {
memories.add(key + ": " + memory.getString(key));
}
return memories;
}
}

View File

@@ -0,0 +1,28 @@
package me.zacharias.chat.core.memory;
import me.zacharias.chat.ollama.OllamaFunctionArgument;
import me.zacharias.chat.ollama.OllamaFunctionTool;
import me.zacharias.chat.ollama.OllamaPerameter;
import me.zacharias.chat.ollama.OllamaToolRespnce;
public class GetMemoriesFunction extends OllamaFunctionTool {
@Override
public String name() {
return "get_memories";
}
@Override
public String description() {
return "Retrieves all the memories.";
}
@Override
public OllamaPerameter parameters() {
return null;
}
@Override
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
return new OllamaToolRespnce(name(), CoreMemory.getInstance().getMemories());
}
}

View File

@@ -24,16 +24,18 @@ public class GetMemoryFunction extends OllamaFunctionTool {
@Override
public String description() {
return "Retrieves all the memory's";
return "Retrieves the memory for a given identity.";
}
@Override
public OllamaPerameter parameters() {
return null;
return OllamaPerameter.builder()
.addProperty("identity", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The identity of the memory to retrieve", true)
.build();
}
@Override
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
return new OllamaToolRespnce(name(), new JSONObject().put("memory_items", new JSONArray(memory.getMemory())).toString());
return new OllamaToolRespnce(name(), memory.getMemory((String) (args[0].value())));
}
}

View File

@@ -0,0 +1,31 @@
package me.zacharias.chat.core.memory;
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 org.json.JSONArray;
public class GetMemoryIdentitiesFunction extends OllamaFunctionTool {
CoreMemory memory = CoreMemory.getInstance();
@Override
public String name() {
return "get_memory_identities";
}
@Override
public String description() {
return "Retrieves all the memory identities.";
}
@Override
public OllamaPerameter parameters() {
return null;
}
@Override
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
return new OllamaToolRespnce(this.name(), new JSONArray(memory.getMemoriesIdentity()).toString());
}
}

View File

@@ -29,7 +29,7 @@ public class RemoveMemoryFunction extends OllamaFunctionTool {
@Override
public OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("memory", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The memory to forget", true)
.addProperty("identity", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The identity of the memory to forget", true)
.build();
}

View File

@@ -1,5 +1,7 @@
package me.zacharias.chat.ollama;
import java.util.ArrayList;
/**
* Represents an argument passed to a tool.
*
@@ -37,4 +39,24 @@ public record OllamaFunctionArgument(String argument, Object value) {
public Object 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 {
protected String source = "";
@Override
public String toString() {
JSONObject ret = new JSONObject();
ret.put("tool", "function");
JSONObject function = new JSONObject();
function.put("name", name());
function.put("name", name()+(source != null ? source : ""));
function.put("description", description());
function.put("parameters", (parameters() == null?
new JSONObject() : new JSONObject(parameters().toString())));

View File

@@ -29,7 +29,7 @@ public class OllamaMessage {
public String toString() {
JSONObject json = new JSONObject();
json.put("role", role.getRole());
json.put("content", content);
json.put("content", content.replace("\n", "\\n"));
return json.toString();
}
}

View File

@@ -3,6 +3,7 @@ package me.zacharias.chat.ollama;
import com.sun.source.util.Plugin;
import me.zacharias.chat.core.Core;
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.FileHandler;
@@ -13,6 +14,7 @@ import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
@@ -41,7 +43,7 @@ public class OllamaObject {
/**
* The tools of the Ollama Object.
*/
ArrayList<OllamaTool> tools;
ArrayList<Pair<OllamaTool, String>> tools;
/**
* 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 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.messages = messages;
this.tools = tools;
@@ -77,7 +79,7 @@ public class OllamaObject {
this.options = options;
this.stream = stream;
this.keep_alive = keep_alive;
LaunchOptions launchOptions = new LaunchOptions();
LaunchOptions launchOptions = LaunchOptions.getInstance();
if(launchOptions.isLoadOld()) {
System.out.println("Loading old data...");
File f = new File(Core.DATA_DIR+"/messages.json");
@@ -105,6 +107,10 @@ public class OllamaObject {
e.printStackTrace();
}
}
else
{
System.out.println("No old data found, skipping loading old data.");
}
}
}
@@ -128,7 +134,7 @@ public class OllamaObject {
* Gets the tools
* @return The tools
*/
public ArrayList<OllamaTool> getTools() {
public ArrayList<Pair<OllamaTool, String>> getTools() {
return tools;
}
@@ -136,8 +142,29 @@ public class OllamaObject {
* Adds a tool to the Ollama Object
* @param tool The tool to add
*/
public void addTool(OllamaTool tool) {
tools.add(tool);
public void addTool(OllamaTool tool, @MagicConstant(valuesFromClass = Core.Source.class) String source) {
// 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) {
@@ -189,8 +216,11 @@ public class OllamaObject {
JSONObject json = new JSONObject();
JSONArray tools = new JSONArray();
for (OllamaTool tool : this.tools) {
tools.put(new JSONObject(tool.toString()));
for (Pair<OllamaTool, String> tool : this.tools) {
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();
@@ -207,7 +237,7 @@ public class OllamaObject {
json.put("keep_alive", keep_alive);
return json.toString();
}
/**
* Creates a new instance of OllamaObjectBuilder.
* @return The {@link OllamaObjectBuilder}
@@ -232,7 +262,7 @@ public class OllamaObject {
/**
* The tools of the Ollama Object.
*/
ArrayList<OllamaTool> tools = new ArrayList<>();
ArrayList<Pair<OllamaTool, String>> tools = new ArrayList<>();
/**
* The format of the Ollama Object.
*/
@@ -321,30 +351,72 @@ public class OllamaObject {
/**
* 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
* @return The {@link OllamaObjectBuilder}
*/
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;
}
/**
* 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
* @return The {@link OllamaObjectBuilder}
*/
public OllamaObjectBuilder addTools(ArrayList<? extends OllamaTool> tools) {
this.tools.addAll(tools);
public OllamaObjectBuilder addToolsExternal(ArrayList<? extends 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#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;
}
/**
* 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
* @return The {@link OllamaObjectBuilder}
*/
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));
return this;
}

View File

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

View File

@@ -0,0 +1,15 @@
package me.zacharias.chat.ollama.utils;
import me.zacharias.chat.ollama.OllamaMessage;
import me.zacharias.chat.ollama.OllamaMessageRole;
public class SystemMessage extends OllamaMessage {
/**
* Creates a new instance of OllamaMessage.
*
* @param systemMessage The content of the message
*/
public SystemMessage(String systemMessage) {
super(OllamaMessageRole.SYSTEM, systemMessage);
}
}

View File

@@ -8,7 +8,12 @@ version = '1.0-SNAPSHOT'
dependencies {
implementation project(":Core")
implementation project(":MALAPITool")
implementation project(":GeniusAPI")
implementation project(":API")
implementation project(":WikipediaTool")
implementation("com.github.docker-java:docker-java-core:3.6.0")
implementation("com.github.docker-java:docker-java-transport-httpclient5:3.6.0")
}
test {

View File

@@ -8,6 +8,9 @@ import me.zacharias.chat.core.files.FileHandlerLocation;
import me.zacharias.chat.core.memory.CoreMemory;
import me.zacharias.chat.mal.api.MALAPITool;
import me.zacharias.chat.ollama.*;
import me.zacharias.chat.ollama.utils.SystemMessage;
import me.zacharias.neuro.dock.genius.GeniusTools;
import me.zacharias.neuro.dock.wikipedia.WikipediaTool;
import org.json.JSONObject;
import java.io.*;
@@ -51,13 +54,17 @@ public class Display {
.keep_alive(10)
//.stream(false)
//.addFileTools(FileHandlerLocation.DATA_FILES)
.addMessages(new SystemMessage("You need to emulate feelings and emotions."))
.build());
core.enablePlugins(Core.PLUGIN_DIRECTORY);
core.addTool(new TimeTool(), Core.Source.INTERNAL);
// TODO: Well Docker failes when luanched.... Fuck
//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();
@@ -98,9 +105,13 @@ public class Display {
case "help":
System.out.print("""
Available commands:
/help Prints this help message.
/bye Exits the program.
/write Flushes the current log stream to file.
/help Prints this help message.
/bye Exits the program.
/write Flushes the current log stream to file.
/list Lists all available tools.
/corelist Lists all tools according to the OllamaObject.
/working Prints the current working directories.
/peek Peeks the current memory.
""");
break;
case "bye":
@@ -114,7 +125,7 @@ public class Display {
case "peek":
CoreMemory coreMemory = CoreMemory.getInstance();
StringBuilder buffer = new StringBuilder("[");
ArrayList<String> memory = new ArrayList<>(coreMemory.getMemory());
ArrayList<String> memory = new ArrayList<>(coreMemory.getMemoriesArray());
for(int i = 0; i < memory.size(); i++) {
String mem = memory.get(i);
buffer.append("\"").append(mem).append("\"");
@@ -126,6 +137,37 @@ public class Display {
writeLog("Memory peek: "+buffer.toString());
System.out.println(buffer.toString());
break;
case "list":
writeLog("Tools installed in this instance");
for(Pair<OllamaFunctionTool, String> funtion : core.getFuntionTools()) {
StringBuilder args = new StringBuilder();
OllamaPerameter perameter = funtion.getKey().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.getKey().name() + "(" + args + ") [" + funtion.getValue() + "]");
writeLog("Function: " + funtion.getKey().name() + "(" + args + ") [" + funtion.getValue() + "]");
}
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":
System.out.println("Working directories:\n" +
" Data: " + Core.DATA_DIR.getAbsolutePath() + "\n" +
" DateFiles: " + FileHandlerLocation.DATA_FILES + "\n" +
" Plugins: " + Core.PLUGIN_DIRECTORY.getAbsolutePath());
break;
default:
System.out.println("Unknown command: " + message);
}

View File

@@ -8,19 +8,23 @@ 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.core.DefaultDockerClientConfig;
import com.github.dockerjava.core.DockerClientBuilder;
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
import com.github.dockerjava.transport.DockerHttpClient;
import me.zacharias.chat.core.Core;
import me.zacharias.chat.core.Pair;
import me.zacharias.chat.ollama.*;
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -112,13 +116,48 @@ public class PythonRunner extends OllamaFunctionTool {
e.printStackTrace();
}
DefaultDockerClientConfig.Builder config
DefaultDockerClientConfig config
= DefaultDockerClientConfig.createDefaultConfigBuilder()
.withDockerHost("tcp://localhost:2375")
.withDockerTlsVerify(false);
dockerClient = DockerClientBuilder
.getInstance(config)
.withDockerTlsVerify(false)
.withDockerCertPath("~/.docker")
.build();
DockerHttpClient dockerHttpClient = new ApacheDockerHttpClient.Builder()
.dockerHost(config.getDockerHost())
.maxConnections(10)
.connectionTimeout(Duration.ofSeconds(100))
.responseTimeout(Duration.ofSeconds(100))
.sslConfig(config.getSSLConfig())
.build();
DockerHttpClient.Request ping = DockerHttpClient.Request.builder()
.method(DockerHttpClient.Request.Method.GET)
.path("/_ping")
.build();
try(DockerHttpClient.Response response = dockerHttpClient.execute(ping))
{
if(!(response.getStatusCode() == 200))
{
writeLog("Failed to ping docker");
System.out.println("Failed to ping docker. Docker components is disabled.");
dockerClient = null;
return;
}
if(!(IOUtils.toString(response.getBody(), Charset.defaultCharset()).equals("OK")))
{
writeLog("Failed to ping docker");
System.out.println("Failed to ping docker. Docker components is disabled.");
dockerClient = null;
return;
}
}
catch (Exception e)
{
writeLog("Failed to ping docker");
System.out.println("Failed to ping docker. Docker components is disabled.");
dockerClient = null;
}
}
@Override
@@ -142,6 +181,10 @@ public class PythonRunner extends OllamaFunctionTool {
@Override
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
if(dockerClient == null)
{
return new OllamaToolRespnce(name(), "Docker is disabled");
}
if(args.length == 0)
{
throw new OllamaToolErrorException(name(), "Missing code argument");

View File

@@ -0,0 +1,165 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.ssl;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.function.Factory;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.reactor.ssl.SSLBufferMode;
import org.apache.hc.core5.reactor.ssl.TlsDetails;
import org.apache.hc.core5.ssl.SSLContexts;
/**
* TLS upgrade strategy for non-blocking client connections.
*
* @since 5.0
*/
@Contract(threading = ThreadingBehavior.STATELESS)
public class DefaultClientTlsStrategy extends AbstractClientTlsStrategy {
/**
* @since 5.4
*/
public static DefaultClientTlsStrategy createDefault() {
return new DefaultClientTlsStrategy(
SSLContexts.createDefault(),
HostnameVerificationPolicy.BOTH,
HttpsSupport.getDefaultHostnameVerifier());
}
/**
* @since 5.4
*/
public static DefaultClientTlsStrategy createSystemDefault() {
return new DefaultClientTlsStrategy(
SSLContexts.createSystemDefault(),
HttpsSupport.getSystemProtocols(),
HttpsSupport.getSystemCipherSuits(),
SSLBufferMode.STATIC,
HostnameVerificationPolicy.BOTH,
HttpsSupport.getDefaultHostnameVerifier());
}
/**
* @deprecated Use {@link #createDefault()}.
*/
@Deprecated
public static TlsStrategy getDefault() {
return createDefault();
}
/**
* @deprecated Use {@link #createSystemDefault()}.
*/
@Deprecated
public static TlsStrategy getSystemDefault() {
return createSystemDefault();
}
/**
* @deprecated To be removed.
*/
@Deprecated
private Factory<SSLEngine, TlsDetails> tlsDetailsFactory;
/**
* @deprecated Use {@link DefaultClientTlsStrategy#DefaultClientTlsStrategy(SSLContext, String[], String[], SSLBufferMode, HostnameVerifier)}
*/
@Deprecated
public DefaultClientTlsStrategy(
final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final SSLBufferMode sslBufferManagement,
final HostnameVerifier hostnameVerifier,
final Factory<SSLEngine, TlsDetails> tlsDetailsFactory) {
super(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, HostnameVerificationPolicy.CLIENT, hostnameVerifier);
this.tlsDetailsFactory = tlsDetailsFactory;
}
/**
* @since 5.4
*/
public DefaultClientTlsStrategy(
final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final SSLBufferMode sslBufferManagement,
final HostnameVerificationPolicy hostnameVerificationPolicy,
final HostnameVerifier hostnameVerifier) {
super(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, hostnameVerificationPolicy, hostnameVerifier);
}
public DefaultClientTlsStrategy(
final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final SSLBufferMode sslBufferManagement,
final HostnameVerifier hostnameVerifier) {
this(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, HostnameVerificationPolicy.CLIENT, hostnameVerifier);
}
public DefaultClientTlsStrategy(
final SSLContext sslContext,
final HostnameVerifier hostnameVerifier) {
this(sslContext, null, null, SSLBufferMode.STATIC, hostnameVerifier);
}
/**
* @since 5.4
*/
public DefaultClientTlsStrategy(
final SSLContext sslContext,
final HostnameVerificationPolicy hostnameVerificationPolicy,
final HostnameVerifier hostnameVerifier) {
this(sslContext, null, null, SSLBufferMode.STATIC, hostnameVerificationPolicy, hostnameVerifier);
}
public DefaultClientTlsStrategy(final SSLContext sslContext) {
this(sslContext, HttpsSupport.getDefaultHostnameVerifier());
}
@Override
void applyParameters(final SSLEngine sslEngine, final SSLParameters sslParameters, final String[] appProtocols) {
sslParameters.setApplicationProtocols(appProtocols);
sslEngine.setSSLParameters(sslParameters);
}
@Override
@SuppressWarnings("deprecated")
TlsDetails createTlsDetails(final SSLEngine sslEngine) {
return tlsDetailsFactory != null ? tlsDetailsFactory.create(sslEngine) : null;
}
}

View File

@@ -0,0 +1,26 @@
package org.apache.hc.client5.http.ssl;
/**
* Hostname verification policy.
*
* @see javax.net.ssl.HostnameVerifier
* @see DefaultHostnameVerifier
*
* @since 5.4
*/
public enum HostnameVerificationPolicy {
/**
* Hostname verification is delegated to the JSSE provider, usually executed during the TLS handshake.
*/
BUILTIN,
/**
* Hostname verification is executed by HttpClient post TLS handshake.
*/
CLIENT,
/**
* Hostname verification is executed by the JSSE provider and by HttpClient post TLS handshake.
*/
BOTH
}

View File

@@ -0,0 +1,31 @@
package org.apache.hc.client5.http.ssl;
import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.protocol.HttpContext;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.Socket;
@Contract(threading = ThreadingBehavior.STATELESS)
public interface TlsSocketStrategy {
/**
* Upgrades the given plain socket and executes the TLS handshake over it.
*
* @param socket the existing plain socket
* @param target the name of the target host.
* @param port the port to connect to on the target host.
* @param context the actual HTTP context.
* @param attachment connect request attachment.
* @return socket upgraded to TLS.
*/
SSLSocket upgrade(
Socket socket,
String target,
int port,
Object attachment,
HttpContext context) throws IOException;
}

25
GeniusAPI/README.md Normal file
View File

@@ -0,0 +1,25 @@
# Genius API
## Important notes
### Legal Notice
This tool is provided for educational and experimental purposes only.
Please be aware that the Genius API Terms of Service **prohibit** web scraping of their website. This project contains code that performs scraping to retrieve song lyrics directly from Genius.com, which may violate those terms.
Since Genius does not provide official API endpoints for lyrics due to copyright restrictions, this tool includes scraping functionality as a workaround. However, scraping may result in legal or technical consequences such as IP bans, rate limiting, or other restrictions imposed by Genius.
**Use this tool responsibly and at your own risk.** The author does not encourage or endorse violating any third-party terms of service or applicable laws and disclaims any liability arising from misuse.
Whenever possible, prefer using official API endpoints and respect copyright laws.
## API key required
This tool requires a Genius API key to work since it fetches song metadata from the Genius API.
The API key shuld be stored in `${DATA_DIR}/geniusapi.json` using the following format:
```json
{
"client_id": "<your_client_id>",
"client_secret": "<your_client_secret>"
}
```

24
GeniusAPI/build.gradle Normal file
View File

@@ -0,0 +1,24 @@
plugins {
id 'java'
}
group = 'me.zacharias'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
implementation "org.jsoup:jsoup:1.20.1"
implementation 'io.github.classgraph:classgraph:4.8.158'
implementation project(":Core")
}
test {
useJUnitPlatform()
}

View File

@@ -0,0 +1,11 @@
package me.zacharias.neuro.dock.genius;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface GeniusEndpoint {
}

View File

@@ -0,0 +1,12 @@
package me.zacharias.neuro.dock.genius;
import me.zacharias.chat.ollama.OllamaFunctionTool;
public abstract class GeniusEndpointTool extends OllamaFunctionTool {
protected GeniusTools geniusToolsInstance;
public GeniusEndpointTool(GeniusTools geniusTools) {
super();
this.geniusToolsInstance = geniusTools;
}
}

View File

@@ -0,0 +1,126 @@
package me.zacharias.neuro.dock.genius;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import me.zacharias.chat.core.Core;
import me.zacharias.chat.ollama.OllamaFunctionTool;
import me.zacharias.chat.ollama.OllamaFunctionTools;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
public class GeniusTools {
public final String Client_ID;
public final String Client_Secret;
public final String Access_Token;
public final String BaseURL = "https://api.genius.com";
public final OllamaFunctionTools GeniusTools;
public final File CacheFile = new File(Core.DATA_DIR + "/genius_cache.json");
public JSONObject CacheData;
public GeniusTools() {
super();
try {
JSONObject obj = new JSONObject(Files.readString(Path.of(Core.DATA_DIR + "/geniusapi.json")));
this.Client_ID = obj.getString("client_id");
this.Client_Secret = obj.getString("client_secret");
this.Access_Token = obj.getString("access_token");
} catch (IOException e) {
throw new RuntimeException(e);
}
if(CacheFile.exists()) {
try{
CacheData = new JSONObject(Files.readString(CacheFile.toPath()));
}catch (IOException ex)
{
ex.printStackTrace();
if(CacheData == null)
CacheData = new JSONObject();
CacheFile.delete();
}
}
else {
CacheData = new JSONObject();
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
Files.writeString(CacheFile.toPath(), CacheData.toString());
} catch (IOException e) {
e.printStackTrace();
}
}));
OllamaFunctionTools.OllamaFunctionToolsBuilder builder = new OllamaFunctionTools.OllamaFunctionToolsBuilder();
try(ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("me.zacharias.neuro.dock.genius.endpoints").scan()) {
ClassInfoList endpoints = scanResult.getClassesWithAnnotation(GeniusEndpoint.class.getName());
for (ClassInfo classInfo : endpoints) {
Class<?> clazz = classInfo.loadClass();
if (OllamaFunctionTool.class.isAssignableFrom(clazz)) {
//System.out.println("Found endpoint: " + clazz.getName() + " With constructor: " + clazz.getDeclaredConstructors().f.getName() + " and arguments: " + Arrays.toString(clazz.getDeclaredConstructor().getParameterTypes()));
GeniusEndpointTool tool = (GeniusEndpointTool) clazz.getDeclaredConstructor(GeniusTools.class).newInstance(this);
builder.addTool(tool, Core.Source.INTERNAL);
}
}
} catch (InvocationTargetException | InstantiationException | IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
this.GeniusTools = builder.build();
}
public OllamaFunctionTools getGeniusTools() {
return this.GeniusTools;
}
public JSONObject getGeniusEndpoint(String endpoint, Map<String, String> params) {
try {
URL url = new URL(this.BaseURL + endpoint + "?" + ParameterStringBuilder.getParamsString(params));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + this.Access_Token);
conn.setRequestProperty("Accept", "application/json");
if (conn.getResponseCode() != 200) {
throw new IOException("Failed to connect to Genius API: " + conn.getResponseCode());
}
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
return new JSONObject(response.toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public String hasCache(int song_id) {
if(CacheData.has(Integer.toString(song_id)))
return CacheData.getString(Integer.toString(song_id));
return null;
}
public void cacheLyrics(int song_id, String lyrics) {
CacheData.put(Integer.toString(song_id), lyrics);
}
}

View File

@@ -0,0 +1,27 @@
package me.zacharias.neuro.dock.genius;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
public class ParameterStringBuilder {
public static String getParamsString(Map<String, String> params)
throws UnsupportedEncodingException {
if(params == null)
return "";
StringBuilder result = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
result.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8));
result.append("=");
result.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
result.append("&");
}
String resultString = result.toString();
return !resultString.isEmpty()
? resultString.substring(0, resultString.length() - 1)
: resultString;
}
}

View File

@@ -0,0 +1,68 @@
package me.zacharias.neuro.dock.genius.endpoints;
import me.zacharias.chat.ollama.OllamaFunctionArgument;
import me.zacharias.chat.ollama.OllamaPerameter;
import me.zacharias.chat.ollama.OllamaToolRespnce;
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
import me.zacharias.neuro.dock.genius.GeniusEndpoint;
import me.zacharias.neuro.dock.genius.GeniusEndpointTool;
import me.zacharias.neuro.dock.genius.GeniusTools;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.Map;
import static me.zacharias.chat.ollama.OllamaPerameter.OllamaPerameterBuilder.Type.STRING;
@GeniusEndpoint
public class FindSong extends GeniusEndpointTool {
public FindSong(GeniusTools geniusTools) {
super(geniusTools);
}
@Override
public String name() {
return "findsong";
}
@Override
public String description() {
return "Finds a song by title from the Genius API.";
}
@Override
public OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("title", STRING, "The title, artitst, and song_id of the song to find.", true)
.build();
}
@Override
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
String title = (String) args[0].value();
if (title == null || title.isEmpty()) {
throw new OllamaToolErrorException(this.name(), "Title cannot be null or empty.");
}
JSONObject response = this.geniusToolsInstance.getGeniusEndpoint("/search", Map.of("q", title));
JSONArray responseData = new JSONArray();
for(Object obj : response.getJSONObject("response").getJSONArray("hits")) {
if(obj instanceof JSONObject songResult) {
JSONObject song = songResult.getJSONObject("result");
JSONObject songData = new JSONObject();
songData.put("title", song.getString("title"));
songData.put("artist", song.getString("artist_names"));
songData.put("id", song.getInt("id"));
responseData.put(songData);
}
}
if (responseData.isEmpty()) {
throw new OllamaToolErrorException(this.name(), "No songs found for the given title.");
}
return new OllamaToolRespnce(this.name(), responseData.toString());
}
}

View File

@@ -0,0 +1,127 @@
package me.zacharias.neuro.dock.genius.endpoints;
import me.zacharias.chat.ollama.OllamaFunctionArgument;
import me.zacharias.chat.ollama.OllamaPerameter;
import me.zacharias.chat.ollama.OllamaToolRespnce;
import me.zacharias.chat.ollama.exceptions.OllamaToolErrorException;
import me.zacharias.neuro.dock.genius.GeniusEndpoint;
import me.zacharias.neuro.dock.genius.GeniusEndpointTool;
import me.zacharias.neuro.dock.genius.GeniusTools;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import java.util.stream.Collectors;
import static me.zacharias.chat.core.Core.writeLog;
/**
* This class is an Ollama tool that fetches the lyrics of a song by its ID from the Genius API.
* It extends the GeniusEndpointTool to utilize the GeniusTools instance for API calls and caching.
* <p>
* <h1>Legal Notice</h1>
* <p>
* I (the developer) do NOT own the rights to the lyrics fetched by this tool, nor do I endorse scraping lyrics from Genius.com,
* which may violate their Terms of Service.
* </p>
* <p>
* I disclaim any responsibility or liability for how this tool is used.
* Use this tool at your own risk, and please respect all applicable copyright laws and third-party terms.
* </p>
*/
@GeniusEndpoint
public class GetLyrics extends GeniusEndpointTool {
public GetLyrics(GeniusTools geniusTools) {
super(geniusTools);
}
@Override
public String name() {
return "get_lyrics";
}
@Override
public String description() {
return "Gets the lyrics of a song by its ID from the Genius API.";
}
@Override
public OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("song_id", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The ID of the song to get lyrics for.", true)
.build();
}
@Override
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());
if(lyricsStr != null)
{
return new OllamaToolRespnce(this.name(), lyricsStr.trim());
}
JSONObject obj = geniusToolsInstance.getGeniusEndpoint("/songs/" + args[0].value(), null);
String lyrics_path = obj.getJSONObject("response").getJSONObject("song").getString("url");
try {
// WARNING: This request scrapes the lyrics from the Genius website.
// Which is against their Terms of Service. Use responsibly
Document doc = Jsoup.connect(lyrics_path)
.userAgent("insomnia/11.1.0") // TODO: replace with somthing else then insomnia! since we in no way support what ever Insomnia's User-Agent says we do
.header("host", "genius.com")
.header("accept", "*/*")
.get();
writeLog("Fetching lyrics from: " + lyrics_path);
Elements containers = doc.select("div[data-lyrics-container=true]");
StringBuilder lyrics = new StringBuilder();
for (Element container : containers) {
for(Node n : container.childNodes())
{
if(n instanceof Element e) {
if (e.attribute("data-exclude-from-selection") != null && e.attr("data-exclude-from-selection").equals("true")) {
continue;
}
else if(e.tagName().equalsIgnoreCase("br"))
{
lyrics.append("\n");
}
else {
//System.out.println(container.tagName());
String s = e.text();
lyrics.append(s.trim());
}
}
else if(n instanceof TextNode tn)
{
String s = tn.text();
if (!s.isBlank()) {
lyrics.append(s.trim());
}
}
}
}
geniusToolsInstance.cacheLyrics((int) args[0].value(), lyrics.toString());
return new OllamaToolRespnce(this.name(), lyrics.toString().trim());
}catch (Exception ex)
{
ex.printStackTrace();
throw new OllamaToolErrorException(this.name(), "Failed to fetch lyrics.");
}
}
}

View File

@@ -0,0 +1,60 @@
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;
import org.junit.jupiter.api.Test;
import javax.swing.text.html.HTML;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import static me.zacharias.chat.core.Core.writeLog;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class LyricsFetch {
Pattern pattern = Pattern.compile("(?i)\\[(verse.*)|(chorus.*)|(bridge.*)|(outro.*)|(intro.*)]");
@Test
public void testFetchLyrics() throws Exception {
Document doc = Jsoup.connect("https://genius.com/Neuro-sama-life-lyrics")
.userAgent("Mozilla/5.0")
.get();
Elements containers = doc.select("div[data-lyrics-container=true]");
StringBuilder lyrics = new StringBuilder();
for (Element container : containers) {
for(Node n : container.childNodes())
{
if(n instanceof Element e) {
if (e.attribute("data-exclude-from-selection") != null && e.attr("data-exclude-from-selection").equals("true")) {
continue;
}
else if(e.tagName().equalsIgnoreCase("br"))
{
lyrics.append("\n");
}
else {
System.out.println(container.tagName());
String s = e.text();
lyrics.append(s.trim());
}
}
else if(n instanceof TextNode tn)
{
String s = tn.text();
if (!s.isBlank()) {
lyrics.append(s.trim());
}
}
}
}
System.out.println(lyrics.toString());
assertNotNull(lyrics.toString());
}
}

View File

@@ -8,6 +8,8 @@ import java.util.Map;
public class ParameterStringBuilder {
public static String getParamsString(Map<String, String> params)
throws UnsupportedEncodingException {
if(params == null)
return "";
StringBuilder result = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {

View File

@@ -1,5 +1,6 @@
# Chat thing
(better name pending probably)
# NeuroDock
Note:
This project is not in colabiration with Neuro-Sama, Vedal987, Alexcra. This is an independent project and name, aswell as use case similarities are cucidental.
## What is it?
Well it's basically a font end for Ollama like [Open-WebUI](https://openwebui.com/), but meant to run a local model and open up a somewhat easy to use API system for tools.
@@ -7,7 +8,15 @@ Well it's basically a font end for Ollama like [Open-WebUI](https://openwebui.co
## Use cases?
While the primary goal is providing a somewhat more modular frontend for Ollama, you can also use this system to integrate AI into your own application, For example AI could act as a player in a game or interact with external system with the use of tools via the API
simple examples are in the Display module where i gave it the ability to access python through docker and get the current date and time both with the [OllamaFunctionTool](https://server.4zellen.se:3000/Zacharias/chat_thing/src/branch/master/Core/src/main/java/me/zacharias/chat/ollama/OllamaFuntionTool.java) thru my Ollama framework
simple examples are in the Display module where i gave it the ability to access python through docker and get the current date and time both with the [OllamaFunctionTool](https://server.4zellen.se:3000/Zacharias/chat_thing/src/branch/master/Core/src/main/java/me/zacharias/chat/ollama/OllamaFunctionTool.java) thru my Ollama framework
## Legal Notice Regarding Scraping
A submodule of this project includes functionality that scrapes song lyrics directly from Genius.com, which violates Genius API's Terms of Service.
The author **does not endorse or encourage** scraping or any other use that violates third-party terms or copyright laws.
Use this software **at your own risk**. The author disclaims any liability for legal or technical consequences arising from its use.
## API
The documentation for the API is available at the gitea wiki under [API docs](https://server.4zellen.se:3000/Chat_things/NeuroDock/wiki/API-Docs)

View File

@@ -0,0 +1,23 @@
plugins {
id 'java'
}
group = 'me.zacharias'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
implementation("io.github.fastily:jwiki:1.11.0")
implementation project(":Core")
}
test {
useJUnitPlatform()
}

View File

@@ -0,0 +1,33 @@
package me.zacharias.neuro.dock.wikipedia;
import io.github.fastily.jwiki.core.Wiki;
import me.zacharias.chat.ollama.OllamaFunctionArgument;
import me.zacharias.chat.ollama.OllamaFunctionTool;
import me.zacharias.chat.ollama.OllamaPerameter;
import me.zacharias.chat.ollama.OllamaToolRespnce;
public class GetWikiPageText extends OllamaFunctionTool {
Wiki wiki = WikipediaTool.wiki;
@Override
public String name() {
return "get_wiki_page_text";
}
@Override
public String description() {
return "Gets the text of a Wikipedia page by its title.";
}
@Override
public OllamaPerameter parameters() {
return OllamaPerameter.builder()
.addProperty("title", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The title of the Wikipedia page to retrieve text from.", true)
.build();
}
@Override
public OllamaToolRespnce function(OllamaFunctionArgument... args) {
return new OllamaToolRespnce(name(), wiki.getPageText((String) args[0].value()));
}
}

View File

@@ -0,0 +1,23 @@
package me.zacharias.neuro.dock.wikipedia;
import io.github.fastily.jwiki.core.Wiki;
import me.zacharias.chat.core.Core;
import me.zacharias.chat.ollama.OllamaFunctionTools;
public class WikipediaTool {
private OllamaFunctionTools wikipediaToolsInstance;
public static Wiki wiki = new Wiki.Builder()
//.withDomain("en.wikipedia.org")
.build();
public WikipediaTool() {
this.wikipediaToolsInstance = OllamaFunctionTools.builder()
.addTool(new GetWikiPageText(), Core.Source.INTERNAL)
.build();
}
public OllamaFunctionTools getWikipediaToolsInstance() {
return wikipediaToolsInstance;
}
}

View File

@@ -7,6 +7,9 @@ group = 'me.zacharias'
version = '1.0-SNAPSHOT'
allprojects {
apply plugin: 'maven-publish'
repositories {
mavenCentral()
}
@@ -19,7 +22,6 @@ subprojects {
dependencies {
implementation("org.json:json:20250107")
implementation("com.github.docker-java:docker-java:3.4.1")
implementation("org.jetbrains:annotations:23.1.0")
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
@@ -43,4 +45,32 @@ subprojects {
archives sourcesJar
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

@@ -47,7 +47,7 @@ public class Launcher {
}
}
case "--api" -> {
System.out.println("API available at https://server.4zellen.se:3000/Zacharias/chat_thing/wiki/API-Docs");
System.out.println("API available at https://git.server.4zellen.se/Zacharias/chat_thing/wiki/API-Docs");
return;
}
case "--help", "-h" -> {
@@ -59,6 +59,7 @@ public class Launcher {
-o --output Redirects the API Server output to another file
-y Auto accepts to prompts, used for a seamless run. Not recomended when running as Display
-d --dontLoadOld Don't load old messages
-f --full-message Shows the models <think>...</think> block in the output
--api Provides API docs
""");
return;
@@ -71,6 +72,10 @@ public class Launcher {
{
options.setLoadOld(false);
}
case "-f", "--full-message" ->
{
options.setShowFullMessage(true);
}
default -> {
System.out.println("Unknown option: " + arg+"\nUse --help for help");

View File

@@ -2,4 +2,14 @@ rootProject.name = 'AI-test'
include 'API', 'Core', 'Display', 'launcher'
include 'MALAPITool'
include 'MALAPITool'
/**
* LEGAL WARNING:
* This module contains code that scrapes lyrics from Genius.com, which is against Genius API's Terms of Service.
*
* If you wish to avoid any potential legal issues, comment out or remove the line below and ensure no modules depend on this.
* The core NeuroDock system will still function without this module.
*/
include 'GeniusAPI'
include 'WikipediaTool'