Compare commits
6
Commits
4cd4367a19
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
209bde75e1
|
||
|
|
5ae5920478
|
||
|
|
b8280e6ec5
|
||
|
|
939fdb2211
|
||
|
|
663ab68172
|
||
|
|
330d7df389
|
Generated
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
<file type="web" url="file://$PROJECT_DIR$" />
|
<file type="web" url="file://$PROJECT_DIR$" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_26" default="true" project-jdk-name="26" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="graalvm-25" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
+3
-2
@@ -5,7 +5,7 @@ plugins {
|
|||||||
id 'io.spring.dependency-management' version '1.1.4'
|
id 'io.spring.dependency-management' version '1.1.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '1.0-SNAPSHOT'
|
version = APIVersion
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":Core")
|
implementation project(":Core")
|
||||||
@@ -14,9 +14,10 @@ dependencies {
|
|||||||
implementation 'org.springframework.boot:spring-boot-starter-web:4.1.0-M4'
|
implementation 'org.springframework.boot:spring-boot-starter-web:4.1.0-M4'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-webflux:4.1.0-M4'
|
implementation 'org.springframework.boot:spring-boot-starter-webflux:4.1.0-M4'
|
||||||
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.0-M1'
|
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:4.1.0-M4'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test:4.1.0-M4'
|
||||||
|
|
||||||
|
//implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||||
|
|
||||||
//runtimeOnly('org.springframework.boot:spring-boot-starter-web')
|
//runtimeOnly('org.springframework.boot:spring-boot-starter-web')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package me.neurodock.api;
|
|||||||
import me.neurodock.api.payload.request.NewQurryResponceHook;
|
import me.neurodock.api.payload.request.NewQurryResponceHook;
|
||||||
import me.neurodock.api.payload.request.NewToolRequest;
|
import me.neurodock.api.payload.request.NewToolRequest;
|
||||||
import me.neurodock.core.Core;
|
import me.neurodock.core.Core;
|
||||||
import me.neurodock.core.GlobalObjects;
|
|
||||||
import me.neurodock.core.PrintMessageHandler;
|
import me.neurodock.core.PrintMessageHandler;
|
||||||
import me.neurodock.ollama.OllamaObject;
|
import me.neurodock.ollama.OllamaObject;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
@@ -41,8 +40,9 @@ public class APIApplication {
|
|||||||
|
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
if(GlobalObjects.getObject("core") instanceof Core coreInstance) {
|
if(false) {
|
||||||
this.core = coreInstance;
|
// TODO: This needs to be properly refactored.
|
||||||
|
//this.core = coreInstance;
|
||||||
} else {
|
} else {
|
||||||
this.core = new Core(new PrintMessageHandler() {
|
this.core = new Core(new PrintMessageHandler() {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+3
-1
@@ -2,11 +2,13 @@ plugins {
|
|||||||
id 'java-library'
|
id 'java-library'
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '1.9.0'
|
version = coreVersion
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(":Plugin-API")
|
implementation project(":Plugin-API")
|
||||||
api "org.json:json:20250107"
|
api "org.json:json:20250107"
|
||||||
|
implementation 'org.graalvm.polyglot:polyglot:25.1.3'
|
||||||
|
implementation 'org.graalvm.polyglot:js:25.1.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
java {
|
||||||
|
|||||||
@@ -19,10 +19,9 @@ import java.time.LocalDateTime;
|
|||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.function.Consumer;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
@@ -68,17 +67,20 @@ public class Core {
|
|||||||
* The URL of the Ollama API.
|
* The URL of the Ollama API.
|
||||||
*/
|
*/
|
||||||
private URL url;
|
private URL url;
|
||||||
|
/**
|
||||||
|
* Used to cancel/stop a responce, only usefull if
|
||||||
|
*/
|
||||||
|
private AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||||
|
/**
|
||||||
|
* The current connection to Ollama
|
||||||
|
*/
|
||||||
|
private HttpURLConnection activeConnection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The PrintMessageHandler to use.
|
* The PrintMessageHandler to use.
|
||||||
*/
|
*/
|
||||||
private final PrintAdvanceMessageHandler printMessageHandler;
|
private final PrintAdvanceMessageHandler printMessageHandler;
|
||||||
|
|
||||||
public static String DATA;
|
|
||||||
public static File DATA_DIR;
|
|
||||||
public static File PLUGIN_DIRECTORY;
|
|
||||||
public static File CACHE_DIRECTORY;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of Core with the provided PrintMessageHandler,
|
* Creates a new instance of Core with the provided PrintMessageHandler,
|
||||||
* defaulting the Ollama backend to {@code localhost}.
|
* defaulting the Ollama backend to {@code localhost}.
|
||||||
@@ -109,13 +111,6 @@ public class Core {
|
|||||||
initShutdownHook();
|
initShutdownHook();
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
|
||||||
// This should not be enforced like this, instead this should read a data field, or wait for an init to be called....
|
|
||||||
// Unsure of how to properly do this at this time, however.
|
|
||||||
// After looking at things, just run {@link Core.setDataDirectory(String)} somewhere else before initilising the Core object and seems to be fine
|
|
||||||
setDataDirectory("AI-Chat", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setLogDirectory(String logDirectory)
|
public static void setLogDirectory(String logDirectory)
|
||||||
{
|
{
|
||||||
logDir = new File(logDirectory);
|
logDir = new File(logDirectory);
|
||||||
@@ -126,55 +121,6 @@ public class Core {
|
|||||||
logFile = new File(logDirectory, "latest.log");
|
logFile = new File(logDirectory, "latest.log");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the data directory in appropriate locations depending on the host OS, falling back to $WORKING_DIR/data
|
|
||||||
* @param dataDirectory the data directory to use
|
|
||||||
*/
|
|
||||||
public static void setDataDirectory(String dataDirectory, boolean fullDirectory) {
|
|
||||||
String data;
|
|
||||||
|
|
||||||
if(System.getenv("AI_CHAT_DEBUG") != null) {
|
|
||||||
data = "./data";
|
|
||||||
}
|
|
||||||
if(fullDirectory) {
|
|
||||||
data = dataDirectory;
|
|
||||||
}
|
|
||||||
else if(System.getProperty("os.name").toLowerCase().contains("windows")) {
|
|
||||||
String localappdata = System.getenv("LOCALAPPDATA");
|
|
||||||
if(localappdata == null) {
|
|
||||||
localappdata = System.getenv("APPDATA");
|
|
||||||
}
|
|
||||||
data = localappdata + "/"+ dataDirectory;
|
|
||||||
}
|
|
||||||
else if (System.getProperty("os.name").toLowerCase().contains("linux")) {
|
|
||||||
data = System.getenv("HOME") + "/.local/share/" + dataDirectory;
|
|
||||||
}
|
|
||||||
else if (System.getProperty("os.name").toLowerCase().contains("mac")) {
|
|
||||||
data = System.getProperty("user.home") + "/Library/Application Support/"+ dataDirectory;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
data = "./data";
|
|
||||||
}
|
|
||||||
|
|
||||||
DATA = data;
|
|
||||||
DATA_DIR = new File(DATA);
|
|
||||||
if(!DATA_DIR.exists()) {
|
|
||||||
DATA_DIR.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
String pluginDir = DATA + "/plugins";
|
|
||||||
PLUGIN_DIRECTORY = new File(pluginDir);
|
|
||||||
|
|
||||||
if(!PLUGIN_DIRECTORY.exists()) {
|
|
||||||
PLUGIN_DIRECTORY.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
CACHE_DIRECTORY = new File(DATA + "/cache");
|
|
||||||
if(!CACHE_DIRECTORY.exists()) {
|
|
||||||
CACHE_DIRECTORY.mkdirs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the base directories required by the application.
|
* Creates the base directories required by the application.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -183,7 +129,8 @@ public class Core {
|
|||||||
*/
|
*/
|
||||||
private void initDirectories() {
|
private void initDirectories() {
|
||||||
ensureDir(logDir.getAbsolutePath());
|
ensureDir(logDir.getAbsolutePath());
|
||||||
ensureDir(DATA_DIR.getAbsolutePath() + "/messages");
|
ensureDir(Options.getInstance().getDataDir() + "/messages");
|
||||||
|
Options.getInstance().initiateDirectories();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -301,7 +248,7 @@ public class Core {
|
|||||||
/**
|
/**
|
||||||
* Persists the current session's messages from the {@link OllamaObject} to two locations:
|
* Persists the current session's messages from the {@link OllamaObject} to two locations:
|
||||||
* a timestamped archive file under {@code ./messages/}, and a rolling {@code messages.json}
|
* a timestamped archive file under {@code ./messages/}, and a rolling {@code messages.json}
|
||||||
* under {@link #DATA_DIR} for resuming the session later.
|
* under {@link Options#getDataDir()} for resuming the session later.
|
||||||
*
|
*
|
||||||
* @see #buildMessagesArray()
|
* @see #buildMessagesArray()
|
||||||
* @see #writeMessagesTo(File, JSONArray)
|
* @see #writeMessagesTo(File, JSONArray)
|
||||||
@@ -310,8 +257,8 @@ public class Core {
|
|||||||
JSONArray messages = buildMessagesArray();
|
JSONArray messages = buildMessagesArray();
|
||||||
|
|
||||||
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd_HH-mm-ss"));
|
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("dd_HH-mm-ss"));
|
||||||
writeMessagesTo(new File(DATA_DIR.getAbsolutePath()+"/messages/" + timestamp + ".json"), messages);
|
writeMessagesTo(new File(Options.getInstance().getDataDir().getAbsolutePath()+"/messages/" + timestamp + ".json"), messages);
|
||||||
writeMessagesTo(new File(DATA_DIR, "messages.json"), messages);
|
writeMessagesTo(new File(Options.getInstance().getDataDir(), "messages.json"), messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,119 +293,13 @@ public class Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is pending a better name, as this was introduced from being a generic block. This was ugly, and ideally things here should be refactored more.
|
|
||||||
*
|
|
||||||
* TODO: Fix a better name and refactor this method.
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true)
|
|
||||||
private void constructCore(){
|
|
||||||
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);
|
|
||||||
|
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
|
||||||
scheduler.shutdownNow();
|
|
||||||
try {
|
|
||||||
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");
|
|
||||||
|
|
||||||
File messagesFile = new File("./messages/"+now.format(formatter)+".json");
|
|
||||||
|
|
||||||
BufferedWriter messagesWriter = new BufferedWriter(new FileWriter(messagesFile));
|
|
||||||
|
|
||||||
JSONArray messages = new JSONArray();
|
|
||||||
|
|
||||||
for(OllamaMessage message : ollamaObject.getMessages()) {
|
|
||||||
messages.put(message.toJSON());
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesWriter.write(messages.toString());
|
|
||||||
messagesWriter.close();
|
|
||||||
|
|
||||||
File f = new File(DATA_DIR,"messages.json");
|
|
||||||
if(f.exists())
|
|
||||||
{
|
|
||||||
f.delete();
|
|
||||||
}
|
|
||||||
f.createNewFile();
|
|
||||||
|
|
||||||
messagesWriter = new BufferedWriter(new FileWriter(f));
|
|
||||||
|
|
||||||
messagesWriter.write(messages.toString());
|
|
||||||
messagesWriter.close();
|
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A check to exit early if Ollama isn't reachable.
|
* A check to exit early if Ollama isn't reachable.
|
||||||
*/
|
*/
|
||||||
private void confirmOllama()
|
private boolean confirmOllama()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
URL url = new URL("http://" + ollamaIP + ":" + ollamaPort + "/api/version");
|
URL url = URI.create("http://" + ollamaIP + ":" + ollamaPort + "/api/version").toURL();
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
connection.setRequestMethod("GET");
|
connection.setRequestMethod("GET");
|
||||||
connection.setRequestProperty("Content-Type", "application/json");
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
@@ -469,14 +310,14 @@ public class Core {
|
|||||||
|
|
||||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
new RuntimeException("Ollama is un-responsive on url: " + url.toString()).printStackTrace();
|
new RuntimeException("Ollama is un-responsive on url: " + url.toString()).printStackTrace();
|
||||||
System.exit(1);
|
return false;
|
||||||
}
|
}
|
||||||
}catch (IOException ex)
|
}catch (IOException ex)
|
||||||
{
|
{
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
System.out.println("Can not reach Ollama!");
|
return false;
|
||||||
System.exit(1);
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -485,25 +326,20 @@ public class Core {
|
|||||||
* @param ollamaObject The OllamaObject to use
|
* @param ollamaObject The OllamaObject to use
|
||||||
*/
|
*/
|
||||||
public void setOllamaObject(OllamaObject ollamaObject) {
|
public void setOllamaObject(OllamaObject ollamaObject) {
|
||||||
if(this.ollamaObject == null) {
|
this.ollamaObject = ollamaObject;
|
||||||
this.ollamaObject = ollamaObject;
|
|
||||||
|
|
||||||
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
|
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
|
||||||
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
|
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
|
||||||
{
|
{
|
||||||
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
|
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addTool(new AddMemoryFunction(), Source.CORE);
|
addTool(new AddMemoryFunction(), Source.CORE);
|
||||||
addTool(new RemoveMemoryFunction(), Source.CORE);
|
addTool(new RemoveMemoryFunction(), Source.CORE);
|
||||||
addTool(new GetMemoryFunction(), Source.CORE);
|
addTool(new GetMemoryFunction(), Source.CORE);
|
||||||
addTool(new GetMemoriesFunction(), Source.CORE);
|
addTool(new GetMemoriesFunction(), Source.CORE);
|
||||||
addTool(new GetMemoryIdentitiesFunction(), Source.CORE);
|
addTool(new GetMemoryIdentitiesFunction(), Source.CORE);
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new IllegalArgumentException("Ollama object is already set");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -512,18 +348,13 @@ public class Core {
|
|||||||
* @param ollamaObject The OllamaObject to use
|
* @param ollamaObject The OllamaObject to use
|
||||||
*/
|
*/
|
||||||
public void setOllamaObjectNoMemory(OllamaObject ollamaObject) {
|
public void setOllamaObjectNoMemory(OllamaObject ollamaObject) {
|
||||||
if(this.ollamaObject == null) {
|
this.ollamaObject = ollamaObject;
|
||||||
this.ollamaObject = ollamaObject;
|
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
|
||||||
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
|
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
|
||||||
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
|
{
|
||||||
{
|
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
|
||||||
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
throw new IllegalArgumentException("Ollama object is already set");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -594,22 +425,77 @@ public class Core {
|
|||||||
}catch (IOException e) {}
|
}catch (IOException e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void cancel()
|
||||||
|
{
|
||||||
|
cancelled.set(true);
|
||||||
|
HttpURLConnection conn = activeConnection;
|
||||||
|
if (conn != null) {
|
||||||
|
conn.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the OllamaObject to Ollama
|
* Queries Ollama and resolves once the full response is available, discarding any
|
||||||
* @return The response from Ollama
|
* intermediate streaming chunks along the way.
|
||||||
|
* <p>
|
||||||
|
* This is a convenience overload of {@link #qurryOllama(Consumer)} with a no-op chunk
|
||||||
|
* listener. Whether the underlying request is actually sent as a streaming or
|
||||||
|
* non-streaming HTTP request is determined entirely by this query's {@link OllamaObject}'s
|
||||||
|
* {@code stream} setting — this method does not itself impose a mode. If the
|
||||||
|
* {@link OllamaObject} is configured to stream, each intermediate chunk is still received
|
||||||
|
* and parsed internally, just not exposed to the caller; use
|
||||||
|
* {@link #qurryOllama(Consumer)} if intermediate chunks are needed.
|
||||||
|
*
|
||||||
|
* @return a future that resolves to the final response object once generation completes
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<JSONObject> qurryOllama()
|
public CompletableFuture<JSONObject> qurryOllama()
|
||||||
{
|
{
|
||||||
|
return qurryOllama(_ -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries Ollama, invoking {@code onChunk} for each JSON object received from the server,
|
||||||
|
* and resolves once the final response is available.
|
||||||
|
* <p>
|
||||||
|
* Whether this is a streaming or non-streaming exchange is determined by this query's
|
||||||
|
* {@link OllamaObject}'s {@code stream} setting, not by which overload was called:
|
||||||
|
* <ul>
|
||||||
|
* <li>If {@code stream} is {@code true}, Ollama emits one JSON object per line as
|
||||||
|
* generation progresses; {@code onChunk} fires once per line, in order, as each
|
||||||
|
* arrives.</li>
|
||||||
|
* <li>If {@code stream} is {@code false}, Ollama emits the entire response as a single
|
||||||
|
* object once generation is complete; {@code onChunk} fires exactly once with that
|
||||||
|
* full object.</li>
|
||||||
|
* </ul>
|
||||||
|
* In both cases, the returned future resolves once, to the final ({@code done: true})
|
||||||
|
* response object — {@code onChunk} is for observing progress, the returned future is for
|
||||||
|
* "the response is complete."
|
||||||
|
* <p>
|
||||||
|
* If {@link #cancel()} is called while this query is in flight, the underlying connection
|
||||||
|
* is closed and the returned future completes exceptionally with a
|
||||||
|
* {@link java.util.concurrent.CancellationException}.
|
||||||
|
*
|
||||||
|
* @param onChunk callback invoked for each response object received from Ollama, in
|
||||||
|
* arrival order; never invoked with {@code null}
|
||||||
|
* @return a future that resolves to the final response object once generation completes
|
||||||
|
*/
|
||||||
|
public CompletableFuture<JSONObject> qurryOllama(Consumer<JSONObject> onChunk) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
HttpURLConnection connection = null;
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
connection = (HttpURLConnection) url.openConnection();
|
||||||
|
activeConnection = connection;
|
||||||
|
|
||||||
|
if (cancelled.get()) {
|
||||||
|
throw new CancellationException("Query cancelled before request was sent");
|
||||||
|
}
|
||||||
|
|
||||||
connection.setRequestMethod("POST");
|
connection.setRequestMethod("POST");
|
||||||
connection.setRequestProperty("Content-Type", "application/json");
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
connection.setConnectTimeout(80 * 1000);
|
connection.setConnectTimeout(80 * 1000);
|
||||||
|
|
||||||
String ollamaObjectString = ollamaObject.toJSON().toString();
|
String ollamaObjectString = ollamaObject.toJSON().toString();
|
||||||
|
|
||||||
ollamaObjectString = ollamaObjectString.replace("\n", "\\n");
|
ollamaObjectString = ollamaObjectString.replace("\n", "\\n");
|
||||||
|
|
||||||
try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
|
try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
|
||||||
@@ -618,43 +504,64 @@ public class Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int responseCode = connection.getResponseCode();
|
int responseCode = connection.getResponseCode();
|
||||||
|
boolean isStreaming = ollamaObject.isStream(); // whatever the real accessor is
|
||||||
|
JSONObject last = null;
|
||||||
|
StringBuilder rawErrorOrDump = new StringBuilder();
|
||||||
|
StringBuilder messageContent = new StringBuilder();
|
||||||
|
|
||||||
// HTTP_OK or 200 response code generally means that the server ran successfully without any errors
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||||
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;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
response.append(line); // Adds every line to response till the end of file.
|
if (cancelled.get()) throw new CancellationException("Query cancelled mid-response");
|
||||||
|
if (line.isBlank()) continue;
|
||||||
|
|
||||||
|
rawErrorOrDump.append(line);
|
||||||
|
|
||||||
|
if (isStreaming) {
|
||||||
|
// Ollama emits one JSON object per line when streaming — parse and
|
||||||
|
// dispatch each one as it arrives.
|
||||||
|
JSONObject chunkObj = new JSONObject(line);
|
||||||
|
last = chunkObj;
|
||||||
|
if(chunkObj.has("message")) {
|
||||||
|
messageContent.append(chunkObj.getJSONObject("message").optString("content", ""));
|
||||||
|
}
|
||||||
|
onChunk.accept(chunkObj);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
last = new JSONObject(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (IOException ex) {
|
||||||
// If the server returns an error, we read the error stream instead
|
if (cancelled.get()) throw new CancellationException("Query cancelled mid-response");
|
||||||
InputStream errorStream = connection.getErrorStream();
|
InputStream errorStream = connection.getErrorStream();
|
||||||
if (errorStream != null) {
|
if (errorStream != null) {
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream))) {
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream))) {
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) rawErrorOrDump.append(line);
|
||||||
response.append(line);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isStreaming && last != null && last.has("message")) {
|
||||||
|
JSONObject finalObj = new JSONObject(last.toString()); // deep-ish copy via re-parse
|
||||||
|
finalObj.getJSONObject("message").put("content", messageContent.toString());
|
||||||
|
last = finalObj;
|
||||||
|
}
|
||||||
|
|
||||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||||
|
return last;
|
||||||
|
|
||||||
connection.disconnect();
|
|
||||||
return new JSONObject(response.toString());
|
|
||||||
} else {
|
} else {
|
||||||
connection.disconnect();
|
printMessageHandler.printErrorMessage(new OllamaMessage(OllamaMessageRole.SYSTEM,
|
||||||
printMessageHandler.printErrorMessage(new OllamaMessage(OllamaMessageRole.SYSTEM,"Error: HTTP Response code - " + responseCode + "\n" + response.toString()));
|
"Error: HTTP Response code - " + responseCode + "\n" + rawErrorOrDump));
|
||||||
throw new RuntimeException("HTTP Response code - " + responseCode);
|
throw new RuntimeException("HTTP Response code - " + responseCode);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
if (cancelled.get()) throw new CancellationException("Query cancelled");
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
if (connection != null) connection.disconnect();
|
||||||
|
activeConnection = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -892,7 +799,7 @@ public class Core {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getDataDictionary() {
|
public File getDataDictionary() {
|
||||||
return DATA_DIR;
|
return Options.getInstance().getDataDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -902,7 +809,7 @@ public class Core {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public File getCacheDirectory() {
|
public File getCacheDirectory() {
|
||||||
return CACHE_DIRECTORY;
|
return Options.getInstance().getCacheDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addPlugin(LoadedPlugin plugin)
|
public void addPlugin(LoadedPlugin plugin)
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package me.neurodock.core;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class GlobalObjects {
|
|
||||||
private static final Map<String, Object> objects = new HashMap<>();
|
|
||||||
|
|
||||||
public static void addObject(String name, Object object) {
|
|
||||||
if (name == null || object == null) {
|
|
||||||
throw new IllegalArgumentException("Name and object cannot be null");
|
|
||||||
}
|
|
||||||
objects.put(name, object);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Object getObject(String name) {
|
|
||||||
if (name == null) {
|
|
||||||
throw new IllegalArgumentException("Name cannot be null");
|
|
||||||
}
|
|
||||||
return objects.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean removeObject(String name) {
|
|
||||||
if (name == null) {
|
|
||||||
throw new IllegalArgumentException("Name cannot be null");
|
|
||||||
}
|
|
||||||
return objects.remove(name) != null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
package me.neurodock.core;
|
package me.neurodock.core;
|
||||||
|
|
||||||
import com.sun.jdi.connect.spi.TransportService;
|
|
||||||
import me.neurodock.ollama.*;
|
import me.neurodock.ollama.*;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -137,7 +134,7 @@ public class LLMSystemPrompt {
|
|||||||
for (Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
|
for (Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
|
||||||
if(tool.getKey() instanceof OllamaFunctionTool funcTool) {
|
if(tool.getKey() instanceof OllamaFunctionTool funcTool) {
|
||||||
String llmName = funcTool.name() + "_" + (funcTool.getSource() != null ? funcTool.getSource() : "");
|
String llmName = funcTool.name() + "_" + (funcTool.getSource() != null ? funcTool.getSource() : "");
|
||||||
String hint = capabilities.getUsageHints().get(tool.getClass());
|
String hint = capabilities.getUsageHints().get(funcTool.getClass());
|
||||||
|
|
||||||
// fall back to description() if no explicit routing hint was set
|
// fall back to description() if no explicit routing hint was set
|
||||||
String line = hint != null ? hint : funcTool.description();
|
String line = hint != null ? hint : funcTool.description();
|
||||||
@@ -448,7 +445,7 @@ public class LLMSystemPrompt {
|
|||||||
* Loads {@link #behavior} from a text file, resolved in order against:
|
* Loads {@link #behavior} from a text file, resolved in order against:
|
||||||
* <ol>
|
* <ol>
|
||||||
* <li>the classpath</li>
|
* <li>the classpath</li>
|
||||||
* <li>{@link Core#DATA_DIR}</li>
|
* <li>{@link Options#getDataDir()}</li>
|
||||||
* <li>the current runtime/working directory</li>
|
* <li>the current runtime/working directory</li>
|
||||||
* <li>an exact file path match</li>
|
* <li>an exact file path match</li>
|
||||||
* </ol>
|
* </ol>
|
||||||
@@ -553,7 +550,7 @@ public class LLMSystemPrompt {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves and reads a behavior file as UTF-8 text, checking the classpath,
|
* Resolves and reads a behavior file as UTF-8 text, checking the classpath,
|
||||||
* {@link Core#DATA_DIR}, the working directory, and an exact path match, in that order.
|
* {@link Options#getDataDir()}, the working directory, and an exact path match, in that order.
|
||||||
*
|
*
|
||||||
* @param path the file to look for
|
* @param path the file to look for
|
||||||
* @return the full file contents, with each line terminated by {@code \n}
|
* @return the full file contents, with each line terminated by {@code \n}
|
||||||
@@ -566,7 +563,7 @@ public class LLMSystemPrompt {
|
|||||||
if (classpathResourceExists("/"+path)) {
|
if (classpathResourceExists("/"+path)) {
|
||||||
in = BehaviorLoader.class.getResourceAsStream("/"+path);
|
in = BehaviorLoader.class.getResourceAsStream("/"+path);
|
||||||
} else {
|
} else {
|
||||||
Path dataDirPath = Path.of(Core.DATA_DIR.getPath(), path);
|
Path dataDirPath = Path.of(Options.getInstance().getDataDir().getPath(), path);
|
||||||
Path relativePath = Path.of(path);
|
Path relativePath = Path.of(path);
|
||||||
if (Files.exists(dataDirPath)) {
|
if (Files.exists(dataDirPath)) {
|
||||||
in = new FileInputStream(dataDirPath.toFile());
|
in = new FileInputStream(dataDirPath.toFile());
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package me.neurodock.core;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class Options {
|
||||||
|
/**
|
||||||
|
* The singleton options object
|
||||||
|
*/
|
||||||
|
private static Options instance = new Options();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the singleton options object for modification to options
|
||||||
|
* @return the current {@link Options} singleton object
|
||||||
|
*/
|
||||||
|
public static Options getInstance()
|
||||||
|
{
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path data;
|
||||||
|
private File dataDir;
|
||||||
|
private File pluginDirectory;
|
||||||
|
private File cacheDirectory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a new singleton options object
|
||||||
|
* @param options the new singleton object
|
||||||
|
*/
|
||||||
|
public static void setInstance(Options options)
|
||||||
|
{
|
||||||
|
instance = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options setDataDir(Path dataDir, boolean fullDirectory)
|
||||||
|
{
|
||||||
|
Path data = Path.of("./data");
|
||||||
|
|
||||||
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
|
|
||||||
|
Path cache;
|
||||||
|
|
||||||
|
if(System.getenv("AI_CHAT_DEBUG") == null) {
|
||||||
|
if (fullDirectory) {
|
||||||
|
data = dataDir;
|
||||||
|
} else {
|
||||||
|
if (os.contains("windows")) {
|
||||||
|
String localappdata = System.getenv("LOCALAPPDATA");
|
||||||
|
if (localappdata == null) {
|
||||||
|
localappdata = System.getenv("APPDATA");
|
||||||
|
}
|
||||||
|
data = Path.of(localappdata, dataDir.toFile().getPath());
|
||||||
|
} else if (os.contains("linux")) {
|
||||||
|
data = Path.of(System.getenv("HOME"), ".local/share", dataDir.toFile().getPath());
|
||||||
|
} else if (os.contains("mac")) {
|
||||||
|
data = Path.of(System.getProperty("user.home"), "Library/Application Support", dataDir.toFile().getPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (os.contains("win")) {
|
||||||
|
String localAppData = System.getenv("LOCALAPPDATA");
|
||||||
|
cache = Path.of(localAppData != null ? localAppData
|
||||||
|
: System.getProperty("user.home") + "\\AppData\\Local");
|
||||||
|
} else if (os.contains("mac")) {
|
||||||
|
cache = Path.of(System.getProperty("user.home"), "Library", "Caches");
|
||||||
|
} else {
|
||||||
|
// Linux / other unix
|
||||||
|
String xdgCache = System.getenv("XDG_CACHE_HOME");
|
||||||
|
cache = Path.of(xdgCache != null && !xdgCache.isBlank()
|
||||||
|
? xdgCache
|
||||||
|
: System.getProperty("user.home") + "/.cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.data = data;
|
||||||
|
|
||||||
|
this.cacheDirectory = new File(cache.toFile(), data.getFileName().toFile().toString());
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Options initiateDirectories()
|
||||||
|
{
|
||||||
|
this.dataDir = this.data.toFile();
|
||||||
|
if(!this.dataDir.exists()) {
|
||||||
|
this.dataDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
String pluginDir = this.data + "/plugins";
|
||||||
|
pluginDirectory = new File(pluginDir);
|
||||||
|
|
||||||
|
if(!pluginDirectory.exists()) {
|
||||||
|
pluginDirectory.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!cacheDirectory.exists()) {
|
||||||
|
cacheDirectory.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getDataDir() {
|
||||||
|
return dataDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getPluginDirectory() {
|
||||||
|
return pluginDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getCacheDirectory() {
|
||||||
|
return cacheDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getFileHandlerDataLocation()
|
||||||
|
{
|
||||||
|
return Path.of(dataDir.toString(), "files");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package me.neurodock.core.files;
|
package me.neurodock.core.files;
|
||||||
|
|
||||||
import me.neurodock.core.Core;
|
import me.neurodock.core.Core;
|
||||||
|
import me.neurodock.core.Options;
|
||||||
import me.neurodock.core.Pair;
|
import me.neurodock.core.Pair;
|
||||||
import me.neurodock.core.files.tools.ReadFileTool;
|
import me.neurodock.core.files.tools.ReadFileTool;
|
||||||
import me.neurodock.core.files.tools.WriteFileTool;
|
import me.neurodock.core.files.tools.WriteFileTool;
|
||||||
import me.neurodock.ollama.OllamaTool;
|
import me.neurodock.ollama.OllamaTool;
|
||||||
import org.intellij.lang.annotations.MagicConstant;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -27,11 +27,12 @@ public class FileHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance as well as setting the {@link #instance} to this new one
|
* Creates a new instance as well as setting the {@link #instance} to this new one
|
||||||
|
* A good start is to use {@link Options#getFileHandlerDataLocation()} as it's located along all other files
|
||||||
* @param baseDirectory the directory to be used as base directory
|
* @param baseDirectory the directory to be used as base directory
|
||||||
*/
|
*/
|
||||||
public FileHandler(@MagicConstant(valuesFromClass = FileHandlerLocation.class) String baseDirectory) {
|
public FileHandler(Path baseDirectory) {
|
||||||
try {
|
try {
|
||||||
root = Path.of(baseDirectory).toAbsolutePath().normalize();
|
root = baseDirectory.toAbsolutePath().normalize();
|
||||||
if (!root.toFile().exists()) {
|
if (!root.toFile().exists()) {
|
||||||
root.toFile().mkdirs();
|
root.toFile().mkdirs();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
package me.neurodock.core.files;
|
|
||||||
|
|
||||||
import me.neurodock.core.Core;
|
|
||||||
|
|
||||||
public class FileHandlerLocation {
|
|
||||||
public static final String DATA_FILES = Core.DATA+"/files";
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package me.neurodock.core.memory;
|
package me.neurodock.core.memory;
|
||||||
|
|
||||||
import me.neurodock.core.Core;
|
import me.neurodock.core.Options;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ public class CoreMemory {
|
|||||||
/**
|
/**
|
||||||
* The singleton instance of CoreMemory.
|
* The singleton instance of CoreMemory.
|
||||||
*/
|
*/
|
||||||
private static final CoreMemory instance = new CoreMemory(Core.DATA + "/CoreMemory.json");
|
private static final CoreMemory instance = new CoreMemory(Options.getInstance().getDataDir() + "/CoreMemory.json");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Memory type identifier for key-value mapped memory storage.
|
* Memory type identifier for key-value mapped memory storage.
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
package me.neurodock.ollama;
|
package me.neurodock.ollama;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.RenderedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a message sent by a Tool, Assistant(Ollama), or User.
|
* Represents a message sent by a Tool, Assistant(Ollama), or User.
|
||||||
*/
|
*/
|
||||||
@@ -14,6 +24,11 @@ public class OllamaMessage {
|
|||||||
* The content of the message.
|
* The content of the message.
|
||||||
*/
|
*/
|
||||||
String content;
|
String content;
|
||||||
|
/**
|
||||||
|
* The image content of a message.
|
||||||
|
* This is a base64 encoded string of either an Image or an Audio file (this is how Ollama dose this, ask them, not me)
|
||||||
|
*/
|
||||||
|
ArrayList<String> images;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of OllamaMessage.
|
* Creates a new instance of OllamaMessage.
|
||||||
@@ -25,6 +40,25 @@ public class OllamaMessage {
|
|||||||
this.content = content;
|
this.content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of OllamaMessage.
|
||||||
|
* @param role The role of the message
|
||||||
|
* @param content The content of the message
|
||||||
|
* @param images The image of the message
|
||||||
|
* @throws IOException if an error occurs during writing or when not able to create the required ImageOutputStream.
|
||||||
|
*/
|
||||||
|
public OllamaMessage(OllamaMessageRole role, String content, @NotNull Image... images) throws IOException
|
||||||
|
{
|
||||||
|
this(role, content);
|
||||||
|
ArrayList<String> base65Images = new ArrayList<>();
|
||||||
|
for(Image i : images) {
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
ImageIO.write((RenderedImage) i, "PNG", stream);
|
||||||
|
base65Images.add(Base64.getEncoder().encodeToString(stream.toByteArray()));
|
||||||
|
}
|
||||||
|
this.images = base65Images;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The role of who sent this "message", represented as {@link OllamaMessageRole}
|
* @return The role of who sent this "message", represented as {@link OllamaMessageRole}
|
||||||
*/
|
*/
|
||||||
@@ -43,6 +77,10 @@ public class OllamaMessage {
|
|||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
json.put("role", role.getRole());
|
json.put("role", role.getRole());
|
||||||
json.put("content", content.replace("\n", "\\n"));
|
json.put("content", content.replace("\n", "\\n"));
|
||||||
|
if(images != null && !images.isEmpty())
|
||||||
|
{
|
||||||
|
json.put("images", new JSONArray(images));
|
||||||
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package me.neurodock.ollama;
|
package me.neurodock.ollama;
|
||||||
|
|
||||||
import me.neurodock.core.Core;
|
import me.neurodock.core.*;
|
||||||
import me.neurodock.core.LLMSystemPrompt;
|
|
||||||
import me.neurodock.core.LaunchOptions;
|
|
||||||
import me.neurodock.core.Pair;
|
|
||||||
import me.neurodock.core.files.FileHandlerLocation;
|
|
||||||
import me.neurodock.core.files.FileHandler;
|
import me.neurodock.core.files.FileHandler;
|
||||||
|
|
||||||
import org.intellij.lang.annotations.MagicConstant;
|
import org.intellij.lang.annotations.MagicConstant;
|
||||||
@@ -15,6 +11,7 @@ import java.io.BufferedReader;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -56,6 +53,14 @@ public class OllamaObject {
|
|||||||
* The keep alive of the Ollama Object.
|
* The keep alive of the Ollama Object.
|
||||||
*/
|
*/
|
||||||
String keep_alive;
|
String keep_alive;
|
||||||
|
/**
|
||||||
|
* The system prompt object used to re-generate the system prompt.
|
||||||
|
*/
|
||||||
|
LLMSystemPrompt systemPrompt;
|
||||||
|
/**
|
||||||
|
* The level of thining the model shuld do
|
||||||
|
*/
|
||||||
|
Thinking thinking;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of OllamaObject.
|
* Creates a new instance of OllamaObject.
|
||||||
@@ -66,10 +71,14 @@ public class OllamaObject {
|
|||||||
* @param options The options of the Ollama Object. see {@link OllamaObject#options}
|
* @param options The options of the Ollama Object. see {@link OllamaObject#options}
|
||||||
* @param stream If the Ollama Object is streamed. see {@link OllamaObject#stream}
|
* @param stream If the Ollama Object is streamed. see {@link OllamaObject#stream}
|
||||||
* @param keep_alive The keep alive of the Ollama Object. see {@link OllamaObject#keep_alive}
|
* @param keep_alive The keep alive of the Ollama Object. see {@link OllamaObject#keep_alive}
|
||||||
|
* @param prompt The system prompt object of the Ollama Object. See {@link OllamaObject#systemPrompt}
|
||||||
*/
|
*/
|
||||||
private OllamaObject(String model, ArrayList<OllamaMessage> messages, ArrayList<Pair<OllamaTool, String>> tools, JSONObject format, Map<String, Object> options, boolean stream, String keep_alive, LLMSystemPrompt prompt) {
|
private OllamaObject(String model, ArrayList<OllamaMessage> messages, ArrayList<Pair<OllamaTool, String>> tools, JSONObject format, Map<String, Object> options, boolean stream, String keep_alive, LLMSystemPrompt prompt, Thinking thinking) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
this.messages = messages;
|
this.messages = messages;
|
||||||
|
this.systemPrompt = prompt;
|
||||||
|
this.thinking = thinking;
|
||||||
|
this.stream = stream;
|
||||||
|
|
||||||
// Reflecting the reflection injection for ALL tools that was added in the OllamaObjectBuilder, etc, etc comment.. its past midnignt and i'm tired.
|
// Reflecting the reflection injection for ALL tools that was added in the OllamaObjectBuilder, etc, etc comment.. its past midnignt and i'm tired.
|
||||||
for(Pair<OllamaTool, String> tool : tools)
|
for(Pair<OllamaTool, String> tool : tools)
|
||||||
@@ -104,7 +113,7 @@ public class OllamaObject {
|
|||||||
LaunchOptions launchOptions = LaunchOptions.getInstance();
|
LaunchOptions launchOptions = LaunchOptions.getInstance();
|
||||||
if(launchOptions.isLoadOld()) {
|
if(launchOptions.isLoadOld()) {
|
||||||
System.out.println("Loading old data...");
|
System.out.println("Loading old data...");
|
||||||
File f = new File(Core.DATA_DIR+"/messages.json");
|
File f = new File(Options.getInstance().getDataDir(), "messages.json");
|
||||||
if(f.exists()) {
|
if(f.exists()) {
|
||||||
try {
|
try {
|
||||||
BufferedReader br = new BufferedReader(new FileReader(f));
|
BufferedReader br = new BufferedReader(new FileReader(f));
|
||||||
@@ -135,34 +144,39 @@ public class OllamaObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(prompt == null) {
|
if(prompt != null) {
|
||||||
// Do nothing
|
prompt.generateCapabilities(this);
|
||||||
}
|
if (!this.messages.isEmpty()) {
|
||||||
else if(!this.messages.isEmpty())
|
OllamaMessage systemPrompt = this.messages.getFirst();
|
||||||
{
|
if (systemPrompt.getRole() != OllamaMessageRole.SYSTEM) {
|
||||||
OllamaMessage systemPrompt = this.messages.getFirst();
|
System.out.println("FIRST MESSAGE ISN'T A SYSTEM PROMPT!. Pushing all messages forward and injecting the system prompt");
|
||||||
if(systemPrompt.getRole() != OllamaMessageRole.SYSTEM)
|
Core.writeLog("FIRST MESSAGE ISN'T A SYSTEM PROMPT!. Pushing all messages forward and injecting the system prompt");
|
||||||
{
|
ArrayList<OllamaMessage> newMessages = new ArrayList<>();
|
||||||
System.out.println("FIRST MESSAGE ISN'T A SYSTEM PROMPT!. Pushing all messages forward and injecting the system prompt");
|
newMessages.add(prompt.generateSystemPrompt());
|
||||||
Core.writeLog("FIRST MESSAGE ISN'T A SYSTEM PROMPT!. Pushing all messages forward and injecting the system prompt");
|
newMessages.addAll(messages);
|
||||||
ArrayList<OllamaMessage> newMessages = new ArrayList<>();
|
this.messages = newMessages;
|
||||||
newMessages.add(prompt.generateSystemPrompt());
|
} else {
|
||||||
newMessages.addAll(messages);
|
System.out.println("Replacing System prompt...");
|
||||||
this.messages = newMessages;
|
Core.writeLog("Replacing System prompt...");
|
||||||
}
|
systemPrompt = prompt.generateSystemPrompt();
|
||||||
else
|
this.messages.set(0, systemPrompt);
|
||||||
{
|
}
|
||||||
System.out.println("Replacing System prompt...");
|
} else {
|
||||||
Core.writeLog("Replacing System prompt...");
|
System.out.println("No old prompts. Inserting system prompt...");
|
||||||
systemPrompt = prompt.generateSystemPrompt();
|
Core.writeLog("No old prompts. Inserting system prompt...");
|
||||||
this.messages.set(0, systemPrompt);
|
this.messages.add(prompt.generateSystemPrompt());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
{
|
|
||||||
System.out.println("No old prompts. Inserting system prompt...");
|
/**
|
||||||
Core.writeLog("No old prompts. Inserting system prompt...");
|
* Re-generates and sets the system promp according to the {@link LLMSystemPrompt} stored as {@link OllamaObject#systemPrompt}
|
||||||
this.messages.add(prompt.generateSystemPrompt());
|
*/
|
||||||
|
public void reGenerateSystemPrompt()
|
||||||
|
{
|
||||||
|
if(systemPrompt != null) {
|
||||||
|
systemPrompt.generateCapabilities(this);
|
||||||
|
setSystemPrompt(systemPrompt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +349,7 @@ public class OllamaObject {
|
|||||||
json.put("options", options);
|
json.put("options", options);
|
||||||
json.put("stream", stream);
|
json.put("stream", stream);
|
||||||
json.put("keep_alive", keep_alive);
|
json.put("keep_alive", keep_alive);
|
||||||
|
thinking.putInto(json, "think");
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +398,10 @@ public class OllamaObject {
|
|||||||
* This represents the System Prompt for the LLM
|
* This represents the System Prompt for the LLM
|
||||||
*/
|
*/
|
||||||
LLMSystemPrompt systemPrompt;
|
LLMSystemPrompt systemPrompt;
|
||||||
|
/**
|
||||||
|
* Weather the model should think or not (requires supportive model)
|
||||||
|
*/
|
||||||
|
Thinking think;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of {@link OllamaObjectBuilder}.
|
* Creates a new instance of {@link OllamaObjectBuilder}.
|
||||||
@@ -423,10 +442,8 @@ public class OllamaObject {
|
|||||||
/**
|
/**
|
||||||
* Sets if the Ollama Object is streamed.
|
* Sets if the Ollama Object is streamed.
|
||||||
* @param stream If the Ollama Object is streamed
|
* @param stream If the Ollama Object is streamed
|
||||||
* @deprecated This should be false due to being broken in the current version of this system
|
|
||||||
* @return The {@link OllamaObjectBuilder}
|
* @return The {@link OllamaObjectBuilder}
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public OllamaObjectBuilder stream(boolean stream) {
|
public OllamaObjectBuilder stream(boolean stream) {
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
return this;
|
return this;
|
||||||
@@ -564,10 +581,10 @@ public class OllamaObject {
|
|||||||
* object construction. Calling this after {@link #build()} is technically possible
|
* object construction. Calling this after {@link #build()} is technically possible
|
||||||
* but not intended.
|
* but not intended.
|
||||||
* </p>
|
* </p>
|
||||||
* @param baseDirectory the base directory for file access, see {@link FileHandlerLocation}
|
* @param baseDirectory the base directory for file access.
|
||||||
* @return The {@link OllamaObjectBuilder}
|
* @return The {@link OllamaObjectBuilder}
|
||||||
*/
|
*/
|
||||||
public OllamaObjectBuilder addFileTools(@MagicConstant(valuesFromClass = FileHandlerLocation.class) String baseDirectory)
|
public OllamaObjectBuilder addFileTools(Path baseDirectory)
|
||||||
{
|
{
|
||||||
new FileHandler(baseDirectory);
|
new FileHandler(baseDirectory);
|
||||||
|
|
||||||
@@ -581,12 +598,133 @@ public class OllamaObject {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the thinking tier that should be requested from the model when this
|
||||||
|
* {@link OllamaObject} is built.
|
||||||
|
* <p>
|
||||||
|
* <b>Not all models support this.</b> Whether {@code think} has any effect depends
|
||||||
|
* entirely on the model being queried — models without native thinking support will
|
||||||
|
* simply ignore it.
|
||||||
|
* <p>
|
||||||
|
* Among models that <i>do</i> support it, the accepted scheme is not consistent:
|
||||||
|
* <ul>
|
||||||
|
* <li>Some models only support a boolean on/off toggle (thinking enabled or disabled)</li>
|
||||||
|
* <li>Others support graded levels, e.g. {@code "low"}, {@code "medium"}, {@code "high"},
|
||||||
|
* or {@code "max"}</li>
|
||||||
|
* </ul>
|
||||||
|
* Callers are responsible for knowing which scheme the target model expects and supplying
|
||||||
|
* a {@link Thinking} value it can actually interpret — an unsupported value is not
|
||||||
|
* guaranteed to fail cleanly and may instead be silently ignored by Ollama.
|
||||||
|
*
|
||||||
|
* @param think the thinking tier to request, or {@code null}/equivalent to leave
|
||||||
|
* thinking behavior at the model's default
|
||||||
|
* @return this builder, for chaining
|
||||||
|
*/
|
||||||
|
public OllamaObjectBuilder setThinkingTier(Thinking think)
|
||||||
|
{
|
||||||
|
this.think = think;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds the {@link OllamaObject}
|
* Builds the {@link OllamaObject}
|
||||||
* @return The {@link OllamaObject}
|
* @return The {@link OllamaObject}
|
||||||
*/
|
*/
|
||||||
public OllamaObject build() {
|
public OllamaObject build() {
|
||||||
return new OllamaObject(model, messages, tools, format, options, stream, keep_alive, systemPrompt);
|
return new OllamaObject(model, messages, tools, format, options, stream, keep_alive, systemPrompt, think);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the "thinking" tier that can be requested from an Ollama model via the
|
||||||
|
* {@code think} request field.
|
||||||
|
* <p>
|
||||||
|
* Support for this varies by model, and models that do support it do not agree on a
|
||||||
|
* single scheme:
|
||||||
|
* <ul>
|
||||||
|
* <li>Some models only understand a boolean toggle — {@link #TRUE} / {@link #FALSE}</li>
|
||||||
|
* <li>Others understand graded levels — {@link #LOW}, {@link #MEDIUM}, {@link #HIGH},
|
||||||
|
* {@link #MAX}</li>
|
||||||
|
* </ul>
|
||||||
|
* All six values live in this single enum for convenience, but mixing schemes on a model
|
||||||
|
* that doesn't support the one you pick is a caller error — e.g. sending {@link #HIGH} to
|
||||||
|
* a model that only understands {@link #TRUE}/{@link #FALSE} is not guaranteed to fail
|
||||||
|
* cleanly and may simply be ignored. Callers must know which scheme their target model
|
||||||
|
* expects before choosing a value here.
|
||||||
|
*/
|
||||||
|
public static enum Thinking {
|
||||||
|
|
||||||
|
/** Thinking enabled, for models using the boolean on/off scheme. */
|
||||||
|
TRUE("true"),
|
||||||
|
|
||||||
|
/** Thinking disabled, for models using the boolean on/off scheme. */
|
||||||
|
FALSE("false"),
|
||||||
|
|
||||||
|
/** Lowest thinking effort, for models using the graded-level scheme. */
|
||||||
|
LOW("low"),
|
||||||
|
|
||||||
|
/** Moderate thinking effort, for models using the graded-level scheme. */
|
||||||
|
MEDIUM("medium"),
|
||||||
|
|
||||||
|
/** High thinking effort, for models using the graded-level scheme. */
|
||||||
|
HIGH("high"),
|
||||||
|
|
||||||
|
/** Maximum thinking effort, for models using the graded-level scheme. */
|
||||||
|
MAX("max");
|
||||||
|
|
||||||
|
/** The raw string value sent to Ollama's {@code think} request field. */
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
private Thinking(String value)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** True if this constant belongs to the boolean on/off scheme, false if it's a graded level. */
|
||||||
|
public boolean isBoolean() {
|
||||||
|
return this == TRUE || this == FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts this thinking tier into the given JSON object under the given key, using
|
||||||
|
* whichever JSON type Ollama expects for this constant — a real boolean for
|
||||||
|
* {@link #TRUE}/{@link #FALSE}, or a string for the graded levels.
|
||||||
|
*/
|
||||||
|
public void putInto(JSONObject json, String key) {
|
||||||
|
if (isBoolean()) {
|
||||||
|
json.put(key, Boolean.parseBoolean(value)); // real JSON boolean, no quotes
|
||||||
|
} else {
|
||||||
|
json.put(key, value); // JSON string, e.g. "high"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the raw string form of this tier, as expected by Ollama's {@code think}
|
||||||
|
* request field.
|
||||||
|
*
|
||||||
|
* @return the raw value, e.g. {@code "true"} or {@code "high"}
|
||||||
|
*/
|
||||||
|
public String getValue()
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a {@link Thinking} constant from its raw string value.
|
||||||
|
* <p>
|
||||||
|
* Matching is case-insensitive; the input is lowercased before comparison.
|
||||||
|
*
|
||||||
|
* @param value the raw value to resolve, e.g. {@code "true"} or {@code "HIGH"}
|
||||||
|
* @return the matching {@link Thinking} constant
|
||||||
|
* @throws IllegalArgumentException if no constant matches the given value
|
||||||
|
*/
|
||||||
|
public static Thinking fromValue(String value) {
|
||||||
|
for(Thinking roleRole : values()) {
|
||||||
|
if(roleRole.value.equals(value.toLowerCase()))
|
||||||
|
return roleRole;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Invalid value: " + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ import me.neurodock.core.files.FileHandlerException;
|
|||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
public class FileTest {
|
public class FileTest {
|
||||||
@Test
|
@Test
|
||||||
void TestError()
|
void TestError()
|
||||||
{
|
{
|
||||||
FileHandler fileHandler = new FileHandler("./test/files");
|
FileHandler fileHandler = new FileHandler(Path.of("./test/files"));
|
||||||
Assertions.assertThrowsExactly(FileHandlerException.class, () -> fileHandler.readFile("../build.gradle"));
|
Assertions.assertThrowsExactly(FileHandlerException.class, () -> fileHandler.readFile("../build.gradle"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import me.neurodock.core.Core;
|
||||||
|
import me.neurodock.core.LaunchOptions;
|
||||||
|
import me.neurodock.core.Options;
|
||||||
|
import me.neurodock.core.PrintMessageHandler;
|
||||||
|
import me.neurodock.ollama.OllamaMessage;
|
||||||
|
import me.neurodock.ollama.OllamaMessageRole;
|
||||||
|
import me.neurodock.ollama.OllamaObject;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class OllamaImages {
|
||||||
|
@Test
|
||||||
|
public void test1()
|
||||||
|
{
|
||||||
|
AtomicReference<Image> i = new AtomicReference<>();
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
File input = new File("../testImage.png");
|
||||||
|
i.set(ImageIO.read(input));
|
||||||
|
});
|
||||||
|
assertNotNull(i.get());
|
||||||
|
|
||||||
|
LaunchOptions.getInstance().setLoadOld(false);
|
||||||
|
Options.getInstance().setDataDir(Path.of("AI-Chat/Test"), false);
|
||||||
|
|
||||||
|
Core core = new Core(new PrintMessageHandler() {
|
||||||
|
@Override
|
||||||
|
public void printMessage(String message) {
|
||||||
|
System.out.println(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean color() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
core.setOllamaObjectNoMemory(OllamaObject.builder()
|
||||||
|
.setModel("qwen3.5:4b")
|
||||||
|
.keep_alive(0)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
core.getOllamaObject().addMessage(new OllamaMessage(OllamaMessageRole.USER, "Describe this image", i.get()));
|
||||||
|
});
|
||||||
|
|
||||||
|
core.qurryOllama().thenAccept(core::handleResponse).join();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
package me.neurodock.display;
|
package me.neurodock.display;
|
||||||
|
|
||||||
import me.neurodock.core.Core;
|
import me.neurodock.core.Core;
|
||||||
|
import me.neurodock.core.Options;
|
||||||
import me.neurodock.core.Pair;
|
import me.neurodock.core.Pair;
|
||||||
import me.neurodock.core.PrintMessageHandler;
|
import me.neurodock.core.PrintMessageHandler;
|
||||||
import me.neurodock.core.files.FileHandlerLocation;
|
|
||||||
import me.neurodock.core.memory.CoreMemory;
|
import me.neurodock.core.memory.CoreMemory;
|
||||||
import me.neurodock.ollama.*;
|
import me.neurodock.ollama.*;
|
||||||
import me.neurodock.ollama.utils.SystemMessage;
|
import me.neurodock.ollama.utils.SystemMessage;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static me.neurodock.core.Core.writeLog;
|
import static me.neurodock.core.Core.writeLog;
|
||||||
@@ -43,15 +44,17 @@ public class Display {
|
|||||||
public Display()
|
public Display()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
Options.getInstance().setDataDir(Path.of("AI-Chat"), false);
|
||||||
|
|
||||||
core.setOllamaObject/*NoMemory*/(OllamaObject.builder()
|
core.setOllamaObject/*NoMemory*/(OllamaObject.builder()
|
||||||
.setModel("llama3.1:8b")
|
.setModel("llama3.1:8b")
|
||||||
.keep_alive(10)
|
.keep_alive(10)
|
||||||
//.stream(false)
|
//.stream(false)
|
||||||
.addFileTools(FileHandlerLocation.DATA_FILES)
|
.addFileTools(Options.getInstance().getFileHandlerDataLocation())
|
||||||
.addMessages(new SystemMessage("You need to emulate feelings and emotions."))
|
.addMessages(new SystemMessage("You need to emulate feelings and emotions."))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
core.enablePlugins(Core.PLUGIN_DIRECTORY);
|
core.enablePlugins(Options.getInstance().getPluginDirectory());
|
||||||
|
|
||||||
core.addTool(new TimeTool(), Core.Source.CTP);
|
core.addTool(new TimeTool(), Core.Source.CTP);
|
||||||
// TODO: Well Docker failes when luanched.... Fuck
|
// TODO: Well Docker failes when luanched.... Fuck
|
||||||
@@ -158,9 +161,9 @@ public class Display {
|
|||||||
break;
|
break;
|
||||||
case "working":
|
case "working":
|
||||||
System.out.println("Working directories:\n" +
|
System.out.println("Working directories:\n" +
|
||||||
" Data: " + Core.DATA_DIR.getAbsolutePath() + "\n" +
|
" Data: " + Options.getInstance().getDataDir().getAbsolutePath() + "\n" +
|
||||||
" DateFiles: " + FileHandlerLocation.DATA_FILES + "\n" +
|
" DateFiles: " + Options.getInstance().getFileHandlerDataLocation() + "\n" +
|
||||||
" Plugins: " + Core.PLUGIN_DIRECTORY.getAbsolutePath());
|
" Plugins: " + Options.getInstance().getPluginDirectory().getAbsolutePath());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
System.out.println("Unknown command: " + message);
|
System.out.println("Unknown command: " + message);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.github.dockerjava.core.DockerClientImpl;
|
|||||||
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
|
||||||
import com.github.dockerjava.transport.DockerHttpClient;
|
import com.github.dockerjava.transport.DockerHttpClient;
|
||||||
import me.neurodock.core.Core;
|
import me.neurodock.core.Core;
|
||||||
|
import me.neurodock.core.Options;
|
||||||
import me.neurodock.core.Pair;
|
import me.neurodock.core.Pair;
|
||||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||||
import me.neurodock.ollama.OllamaFunctionTool;
|
import me.neurodock.ollama.OllamaFunctionTool;
|
||||||
@@ -434,7 +435,7 @@ public class PythonRunner extends OllamaFunctionTool {
|
|||||||
fullCmd.append(arg).append(" ");
|
fullCmd.append(arg).append(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
File program = new File(Core.CACHE_DIRECTORY, "cmd.sh");
|
File program = new File(Options.getInstance().getCacheDirectory(), "cmd.sh");
|
||||||
if(program.exists()){
|
if(program.exists()){
|
||||||
program.delete();
|
program.delete();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import io.github.classgraph.ClassInfo;
|
|||||||
import io.github.classgraph.ClassInfoList;
|
import io.github.classgraph.ClassInfoList;
|
||||||
import io.github.classgraph.ScanResult;
|
import io.github.classgraph.ScanResult;
|
||||||
import me.neurodock.core.Core;
|
import me.neurodock.core.Core;
|
||||||
|
import me.neurodock.core.Options;
|
||||||
import me.neurodock.ollama.OllamaFunctionTool;
|
import me.neurodock.ollama.OllamaFunctionTool;
|
||||||
import me.neurodock.ollama.OllamaFunctionTools;
|
import me.neurodock.ollama.OllamaFunctionTools;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -27,13 +28,13 @@ public class GeniusTools {
|
|||||||
public final String Access_Token;
|
public final String Access_Token;
|
||||||
public final String BaseURL = "https://api.genius.com";
|
public final String BaseURL = "https://api.genius.com";
|
||||||
public final OllamaFunctionTools GeniusTools;
|
public final OllamaFunctionTools GeniusTools;
|
||||||
public final File CacheFile = new File(Core.DATA_DIR + "/genius_cache.json");
|
public final File CacheFile = new File(Options.getInstance().getDataDir(), "genius_cache.json");
|
||||||
public JSONObject CacheData;
|
public JSONObject CacheData;
|
||||||
|
|
||||||
public GeniusTools() {
|
public GeniusTools() {
|
||||||
super();
|
super();
|
||||||
try {
|
try {
|
||||||
JSONObject obj = new JSONObject(Files.readString(Path.of(Core.DATA_DIR + "/geniusapi.json")));
|
JSONObject obj = new JSONObject(Files.readString(Path.of(Options.getInstance().getDataDir().getPath(), "geniusapi.json")));
|
||||||
this.Client_ID = obj.getString("client_id");
|
this.Client_ID = obj.getString("client_id");
|
||||||
this.Client_Secret = obj.getString("client_secret");
|
this.Client_Secret = obj.getString("client_secret");
|
||||||
this.Access_Token = obj.getString("access_token");
|
this.Access_Token = obj.getString("access_token");
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import io.github.classgraph.ClassInfo;
|
|||||||
import io.github.classgraph.ClassInfoList;
|
import io.github.classgraph.ClassInfoList;
|
||||||
import io.github.classgraph.ScanResult;
|
import io.github.classgraph.ScanResult;
|
||||||
import me.neurodock.core.Core;
|
import me.neurodock.core.Core;
|
||||||
|
import me.neurodock.core.Options;
|
||||||
import me.neurodock.ollama.OllamaFunctionTool;
|
import me.neurodock.ollama.OllamaFunctionTool;
|
||||||
import me.neurodock.ollama.OllamaFunctionTools;
|
import me.neurodock.ollama.OllamaFunctionTools;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -30,7 +31,7 @@ public class MALAPITool {
|
|||||||
public MALAPITool() {
|
public MALAPITool() {
|
||||||
super();
|
super();
|
||||||
try {
|
try {
|
||||||
JSONObject obj = new JSONObject(Files.readString(Path.of(Core.DATA_DIR + "/malapi.json")));
|
JSONObject obj = new JSONObject(Files.readString(Path.of(Options.getInstance().getDataDir().getPath(), "malapi.json")));
|
||||||
this.Client_ID = obj.getString("client_id");
|
this.Client_ID = obj.getString("client_id");
|
||||||
this.Client_Secret = obj.getString("client_secret");
|
this.Client_Secret = obj.getString("client_secret");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ plugins {
|
|||||||
id 'java-library'
|
id 'java-library'
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.1.4.1'
|
version = pluginAPIVersion
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ The author **does not endorse or encourage** scraping or any other use that viol
|
|||||||
|
|
||||||
Use this software **at your own risk**. The author disclaims any liability for legal or technical consequences arising from its use.
|
Use this software **at your own risk**. The author disclaims any liability for legal or technical consequences arising from its use.
|
||||||
|
|
||||||
|
## How to report issues?
|
||||||
|
This is a personal, self-hosted Gitea instance, and I haven't set up account
|
||||||
|
registration or figured out permissions for letting new accounts only open
|
||||||
|
issues — and honestly, I'd rather not have random accounts created here anyway.<br>
|
||||||
|
All issues for NeuroDock should be filed on the **[Codeberg mirror](https://codeberg.org/alienfromdia/NeuroDock/issues)**. Thanks for understanding.
|
||||||
|
|
||||||
## API
|
## API
|
||||||
The documentation for the API is available at the gitea wiki under [API docs](https://git.server.4zellen.se/neurodock/NeuroDock/wiki/API-Docs)
|
The documentation for the API is available at the gitea wiki under [API docs](https://git.server.4zellen.se/neurodock/NeuroDock/wiki/API-Docs)
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -1,3 +1,7 @@
|
|||||||
# This is used in sub-project that dosent explicitly require the API sub-project to only include if thay shuld be included project wide.
|
# This is used in sub-project that dosent explicitly require the API sub-project to only include if thay shuld be included project wide.
|
||||||
useAPI = true
|
useAPI = true
|
||||||
javaVersion = 26
|
javaVersion = 25
|
||||||
|
|
||||||
|
coreVersion = 1.10.1
|
||||||
|
pluginAPIVersion = 0.1.4.1
|
||||||
|
APIVersion = 1.0-SNAPSHOT
|
||||||
Reference in New Issue
Block a user