forked from neurodock/NeuroDock
Whonestly, i have forgoten what this contains, sooo ye :)
Removed Ollama integration swaped it for llama.cpp integration and made backend integration more generic/abstract.. soo ollama will return.. maybe.. probobly some mopdules(GeniusAPI) dosent work due to not being ported yet.... and maybe will never be
This commit is contained in:
@@ -9,6 +9,7 @@ dependencies {
|
||||
api "org.json:json:20250107"
|
||||
implementation 'org.graalvm.polyglot:polyglot:25.1.3'
|
||||
implementation 'org.graalvm.polyglot:js:25.1.3'
|
||||
implementation 'me.xdrop:fuzzywuzzy:1.4.0'
|
||||
}
|
||||
|
||||
java {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package me.neurodock.backend.open.ai;
|
||||
|
||||
import me.neurodock.llm.Message;
|
||||
import org.json.JSONArray;
|
||||
|
||||
public class OpenAIAssistentMessage extends Message {
|
||||
private Reason endReason;
|
||||
|
||||
public OpenAIAssistentMessage(Role role, Object content, Reason endReason) {
|
||||
super(role, content);
|
||||
this.endReason = endReason;
|
||||
}
|
||||
|
||||
public OpenAIAssistentMessage(Role role, Object content, JSONArray toolCalls, Reason endReason) {
|
||||
super(role, content, toolCalls);
|
||||
this.endReason = endReason;
|
||||
}
|
||||
|
||||
public OpenAIAssistentMessage(Role role, String toolID, Object content, Reason endReason) {
|
||||
super(role, toolID, content);
|
||||
this.endReason = endReason;
|
||||
}
|
||||
|
||||
public Reason getEndReason() {
|
||||
return endReason;
|
||||
}
|
||||
|
||||
enum Reason {
|
||||
STOP,
|
||||
LENGTH,
|
||||
TOOL_CALLS,
|
||||
CONTENT_FILTER,
|
||||
FUNCTION_CALL,
|
||||
/**
|
||||
* Still streaming, or null
|
||||
*/
|
||||
NULL;
|
||||
|
||||
public static Reason fromString(String reason) {
|
||||
if(reason == null) {
|
||||
return NULL;
|
||||
}
|
||||
else if(reason.equalsIgnoreCase("STOP")) {
|
||||
return STOP;
|
||||
}
|
||||
else if(reason.equalsIgnoreCase("LENGTH")) {
|
||||
return LENGTH;
|
||||
}
|
||||
else if(reason.equalsIgnoreCase("TOOL_CALLS")) {
|
||||
return TOOL_CALLS;
|
||||
}
|
||||
else if(reason.equalsIgnoreCase("CONTENT_FILTER")) {
|
||||
return CONTENT_FILTER;
|
||||
}
|
||||
else if(reason.equalsIgnoreCase("FUNCTION_CALL")) {
|
||||
return FUNCTION_CALL;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,629 @@
|
||||
package me.neurodock.backend.open.ai;
|
||||
|
||||
import me.neurodock.llm.*;
|
||||
import me.neurodock.llm.exceptions.ModelNotFoundException;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.Tool;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.serializer.ToolSerializer;
|
||||
import me.neurodock.llm.tools.serializer.ToolToJSON;
|
||||
import me.xdrop.fuzzywuzzy.FuzzySearch;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class OpenAIModel implements StreamingModel {
|
||||
|
||||
/** Minimum fuzzy-match ratio (0-100) required for a model name to be considered a match. */
|
||||
private static final int MIN_MODEL_MATCH_RATIO = 40;
|
||||
private static final ToolSerializer DEFAULT_SERIALIZER = new ToolSerializer();
|
||||
|
||||
private URL backendURL;
|
||||
private String model;
|
||||
private HttpURLConnection activeConnection;
|
||||
private AtomicBoolean cancelled = new AtomicBoolean(false);
|
||||
|
||||
static {
|
||||
DEFAULT_SERIALIZER.addSerializer(FunctionTool.class, (ToolToJSON<FunctionTool>) tool -> {
|
||||
JSONObject result = new JSONObject();
|
||||
result.put("type", "function");
|
||||
JSONObject function = new JSONObject();
|
||||
|
||||
ToolParameters parms = tool.parameters();
|
||||
|
||||
JSONObject parameters = new JSONObject();
|
||||
|
||||
parameters.put("type", "object");
|
||||
parameters.put("required", parms.getRequired());
|
||||
|
||||
JSONObject properties = new JSONObject();
|
||||
|
||||
parms.getProperties().forEach((str,prop) -> {
|
||||
JSONObject obj = new JSONObject();
|
||||
obj.put("description", prop.description());
|
||||
obj.put("type", prop.type().name().toLowerCase(Locale.ROOT));
|
||||
properties.put(str, obj);
|
||||
});
|
||||
|
||||
parameters.put("properties", properties);
|
||||
|
||||
function.put("parameters", parameters);
|
||||
function.put("name", tool.name());
|
||||
function.put("description", tool.description());
|
||||
|
||||
result.put("function", function);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code me.neurodock.backend.open.ai.OpenAIModel} bound to a specific backend and model.
|
||||
* <p>
|
||||
* This performs a blocking request against the backend to fetch the list of
|
||||
* available models, then fuzzy-matches {@code modelName} against the returned
|
||||
* model IDs, selecting the closest match above a minimum confidence threshold.
|
||||
*
|
||||
* @param backendURI the base URL of the OpenAI-compatible backend
|
||||
* (e.g. {@code http://localhost:11434})
|
||||
* @param modelName the model name, or partial name, to fuzzy-match against
|
||||
* the backend's available models
|
||||
*
|
||||
* @apiNote This {@code me.neurodock.backend.open.ai.OpenAIModel} does NOT support OpenAI's ChatGPT or
|
||||
* OpenAI's hosted API — it sends no API key. Pointing {@code backendURI} at
|
||||
* {@code api.openai.com} will not be rejected here; the request will instead
|
||||
* fail server-side (typically with a 401 Unauthorized), which {@link #queryJSON}
|
||||
* surfaces as a {@code null} response, causing this constructor to throw
|
||||
* {@link ModelNotFoundException} — even though no model is actually missing.
|
||||
* This class is intended for locally hosted, non-keyed OpenAI-compatible
|
||||
* APIs only.
|
||||
*
|
||||
* @throws ModelNotFoundException if no models could be retrieved from the
|
||||
* backend (e.g. the server is unreachable, misconfigured, or — see
|
||||
* {@code @apiNote} — rejects the request due to a missing API key),
|
||||
* or if none of the returned models sufficiently match {@code modelName}
|
||||
* @throws RuntimeException if {@code backendURI} is malformed
|
||||
*/
|
||||
public OpenAIModel(@NotNull String backendURI, @NotNull String modelName) throws ModelNotFoundException {
|
||||
try {
|
||||
modelName = Objects.requireNonNull(modelName, "modelName cannot be null");
|
||||
this.backendURL = new URI(Objects.requireNonNull(backendURI, "backendURI cannot be null")).toURL();
|
||||
|
||||
JSONObject models = queryJSON(null, "v1/models", "GET");
|
||||
if(models == null || models.isEmpty()) {
|
||||
throw new ModelNotFoundException("No models found");
|
||||
}
|
||||
|
||||
int highestMatch = 0;
|
||||
String highestMatchString = "";
|
||||
|
||||
for(Object obj : models.optJSONArray("data", new JSONArray()))
|
||||
{
|
||||
if(!(obj instanceof JSONObject)) continue;
|
||||
JSONObject model = (JSONObject)obj;
|
||||
|
||||
int matchRatio = FuzzySearch.partialRatio(modelName, model.getString("id"));
|
||||
|
||||
if(matchRatio > highestMatch) {
|
||||
highestMatch = matchRatio;
|
||||
highestMatchString = model.getString("id");
|
||||
}
|
||||
|
||||
// 100% match... might as well return?
|
||||
if(matchRatio == 100) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Too low of a match, failing
|
||||
if(highestMatch < MIN_MODEL_MATCH_RATIO) {
|
||||
throw new ModelNotFoundException(modelName, backendURI);
|
||||
}
|
||||
|
||||
this.model = highestMatchString;
|
||||
|
||||
|
||||
// Creating a shutdown hook to unload the model after the program shutdowns, this is to converse RAM!
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(this::unload));
|
||||
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads the current model.<br>
|
||||
* And ignoring any errors
|
||||
*/
|
||||
public void unload()
|
||||
{
|
||||
try {
|
||||
queryJSON(new JSONObject().put("model", model), "/models/unload", "POST");
|
||||
}
|
||||
catch (Exception _)
|
||||
{
|
||||
// We catch the exception, but since expected exception here is SOLY a 400 Bad Request for when a model was already unloaded, we throw it out.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the conversation history in {@code obj} to the backend and requests
|
||||
* the next assistant response.
|
||||
* <p>
|
||||
* This performs the request asynchronously; the returned future completes
|
||||
* once the backend has responded (or completes exceptionally if the request
|
||||
* fails at the transport level — see {@link #queryJSON}).
|
||||
*
|
||||
* @param obj the chat context, containing the full conversation history
|
||||
* to send to the model
|
||||
* @param tools the tools available to the model for this request (currently
|
||||
* unused — tool support is not yet implemented)
|
||||
* @return a future resolving to the assistant's reply as a {@link Message}.
|
||||
* If the backend returns no choices, or the response is otherwise
|
||||
* malformed/missing, resolves to a {@link Message} with empty content
|
||||
* rather than throwing.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Message> qurryModel(ChatObject obj, List<Tool> tools) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("model", model);
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
for (Message msg : obj.getConversation()) {
|
||||
JSONObject message = new JSONObject();
|
||||
String role = msg.getRole().name().toLowerCase(Locale.ROOT);
|
||||
message.put("role", role);
|
||||
message.put("content", msg.getContent());
|
||||
if (msg.getRole() == Message.Role.TOOL) {
|
||||
message.put("tool_call_id", msg.getToolID());
|
||||
}
|
||||
messages.put(message);
|
||||
}
|
||||
request.put("messages", messages);
|
||||
|
||||
JSONArray toolArray = new JSONArray();
|
||||
|
||||
if(tools == null || tools.isEmpty()) {
|
||||
for(Tool tool : tools) {
|
||||
toolArray.put(DEFAULT_SERIALIZER.serialize(tool));
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject response = queryJSON(request, "v1/chat/completions", "POST");
|
||||
if(response == null) {
|
||||
response = new JSONObject();
|
||||
}
|
||||
|
||||
JSONObject message = response
|
||||
.optJSONArray("choices", new JSONArray())
|
||||
.optJSONObject(0, new JSONObject())
|
||||
.optJSONObject("message", new JSONObject());
|
||||
|
||||
String content = message.optString("content", "");
|
||||
String roleStr = message.optString("role", "assistant");
|
||||
|
||||
OpenAIAssistentMessage.Reason endReason = OpenAIAssistentMessage.Reason.fromString(response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).optString("endReason", null));
|
||||
|
||||
Message msg;
|
||||
|
||||
if(response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).has("tool_calls")) {
|
||||
msg = new OpenAIAssistentMessage(
|
||||
Message.Role.valueOf(roleStr.toUpperCase(Locale.ROOT)),
|
||||
(Object) content,
|
||||
response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).getJSONArray("tool_calls"),
|
||||
endReason
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = new OpenAIAssistentMessage(Message.Role.valueOf(roleStr.toUpperCase(Locale.ROOT)), content, endReason);
|
||||
}
|
||||
|
||||
return msg;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Message> singleFire(Message msg) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("model", model);
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
JSONObject message = new JSONObject();
|
||||
String role = msg.getRole().name().toLowerCase(Locale.ROOT);
|
||||
message.put("role", role);
|
||||
message.put("content", msg.getContent());
|
||||
if (msg.getRole() == Message.Role.TOOL) {
|
||||
message.put("tool_call_id", msg.getToolID());
|
||||
}
|
||||
messages.put(message);
|
||||
request.put("messages", messages);
|
||||
|
||||
JSONArray toolArray = new JSONArray();
|
||||
|
||||
JSONObject response = queryJSON(request, "v1/chat/completions", "POST");
|
||||
if(response == null) {
|
||||
response = new JSONObject();
|
||||
}
|
||||
|
||||
JSONObject resMessage = response
|
||||
.optJSONArray("choices", new JSONArray())
|
||||
.optJSONObject(0, new JSONObject())
|
||||
.optJSONObject("message", new JSONObject());
|
||||
|
||||
String content = resMessage.optString("content", "");
|
||||
String roleStr = resMessage.optString("role", "assistant");
|
||||
|
||||
OpenAIAssistentMessage.Reason endReason = OpenAIAssistentMessage.Reason.fromString(response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).optString("endReason", null));
|
||||
|
||||
return new OpenAIAssistentMessage(Message.Role.valueOf(roleStr.toUpperCase(Locale.ROOT)), content, endReason);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Message> singleFire(Message msg, List<Tool> tools) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("model", model);
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
JSONObject message = new JSONObject();
|
||||
String role = msg.getRole().name().toLowerCase(Locale.ROOT);
|
||||
message.put("role", role);
|
||||
message.put("content", msg.getContent());
|
||||
if (msg.getRole() == Message.Role.TOOL) {
|
||||
message.put("tool_call_id", msg.getToolID());
|
||||
}
|
||||
messages.put(message);
|
||||
request.put("messages", messages);
|
||||
|
||||
JSONArray toolArray = new JSONArray();
|
||||
|
||||
if(tools == null || tools.isEmpty()) {
|
||||
for(Tool tool : tools) {
|
||||
toolArray.put(DEFAULT_SERIALIZER.serialize(tool));
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject response = queryJSON(request, "v1/chat/completions", "POST");
|
||||
if(response == null) {
|
||||
response = new JSONObject();
|
||||
}
|
||||
|
||||
JSONObject resMessage = response
|
||||
.optJSONArray("choices", new JSONArray())
|
||||
.optJSONObject(0, new JSONObject())
|
||||
.optJSONObject("message", new JSONObject());
|
||||
|
||||
String content = resMessage.optString("content", "");
|
||||
String roleStr = resMessage.optString("role", "assistant");
|
||||
|
||||
OpenAIAssistentMessage.Reason endReason = OpenAIAssistentMessage.Reason.fromString(response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).optString("endReason", null));
|
||||
|
||||
return new OpenAIAssistentMessage(Message.Role.valueOf(roleStr.toUpperCase(Locale.ROOT)), content, endReason);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ToolSerializer getToolSerializer() {
|
||||
return DEFAULT_SERIALIZER;
|
||||
}
|
||||
|
||||
public static ToolSerializer getDefaultToolSerializer() {
|
||||
return DEFAULT_SERIALIZER;
|
||||
}
|
||||
|
||||
public void cancel()
|
||||
{
|
||||
cancelled.set(true);
|
||||
HttpURLConnection conn = activeConnection;
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject queryJSON(JSONObject payload, String endpoint, String method) {
|
||||
return Objects.requireNonNull(queryJSON(payload, endpoint, method, null)).join();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a blocking HTTP request to the given endpoint on the configured backend
|
||||
* and parses the response body as JSON.
|
||||
*
|
||||
* @param payload the JSON request body to send, or {@code null} to send no body
|
||||
* @param endpoint the endpoint to request, resolved against {@link #backendURL}
|
||||
* (e.g. {@code "v1/models"})
|
||||
* @param method the HTTP method to use (e.g. {@code "GET"}, {@code "POST"})
|
||||
* @return the parsed {@link JSONObject} response body if the request succeeds
|
||||
* with a 200 status, or {@code null} if a non-200 status is received
|
||||
*
|
||||
* @throws RuntimeException if an I/O error occurs while communicating with
|
||||
* the backend, or if {@code endpoint} cannot be resolved into a valid URL
|
||||
*/
|
||||
@Nullable
|
||||
public CompletableFuture<JSONObject> queryJSON(JSONObject payload, String endpoint, String method,
|
||||
Consumer<JSONObject> onChunk) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
URL url = backendURL.toURI().resolve(endpoint).toURL();
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
activeConnection = connection;
|
||||
|
||||
if (cancelled.get()) {
|
||||
throw new CancellationException("Query cancelled before request was sent");
|
||||
}
|
||||
|
||||
boolean isStreaming = onChunk != null;
|
||||
|
||||
connection.setRequestMethod(method);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
if (isStreaming) {
|
||||
connection.setRequestProperty("Accept", "text/event-stream");
|
||||
}
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(80 * 1000);
|
||||
|
||||
if (payload != null) {
|
||||
if (isStreaming) {
|
||||
payload.put("stream", true);
|
||||
}
|
||||
|
||||
String payloadString = payload.toString().replace("\n", "\\n");
|
||||
try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
|
||||
wr.write(payloadString.getBytes(StandardCharsets.UTF_8));
|
||||
wr.flush();
|
||||
}
|
||||
}
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
StringBuilder rawErrorOrDump = new StringBuilder();
|
||||
JSONObject result;
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
if (isStreaming) {
|
||||
result = readStream(reader, onChunk);
|
||||
} else {
|
||||
// Non-streaming: OpenAI-compatible endpoints return one JSON object body
|
||||
StringBuilder messageContent = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (cancelled.get()) throw new CancellationException("Query cancelled mid-response");
|
||||
messageContent.append(line);
|
||||
}
|
||||
result = new JSONObject(messageContent.toString());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if (cancelled.get()) throw new CancellationException("Query cancelled mid-response");
|
||||
InputStream errorStream = connection.getErrorStream();
|
||||
if (errorStream != null) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) rawErrorOrDump.append(line);
|
||||
}
|
||||
}
|
||||
result = null;
|
||||
}
|
||||
|
||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||
return result;
|
||||
} else {
|
||||
throw new RuntimeException("HTTP Response code - " + responseCode);
|
||||
}
|
||||
} catch (IOException | URISyntaxException e) {
|
||||
if (cancelled.get()) throw new CancellationException("Query cancelled");
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (connection != null) connection.disconnect();
|
||||
activeConnection = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumes an OpenAI-style SSE stream, dispatching each parsed chunk to
|
||||
* {@code onChunk} and accumulating the deltas into a single final
|
||||
* {@link JSONObject} shaped like a non-streaming chat completion response
|
||||
* ({@code choices[0].message.content} holding the full assembled text).
|
||||
*/
|
||||
private JSONObject readStream(BufferedReader reader, Consumer<JSONObject> onChunk) throws IOException {
|
||||
StringBuilder messageContent = new StringBuilder();
|
||||
JSONObject last = null;
|
||||
String role = "assistant";
|
||||
String line;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (cancelled.get()) throw new CancellationException("Query cancelled mid-response");
|
||||
if (line.isBlank() || !line.startsWith("data:")) continue;
|
||||
|
||||
String data = line.substring(5).trim();
|
||||
if (data.equals("[DONE]")) break;
|
||||
|
||||
JSONObject chunk = new JSONObject(data);
|
||||
onChunk.accept(chunk);
|
||||
last = chunk;
|
||||
|
||||
JSONObject choice = chunk.getJSONArray("choices").getJSONObject(0);
|
||||
JSONObject delta = choice.getJSONObject("delta");
|
||||
if (delta.has("role") && delta.get("role") != null)
|
||||
{
|
||||
role = delta.optString("role");
|
||||
}
|
||||
if (delta.has("content"))
|
||||
{
|
||||
messageContent.append(delta.optString("content", ""));
|
||||
}
|
||||
}
|
||||
|
||||
if (last == null) return null;
|
||||
|
||||
// Re-shape the final chunk into a single OpenAI-style response object,
|
||||
// so callers get the same shape whether they streamed or not.
|
||||
JSONObject finalObj = new JSONObject(last.toString());
|
||||
JSONObject message = new JSONObject();
|
||||
message.put("role", role);
|
||||
message.put("content", messageContent.toString());
|
||||
finalObj.getJSONArray("choices").getJSONObject(0).put("message", message);
|
||||
|
||||
return finalObj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Message> qurryModel(ChatObject obj, List<Tool> tools, Consumer<JSONObject> chunkConsumer) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("model", model);
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
for (Message msg : obj.getConversation()) {
|
||||
JSONObject message = new JSONObject();
|
||||
String role = msg.getRole().name().toLowerCase(Locale.ROOT);
|
||||
message.put("role", role);
|
||||
message.put("content", msg.getContent());
|
||||
if (msg.getRole() == Message.Role.TOOL) {
|
||||
message.put("tool_call_id", msg.getToolID());
|
||||
}
|
||||
messages.put(message);
|
||||
}
|
||||
request.put("messages", messages);
|
||||
|
||||
JSONArray toolArray = new JSONArray();
|
||||
|
||||
if(tools == null || tools.isEmpty()) {
|
||||
for(Tool tool : tools) {
|
||||
toolArray.put(DEFAULT_SERIALIZER.serialize(tool));
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject response = queryJSON(request, "v1/chat/completions", "POST", chunkConsumer).join();
|
||||
if(response == null) {
|
||||
response = new JSONObject();
|
||||
}
|
||||
|
||||
JSONObject message = response
|
||||
.optJSONArray("choices", new JSONArray())
|
||||
.optJSONObject(0, new JSONObject())
|
||||
.optJSONObject("message", new JSONObject());
|
||||
|
||||
String content = message.optString("content", "");
|
||||
String roleStr = message.optString("role", "assistant");
|
||||
|
||||
OpenAIAssistentMessage.Reason endReason = OpenAIAssistentMessage.Reason.fromString(response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).optString("endReason", null));
|
||||
|
||||
Message msg;
|
||||
|
||||
if(response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).has("tool_calls")) {
|
||||
msg = new OpenAIAssistentMessage(
|
||||
Message.Role.valueOf(roleStr.toUpperCase(Locale.ROOT)),
|
||||
(Object) content,
|
||||
response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).getJSONArray("tool_calls"),
|
||||
endReason
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = new OpenAIAssistentMessage(Message.Role.valueOf(roleStr.toUpperCase(Locale.ROOT)), content, endReason);
|
||||
}
|
||||
|
||||
return msg;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Message> singleFire(Message msg, Consumer<JSONObject> chunkConsumer) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("model", model);
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
JSONObject message = new JSONObject();
|
||||
String role = msg.getRole().name().toLowerCase(Locale.ROOT);
|
||||
message.put("role", role);
|
||||
message.put("content", msg.getContent());
|
||||
if (msg.getRole() == Message.Role.TOOL) {
|
||||
message.put("tool_call_id", msg.getToolID());
|
||||
}
|
||||
messages.put(message);
|
||||
request.put("messages", messages);
|
||||
|
||||
JSONArray toolArray = new JSONArray();
|
||||
|
||||
JSONObject response = queryJSON(request, "v1/chat/completions", "POST", chunkConsumer).join();
|
||||
if(response == null) {
|
||||
response = new JSONObject();
|
||||
}
|
||||
|
||||
JSONObject resMessage = response
|
||||
.optJSONArray("choices", new JSONArray())
|
||||
.optJSONObject(0, new JSONObject())
|
||||
.optJSONObject("message", new JSONObject());
|
||||
|
||||
String content = resMessage.optString("content", "");
|
||||
String roleStr = resMessage.optString("role", "assistant");
|
||||
|
||||
OpenAIAssistentMessage.Reason endReason = OpenAIAssistentMessage.Reason.fromString(response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).optString("endReason", null));
|
||||
|
||||
return new OpenAIAssistentMessage(Message.Role.valueOf(roleStr.toUpperCase(Locale.ROOT)), content, endReason);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Message> singleFire(Message msg, List<Tool> tools, Consumer<JSONObject> chunkConsumer) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
JSONObject request = new JSONObject();
|
||||
request.put("model", model);
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
JSONObject message = new JSONObject();
|
||||
String role = msg.getRole().name().toLowerCase(Locale.ROOT);
|
||||
message.put("role", role);
|
||||
message.put("content", msg.getContent());
|
||||
if (msg.getRole() == Message.Role.TOOL) {
|
||||
message.put("tool_call_id", msg.getToolID());
|
||||
}
|
||||
messages.put(message);
|
||||
request.put("messages", messages);
|
||||
|
||||
JSONArray toolArray = new JSONArray();
|
||||
|
||||
if(tools == null || tools.isEmpty()) {
|
||||
for(Tool tool : tools) {
|
||||
toolArray.put(DEFAULT_SERIALIZER.serialize(tool));
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject response = queryJSON(request, "v1/chat/completions", "POST", chunkConsumer).join();
|
||||
if(response == null) {
|
||||
response = new JSONObject();
|
||||
}
|
||||
|
||||
JSONObject resMessage = response
|
||||
.optJSONArray("choices", new JSONArray())
|
||||
.optJSONObject(0, new JSONObject())
|
||||
.optJSONObject("message", new JSONObject());
|
||||
|
||||
String content = resMessage.optString("content", "");
|
||||
String roleStr = resMessage.optString("role", "assistant");
|
||||
|
||||
OpenAIAssistentMessage.Reason endReason = OpenAIAssistentMessage.Reason.fromString(response.optJSONArray("choices", new JSONArray()).optJSONObject(0, new JSONObject()).optString("endReason", null));
|
||||
|
||||
return new OpenAIAssistentMessage(Message.Role.valueOf(roleStr.toUpperCase(Locale.ROOT)), content, endReason);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,36 @@
|
||||
package me.neurodock.core;
|
||||
|
||||
import me.neurodock.core.memory.*;
|
||||
import me.neurodock.ollama.*;
|
||||
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.neurodock.llm.ChatObject;
|
||||
import me.neurodock.llm.Message;
|
||||
import me.neurodock.llm.Model;
|
||||
import me.neurodock.llm.StreamingModel;
|
||||
import me.neurodock.llm.exceptions.ToolException;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.Tool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import me.neurodock.plugin.Data;
|
||||
import me.neurodock.plugin.LoadedPlugin;
|
||||
import me.neurodock.plugin.loader.Loader;
|
||||
import me.neurodock.plugin.exceptions.PluginLoadingException;
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.naming.OperationNotSupportedException;
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import static me.neurodock.ollama.OllamaFunctionArgument.deconstructOllamaFunctionArguments;
|
||||
import static me.neurodock.plugin.tool.Tool.RPCP_SOURCE;
|
||||
|
||||
/**
|
||||
* The Main class for the System, responsible for managing the OllamaObject, tools, and the Ollama API.
|
||||
*/
|
||||
@@ -49,32 +52,18 @@ public class Core {
|
||||
/**
|
||||
* The OllamaObject to use.
|
||||
*/
|
||||
private OllamaObject ollamaObject;
|
||||
private Model model;
|
||||
/**
|
||||
* The list of tools to use.
|
||||
*/
|
||||
private ArrayList<Pair<OllamaFunctionTool, String>> funtionTools = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* The IP of the Ollama API.
|
||||
*/
|
||||
private String ollamaIP;
|
||||
/**
|
||||
* The port of the Ollama API.
|
||||
*/
|
||||
private int ollamaPort = 11434;
|
||||
/**
|
||||
* The URL of the Ollama API.
|
||||
*/
|
||||
private URL url;
|
||||
private final ArrayList<Tool> tools = new ArrayList<>();
|
||||
|
||||
private ChatObject chatObject = new ChatObject();
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -87,25 +76,11 @@ public class Core {
|
||||
*
|
||||
* @param printMessageHandler The PrintMessageHandler to use as the default output
|
||||
*/
|
||||
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler) {
|
||||
this(printMessageHandler, "localhost");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of Core with the provided PrintMessageHandler
|
||||
* and a specific Ollama backend address.
|
||||
*
|
||||
* @param printMessageHandler The PrintMessageHandler to use as the default output
|
||||
* @param ollamaIP The IP or hostname of the Ollama backend
|
||||
*/
|
||||
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler, @NotNull String ollamaIP)
|
||||
public Core(@NotNull PrintAdvanceMessageHandler printMessageHandler)
|
||||
{
|
||||
this.printMessageHandler = printMessageHandler;
|
||||
this.ollamaIP = ollamaIP;
|
||||
|
||||
initDirectories();
|
||||
initOllamaUrl();
|
||||
confirmOllama();
|
||||
initLogWriter();
|
||||
initScheduler();
|
||||
initShutdownHook();
|
||||
@@ -143,19 +118,6 @@ public class Core {
|
||||
if (!dir.exists()) dir.mkdir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the Ollama API {@link #url} from {@link #ollamaIP} and {@link #ollamaPort}.
|
||||
*
|
||||
* @throws RuntimeException If the resulting URL is malformed or the URI syntax is invalid
|
||||
*/
|
||||
private void initOllamaUrl() {
|
||||
try {
|
||||
url = new URI("http://" + ollamaIP + ":" + ollamaPort + "/api/chat").toURL();
|
||||
} catch (MalformedURLException | URISyntaxException e) {
|
||||
throw new RuntimeException("Failed to construct Ollama URL", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the {@link #logWriter}, rotating any pre-existing log file beforehand.
|
||||
*
|
||||
@@ -246,7 +208,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 Model} to two locations:
|
||||
* a timestamped archive file under {@code ./messages/}, and a rolling {@code messages.json}
|
||||
* under {@link Options#getDataDir()} for resuming the session later.
|
||||
*
|
||||
@@ -262,18 +224,29 @@ public class Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all messages from the {@link OllamaObject} and serializes them into a {@link JSONArray}.
|
||||
* Collects all messages from the {@link Model} and serializes them into a {@link JSONArray}.
|
||||
*
|
||||
* @return A {@link JSONArray} containing all current messages
|
||||
*/
|
||||
private JSONArray buildMessagesArray() {
|
||||
JSONArray messages = new JSONArray();
|
||||
for (OllamaMessage message : ollamaObject.getMessages()) {
|
||||
messages.put(message.toJSON());
|
||||
for (Message msg : this.chatObject.getConversation()) {
|
||||
JSONObject message = new JSONObject();
|
||||
String role = msg.getRole().name().toLowerCase(Locale.ROOT);
|
||||
message.put("role", role);
|
||||
message.put("content", msg.getContent());
|
||||
if (msg.getRole() == Message.Role.TOOL) {
|
||||
message.put("tool_call_id", msg.getToolID());
|
||||
}
|
||||
messages.put(message);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
public ChatObject getChatObject() {
|
||||
return chatObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@link JSONArray} of messages to the given file, overwriting it if it already exists.
|
||||
*
|
||||
@@ -294,106 +267,69 @@ public class Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* A check to exit early if Ollama isn't reachable.
|
||||
* Sets the {@link #model} object to the provided argument,
|
||||
* Also adds the memory base system. See {@link Core#setModelNoMemory(Model)} if you don't want to add memory functions
|
||||
* @param model The Model to use
|
||||
*/
|
||||
private boolean confirmOllama()
|
||||
{
|
||||
try {
|
||||
URL url = URI.create("http://" + ollamaIP + ":" + ollamaPort + "/api/version").toURL();
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(3 * 1000);
|
||||
public void setModel(Model model) {
|
||||
this.model = model;
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
|
||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||
new RuntimeException("Ollama is un-responsive on url: " + url.toString()).printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}catch (IOException ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
addTool(new AddMemoryFunction());
|
||||
addTool(new RemoveMemoryFunction());
|
||||
addTool(new GetMemoryFunction());
|
||||
addTool(new GetMemoriesFunction());
|
||||
addTool(new GetMemoryIdentitiesFunction());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #ollamaObject} object to the provided argument,
|
||||
* Also adds the memory base system. See {@link Core#setOllamaObjectNoMemory} if you don't want to add memory functions
|
||||
* @param ollamaObject The OllamaObject to use
|
||||
* Sets the {@link #model} object to the provided argument,
|
||||
* Does not add the base system for memory. see {@link #setModel(Model)} if you want to add memory function
|
||||
* @param model The Model to use
|
||||
*/
|
||||
public void setOllamaObject(OllamaObject ollamaObject) {
|
||||
this.ollamaObject = ollamaObject;
|
||||
|
||||
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
|
||||
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
|
||||
{
|
||||
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link #ollamaObject} object to the provided argument,
|
||||
* Dose not add the base system for memory. see {@link #setOllamaObject} if you want to add memory function
|
||||
* @param ollamaObject The OllamaObject to use
|
||||
*/
|
||||
public void setOllamaObjectNoMemory(OllamaObject ollamaObject) {
|
||||
this.ollamaObject = ollamaObject;
|
||||
for(Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
|
||||
if(tool.getKey() instanceof OllamaFunctionTool functionTool)
|
||||
{
|
||||
funtionTools.add(new Pair<>(functionTool, tool.getValue()));
|
||||
}
|
||||
}
|
||||
public void setModelNoMemory(Model model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new tool to the System
|
||||
* @param functionTool The tool to add
|
||||
* @param source The source of the tool
|
||||
*/
|
||||
public void addTool(OllamaFunctionTool functionTool, @MagicConstant(valuesFromClass = Source.class) String source) {
|
||||
funtionTools.add(new Pair<>(functionTool, source));
|
||||
ollamaObject.addTool(functionTool, source);
|
||||
public void addTool(Tool functionTool) {
|
||||
tools.add(functionTool);
|
||||
}
|
||||
|
||||
public void addMessage(Message message) {
|
||||
chatObject.addMessage(message);
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Adds a list of tools to the System
|
||||
* @param tools The tools to add
|
||||
*/
|
||||
@SuppressWarnings("MagicConstant")
|
||||
public void addTools(OllamaFunctionTools tools)
|
||||
/*@SuppressWarnings("MagicConstant")
|
||||
public void addTools(Tools tools)
|
||||
{
|
||||
for(Pair<OllamaFunctionTool, String> tool : tools)
|
||||
for(Pair<Tool, String> tool : tools)
|
||||
{
|
||||
addTool(tool.getKey(), tool.getValue());
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Gets the list of tools added to the System
|
||||
* @return The list of tools added to the System compressed as Pairs of the tool and the source
|
||||
*/
|
||||
public ArrayList<Pair<OllamaFunctionTool, String>> getFuntionTools() {
|
||||
return funtionTools;
|
||||
public ArrayList<Tool> getTools() {
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Ollama Object
|
||||
* @return The Ollama Object
|
||||
*/
|
||||
public OllamaObject getOllamaObject() {
|
||||
return ollamaObject;
|
||||
public Model getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -401,14 +337,10 @@ public class Core {
|
||||
* @param name The tool to remove
|
||||
*/
|
||||
public void removeTool(String name) {
|
||||
Pair<OllamaFunctionTool, String> funtionTool = funtionTools.stream().filter(tool -> tool.getKey().name().equalsIgnoreCase(name)).findFirst().orElse(null);
|
||||
funtionTools.stream()
|
||||
.filter(tool -> tool.getKey().name().equalsIgnoreCase(name))
|
||||
tools.stream()
|
||||
.filter(tool -> tool.name().equalsIgnoreCase(name))
|
||||
.findFirst()
|
||||
.ifPresentOrElse(tool -> {
|
||||
funtionTools.remove(tool);
|
||||
ollamaObject.removeTool(tool.getKey());
|
||||
}, () -> {
|
||||
.ifPresentOrElse(tools::remove, () -> {
|
||||
|
||||
new IllegalArgumentException("Function tool with name '"+name+"' does not exist")
|
||||
.printStackTrace();
|
||||
@@ -425,182 +357,51 @@ public class Core {
|
||||
}catch (IOException e) {}
|
||||
}
|
||||
|
||||
public void cancel()
|
||||
public CompletableFuture<Message> queryModel()
|
||||
{
|
||||
cancelled.set(true);
|
||||
HttpURLConnection conn = activeConnection;
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
return model.qurryModel(chatObject, tools);
|
||||
}
|
||||
|
||||
public CompletableFuture<Message> queryModel(Consumer<JSONObject> consumer) throws OperationNotSupportedException {
|
||||
if(model instanceof StreamingModel streamingModel) {
|
||||
return streamingModel.qurryModel(chatObject, tools, consumer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries Ollama and resolves once the full response is available, discarding any
|
||||
* 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()
|
||||
{
|
||||
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(() -> {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
activeConnection = connection;
|
||||
|
||||
if (cancelled.get()) {
|
||||
throw new CancellationException("Query cancelled before request was sent");
|
||||
}
|
||||
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
connection.setConnectTimeout(80 * 1000);
|
||||
|
||||
String ollamaObjectString = ollamaObject.toJSON().toString();
|
||||
ollamaObjectString = ollamaObjectString.replace("\n", "\\n");
|
||||
|
||||
try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
|
||||
wr.write(ollamaObjectString.getBytes(StandardCharsets.UTF_8));
|
||||
wr.flush();
|
||||
}
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
boolean isStreaming = ollamaObject.isStream(); // whatever the real accessor is
|
||||
JSONObject last = null;
|
||||
StringBuilder rawErrorOrDump = new StringBuilder();
|
||||
StringBuilder messageContent = new StringBuilder();
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
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 (IOException ex) {
|
||||
if (cancelled.get()) throw new CancellationException("Query cancelled mid-response");
|
||||
InputStream errorStream = connection.getErrorStream();
|
||||
if (errorStream != null) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) rawErrorOrDump.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) {
|
||||
return last;
|
||||
} else {
|
||||
printMessageHandler.printErrorMessage(new OllamaMessage(OllamaMessageRole.SYSTEM,
|
||||
"Error: HTTP Response code - " + responseCode + "\n" + rawErrorOrDump));
|
||||
throw new RuntimeException("HTTP Response code - " + responseCode);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (cancelled.get()) throw new CancellationException("Query cancelled");
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (connection != null) connection.disconnect();
|
||||
activeConnection = null;
|
||||
}
|
||||
});
|
||||
throw new OperationNotSupportedException("The chosen backend of type \""+model.getClass().getSimpleName()+"\" dose not support streaming");
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the response from Ollama.
|
||||
*
|
||||
* <p>
|
||||
* Processes tool calls, logs information, appends messages to the OllamaObject,
|
||||
* and prints output to the user.
|
||||
*
|
||||
* @param response The response from Ollama
|
||||
*/
|
||||
public void handleResponse(JSONObject response) {
|
||||
public void handleResponse(Message response) {
|
||||
if(response == null) return;
|
||||
|
||||
writeLog("Raw response: " + response.toString());
|
||||
JSONObject message = response.getJSONObject("message");
|
||||
|
||||
if(!message.has("tool_calls")) {
|
||||
checkIfResponceMessage(response);
|
||||
return;
|
||||
}
|
||||
|
||||
ollamaObject.addMessage(new OllamaMessageToolCall(
|
||||
OllamaMessageRole.fromRole(message.optString("role")),
|
||||
message.getString("content"),
|
||||
message.getJSONArray("tool_calls")
|
||||
));
|
||||
//chatObject.addMessage(response);
|
||||
|
||||
List<CompletableFuture<Void>> futures = new ArrayList<>();
|
||||
|
||||
// Process each tool call
|
||||
for(Object call : message.getJSONArray("tool_calls")) {
|
||||
JSONArray toolCalls = response.getToolCalls();
|
||||
|
||||
if(toolCalls == null) toolCalls = new JSONArray();
|
||||
|
||||
if(toolCalls.length() == 0)
|
||||
{
|
||||
checkIfResponceMessage(response);
|
||||
return;
|
||||
}
|
||||
|
||||
for(Object call : toolCalls) {
|
||||
futures.add(processToolCall(call));
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(result -> {
|
||||
checkIfResponceMessage(response);
|
||||
qurryOllama().thenAccept(this::handleResponse);
|
||||
model.qurryModel(chatObject, tools).thenAccept(this::handleResponse);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -615,17 +416,16 @@ public class Core {
|
||||
private CompletableFuture<Void> processToolCall(Object call) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
if (!(call instanceof JSONObject jsonObject)) return;
|
||||
if (!jsonObject.has("function")) return;
|
||||
|
||||
JSONObject function = jsonObject.getJSONObject("function");
|
||||
OllamaFunctionTool func = findTool(function);
|
||||
Tool func = findTool(function);
|
||||
|
||||
if (func == null) {
|
||||
reportToolNotFound(function);
|
||||
return;
|
||||
}
|
||||
|
||||
JSONObject arguments = function.getJSONObject("arguments");
|
||||
JSONObject arguments = new JSONObject(function.getString("arguments"));
|
||||
renderToolCalling(func.renderCalling(function), function, arguments);
|
||||
executeToolCall(func, arguments);
|
||||
});
|
||||
@@ -637,11 +437,10 @@ public class Core {
|
||||
* @param function the function JSON containing the tool name
|
||||
* @return the OllamaFunctionTool if found, {@code null} otherwise
|
||||
*/
|
||||
private OllamaFunctionTool findTool(JSONObject function) {
|
||||
return funtionTools.stream()
|
||||
.filter(f -> (f.getKey().name() + "_" + f.getValue())
|
||||
private Tool findTool(JSONObject function) {
|
||||
return tools.stream()
|
||||
.filter(f -> (f.name())
|
||||
.equalsIgnoreCase(function.getString("name")))
|
||||
.map(Pair::getKey)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
@@ -652,11 +451,13 @@ public class Core {
|
||||
* @param function the function JSON representing a hallucinated or removed function
|
||||
*/
|
||||
private void reportToolNotFound(JSONObject function) {
|
||||
OllamaToolError error = new OllamaToolError(
|
||||
Message error = new Message(
|
||||
Message.Role.TOOL,
|
||||
function.getString("name"),
|
||||
"Function '" + function.getString("name") + "' does not exist"
|
||||
);
|
||||
ollamaObject.addMessage(error);
|
||||
printMessageHandler.printToolCalling(error.getError());
|
||||
chatObject.addMessage(error);
|
||||
printMessageHandler.printToolCalling(error.getContent().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -693,37 +494,45 @@ public class Core {
|
||||
* @param func the {@link OllamaFunctionTool} to execute
|
||||
* @param arguments the raw JSON arguments for the tool
|
||||
*/
|
||||
private void executeToolCall(OllamaFunctionTool func, JSONObject arguments) {
|
||||
ArrayList<OllamaFunctionArgument> args = new ArrayList<>();
|
||||
for(String key : arguments.keySet()) {
|
||||
args.add(new OllamaFunctionArgument(key, arguments.get(key)));
|
||||
}
|
||||
private void executeToolCall(Tool tool, JSONObject arguments) {
|
||||
if(tool instanceof FunctionTool func) {
|
||||
ToolArguments args = new ToolArguments();
|
||||
for (String key : arguments.keySet()) {
|
||||
args.addArgument(key, arguments.get(key));
|
||||
}
|
||||
|
||||
try {
|
||||
OllamaToolResponse response = func.function(args.toArray(new OllamaFunctionArgument[0]));
|
||||
ollamaObject.addMessage(response);
|
||||
printMessageHandler.printMessage(response);
|
||||
writeLog("Successfully function call " + func.name() + " output: " + response.getResponse());
|
||||
} catch(OllamaToolErrorException e) {
|
||||
OllamaToolError error = new OllamaToolError(e.getMessage());
|
||||
ollamaObject.addMessage(error);
|
||||
printMessageHandler.printErrorMessage(error);
|
||||
writeLog("ERROR: " + e.getMessage());
|
||||
try {
|
||||
ToolResponse response = func.function(args);
|
||||
if(response.getToolID() == null) {
|
||||
response.setToolID(tool.name());
|
||||
}
|
||||
chatObject.addMessage(response);
|
||||
printMessageHandler.printMessage(response);
|
||||
writeLog("Successfully function call " + func.name() + " output: " + response.getResponse());
|
||||
} catch (ToolException e) {
|
||||
Message error = new Message(
|
||||
Message.Role.TOOL,
|
||||
tool.name(),
|
||||
e.getMessage()
|
||||
);
|
||||
chatObject.addMessage(error);
|
||||
printMessageHandler.printErrorMessage(error);
|
||||
writeLog("ERROR: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
// TODO: Add support for misc tools
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the response contains a message and if so, prints it to the user
|
||||
* @param responce the Ollama response
|
||||
*/
|
||||
private void checkIfResponceMessage(JSONObject responce) {
|
||||
String message = responce.getJSONObject("message").getString("content");
|
||||
if(responce.getJSONObject("message").has("content") && !message.isBlank())
|
||||
private void checkIfResponceMessage(Message responce) {
|
||||
if(responce.getContent() instanceof String str)
|
||||
{
|
||||
OllamaMessage ollamaMessage = new OllamaMessage(OllamaMessageRole.ASSISTANT, message);
|
||||
printMessageHandler.printMessage(ollamaMessage);
|
||||
writeLog("Response content: "+ message);
|
||||
ollamaObject.addMessage(ollamaMessage);
|
||||
printMessageHandler.printMessage(responce);
|
||||
writeLog("Response content: "+ str);
|
||||
chatObject.addMessage(responce);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,9 +595,9 @@ public class Core {
|
||||
}
|
||||
|
||||
data.plugins.forEach(loadedPlugin -> {
|
||||
for(OllamaFunctionTool tool : loader.getTools(loadedPlugin.plugin()))
|
||||
for(Tool tool : loader.getTools(loadedPlugin.plugin()))
|
||||
{
|
||||
addTool(tool, Source.RPCP);
|
||||
addTool(tool);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -822,34 +631,4 @@ public class Core {
|
||||
plugins.remove(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the source of a tool.
|
||||
* <p>
|
||||
* This is intended for use with {@link Core#addTool(OllamaFunctionTool, String)}
|
||||
* to indicate the category from which a tool originates.
|
||||
*/
|
||||
public static class Source {
|
||||
/**
|
||||
* Tools boundeld with the Core runtime (memory, file access, etc.)
|
||||
* DO NOT USE THIS unless you are poking at core stuff :).
|
||||
*/
|
||||
public static final String CORE = "Core";
|
||||
|
||||
/**
|
||||
* Compile-Time Plugins: boundeld at build time as part of the application.
|
||||
* Examples: MALAPITool, GeniusAPI, WikipediaTool.
|
||||
*/
|
||||
public static final String CTP = "CTP";
|
||||
|
||||
/**
|
||||
* Runtime Pre-Compiled Plugins: external plugins loaded at runtime via Plugin-API.
|
||||
*/
|
||||
public static final String RPCP = RPCP_SOURCE;
|
||||
|
||||
/**
|
||||
* Tools defined via the RESt API interface dynamically.
|
||||
*/
|
||||
public static final String API = "API";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package me.neurodock.core;
|
||||
|
||||
import me.neurodock.ollama.*;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.Tool;
|
||||
import me.neurodock.llm.Message;
|
||||
import me.neurodock.llm.Model;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.*;
|
||||
@@ -39,7 +42,7 @@ import java.util.Map;
|
||||
* <p>
|
||||
* Instances are assembled via {@link #builder()}. Callers who need capability routing
|
||||
* hints compiled from a live set of registered tools must call
|
||||
* {@link #generateCapabilities(OllamaObject)} before {@link #generateSystemPrompt()};
|
||||
* {@link #generateCapabilities(ArrayList)} before {@link #generateSystemPrompt()};
|
||||
* see that method's docs for why this is a separate step rather than being folded into
|
||||
* construction.
|
||||
*
|
||||
@@ -60,12 +63,12 @@ public class LLMSystemPrompt {
|
||||
* Usage-hint definitions for registered tools, or {@code null} if this prompt doesn't
|
||||
* compile a capabilities section. This holds the *rules* for what to say about each
|
||||
* tool; the actual compiled text lives in {@link #compiledCapabilities} and is only
|
||||
* populated once {@link #generateCapabilities(OllamaObject)} has been called.
|
||||
* populated once {@link #generateCapabilities(ArrayList)} has been called.
|
||||
*/
|
||||
private Capabilities capabilities;
|
||||
/**
|
||||
* The compiled, ready-to-render {@code <Capabilities>} body, built by
|
||||
* {@link #generateCapabilities(OllamaObject)} from {@link #capabilities} and a live
|
||||
* {@link #generateCapabilities(ArrayList)} from {@link #capabilities} and a live
|
||||
* set of registered tools. {@code null} until that method has been called at least
|
||||
* once, or if {@link #capabilities} was never set.
|
||||
*/
|
||||
@@ -103,18 +106,18 @@ public class LLMSystemPrompt {
|
||||
|
||||
/**
|
||||
* Compiles the {@code <Capabilities>} section from the tools currently registered on
|
||||
* the given {@link OllamaObject}, using the usage hints defined in {@link #capabilities}.
|
||||
* the given {@link ArrayList<Tool>}, using the usage hints defined in {@link #capabilities}.
|
||||
* <p>
|
||||
* This is intentionally a separate step from construction rather than something the
|
||||
* constructor does automatically: which tools are registered on an {@link OllamaObject}
|
||||
* constructor does automatically: which tools are registered on an {@link ArrayList<Tool>}
|
||||
* can change after this prompt is built (tools added/removed at runtime), so the
|
||||
* compiled capabilities text needs to be regenerated on demand against whatever the
|
||||
* current tool set actually is, not frozen at construction time.
|
||||
* <p>
|
||||
* For each registered {@link OllamaFunctionTool}, this looks up a usage hint by the
|
||||
* For each registered {@link FunctionTool}, this looks up a usage hint by the
|
||||
* tool's {@link Class} via {@link Capabilities#getUsageHints()}. If no hint was
|
||||
* registered for that class, the tool is silently omitted from the compiled output —
|
||||
* there is no fallback to {@link OllamaFunctionTool#description()}, since that text
|
||||
* there is no fallback to {@link FunctionTool#description()}, since that text
|
||||
* is written to help the model choose a tool mid-conversation, not to explain routing
|
||||
* up front in a system prompt; conflating the two would blur what each is for.
|
||||
* <p>
|
||||
@@ -122,18 +125,18 @@ public class LLMSystemPrompt {
|
||||
* section at all), this clears {@link #compiledCapabilities} to {@code null} and
|
||||
* returns without inspecting {@code ollamaObject}.
|
||||
*
|
||||
* @param ollamaObject the object whose currently-registered tools should be inspected
|
||||
* @param tools the object whose currently-registered tools should be inspected
|
||||
*/
|
||||
public void generateCapabilities(OllamaObject ollamaObject) {
|
||||
public void generateCapabilities(ArrayList<Tool> tools) {
|
||||
if(capabilities == null) {
|
||||
compiledCapabilities = null;
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (Pair<OllamaTool, String> tool : ollamaObject.getTools()) {
|
||||
if(tool.getKey() instanceof OllamaFunctionTool funcTool) {
|
||||
String llmName = funcTool.name() + "_" + (funcTool.getSource() != null ? funcTool.getSource() : "");
|
||||
for (Tool tool : tools) {
|
||||
if(tool instanceof FunctionTool funcTool) {
|
||||
String llmName = funcTool.name();
|
||||
String hint = capabilities.getUsageHints().get(funcTool.getClass());
|
||||
|
||||
// fall back to description() if no explicit routing hint was set
|
||||
@@ -148,7 +151,7 @@ public class LLMSystemPrompt {
|
||||
|
||||
/**
|
||||
* Renders this prompt's currently-set sections into a single system-role
|
||||
* {@link OllamaMessage}, ready to be sent to Ollama.
|
||||
* {@link Message}, ready to be sent to Ollama.
|
||||
* <p>
|
||||
* Each section — {@link #identity}, {@link #context}, {@link #compiledCapabilities},
|
||||
* {@link #behavior}, {@link #outputFormat} — is wrapped in its own XML-style tag and
|
||||
@@ -157,13 +160,13 @@ public class LLMSystemPrompt {
|
||||
* skipped rather than emitted as empty tags.
|
||||
* <p>
|
||||
* Note that {@link #compiledCapabilities} reflects whatever tool set was live the last
|
||||
* time {@link #generateCapabilities(OllamaObject)} was called — call that first if the
|
||||
* time {@link #generateCapabilities(ArrayList)} was called — call that first if the
|
||||
* registered tools may have changed since.
|
||||
*
|
||||
* @return a new {@link OllamaMessage} with role {@link OllamaMessageRole#SYSTEM}
|
||||
* @return a new {@link Message} with role {@link Message.Role#SYSTEM}
|
||||
* containing the compiled prompt text
|
||||
*/
|
||||
public OllamaMessage generateSystemPrompt() {
|
||||
public Message generateSystemPrompt() {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
@@ -185,7 +188,7 @@ public class LLMSystemPrompt {
|
||||
builder.append("<Output>").append(System.lineSeparator()).append(outputFormat.build()).append(System.lineSeparator()).append("</Output>").append(System.lineSeparator());
|
||||
}
|
||||
|
||||
return new OllamaMessage(OllamaMessageRole.SYSTEM, builder.toString());
|
||||
return new Message(Message.Role.SYSTEM, builder.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,19 +303,19 @@ public class LLMSystemPrompt {
|
||||
/**
|
||||
* Defines per-tool usage-routing hints for the {@code <Capabilities>} section of a
|
||||
* system prompt — i.e. "use this tool only in these cases" guidance, distinct from a
|
||||
* tool's own {@link OllamaFunctionTool#description()}.
|
||||
* tool's own {@link FunctionTool#description()}.
|
||||
* <p>
|
||||
* Hints are keyed by the tool's {@link Class} rather than by a registered instance or
|
||||
* its LLM-facing name, since NeuroDock's convention is one instance per tool class —
|
||||
* see {@link #use(Class, String)}. This class only holds the hint definitions; the
|
||||
* actual compiled text is produced by
|
||||
* {@link LLMSystemPrompt#generateCapabilities(OllamaObject)}.
|
||||
* {@link LLMSystemPrompt#generateCapabilities(ArrayList)}.
|
||||
*/
|
||||
public static class Capabilities {
|
||||
/**
|
||||
* Usage hints keyed by tool class, in the order they were registered.
|
||||
*/
|
||||
private final LinkedHashMap<Class<? extends OllamaFunctionTool>, String> usageHints = new LinkedHashMap<>();
|
||||
private final LinkedHashMap<Class<? extends FunctionTool>, String> usageHints = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* Registers a usage-routing hint for a tool class — guidance on when the model
|
||||
@@ -321,13 +324,13 @@ public class LLMSystemPrompt {
|
||||
* <p>
|
||||
* Keyed by class rather than instance because a tool's LLM-facing name (which
|
||||
* depends on its registration source) isn't known until it's actually registered
|
||||
* on an {@link OllamaObject}; class identity is stable and known up front.
|
||||
* on an {@link Model}; class identity is stable and known up front.
|
||||
*
|
||||
* @param toolClass the tool class this hint applies to
|
||||
* @param usageHint the routing guidance text for this tool
|
||||
* @return this {@link Capabilities}, for chaining
|
||||
*/
|
||||
public Capabilities use(Class<? extends OllamaFunctionTool> toolClass, String usageHint) {
|
||||
public Capabilities use(Class<? extends FunctionTool> toolClass, String usageHint) {
|
||||
usageHints.put(toolClass, usageHint);
|
||||
return this;
|
||||
}
|
||||
@@ -337,7 +340,7 @@ public class LLMSystemPrompt {
|
||||
*
|
||||
* @return the live map backing this {@link Capabilities}
|
||||
*/
|
||||
Map<Class<? extends OllamaFunctionTool>, String> getUsageHints() {
|
||||
Map<Class<? extends FunctionTool>, String> getUsageHints() {
|
||||
return usageHints;
|
||||
}
|
||||
}
|
||||
@@ -485,7 +488,7 @@ public class LLMSystemPrompt {
|
||||
/**
|
||||
* Sets the capability usage-hint definitions for the prompt being built. These are
|
||||
* only compiled into prompt text once
|
||||
* {@link LLMSystemPrompt#generateCapabilities(OllamaObject)} is called on the built
|
||||
* {@link LLMSystemPrompt#generateCapabilities(ArrayList)} is called on the built
|
||||
* instance.
|
||||
*
|
||||
* @param capabilities the usage-hint definitions to use
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package me.neurodock.core;
|
||||
|
||||
import me.neurodock.ollama.OllamaMessage;
|
||||
import me.neurodock.ollama.OllamaMessageRole;
|
||||
import me.neurodock.llm.Message;
|
||||
|
||||
/**
|
||||
* Represents a {@link PrintAdvanceMessageHandler}.
|
||||
* This is used by the {@link Core} to print messages for the user, LLM, Tools, or potentialy System, see {@link OllamaMessageRole}.
|
||||
* This is used by the {@link Core} to print messages for the user, LLM, Tools, or potentialy System, see {@link Message.Role}.
|
||||
* For a simpler version see {@link PrintMessageHandler}
|
||||
*/
|
||||
public interface PrintAdvanceMessageHandler {
|
||||
/**
|
||||
* Expected to handle the printing of the provided {@link OllamaMessage}.
|
||||
* @param message The {@link OllamaMessage} requested to be printed from a veriity of sources, see {@link OllamaMessageRole}
|
||||
* Expected to handle the printing of the provided {@link Message}.
|
||||
* @param message The {@link Message} requested to be printed from a veriity of sources, see {@link Message}
|
||||
*/
|
||||
void printMessage(OllamaMessage message);
|
||||
void printMessage(Message message);
|
||||
|
||||
/**
|
||||
* Default method to print error messages to the user or API Client.
|
||||
@@ -21,7 +20,7 @@ public interface PrintAdvanceMessageHandler {
|
||||
* @param errorMessage The error message to be printed.
|
||||
* If color is not supported, it will print the message without color.
|
||||
*/
|
||||
void printErrorMessage(OllamaMessage errorMessage);
|
||||
void printErrorMessage(Message errorMessage);
|
||||
|
||||
/**
|
||||
* Used when a tool is to have it's calling rendered
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package me.neurodock.core;
|
||||
|
||||
import me.neurodock.ollama.OllamaMessage;
|
||||
import me.neurodock.ollama.OllamaMessageRole;
|
||||
|
||||
import static me.neurodock.ollama.OllamaFunctionArgument.deconstructOllamaFunctionArguments;
|
||||
import me.neurodock.llm.Message;
|
||||
|
||||
/**
|
||||
* Represents a PrintMessageHandler.
|
||||
@@ -21,11 +19,11 @@ public interface PrintMessageHandler extends PrintAdvanceMessageHandler {
|
||||
* This is a default implementation when wrapping {@link PrintAdvanceMessageHandler} to this "simpler" {@link PrintMessageHandler}
|
||||
*
|
||||
*
|
||||
* @param message The {@link OllamaMessage} requested to be printed from a veriity of sources, see {@link OllamaMessageRole}
|
||||
* @param message The {@link Message} requested to be printed from a veriity of sources, see {@link Message.Role}
|
||||
*/
|
||||
default void printMessage(OllamaMessage message) {
|
||||
default void printMessage(Message message) {
|
||||
printMessage(switch (message.getRole()) {
|
||||
case ASSISTANT -> (color()?"\u001b[32m":"")+(LaunchOptions.getInstance().isShowFullMessage()? message.getContent() : message.getContent().replaceAll("(?s)<think>.*?</think>", "")) +(color()?"\u001b[0m":"");
|
||||
case ASSISTANT -> (color()?"\u001b[32m":"")+(LaunchOptions.getInstance().isShowFullMessage()? message.getContent() : message.getContent().toString().replaceAll("(?s)<think>.*?</think>", "")) +(color()?"\u001b[0m":"");
|
||||
case TOOL -> (color() ?"\u001b[31m":"")+message.getContent()+(color()?"\u001b[0m":"");
|
||||
case USER, SYSTEM -> (color() ?"\u001b[37m":"")+message.getContent()+(color()?"\u001b[0m":"");
|
||||
default -> throw new IllegalArgumentException("Invalid message role");
|
||||
@@ -62,8 +60,8 @@ public interface PrintMessageHandler extends PrintAdvanceMessageHandler {
|
||||
* @param errorMessage The error message to be printed.
|
||||
* If color is not supported, it will print the message without color.
|
||||
*/
|
||||
default void printErrorMessage(OllamaMessage errorMessage) {
|
||||
printError(errorMessage.getContent());
|
||||
default void printErrorMessage(Message errorMessage) {
|
||||
printError(errorMessage.getContent().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,7 +5,7 @@ import me.neurodock.core.Options;
|
||||
import me.neurodock.core.Pair;
|
||||
import me.neurodock.core.files.tools.ReadFileTool;
|
||||
import me.neurodock.core.files.tools.WriteFileTool;
|
||||
import me.neurodock.ollama.OllamaTool;
|
||||
import me.neurodock.llm.tools.Tool;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
@@ -50,14 +50,14 @@ public class FileHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all {@link OllamaTool}'s this module adds
|
||||
* Returns a list of all {@link Tool}'s this module adds
|
||||
* @return
|
||||
*/
|
||||
public static ArrayList<Pair<? extends OllamaTool, String>> getTools() {
|
||||
ArrayList<Pair<? extends OllamaTool, String>> fileTools = new ArrayList<>();
|
||||
public static ArrayList<Tool> getTools() {
|
||||
ArrayList<Tool> fileTools = new ArrayList<>();
|
||||
|
||||
fileTools.add(new Pair<>(new ReadFileTool(), Core.Source.CORE));
|
||||
fileTools.add(new Pair<>(new WriteFileTool(), Core.Source.CORE));
|
||||
fileTools.add(new ReadFileTool());
|
||||
fileTools.add(new WriteFileTool());
|
||||
|
||||
return fileTools;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package me.neurodock.core.files.tools;
|
||||
|
||||
import me.neurodock.core.files.FileHandler;
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.neurodock.llm.exceptions.ToolException;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ReadFileTool extends OllamaFunctionTool {
|
||||
public class ReadFileTool extends FunctionTool {
|
||||
FileHandler fs = FileHandler.getInstance();
|
||||
@Override
|
||||
public @NotNull String name() {
|
||||
@@ -24,23 +24,18 @@ public class ReadFileTool extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
.addProperty("file_path", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The path to the file to be read", true)
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.builder()
|
||||
.addProperty("file_path", ToolParameters.ToolParametersBuilder.Type.STRING, "The path to the file to be read", true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
String orgPath = null;
|
||||
for (OllamaFunctionArgument arg : args) {
|
||||
if(arg.argument().equals("file_path")) {
|
||||
orgPath = (String) arg.value();
|
||||
}
|
||||
}
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
String orgPath = args.optArgument("file_path", String.class);
|
||||
|
||||
if(orgPath == null) {
|
||||
throw new OllamaToolErrorException(this.name(), "Missing required argument 'file_path'");
|
||||
throw new ToolException(this, "Missing required argument 'file_path'");
|
||||
}
|
||||
|
||||
Path filePath = null;
|
||||
@@ -49,19 +44,19 @@ public class ReadFileTool extends OllamaFunctionTool {
|
||||
filePath = fs.resolve(orgPath);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new OllamaToolErrorException(this.name(), ex);
|
||||
throw new ToolException(this, ex);
|
||||
}
|
||||
|
||||
File file = filePath.toFile();
|
||||
if(!file.exists()) {
|
||||
throw new OllamaToolErrorException(this.name(), "File does not exist: " + fs.path(file));
|
||||
throw new ToolException(this, "File does not exist: " + fs.path(file));
|
||||
}
|
||||
|
||||
BufferedReader br = null;
|
||||
try{
|
||||
br = new BufferedReader(new FileReader(file));
|
||||
}catch (FileNotFoundException ex) {
|
||||
throw new OllamaToolErrorException(this.name(), "File not found: " + fs.path(file));
|
||||
throw new ToolException(this, "File not found: " + fs.path(file));
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String tmp = null;
|
||||
@@ -71,8 +66,8 @@ public class ReadFileTool extends OllamaFunctionTool {
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new OllamaToolErrorException(this.name(), "Error reading file: " + fs.path(file));
|
||||
throw new ToolException(this, "Error reading file: " + fs.path(file));
|
||||
}
|
||||
return new OllamaToolResponse(this.name(), sb.toString());
|
||||
return new ToolResponse(this.name(), sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package me.neurodock.core.files.tools;
|
||||
|
||||
import me.neurodock.core.files.FileHandler;
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.ollama.OllamaPerameter.OllamaPerameterBuilder.Type;
|
||||
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.neurodock.llm.exceptions.ToolException;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import static me.neurodock.llm.tools.ToolParameters.ToolParametersBuilder.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class WriteFileTool extends OllamaFunctionTool {
|
||||
public class WriteFileTool extends FunctionTool {
|
||||
FileHandler fs = FileHandler.getInstance();
|
||||
|
||||
@Override
|
||||
@@ -21,8 +21,8 @@ public class WriteFileTool extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.builder()
|
||||
.addProperty("file_path", Type.STRING, "The path to the file to write to", true)
|
||||
.addProperty("file_content", Type.STRING, "The content to write to the file", true)
|
||||
.addProperty("overwrite", Type.BOOLEAN, "Overwrite the file, defaults to false", false)
|
||||
@@ -30,30 +30,14 @@ public class WriteFileTool extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
String path = null;
|
||||
String content = null;
|
||||
boolean overwrite = false;
|
||||
|
||||
for(OllamaFunctionArgument arg : args)
|
||||
{
|
||||
switch (arg.argument())
|
||||
{
|
||||
case "file_path" -> {
|
||||
path = (String) arg.value();
|
||||
}
|
||||
case "file_content" -> {
|
||||
content = (String) arg.value();
|
||||
}
|
||||
case "overwrite" -> {
|
||||
overwrite = (boolean) arg.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
String path = args.getArgument("file_path", String.class);
|
||||
String content = args.getArgument("file_content", String.class);
|
||||
boolean overwrite = args.getArgument("overwrite", Boolean.class);
|
||||
|
||||
if(path == null || content == null || path.isBlank() || content.isBlank())
|
||||
{
|
||||
throw new OllamaToolErrorException(name(), "file_content or file_path is empty or null");
|
||||
throw new ToolException(this, "file_content or file_path is empty or null");
|
||||
}
|
||||
|
||||
Path filePath = null;
|
||||
@@ -62,12 +46,12 @@ public class WriteFileTool extends OllamaFunctionTool {
|
||||
filePath = fs.resolve(path);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new OllamaToolErrorException(this.name(), ex);
|
||||
throw new ToolException(this, ex);
|
||||
}
|
||||
|
||||
File file = filePath.toFile();
|
||||
if(file.exists() && !overwrite) {
|
||||
throw new OllamaToolErrorException(this.name(), "File already exists, and instructed to not be overwritten: " + fs.path(file));
|
||||
throw new ToolException(this, "File already exists, and instructed to not be overwritten: " + fs.path(file));
|
||||
}
|
||||
else if(file.exists() && overwrite)
|
||||
{
|
||||
@@ -77,17 +61,17 @@ public class WriteFileTool extends OllamaFunctionTool {
|
||||
try {
|
||||
file.createNewFile();
|
||||
} catch (IOException e) {
|
||||
throw new OllamaToolErrorException(name(), e);
|
||||
throw new ToolException(this, e);
|
||||
}
|
||||
|
||||
try(BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))))
|
||||
{
|
||||
bw.write(content);
|
||||
return new OllamaToolResponse(name(), "Successfully wrote data to "+fs.path(file));
|
||||
return new ToolResponse(name(), "Successfully wrote data to "+fs.path(file));
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new OllamaToolErrorException(name(), ex);
|
||||
throw new ToolException(this, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package me.neurodock.core.memory;
|
||||
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.neurodock.llm.exceptions.ToolException;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Provides the add_memory function.<br>
|
||||
* This function adds a string to the memory.
|
||||
*/
|
||||
public class AddMemoryFunction extends OllamaFunctionTool {
|
||||
public class AddMemoryFunction extends FunctionTool {
|
||||
/**
|
||||
* The CoreMemory instance.
|
||||
*/
|
||||
@@ -28,37 +28,23 @@ public class AddMemoryFunction extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull 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)
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.builder()
|
||||
.addProperty("memory", ToolParameters.ToolParametersBuilder.Type.STRING, "The memory to remember", true)
|
||||
.addProperty("identity", ToolParameters.ToolParametersBuilder.Type.STRING, "The identity of the memory to remember", true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
if (args.length == 0) {
|
||||
throw new OllamaToolErrorException(name(), "Missing memory argument");
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
String memory = args.optArgument("memory", String.class);
|
||||
String identity = args.optArgument("identity", String.class);
|
||||
|
||||
if (memory == null || identity == null) {
|
||||
throw new OllamaToolErrorException(name(), "Missing memory or identity argument");
|
||||
throw new ToolException(this, "Missing memory or identity argument");
|
||||
}
|
||||
|
||||
this.memory.addMemory(identity, memory);
|
||||
return new OllamaToolResponse(name(), "Added "+identity+" to the memory");
|
||||
return new ToolResponse(name(), "Added "+identity+" to the memory");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package me.neurodock.core.memory;
|
||||
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class GetMemoriesFunction extends OllamaFunctionTool {
|
||||
public class GetMemoriesFunction extends FunctionTool {
|
||||
@Override
|
||||
public @NotNull String name() {
|
||||
return "get_memories";
|
||||
@@ -18,12 +18,12 @@ public class GetMemoriesFunction extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return null;
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
return new OllamaToolResponse(name(), CoreMemory.getInstance().getMappedMemories());
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
return new ToolResponse(name(), CoreMemory.getInstance().getMappedMemories());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package me.neurodock.core.memory;
|
||||
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Provides the get_memory function.<br>
|
||||
* This function retrives all the memory.
|
||||
*/
|
||||
public class GetMemoryFunction extends OllamaFunctionTool {
|
||||
public class GetMemoryFunction extends FunctionTool {
|
||||
/**
|
||||
* The CoreMemory instance.
|
||||
*/
|
||||
@@ -27,16 +27,16 @@ public class GetMemoryFunction extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
.addProperty("identity", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The identity of the memory to retrieve", true)
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.builder()
|
||||
.addProperty("identity", ToolParameters.ToolParametersBuilder.Type.STRING, "The identity of the memory to retrieve", true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
return memory.getMemory((String) (args[0].value()))
|
||||
.map(value -> new OllamaToolResponse(name(), value))
|
||||
.orElse(OllamaToolResponse.empty("No memory found for key: " + args[0].value()));
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
return memory.getMemory(args.optArgument("identity", String.class))
|
||||
.map(value -> new ToolResponse(name(), value))
|
||||
.orElse(ToolResponse.empty(name(), "No memory found for key: " + args.optArgument("identity", String.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package me.neurodock.core.memory;
|
||||
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONArray;
|
||||
|
||||
public class GetMemoryIdentitiesFunction extends OllamaFunctionTool {
|
||||
public class GetMemoryIdentitiesFunction extends FunctionTool {
|
||||
CoreMemory memory = CoreMemory.getInstance();
|
||||
|
||||
@Override
|
||||
@@ -21,12 +21,12 @@ public class GetMemoryIdentitiesFunction extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return null;
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
return new OllamaToolResponse(this.name(), new JSONArray(memory.getMemoriesIdentity()).toString());
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
return new ToolResponse(this.name(), new JSONArray(memory.getMemoriesIdentity()).toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package me.neurodock.core.memory;
|
||||
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.neurodock.llm.exceptions.ToolException;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Provides the remove_memory function.<br>
|
||||
* This function removes a value from the memory.
|
||||
*/
|
||||
public class RemoveMemoryFunction extends OllamaFunctionTool {
|
||||
public class RemoveMemoryFunction extends FunctionTool {
|
||||
/**
|
||||
* The CoreMemory instance.
|
||||
*/
|
||||
@@ -28,19 +28,19 @@ public class RemoveMemoryFunction extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
.addProperty("identity", OllamaPerameter.OllamaPerameterBuilder.Type.STRING, "The identity of the memory to forget", true)
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.builder()
|
||||
.addProperty("identity", ToolParameters.ToolParametersBuilder.Type.STRING, "The identity of the memory to forget", true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
if (args.length == 0) {
|
||||
throw new OllamaToolErrorException(name(), "Missing memory argument");
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
String value = args.optArgument("identity", String.class);
|
||||
if(value == null || value.isEmpty()) {
|
||||
throw new ToolException(this, "Missing identity argument");
|
||||
}
|
||||
String value = (String) args[0].value();
|
||||
memory.removeMemory(value);
|
||||
return new OllamaToolResponse(name(), "Removed "+value+" to the memory");
|
||||
return new ToolResponse(name(), "Removed "+value+" to the memory");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
package me.neurodock.core.memory.array;
|
||||
|
||||
import me.neurodock.core.memory.CoreMemory;
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaPerameter.OllamaPerameterBuilder.Type;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.neurodock.llm.exceptions.ToolException;
|
||||
import me.neurodock.llm.tools.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class AddArrayMemory extends OllamaFunctionTool {
|
||||
public class AddArrayMemory extends FunctionTool {
|
||||
|
||||
private CoreMemory memory = CoreMemory.getInstance();
|
||||
|
||||
@@ -19,37 +15,29 @@ public class AddArrayMemory extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
.addProperty("memory", Type.STRING, "The memory to remember", true)
|
||||
.addProperty("index", Type.INT, "The index to put it at. Should be avoided, unless overwriting", false)
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.builder()
|
||||
.addProperty("memory", ToolParameters.ToolParametersBuilder.Type.STRING, "The memory to remember", true)
|
||||
.addProperty("index", ToolParameters.ToolParametersBuilder.Type.INT, "The index to put it at. Should be avoided, unless overwriting", false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
if (args.length > 1) {
|
||||
String memory = null;
|
||||
int index = -1;
|
||||
for (OllamaFunctionArgument arg : args) {
|
||||
if(arg.argument().equals("memory")) {
|
||||
memory = (String) arg.value();
|
||||
}
|
||||
else if(arg.argument().equals("index")) {
|
||||
index = (Integer) arg.value();
|
||||
}
|
||||
}
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
if (args.hasArgument("index")) {
|
||||
String memory = args.optArgument("memory", String.class);
|
||||
int index = args.optArgument("index", Integer.class);
|
||||
if (memory == null || index < 0) {
|
||||
throw new OllamaToolErrorException(name(), "no memory or index provided");
|
||||
throw new ToolException(this, "no memory or index provided");
|
||||
}
|
||||
this.memory.addMemory(memory, index);
|
||||
}
|
||||
else {
|
||||
if(!args[0].argument().equals("memory")) {
|
||||
throw new OllamaToolErrorException(name(), "no memory provided");
|
||||
if(!args.hasArgument("memory")) {
|
||||
throw new ToolException(this, "no memory provided");
|
||||
}
|
||||
this.memory.addMemory(name(), (String) args[0].value());
|
||||
this.memory.addMemory(name(), args.optArgument("memory", String.class));
|
||||
}
|
||||
return new OllamaToolResponse(name(), "Added arrayed memory");
|
||||
return new ToolResponse(name(), "Added arrayed memory");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package me.neurodock.core.memory.array;
|
||||
|
||||
import me.neurodock.core.memory.CoreMemory;
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.ollama.OllamaPerameter.OllamaPerameterBuilder.Type;
|
||||
import me.neurodock.llm.exceptions.ToolException;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class GetArrayMemory extends OllamaFunctionTool {
|
||||
public class GetArrayMemory extends FunctionTool {
|
||||
|
||||
CoreMemory memory = CoreMemory.getInstance();
|
||||
|
||||
@@ -18,19 +18,17 @@ public class GetArrayMemory extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
.addProperty("index", Type.STRING, "The index to retrieve memory from", false)
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.builder()
|
||||
.addProperty("index", ToolParameters.ToolParametersBuilder.Type.STRING, "The index to retrieve memory from", false)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
if (args.length != 1) {
|
||||
return new OllamaToolResponse(name(), memory.getArrayMemories().toString());
|
||||
}
|
||||
return memory.getMemory((Integer) args[0].value())
|
||||
.map(value -> new OllamaToolResponse(name(), value))
|
||||
.orElse(OllamaToolResponse.empty("No memory found for key: " + args[0].value()));
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
if(!args.hasArgument("index")) throw new ToolException(this, "Missing index");
|
||||
return memory.getMemory(args.optArgument("index", Integer.class, -1))
|
||||
.map(value -> new ToolResponse(name(), value))
|
||||
.orElse(ToolResponse.empty("No memory found for key: " + args.optArgument("index", -1)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package me.neurodock.core.memory.array;
|
||||
|
||||
import me.neurodock.core.memory.CoreMemory;
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class GetArrayedMemories extends OllamaFunctionTool {
|
||||
public class GetArrayedMemories extends FunctionTool {
|
||||
private CoreMemory memory = CoreMemory.getInstance();
|
||||
@Override
|
||||
public @NotNull String name() {
|
||||
@@ -15,12 +15,12 @@ public class GetArrayedMemories extends OllamaFunctionTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return OllamaPerameter.empty();
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
return new OllamaToolResponse(name(), memory.getArrayMemories().toString());
|
||||
public @NotNull ToolResponse function(ToolArguments args) {
|
||||
return new ToolResponse(name(), memory.getArrayMemories().toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package me.neurodock.llm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ChatObject {
|
||||
private ArrayList<Message> messages = new ArrayList<>();
|
||||
|
||||
public List<Message> getConversation()
|
||||
{
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void addMessage(Message msg)
|
||||
{
|
||||
messages.add(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package me.neurodock.llm;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
||||
public class Message {
|
||||
|
||||
protected Role role;
|
||||
protected Object content;
|
||||
protected String toolID;
|
||||
protected JSONArray toolCalls;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param role
|
||||
* @param content
|
||||
* @throws IllegalArgumentException If the backend does not support the content or role provided
|
||||
* @implSpec Implementers of a backend is expected to sanitize the content to be valid for your backend.
|
||||
* Throw {@link IllegalArgumentException} for cases where your backend does not support the arguments provided.
|
||||
* <b>DO NOT</b> default to somthing else that is for the user of your backend to handle.
|
||||
* You may however provide your own builder/contstuctor that can make such assumptions or defaults but this contractor <b>SHULD NEVER</b> do that
|
||||
*/
|
||||
public Message(Role role, Object content)
|
||||
{
|
||||
this.role = role;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public Message(Role role, Object content, JSONArray toolCalls)
|
||||
{
|
||||
this(role, content);
|
||||
this.toolCalls = toolCalls;
|
||||
}
|
||||
|
||||
public Message(Role role, String toolID, Object content)
|
||||
{
|
||||
this(role, content);
|
||||
this.toolID = toolID;
|
||||
}
|
||||
|
||||
public Role getRole() {
|
||||
return role;
|
||||
}
|
||||
public Object getContent() {
|
||||
return content;
|
||||
}
|
||||
public String getToolID() {
|
||||
return toolID;
|
||||
}
|
||||
|
||||
public JSONArray getToolCalls() {
|
||||
return toolCalls;
|
||||
}
|
||||
|
||||
public static enum Role {
|
||||
USER,
|
||||
ASSISTANT,
|
||||
TOOL,
|
||||
SYSTEM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Message{" +
|
||||
"role=" + role +
|
||||
", content=" + content +
|
||||
", toolID='" + toolID + '\'' +
|
||||
", toolCalls=" + toolCalls +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package me.neurodock.llm;
|
||||
|
||||
import me.neurodock.llm.tools.Tool;
|
||||
import me.neurodock.llm.tools.serializer.ToolSerializer;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* This interface is used for when a backend dose not supports streaming responses, otherwise use {@link StreamingModel}
|
||||
*/
|
||||
public interface Model {
|
||||
|
||||
/**
|
||||
* Sends the given chat context to the model and requests the next response.
|
||||
*
|
||||
* @param obj the chat context, containing the conversation history to
|
||||
* send to the model
|
||||
* @param tools the tools available to the model for this request, or an
|
||||
* empty list if none
|
||||
* @return a future resolving to the model's reply as a {@link Message}
|
||||
*
|
||||
* @apiNote This method does not append the resulting {@link Message} to
|
||||
* {@code obj} itself. The caller is expected to append it to
|
||||
* {@code obj}'s conversation once the future completes, if the reply
|
||||
* should persist as part of the chat history.
|
||||
*/
|
||||
CompletableFuture<Message> qurryModel(ChatObject obj, List<Tool> tools);
|
||||
|
||||
/**
|
||||
* Sends a single, standalone message to the model, without any prior
|
||||
* conversation context.
|
||||
*
|
||||
* @param msg the message to send
|
||||
* @return a future resolving to the model's reply as a {@link Message}
|
||||
*/
|
||||
CompletableFuture<Message> singleFire(Message msg);
|
||||
|
||||
/**
|
||||
* Sends a single, standalone message to the model with tool support,
|
||||
* without any prior conversation context.
|
||||
*
|
||||
* @apiNote If no tools are needed for this request, prefer
|
||||
* {@link #singleFire(Message)} instead of passing an empty or null
|
||||
* {@code tools} list here.
|
||||
* @param msg the message to send
|
||||
* @param tools the tools available to the model for this request
|
||||
* @return a future resolving to the model's reply as a {@link Message}
|
||||
*/
|
||||
CompletableFuture<Message> singleFire(Message msg, List<Tool> tools);
|
||||
|
||||
/**
|
||||
* @return this backend's {@link ToolSerializer}, used to convert
|
||||
* {@link Tool}s into the JSON shape this backend expects.
|
||||
* Each backend instance owns its own — this is not shared
|
||||
* across the JVM.
|
||||
*/
|
||||
ToolSerializer getToolSerializer();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.neurodock.llm;
|
||||
|
||||
import me.neurodock.llm.tools.Tool;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* This interface is used for when a backend supports streaming responses, otherwise use {@link Model}
|
||||
*/
|
||||
public interface StreamingModel extends Model {
|
||||
CompletableFuture<Message> qurryModel(ChatObject obj, List<Tool> tools, Consumer<JSONObject> chunkConsumer);
|
||||
CompletableFuture<Message> singleFire(Message msg, Consumer<JSONObject> chunkConsumer);
|
||||
CompletableFuture<Message> singleFire(Message msg, List<Tool> tools, Consumer<JSONObject> chunkConsumer);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package me.neurodock.llm.exceptions;
|
||||
|
||||
public class ModelNotFoundException extends RuntimeException {
|
||||
public ModelNotFoundException(String modelName) {
|
||||
super("Model not found: " + modelName);
|
||||
}
|
||||
|
||||
public ModelNotFoundException(String modelName, String backend) {
|
||||
super("Model not found: \"" + modelName + "\" at backend: \"" + backend + "\"");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package me.neurodock.llm.exceptions;
|
||||
|
||||
import me.neurodock.llm.Message;
|
||||
import me.neurodock.llm.tools.Tool;
|
||||
|
||||
public class ToolException extends RuntimeException {
|
||||
protected Tool exceptingTool;
|
||||
public ToolException(Tool tool, String message) {
|
||||
super(message);
|
||||
exceptingTool = tool;
|
||||
}
|
||||
|
||||
public ToolException(Tool tool, Throwable cause) {
|
||||
this(tool, cause.getMessage());
|
||||
}
|
||||
|
||||
public Message getErrorMessage()
|
||||
{
|
||||
return new Message(Message.Role.TOOL, getMessage());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package me.neurodock.llm.tools;
|
||||
|
||||
import me.neurodock.core.ToolCallingRender;
|
||||
import me.neurodock.llm.exceptions.ToolException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public abstract class FunctionTool implements Tool {
|
||||
/**
|
||||
* The name of the tool. Used by the model to identify what it's calling.
|
||||
* @return the tool's name
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public abstract String name();
|
||||
|
||||
/**
|
||||
* The description of the tool. Used by the model to understand what the
|
||||
* tool does. May be {@code null} to omit it from the serialized JSON.
|
||||
* @return the tool's description, or {@code null}
|
||||
*/
|
||||
public String description() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameters this tool accepts.
|
||||
* @return the tool's parameter schema
|
||||
*/
|
||||
@NotNull
|
||||
public abstract ToolParameters parameters();
|
||||
|
||||
/**
|
||||
* Invokes the tool.
|
||||
* @param args the arguments passed by the model
|
||||
* @return the tool's response
|
||||
* @throws ToolException if the tool encounters an error
|
||||
*/
|
||||
@NotNull
|
||||
public abstract ToolResponse function(ToolArguments args) throws ToolException;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.neurodock.llm.tools;
|
||||
|
||||
import me.neurodock.core.ToolCallingRender;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public interface Tool {
|
||||
static List<Tool> emptyTools() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the tool, as sent to the model. Used to route an
|
||||
* incoming tool call back to this tool.
|
||||
*
|
||||
* @return the tool's name
|
||||
*/
|
||||
@NotNull
|
||||
String name();
|
||||
|
||||
default ToolCallingRender renderCalling(JSONObject calling) {
|
||||
return new ToolCallingRender.Default();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package me.neurodock.llm.tools;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class ToolArguments {
|
||||
Map<String, Object> arguments = new HashMap<>();
|
||||
|
||||
public void addArguments(Map<String, Object> args)
|
||||
{
|
||||
arguments.putAll(args);
|
||||
}
|
||||
|
||||
public void addArgument(String name, Object value)
|
||||
{
|
||||
arguments.put(name, value);
|
||||
}
|
||||
|
||||
public Object getArgument(String name)
|
||||
{
|
||||
if(!arguments.containsKey(name)) throw new NoSuchElementException("Missing required argument: " + name);;
|
||||
return arguments.get(name);
|
||||
}
|
||||
|
||||
public <T> T getArgument(String name, Class<T> type)
|
||||
{
|
||||
if(!arguments.containsKey(name)) throw new NoSuchElementException("Missing required argument: " + name);;
|
||||
Object o = arguments.get(name);
|
||||
if(type.isAssignableFrom(o.getClass())) return type.cast(o);
|
||||
throw new ClassCastException("Argument '" + name + "' is " + o.getClass().getSimpleName() + ", expected " + type.getSimpleName());
|
||||
}
|
||||
|
||||
public Object optArgument(String name)
|
||||
{
|
||||
if(!arguments.containsKey(name)) return null;
|
||||
return arguments.get(name);
|
||||
}
|
||||
|
||||
public Object optArgument(String name, Object defaultValue)
|
||||
{
|
||||
if(!arguments.containsKey(name)) return defaultValue;
|
||||
return arguments.get(name);
|
||||
}
|
||||
|
||||
public <T> T optArgument(String name, Class<T> type)
|
||||
{
|
||||
if(!arguments.containsKey(name)) return null;
|
||||
Object o = arguments.get(name);
|
||||
if(type.isAssignableFrom(o.getClass())) return type.cast(o);
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> T optArgument(String name, Class<T> type, T defaultValue)
|
||||
{
|
||||
if(!arguments.containsKey(name)) return defaultValue;
|
||||
Object o = arguments.get(name);
|
||||
if(type.isAssignableFrom(o.getClass())) return type.cast(o);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public boolean hasArgument(String name)
|
||||
{
|
||||
return arguments.containsKey(name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package me.neurodock.llm.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents the parameters of a tool.
|
||||
* This is used by Ollama to determine the parameters of a tool.
|
||||
*/
|
||||
public class ToolParameters {
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link ToolParameters}.
|
||||
* @param properties The properties of the parameters
|
||||
* @param required The required parameters
|
||||
*/
|
||||
private ToolParameters(Map<String, ToolParametersBuilder.Property> propertyMap, ArrayList<String> required) {
|
||||
this.propertyMap = propertyMap;
|
||||
this.required = required;
|
||||
};
|
||||
|
||||
/**
|
||||
* The properties of the parameters.
|
||||
*/
|
||||
private Map<String, ToolParametersBuilder.Property> propertyMap;
|
||||
/**
|
||||
* The required parameters.
|
||||
*/
|
||||
private ArrayList<String> required;
|
||||
|
||||
/**
|
||||
* Gets the properties of the {@link ToolParameters}
|
||||
* @return The properties of the {@link ToolParameters}
|
||||
*/
|
||||
public Map<String, ToolParametersBuilder.Property> getProperties() {
|
||||
return propertyMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the required parameters of the {@link ToolParameters}
|
||||
* @return The required parameters of the {@link ToolParameters}
|
||||
*/
|
||||
public ArrayList<String> getRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link ToolParametersBuilder}.
|
||||
* @return The {@link ToolParametersBuilder}
|
||||
*/
|
||||
public static ToolParametersBuilder builder() {
|
||||
return new ToolParametersBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty {@link ToolParameters}
|
||||
* @return an empty {@link ToolParameters}
|
||||
* @apiNote This is equvalent to
|
||||
* <pre>{@code
|
||||
* ToolParameters.builder().build();
|
||||
* }</pre>
|
||||
*/
|
||||
public static ToolParameters empty() {
|
||||
return builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a builder for {@link ToolParameters}.
|
||||
*/
|
||||
public static class ToolParametersBuilder {
|
||||
/**
|
||||
* The properties of the parameters.
|
||||
*/
|
||||
private Map<String, Property> propertyMap = new HashMap<>();
|
||||
/**
|
||||
* The required parameters.
|
||||
*/
|
||||
private ArrayList<String> required = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Add an optinal perameter to this {@link ToolParametersBuilder}
|
||||
* @param name The name of the parameter
|
||||
* @param type The type of the parameter
|
||||
* @param description The description of the parameter
|
||||
* @return The {@link ToolParametersBuilder}
|
||||
* @apiNote Prefer {@link #addProperty(String, Type, String, boolean)} to be explicit about required state
|
||||
*/
|
||||
public ToolParametersBuilder addProperty(String name, Type type, String description) {
|
||||
return addProperty(name, type, description, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a potentialy required peremeter to this {@link ToolParametersBuilder}.
|
||||
* @param name The name of the parameter
|
||||
* @param type The type of the parameter
|
||||
* @param description The description of the parameter
|
||||
* @param required The required state of the parameter
|
||||
* @return The {@link ToolParametersBuilder}
|
||||
*/
|
||||
public ToolParametersBuilder addProperty(String name, Type type, String description, boolean required) {
|
||||
if(name == null || type == null || description == null) {
|
||||
return this;
|
||||
}
|
||||
propertyMap.put(name, new Property(type, description));
|
||||
if(required) {
|
||||
this.required.add(name);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a previusly optinal perameter required for this {@link ToolParametersBuilder}
|
||||
* @param name The name of the parameter
|
||||
* @return The {@link ToolParametersBuilder}
|
||||
*/
|
||||
public ToolParametersBuilder required(String name) {
|
||||
if (!propertyMap.containsKey(name)) {
|
||||
throw new IllegalArgumentException("Cannot require unknown property: " + name);
|
||||
}
|
||||
required.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a property from the parameters.
|
||||
* @param name The name of the property to remove
|
||||
* @return The {@link ToolParametersBuilder}
|
||||
*/
|
||||
public ToolParametersBuilder removeProperty(String name) {
|
||||
propertyMap.remove(name);
|
||||
required.remove(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link ToolParameters}
|
||||
* @return The {@link ToolParameters}
|
||||
*/
|
||||
public ToolParameters build() {
|
||||
return new ToolParameters(propertyMap, required);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a property of a parameter.
|
||||
*
|
||||
* @param type The type of the property.
|
||||
* @param description The description of the property.
|
||||
*/
|
||||
public record Property(Type type, String description) {
|
||||
/**
|
||||
* Creates a new instance of {@link Property}.
|
||||
*
|
||||
* @param type The type of the property
|
||||
* @param description The description of the property
|
||||
*/
|
||||
public Property {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the type of parameter.
|
||||
*/
|
||||
public enum Type {
|
||||
/**
|
||||
* Represents a string parameter.
|
||||
*/
|
||||
STRING("string"),
|
||||
/**
|
||||
* Represents an integer parameter.
|
||||
*/
|
||||
INT("integer"),
|
||||
/**
|
||||
* Represents a boolean parameter.
|
||||
*/
|
||||
BOOLEAN("boolean"),
|
||||
/**
|
||||
* Represents a enum parameter.
|
||||
*/
|
||||
ENUM("enum"),
|
||||
/**
|
||||
* Represents a array parameter.
|
||||
*/
|
||||
ARRAY("array"),
|
||||
/**
|
||||
* Represents a object parameter.
|
||||
*/
|
||||
OBJECT("object");
|
||||
|
||||
/**
|
||||
* The type of the parameter.
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
* Gets the type of the parameter.
|
||||
* @return The type of the parameter
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link Type}.
|
||||
* @param type The type of the parameter
|
||||
*/
|
||||
Type(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-11
@@ -1,33 +1,35 @@
|
||||
package me.neurodock.ollama;
|
||||
package me.neurodock.llm.tools;
|
||||
|
||||
import me.neurodock.llm.Message;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents a response from a tool.
|
||||
*/
|
||||
public class OllamaToolResponse extends OllamaMessage {
|
||||
public class ToolResponse extends Message {
|
||||
|
||||
/**
|
||||
* Returns an empty tool response
|
||||
* See {@link OllamaToolResponse#empty(String, String)} for a reasoned/described response
|
||||
* See {@link ToolResponse#empty(String, String)} for a reasoned/described response
|
||||
* @param tool The tool that responded
|
||||
* @return an empty tool response
|
||||
*/
|
||||
public static OllamaToolResponse empty(String tool)
|
||||
public static ToolResponse empty(String tool)
|
||||
{
|
||||
return empty(tool, "No reason provided");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty tool response with a reason/description.
|
||||
* See {@link OllamaToolResponse#empty(String)} for reason/description less response
|
||||
* See {@link ToolResponse#empty(String)} for reason/description less response
|
||||
* @param tool The tool that responded
|
||||
* @param description A description for why this is empty
|
||||
* @return an empty tool response with a reason
|
||||
*/
|
||||
public static OllamaToolResponse empty(String tool, String description)
|
||||
public static ToolResponse empty(String tool, String description)
|
||||
{
|
||||
return new OllamaToolResponse(tool, "Empty! reason: " + description);
|
||||
return new ToolResponse(tool, "Empty! reason: " + description);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,12 +42,12 @@ public class OllamaToolResponse extends OllamaMessage {
|
||||
private final String response;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link OllamaToolResponse}.
|
||||
* Creates a new instance of {@link ToolResponse}.
|
||||
* @param tool The tool that responded
|
||||
* @param response The response from the tool
|
||||
*/
|
||||
public OllamaToolResponse(String tool, String response) {
|
||||
super(OllamaMessageRole.TOOL, new JSONObject().put("tool", tool).put("result", response).toString());
|
||||
public ToolResponse(String tool, String response) {
|
||||
super(Message.Role.TOOL, new JSONObject().put("tool", tool).put("result", response).toString());
|
||||
this.tool = tool;
|
||||
this.response = response;
|
||||
}
|
||||
@@ -65,4 +67,8 @@ public class OllamaToolResponse extends OllamaMessage {
|
||||
public String getResponse() {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
public void setToolID(@NotNull String name) {
|
||||
this.toolID = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package me.neurodock.llm.tools.serializer;
|
||||
|
||||
import me.neurodock.llm.tools.Tool;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ToolSerializer {
|
||||
private final Map<Class<? extends Tool>, ToolToJSON<? extends Tool>> serializers = new HashMap<>();
|
||||
|
||||
public <T extends Tool> boolean addSerializer(Class<T> clazz, ToolToJSON<T> serializer) {
|
||||
if(!serializers.containsKey(clazz)) {
|
||||
serializers.put(clazz, serializer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public <T extends Tool> ToolToJSON<T> getSerializer(Class<T> clazz) {
|
||||
return (ToolToJSON<T>) serializers.get(clazz);
|
||||
}
|
||||
|
||||
public <T extends Tool> JSONObject serialize(T tool)
|
||||
{
|
||||
if(!serializers.containsKey(tool.getClass())) throw new IllegalArgumentException("Tool " + tool.getClass() + " not found!");
|
||||
@SuppressWarnings({"unchecked"})
|
||||
ToolToJSON<T> serializer = (ToolToJSON<T>) serializers.get(tool.getClass());
|
||||
return serializer.toJSON(tool);
|
||||
}
|
||||
|
||||
public boolean canSerialize(Class<? extends Tool> clazz) {
|
||||
return serializers.containsKey(clazz);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package me.neurodock.llm.tools.serializer;
|
||||
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.Tool;
|
||||
import org.json.JSONObject;
|
||||
|
||||
public interface ToolToJSON<T extends Tool> {
|
||||
/**
|
||||
* @param tool the tool to serialize
|
||||
* @return the JSON representation of {@code tool}, in the shape this
|
||||
* backend expects
|
||||
*/
|
||||
JSONObject toJSON(T tool);
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Represents an argument passed to a tool.
|
||||
*
|
||||
* @param argument The argument name
|
||||
* @param value The argument value
|
||||
*/
|
||||
public record OllamaFunctionArgument(String argument, Object value) {
|
||||
/**
|
||||
* Creates a new instance of OllamaFunctionArgument.<br>
|
||||
* This is used by Ollama to pass arguments to a tool.
|
||||
*
|
||||
* @param argument The argument name
|
||||
* @param value The argument value
|
||||
*/
|
||||
public OllamaFunctionArgument {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the argument name
|
||||
*
|
||||
* @return The argument name
|
||||
*/
|
||||
@Override
|
||||
public String argument() {
|
||||
return argument;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the argument value.<br>
|
||||
* This needs to be cast to the correct type by the tool itself
|
||||
*
|
||||
* @return The argument value
|
||||
*/
|
||||
@Override
|
||||
public Object value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public <T extends Enum<T>> T getValue(Class<T> enumClass) {
|
||||
if(value instanceof String str) {
|
||||
return Enum.valueOf(enumClass, str);
|
||||
}
|
||||
throw new IllegalArgumentException(String.format("%s is not a valid enum value of type %s", value.toString(), enumClass.getName()));
|
||||
}
|
||||
|
||||
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(", $", "");
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
import me.neurodock.core.Core;
|
||||
import me.neurodock.core.ToolCallingRender;
|
||||
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents a tool that Ollama can call.
|
||||
*/
|
||||
public abstract class OllamaFunctionTool implements OllamaTool {
|
||||
|
||||
/**
|
||||
* This field is set via Reflection injection from {@link OllamaObject#addTool(OllamaTool, String)}.
|
||||
* As is, it will be overwritten. Do not set it yourself.
|
||||
*/
|
||||
protected String source = "";
|
||||
|
||||
/**
|
||||
* returns the source of this tool
|
||||
* @return String source
|
||||
*/
|
||||
public final String getSource()
|
||||
{
|
||||
return source;
|
||||
}
|
||||
|
||||
public JSONObject toJSON() {
|
||||
JSONObject ret = new JSONObject();
|
||||
ret.put("type", "function");
|
||||
|
||||
JSONObject function = new JSONObject();
|
||||
function.put("name", name()+"_"+(source != null ? source : ""));
|
||||
if(description() != null) {
|
||||
function.put("description", description());
|
||||
}
|
||||
function.put("parameters", (parameters() == null?
|
||||
new JSONObject() : parameters().toJSON()));
|
||||
|
||||
ret.put("function", function);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string representation of this tool calling for rendering.
|
||||
*
|
||||
* @param calling the raw JSON sent from Ollama describing the complete function call
|
||||
* @return an {@link Optional} containing the formatted representation if this calling
|
||||
* should be rendered, or {@link Optional#empty()} otherwise
|
||||
*/
|
||||
public ToolCallingRender renderCalling(JSONObject calling)
|
||||
{
|
||||
return new ToolCallingRender.Suppress();
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the tool
|
||||
* This is used by Ollama to know what the tool is
|
||||
* @return The name of the tool
|
||||
*/
|
||||
@NotNull
|
||||
abstract public String name();
|
||||
|
||||
/**
|
||||
* The description of the tool
|
||||
* This is used by Ollama to know what the tool does
|
||||
* @return The description of the tool
|
||||
*/
|
||||
public String description(){
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameters of the tool
|
||||
* This is used by Ollama to know what parameters the tool takes
|
||||
* If null, the tool does not take any parameters
|
||||
*
|
||||
* @return The parameters of the tool or null if the tool does not take any parameters
|
||||
*/
|
||||
@NotNull
|
||||
abstract public OllamaPerameter parameters();
|
||||
|
||||
/**
|
||||
* The function of the tool.<br>
|
||||
* This is used by Ollama to call the tool.<br>
|
||||
* Throw {@link OllamaToolErrorException} if the tool encounters an error instead of normal exceptions. The {@link OllamaToolErrorException} gets handled more gracefully by {@link Core#handleResponse(JSONObject)}
|
||||
* @param args The arguments to pass to the tool, if any
|
||||
* @return The response from the tool, if null return {@link OllamaToolResponse}
|
||||
* @throws OllamaToolErrorException If the tool encounters an error
|
||||
*/
|
||||
abstract public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args);
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
import me.neurodock.core.Core;
|
||||
import me.neurodock.core.Pair;
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Spliterator;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class OllamaFunctionTools implements Iterable<Pair<OllamaFunctionTool, String>> {
|
||||
/**
|
||||
* A list of tools for the OllamaObject.
|
||||
* OPS! Shuld only be ussed to add a set of tools to the OllamaObject, not to be used for storage of tools internaly or externaly
|
||||
*/
|
||||
private ArrayList<OllamaFunctionTool> tools;
|
||||
/**
|
||||
* A list of source for the tools.
|
||||
* OPS! Shuld only be ussed to add a set of tools to the OllamaObject, not to be used for storage of tools internaly or externaly
|
||||
* OPS! most be the same size as the tools list! since each tool matches to a source!
|
||||
*/
|
||||
private ArrayList<String> source;
|
||||
|
||||
/**
|
||||
* Gets the tools of the {@link OllamaFunctionTools}
|
||||
* @param tools A list of {@link OllamaFunctionTool}
|
||||
* @param source The source of the tools
|
||||
*/
|
||||
private OllamaFunctionTools(ArrayList<OllamaFunctionTool> tools, ArrayList<String> source) {
|
||||
if(source == null || tools == null || source.size() != tools.size() || source.isEmpty())
|
||||
throw new IllegalArgumentException("The source and tools must be the same size! and not empty!");
|
||||
this.tools = tools;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tools of the {@link OllamaFunctionTools}
|
||||
* @param tools A list of {@link OllamaFunctionTool}
|
||||
* @param source The source of the tools
|
||||
*/
|
||||
private OllamaFunctionTools(OllamaFunctionTool[] tools, @MagicConstant(valuesFromClass = Core.Source.class) String[] source) {
|
||||
if(source == null || tools == null || source.length != tools.length || source.length == 0)
|
||||
throw new IllegalArgumentException("The source and tools must be the same size! and not empty!");
|
||||
this.tools = new ArrayList<>();
|
||||
this.source = new ArrayList<>();
|
||||
|
||||
for (OllamaFunctionTool tool : tools) {
|
||||
this.tools.add(tool);
|
||||
this.source.add(tool.name());
|
||||
}
|
||||
|
||||
for (String s : source) {
|
||||
if (s.equals(Core.Source.CTP)) {
|
||||
this.source.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static OllamaFunctionToolsBuilder builder() {
|
||||
return new OllamaFunctionToolsBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Iterator<Pair<OllamaFunctionTool, String>> iterator() {
|
||||
ArrayList<Pair<OllamaFunctionTool, String>> pairs = new ArrayList<>();
|
||||
for (int i = 0; i < tools.size(); i++) {
|
||||
pairs.add(new Pair<>(tools.get(i), source.get(i)));
|
||||
}
|
||||
return pairs.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super Pair<OllamaFunctionTool, String>> action) {
|
||||
for (Pair<OllamaFunctionTool, String> pair : this) {
|
||||
action.accept(pair);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<Pair<OllamaFunctionTool, String>> spliterator() {
|
||||
// TODO: Implement this method
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
//return Iterable.super.spliterator();
|
||||
}
|
||||
|
||||
public static class OllamaFunctionToolsBuilder {
|
||||
private ArrayList<OllamaFunctionTool> tools = new ArrayList<>();
|
||||
private ArrayList<String> source = new ArrayList<>();
|
||||
|
||||
public OllamaFunctionToolsBuilder addTool(OllamaFunctionTool tool, @MagicConstant(valuesFromClass = Core.Source.class) String source) {
|
||||
this.tools.add(tool);
|
||||
this.source.add(source);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OllamaFunctionToolsBuilder addTools(HashMap<OllamaFunctionTool, String> tools) {
|
||||
for (OllamaFunctionTool tool : tools.keySet()) {
|
||||
this.tools.add(tool);
|
||||
this.source.add(tools.get(tool));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public OllamaFunctionTools build() {
|
||||
return new OllamaFunctionTools(tools, source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONArray;
|
||||
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.
|
||||
*/
|
||||
public class OllamaMessage {
|
||||
/**
|
||||
* The role of the message.
|
||||
*/
|
||||
OllamaMessageRole role;
|
||||
/**
|
||||
* The content of the message.
|
||||
*/
|
||||
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.
|
||||
* @param role The role of the message
|
||||
* @param content The content of the message
|
||||
*/
|
||||
public OllamaMessage(OllamaMessageRole role, String content) {
|
||||
this.role = role;
|
||||
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}
|
||||
*/
|
||||
public OllamaMessageRole getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The "message" or content sent by a source
|
||||
*/
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public JSONObject toJSON() {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("role", role.getRole());
|
||||
json.put("content", content.replace("\n", "\\n"));
|
||||
if(images != null && !images.isEmpty())
|
||||
{
|
||||
json.put("images", new JSONArray(images));
|
||||
}
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
/**
|
||||
* Represents the role of a message.
|
||||
* This is used by Ollama to determine the role of a message.
|
||||
*/
|
||||
public enum OllamaMessageRole {
|
||||
/**
|
||||
* Represents a user message.
|
||||
*/
|
||||
USER("user"),
|
||||
/**
|
||||
* Represents an assistant message.
|
||||
*/
|
||||
ASSISTANT("assistant"),
|
||||
/**
|
||||
* Represents a tool message
|
||||
*/
|
||||
TOOL("tool"),
|
||||
/**
|
||||
* Represents a system message.
|
||||
*/
|
||||
SYSTEM("system");
|
||||
|
||||
/**
|
||||
* The role of the message.
|
||||
*/
|
||||
private String role;
|
||||
|
||||
/**
|
||||
* Creates a new instance of OllamaMessageRole.
|
||||
* @param role The role of the message
|
||||
*/
|
||||
OllamaMessageRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the role of the message.
|
||||
* @return The role of the message
|
||||
*/
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the role of the message from a string.
|
||||
* @param role The role of the message as a string
|
||||
* @return The role of the message
|
||||
*/
|
||||
public static OllamaMessageRole fromRole(String role) {
|
||||
for(OllamaMessageRole roleRole : values()) {
|
||||
if(roleRole.role.equals(role.toLowerCase()))
|
||||
return roleRole;
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid role: " + role);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents a message sent by a Tool.
|
||||
*/
|
||||
public class OllamaMessageToolCall extends OllamaMessage{
|
||||
|
||||
/**
|
||||
* The tool calls in the message
|
||||
*/
|
||||
private JSONArray tool_calls;
|
||||
|
||||
/**
|
||||
* Creates a new instance of OllamaMessage
|
||||
* @param role The role of the message
|
||||
* @param content The content of the message
|
||||
* @param tool_calls The tool calls in the message
|
||||
*/
|
||||
public OllamaMessageToolCall(OllamaMessageRole role, String content, JSONArray tool_calls) {
|
||||
super(role, content);
|
||||
this.tool_calls = tool_calls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject toJSON() {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("role", role);
|
||||
json.put("content", content);
|
||||
json.put("tool_calls", tool_calls);
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -1,730 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
import me.neurodock.core.*;
|
||||
import me.neurodock.core.files.FileHandler;
|
||||
|
||||
import org.intellij.lang.annotations.MagicConstant;
|
||||
import org.json.JSONArray;
|
||||
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.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Represents an Ollama Object.
|
||||
* This is used to represent the state of the Ollama Object.
|
||||
* This is used by the Core to store the state of the Ollama Object.
|
||||
* This is used by the API to send the state of the Ollama Object to the client.
|
||||
* @see Core#setOllamaObject(OllamaObject)
|
||||
*/
|
||||
public class OllamaObject {
|
||||
/**
|
||||
* The model of the Ollama Object.
|
||||
*/
|
||||
String model;
|
||||
/**
|
||||
* The messages of the Ollama Object.
|
||||
*/
|
||||
ArrayList<OllamaMessage> messages;
|
||||
/**
|
||||
* The tools of the Ollama Object.
|
||||
*/
|
||||
ArrayList<Pair<OllamaTool, String>> tools;
|
||||
/**
|
||||
* The format of the Ollama Object.
|
||||
*/
|
||||
JSONObject format;
|
||||
/**
|
||||
* The options of the Ollama Object.
|
||||
*/
|
||||
Map<String, Object> options;
|
||||
/**
|
||||
* If the Ollama Object is streamed.
|
||||
*/
|
||||
boolean stream;
|
||||
/**
|
||||
* The keep alive of the Ollama Object.
|
||||
*/
|
||||
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.
|
||||
* @param model The model of the Ollama Object. see {@link OllamaObject#model}
|
||||
* @param messages The messages of the Ollama Object. see {@link OllamaObject#messages}
|
||||
* @param tools The tools of the Ollama Object. see {@link OllamaObject#tools}
|
||||
* @param format The format of the Ollama Object. see {@link OllamaObject#format}
|
||||
* @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 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, Thinking thinking) {
|
||||
this.model = model;
|
||||
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.
|
||||
for(Pair<OllamaTool, String> tool : tools)
|
||||
{
|
||||
Class<?> clazz = tool.getKey().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.getKey(), tool.getValue());
|
||||
} catch (IllegalAccessException e) {
|
||||
Core.writeLog("ERROR: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.tools = tools;
|
||||
this.format = format;
|
||||
this.options = options;
|
||||
this.stream = stream;
|
||||
this.keep_alive = keep_alive;
|
||||
LaunchOptions launchOptions = LaunchOptions.getInstance();
|
||||
if(launchOptions.isLoadOld()) {
|
||||
System.out.println("Loading old data...");
|
||||
File f = new File(Options.getInstance().getDataDir(), "messages.json");
|
||||
if(f.exists()) {
|
||||
try {
|
||||
BufferedReader br = new BufferedReader(new FileReader(f));
|
||||
StringBuilder data = new StringBuilder();
|
||||
String buffer = null;
|
||||
while ((buffer = br.readLine()) != null) {
|
||||
data.append(buffer).append("\n");
|
||||
}
|
||||
JSONArray jsonArray = new JSONArray(data.toString());
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
JSONObject obj = jsonArray.getJSONObject(i);
|
||||
OllamaMessage message;
|
||||
if (!obj.has("tool_calls")) {
|
||||
message = new OllamaMessage(OllamaMessageRole.fromRole(obj.getString("role")), obj.getString("content"));
|
||||
} else {
|
||||
message = new OllamaMessageToolCall(OllamaMessageRole.fromRole(obj.getString("role")), obj.getString("content"), obj.getJSONArray("tool_calls"));
|
||||
}
|
||||
this.messages.add(message);
|
||||
}
|
||||
}catch (Exception e) {
|
||||
System.out.println("Error loading old data");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
System.out.println("No old data found, skipping loading old data.");
|
||||
}
|
||||
}
|
||||
|
||||
if(prompt != null) {
|
||||
prompt.generateCapabilities(this);
|
||||
if (!this.messages.isEmpty()) {
|
||||
OllamaMessage systemPrompt = this.messages.getFirst();
|
||||
if (systemPrompt.getRole() != OllamaMessageRole.SYSTEM) {
|
||||
System.out.println("FIRST MESSAGE ISN'T A SYSTEM PROMPT!. Pushing all messages forward and injecting the system prompt");
|
||||
Core.writeLog("FIRST MESSAGE ISN'T A SYSTEM PROMPT!. Pushing all messages forward and injecting the system prompt");
|
||||
ArrayList<OllamaMessage> newMessages = new ArrayList<>();
|
||||
newMessages.add(prompt.generateSystemPrompt());
|
||||
newMessages.addAll(messages);
|
||||
this.messages = newMessages;
|
||||
} else {
|
||||
System.out.println("Replacing System prompt...");
|
||||
Core.writeLog("Replacing System prompt...");
|
||||
systemPrompt = prompt.generateSystemPrompt();
|
||||
this.messages.set(0, systemPrompt);
|
||||
}
|
||||
} else {
|
||||
System.out.println("No old prompts. Inserting system prompt...");
|
||||
Core.writeLog("No old prompts. Inserting system prompt...");
|
||||
this.messages.add(prompt.generateSystemPrompt());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-generates and sets the system promp according to the {@link LLMSystemPrompt} stored as {@link OllamaObject#systemPrompt}
|
||||
*/
|
||||
public void reGenerateSystemPrompt()
|
||||
{
|
||||
if(systemPrompt != null) {
|
||||
systemPrompt.generateCapabilities(this);
|
||||
setSystemPrompt(systemPrompt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model of the Ollama Object.
|
||||
* @return The model of the Ollama Object
|
||||
*/
|
||||
public String getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the messages
|
||||
* @return The messages
|
||||
*/
|
||||
public ArrayList<OllamaMessage> getMessages() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tools
|
||||
* @return The tools
|
||||
*/
|
||||
public ArrayList<Pair<OllamaTool, String>> getTools() {
|
||||
return tools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tool to the Ollama Object
|
||||
* @param tool The tool to add
|
||||
*/
|
||||
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<?> 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) {
|
||||
tools.remove(tool);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the format of the Ollama Object.
|
||||
* @return The format of the Ollama Object
|
||||
*/
|
||||
public JSONObject getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the options of the Ollama Object.
|
||||
* @return The options of the Ollama Object
|
||||
*/
|
||||
public Map<String, Object> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets if the Ollama Object is streamed.
|
||||
* @return If the Ollama Object is streamed
|
||||
*/
|
||||
public boolean isStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the keep alive of the Ollama Object.
|
||||
* @return The keep alive of the Ollama Object
|
||||
*/
|
||||
public String getKeep_alive() {
|
||||
return keep_alive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to the Ollama Object
|
||||
* @param message The message to add
|
||||
*/
|
||||
public void addMessage(OllamaMessage message) {
|
||||
messages.add(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current list of messages, AND clears them.
|
||||
* You will need to re-submit the System prompt if you had one
|
||||
* @return an ArrayList containing all messages stored in this {@link OllamaObject}
|
||||
*/
|
||||
public List<OllamaMessage> dumpMessages()
|
||||
{
|
||||
ArrayList<OllamaMessage> messages = new ArrayList<>(this.messages);
|
||||
this.messages.clear();
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all messages from a povided list to this {@link OllamaObject}
|
||||
* @param messages the message list to add
|
||||
*/
|
||||
public void addAllMessages(List<OllamaMessage> messages) {
|
||||
this.messages.addAll(messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the system message, replacing any existing one at the start of {@link #messages},
|
||||
* or inserting one at the start if none exists yet.
|
||||
* @param message the system message text
|
||||
*/
|
||||
public void setSystemMessage(String message) {
|
||||
applySystemPrompt(new OllamaMessage(OllamaMessageRole.SYSTEM, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current system prompt if one exists at the start of {@link #messages},
|
||||
* or inserts one at the start if none exists yet.
|
||||
*/
|
||||
public void setSystemPrompt(LLMSystemPrompt prompt) {
|
||||
applySystemPrompt(prompt.generateSystemPrompt()); // shared with the constructor
|
||||
}
|
||||
|
||||
private void applySystemPrompt(OllamaMessage message) {
|
||||
if (message == null) return;
|
||||
if (messages.isEmpty()) {
|
||||
messages.add(message);
|
||||
} else if (messages.getFirst().getRole() != OllamaMessageRole.SYSTEM) {
|
||||
messages.addFirst(message);
|
||||
} else {
|
||||
messages.set(0, message);
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject toJSON()
|
||||
{
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
JSONArray tools = new JSONArray();
|
||||
for (Pair<OllamaTool, String> tool : this.tools) {
|
||||
if(tool.getKey().getClass().isInterface()) continue;
|
||||
JSONObject obj = tool.getKey().toJSON();
|
||||
//obj.put("name", obj.getString("name") + tool.getValue()); // Injects the source of the tool into the name
|
||||
tools.put(obj);
|
||||
}
|
||||
|
||||
JSONArray messages = new JSONArray();
|
||||
for (OllamaMessage message : this.messages) {
|
||||
messages.put(message.toJSON());
|
||||
}
|
||||
|
||||
json.put("model", model);
|
||||
json.put("messages", messages);
|
||||
json.put("tools", tools);
|
||||
json.put("format", format);
|
||||
json.put("options", options);
|
||||
json.put("stream", stream);
|
||||
json.put("keep_alive", keep_alive);
|
||||
thinking.putInto(json, "think");
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of OllamaObjectBuilder.
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public static OllamaObjectBuilder builder()
|
||||
{
|
||||
return new OllamaObjectBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a builder for OllamaObject.
|
||||
*/
|
||||
public static class OllamaObjectBuilder {
|
||||
/**
|
||||
* The model of the Ollama Object.
|
||||
*/
|
||||
String model;
|
||||
/**
|
||||
* The messages of the Ollama Object.
|
||||
*/
|
||||
ArrayList<OllamaMessage> messages = new ArrayList<>();
|
||||
/**
|
||||
* The tools of the Ollama Object.
|
||||
*/
|
||||
ArrayList<Pair<OllamaTool, String>> tools = new ArrayList<>();
|
||||
/**
|
||||
* The format of the Ollama Object.
|
||||
*/
|
||||
JSONObject format;
|
||||
/**
|
||||
* The options of the Ollama Object.
|
||||
*/
|
||||
Map<String, Object> options = new HashMap<>();
|
||||
/**
|
||||
* If the Ollama Object is streamed.
|
||||
*/
|
||||
boolean stream = false;
|
||||
/**
|
||||
* The keep alive of the Ollama Object.
|
||||
*/
|
||||
String keep_alive;
|
||||
/**
|
||||
* This represents the System Prompt for the LLM
|
||||
*/
|
||||
LLMSystemPrompt systemPrompt;
|
||||
/**
|
||||
* Weather the model should think or not (requires supportive model)
|
||||
*/
|
||||
Thinking think;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link OllamaObjectBuilder}.
|
||||
*/
|
||||
public OllamaObjectBuilder() {}
|
||||
|
||||
/**
|
||||
* Sets the format of the Ollama Object as a JSON schema.
|
||||
* @param format The format of the Ollama Object as a JSON schema
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder format(String format) {
|
||||
this.format = new JSONObject(format);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the options of the Ollama Object.
|
||||
* @param options The options of the Ollama Object
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder options(Map<String, Object> options) {
|
||||
this.options.putAll(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an option of the Ollama Object.
|
||||
* @param key The key of the option
|
||||
* @param value The value of the option
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder option(String key, String value) {
|
||||
this.options.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the Ollama Object is streamed.
|
||||
* @param stream If the Ollama Object is streamed
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder stream(boolean stream) {
|
||||
this.stream = stream;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keep alive of the Ollama Object.<br>
|
||||
* This is a string formated as "minutes"m or "hours"h or "days"d
|
||||
* @param keep_alive The keep alive of the Ollama Object
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder keep_alive(String keep_alive) {
|
||||
this.keep_alive = keep_alive;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the keep alive of the Ollama Object.<br>
|
||||
* @param minutes The keep alive of the Ollama Object in minutes
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder keep_alive(int minutes) {
|
||||
this.keep_alive = minutes+"m";
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(new Pair<>(tool, Core.Source.CTP));
|
||||
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 addToolsExternal(ArrayList<? extends OllamaTool> tools) {
|
||||
for (OllamaTool tool : tools) {
|
||||
this.tools.add(new Pair<>(tool, Core.Source.CTP));
|
||||
}
|
||||
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.CTP));
|
||||
}
|
||||
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}
|
||||
*/
|
||||
@SafeVarargs
|
||||
public final OllamaObjectBuilder addTools(Pair<OllamaTool, String>... tools) {
|
||||
this.tools.addAll(List.of(tools));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds messages to the Ollama Object
|
||||
* @param messages The messages to add
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder addMessages(OllamaMessage... messages) {
|
||||
this.messages.addAll(List.of(messages));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a message to the Ollama Object
|
||||
* @param messages The message to add
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder addMessage(OllamaMessage messages) {
|
||||
this.messages.add(messages);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the model of the Ollama Object
|
||||
* @param model The model of the Ollama Object
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder setModel(String model) {
|
||||
this.model = model;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the {@link FileHandler} singleton with the given base directory and adds its tools.
|
||||
* <p>
|
||||
* The {@link FileHandler} is intentionally constructed here rather than eagerly,
|
||||
* as file access tooling should only be initialized if explicitly requested during
|
||||
* object construction. Calling this after {@link #build()} is technically possible
|
||||
* but not intended.
|
||||
* </p>
|
||||
* @param baseDirectory the base directory for file access.
|
||||
* @return The {@link OllamaObjectBuilder}
|
||||
*/
|
||||
public OllamaObjectBuilder addFileTools(Path baseDirectory)
|
||||
{
|
||||
new FileHandler(baseDirectory);
|
||||
|
||||
//throw new IllegalArgumentException("FileHandler is not supported yet!");
|
||||
return addTools(FileHandler.getTools());
|
||||
}
|
||||
|
||||
|
||||
public OllamaObjectBuilder setSystemPrompt(LLMSystemPrompt systemPrompt) {
|
||||
this.systemPrompt = systemPrompt;
|
||||
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}
|
||||
* @return The {@link OllamaObject}
|
||||
*/
|
||||
public OllamaObject build() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
import me.neurodock.plugin.tool.ToolParameters;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Represents the parameters of a tool.
|
||||
* This is used by Ollama to determine the parameters of a tool.
|
||||
*/
|
||||
public class OllamaPerameter {
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link OllamaPerameter}.
|
||||
* @param properties The properties of the parameters
|
||||
* @param required The required parameters
|
||||
*/
|
||||
private OllamaPerameter(JSONObject properties, String[] required) {
|
||||
this.properties = properties;
|
||||
this.required = required;
|
||||
};
|
||||
|
||||
public JSONObject toJSON() {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("type", "object");
|
||||
json.put("properties", properties);
|
||||
json.put("required", required);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* the properties of the parameters
|
||||
*/
|
||||
private final JSONObject properties;
|
||||
/**
|
||||
* the required parameters
|
||||
*/
|
||||
private final String[] required;
|
||||
|
||||
/**
|
||||
* Gets the properties of the {@link OllamaPerameter}
|
||||
* @return The properties of the {@link OllamaPerameter}
|
||||
*/
|
||||
public JSONObject getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the required parameters of the {@link OllamaPerameter}
|
||||
* @return The required parameters of the {@link OllamaPerameter}
|
||||
*/
|
||||
public String[] getRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link OllamaPerameterBuilder}.
|
||||
* @return The {@link OllamaPerameterBuilder}
|
||||
*/
|
||||
public static OllamaPerameterBuilder builder() {
|
||||
return new OllamaPerameterBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an empty {@link OllamaPerameter}
|
||||
* @return an empty {@link OllamaPerameter}
|
||||
* @apiNote This is equvalent to
|
||||
* <pre>{@code
|
||||
* OllamaPerameter.builder().build();
|
||||
* }</pre>
|
||||
*/
|
||||
public static OllamaPerameter empty() {
|
||||
return builder().build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a builder for {@link OllamaPerameter}.
|
||||
*/
|
||||
public static class OllamaPerameterBuilder {
|
||||
/**
|
||||
* The properties of the parameters.
|
||||
*/
|
||||
Map<String, Property> propertyMap = new HashMap<>();
|
||||
/**
|
||||
* The required parameters.
|
||||
*/
|
||||
ArrayList<String> required = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Add an optinal perameter to this {@link OllamaPerameterBuilder}
|
||||
* @param name The name of the parameter
|
||||
* @param type The type of the parameter
|
||||
* @param description The description of the parameter
|
||||
* @return The {@link OllamaPerameterBuilder}
|
||||
* @apiNote Prefer {@link #addProperty(String, Type, String, boolean)} to be explicit about required state
|
||||
*/
|
||||
public OllamaPerameterBuilder addProperty(String name, Type type, String description) {
|
||||
return OllamaPerameterBuilder.this.addProperty(name, type, description, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a potentialy required peremeter to this {@link OllamaPerameterBuilder}.
|
||||
* @param name The name of the parameter
|
||||
* @param type The type of the parameter
|
||||
* @param description The description of the parameter
|
||||
* @param required The required state of the parameter
|
||||
* @return The {@link OllamaPerameterBuilder}
|
||||
*/
|
||||
public OllamaPerameterBuilder addProperty(String name, Type type, String description, boolean required) {
|
||||
if(name == null || type == null || description == null) {
|
||||
return this;
|
||||
}
|
||||
propertyMap.put(name, new Property(type.getType(), description));
|
||||
if(required) {
|
||||
this.required.add(name);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T extends Enum<T>> OllamaPerameterBuilder addEnumProperty(String name, Class<T> enumClass, String description, boolean required) {
|
||||
StringBuilder newDescription = new StringBuilder(description);
|
||||
if(newDescription.charAt(newDescription.length() - 1) != '.') {
|
||||
newDescription.append(". ");
|
||||
}
|
||||
else
|
||||
{
|
||||
newDescription.append(' ');
|
||||
}
|
||||
|
||||
newDescription.append("Enum values: ");
|
||||
|
||||
Iterator<T> it = Arrays.stream(enumClass.getEnumConstants()).iterator();
|
||||
|
||||
while(it.hasNext()) {
|
||||
T item = it.next();
|
||||
newDescription.append(item.name());
|
||||
if(it.hasNext()) {
|
||||
newDescription.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
return addProperty(name, Type.ENUM, newDescription.toString(), required);
|
||||
}
|
||||
|
||||
public <T extends Enum<T>> OllamaPerameterBuilder addEnumProperty(Class<T> enumClass, String description) {
|
||||
return addEnumProperty(enumClass.getSimpleName(), enumClass, description, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a previusly optinal perameter required for this {@link OllamaPerameterBuilder}
|
||||
* @param name The name of the parameter
|
||||
* @return The {@link OllamaPerameterBuilder}
|
||||
*/
|
||||
public OllamaPerameterBuilder required(String name) {
|
||||
required.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a property from the parameters.
|
||||
* @param name The name of the property to remove
|
||||
* @return The {@link OllamaPerameterBuilder}
|
||||
*/
|
||||
public OllamaPerameterBuilder removeProperty(String name) {
|
||||
propertyMap.remove(name);
|
||||
required.remove(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coverts a RPCP Tool Perameter to a CTP Ollama Peameter
|
||||
* @param parameters
|
||||
* @return
|
||||
*/
|
||||
public OllamaPerameterBuilder of(ToolParameters parameters) {
|
||||
required.addAll(Arrays.asList(parameters.getRequired()));
|
||||
for(String key : parameters.getProperties().keySet())
|
||||
{
|
||||
Property property = new Property(parameters.getProperties().getJSONObject(key).getString("type"),
|
||||
parameters.getProperties().getJSONObject(key).getString("description"));
|
||||
propertyMap.put(key, property);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the {@link OllamaPerameter}
|
||||
* @return The {@link OllamaPerameter}
|
||||
*/
|
||||
public OllamaPerameter build() {
|
||||
JSONObject properties = new JSONObject();
|
||||
for(String name : propertyMap.keySet()) {
|
||||
properties.put(name, propertyMap.get(name).toJSON());
|
||||
}
|
||||
return new OllamaPerameter(properties, required.toArray(new String[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a property of a parameter.
|
||||
*/
|
||||
private class Property {
|
||||
/**
|
||||
* The type of the property.
|
||||
*/
|
||||
String type;
|
||||
/**
|
||||
* The description of the property.
|
||||
*/
|
||||
String description;
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link Property}.
|
||||
* @param type The type of the property
|
||||
* @param description The description of the property
|
||||
*/
|
||||
public Property(String type, String description) {
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public JSONObject toJSON() {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put("type", type);
|
||||
json.put("description", description);
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the type of parameter.
|
||||
*/
|
||||
public enum Type {
|
||||
/**
|
||||
* Represents a string parameter.
|
||||
*/
|
||||
STRING("string"),
|
||||
/**
|
||||
* Represents an integer parameter.
|
||||
*/
|
||||
INT("int"),
|
||||
/**
|
||||
* Represents a boolean parameter.
|
||||
*/
|
||||
BOOLEAN("boolean"),
|
||||
/**
|
||||
* Represents a enum parameter.
|
||||
*/
|
||||
ENUM("enum"),
|
||||
/**
|
||||
* Represents a array parameter.
|
||||
*/
|
||||
ARRAY("array"),
|
||||
/**
|
||||
* Represents a object parameter.
|
||||
*/
|
||||
OBJECT("object");
|
||||
|
||||
/**
|
||||
* The type of the parameter.
|
||||
*/
|
||||
private final String type;
|
||||
|
||||
/**
|
||||
* Gets the type of the parameter.
|
||||
* @return The type of the parameter
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link Type}.
|
||||
* @param type The type of the parameter
|
||||
*/
|
||||
Type(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
Type of(String type)
|
||||
{
|
||||
return switch (type) {
|
||||
case "string" -> STRING;
|
||||
case "int" -> INT;
|
||||
case "boolean" -> BOOLEAN;
|
||||
case "enum" -> ENUM;
|
||||
case "array" -> ARRAY;
|
||||
case "object" -> OBJECT;
|
||||
default -> throw new IllegalArgumentException("Unknown type " + type);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents a tool.
|
||||
*/
|
||||
public interface OllamaTool {
|
||||
public JSONObject toJSON();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package me.neurodock.ollama;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an error from a tool.<br>
|
||||
* This is used by a tool to indicate to Ollama that an error occurred.
|
||||
*/
|
||||
public class OllamaToolError extends OllamaMessage {
|
||||
/**
|
||||
* The error from the tool.
|
||||
*/
|
||||
String error;
|
||||
|
||||
/**
|
||||
* Creates a new instance of OllamaToolError.
|
||||
* @param error The error from the tool
|
||||
*/
|
||||
public OllamaToolError(String error) {
|
||||
super(OllamaMessageRole.TOOL, new JSONObject().put("error", error).toString());
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error from the tool.
|
||||
* @return The error from the tool
|
||||
*/
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package me.neurodock.ollama.exceptions;
|
||||
|
||||
import me.neurodock.core.Core;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Represents an error from a tool.<br>
|
||||
* This is used internally by tools instead of {@link Exception}, to then be handled gracefully by {@link Core#handleResponse(JSONObject)}
|
||||
*/
|
||||
public class OllamaToolErrorException extends RuntimeException {
|
||||
/**
|
||||
* The tool that caused the error.
|
||||
*/
|
||||
private final String tool;
|
||||
/**
|
||||
* The error from the tool.
|
||||
*/
|
||||
private final String error;
|
||||
|
||||
/**
|
||||
* Creates a new instance of OllamaToolErrorException.
|
||||
* @param tool The tool that caused the error
|
||||
* @param error The error from the tool
|
||||
*/
|
||||
public OllamaToolErrorException(String tool, String error) {
|
||||
super(tool + ": " + error);
|
||||
this.tool = tool;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public OllamaToolErrorException(String tool, Exception ex) {
|
||||
this(tool, ex.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tool that caused the error.
|
||||
* @return The tool that caused the error
|
||||
*/
|
||||
public String getTool() {
|
||||
return tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the error from the tool.
|
||||
* @return The error from the tool
|
||||
*/
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package me.neurodock.ollama.utils;
|
||||
|
||||
import me.neurodock.ollama.OllamaMessage;
|
||||
import me.neurodock.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);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,9 @@ package me.neurodock.plugin.loader;
|
||||
|
||||
import jdk.jshell.spi.ExecutionControl;
|
||||
import me.neurodock.core.Pair;
|
||||
import me.neurodock.ollama.*;
|
||||
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.plugin.Data;
|
||||
import me.neurodock.plugin.LoadedPlugin;
|
||||
import me.neurodock.plugin.Plugin;
|
||||
@@ -11,11 +12,11 @@ import me.neurodock.plugin.PluginMetadata;
|
||||
import me.neurodock.plugin.exceptions.PluginLoadingException;
|
||||
import me.neurodock.plugin.exceptions.ToolRuntimeException;
|
||||
import me.neurodock.plugin.tool.Tool;
|
||||
import me.neurodock.plugin.tool.ToolArguments;
|
||||
import me.neurodock.plugin.tool.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.naming.OperationNotSupportedException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -40,12 +41,12 @@ public class Loader {
|
||||
* Mock example!
|
||||
* @return
|
||||
*/
|
||||
public OllamaFunctionTool[] getTools(Plugin plugin) {
|
||||
public me.neurodock.llm.tools.Tool[] getTools(Plugin plugin) {
|
||||
|
||||
ArrayList<OllamaFunctionTool> tools = new ArrayList<>();
|
||||
ArrayList<me.neurodock.llm.tools.Tool> tools = new ArrayList<>();
|
||||
|
||||
for(Tool tool : plugin.getTools()) {
|
||||
tools.add(new OllamaFunctionTool() {
|
||||
tools.add(new FunctionTool() {
|
||||
@Override
|
||||
public @NotNull String name() {
|
||||
return tool.name() + "_" + plugin.getMetadata().getName();
|
||||
@@ -57,14 +58,16 @@ public class Loader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder().of(tool.parameters()).build();
|
||||
public @NotNull ToolParameters parameters() {
|
||||
throw new RuntimeException(new OperationNotSupportedException("No"));
|
||||
//return ToolParameters.builder().of(tool.parameters()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
public @NotNull me.neurodock.llm.tools.ToolResponse function(ToolArguments args) {
|
||||
throw new UnsupportedOperationException("no");
|
||||
// Wrap OllamaFunctionArguments[] to ether ToolArgument[] or ToolArguments(current implementation)
|
||||
ToolArguments toolArgs = new ToolArguments();
|
||||
/*ToolArguments toolArgs = new ToolArguments();
|
||||
for (OllamaFunctionArgument arg : args) {
|
||||
toolArgs.addArgument(arg.argument(), arg.value());
|
||||
}
|
||||
@@ -75,17 +78,12 @@ public class Loader {
|
||||
throw new OllamaToolErrorException(name(), e);
|
||||
}
|
||||
// Wrap ToolResponce to an OllamaToolRespnce(Well I see a typo here now)
|
||||
return new OllamaToolResponse(toolResponse.name(), toolResponse.response());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject toJSON() {
|
||||
return tool.getToolJSON();
|
||||
return new OllamaToolResponse(toolResponse.name(), toolResponse.response());*/
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return tools.toArray(tools.toArray(new OllamaFunctionTool[0]));
|
||||
return tools.toArray(tools.toArray(new me.neurodock.llm.tools.Tool[0]));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
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,12 +0,0 @@
|
||||
package plugin;
|
||||
|
||||
import me.neurodock.plugin.Plugin;
|
||||
import me.neurodock.plugin.PluginMetadata;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class Test extends Plugin {
|
||||
@Override
|
||||
public @NotNull PluginMetadata getMetadata() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package plugin;
|
||||
|
||||
//// This class is broken due to the current refactoring on the RPCP part of this project.
|
||||
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
//import me.zacharias.chat.plugin.annotation.OllamaTool;
|
||||
//import me.zacharias.chat.plugin.annotation.injectons.InjectPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
//@OllamaTool
|
||||
public class Tool extends OllamaFunctionTool {
|
||||
//@InjectPlugin(classType = Test.class)
|
||||
Test core;
|
||||
|
||||
@Override
|
||||
public @NotNull String name() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
package me.neurodock.display;
|
||||
|
||||
import me.neurodock.backend.open.ai.OpenAIModel;
|
||||
import me.neurodock.core.Core;
|
||||
import me.neurodock.core.Options;
|
||||
import me.neurodock.core.Pair;
|
||||
import me.neurodock.core.PrintMessageHandler;
|
||||
import me.neurodock.core.memory.CoreMemory;
|
||||
import me.neurodock.llm.ChatObject;
|
||||
import me.neurodock.llm.Message;
|
||||
import me.neurodock.llm.exceptions.ToolException;
|
||||
import me.neurodock.llm.tools.*;
|
||||
import me.neurodock.ollama.*;
|
||||
import me.neurodock.ollama.utils.SystemMessage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.*;
|
||||
@@ -21,7 +27,14 @@ import static me.neurodock.core.Core.writeLog;
|
||||
* Somewhat meant to be used as a Debug tool for testing your API.
|
||||
*/
|
||||
public class Display {
|
||||
|
||||
|
||||
/*
|
||||
* Inizilizer
|
||||
*/
|
||||
{
|
||||
Options.getInstance().setDataDir(Path.of("AI-Chat"), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Core instance.
|
||||
*/
|
||||
@@ -43,16 +56,17 @@ public class Display {
|
||||
*/
|
||||
public Display()
|
||||
{
|
||||
OpenAIModel model = new OpenAIModel("http://localhost:8080", "MiniCPM5");
|
||||
|
||||
Options.getInstance().setDataDir(Path.of("AI-Chat"), false);
|
||||
|
||||
core.setOllamaObject/*NoMemory*/(OllamaObject.builder()
|
||||
/*core.setOllamaObject/*NoMemory*//*(OllamaObject.builder()
|
||||
.setModel("llama3.1:8b")
|
||||
.keep_alive(10)
|
||||
//.stream(false)
|
||||
.addFileTools(Options.getInstance().getFileHandlerDataLocation())
|
||||
.addMessages(new SystemMessage("You need to emulate feelings and emotions."))
|
||||
.build());
|
||||
.build());*/
|
||||
|
||||
core.setModel(model);
|
||||
|
||||
core.enablePlugins(Options.getInstance().getPluginDirectory());
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package me.neurodock.genius;
|
||||
|
||||
import me.neurodock.ollama.OllamaFunctionTool;
|
||||
import me.neurodock.llm.tools.FunctionTool;
|
||||
|
||||
public abstract class GeniusEndpointTool extends OllamaFunctionTool {
|
||||
public abstract class GeniusEndpointTool extends FunctionTool {
|
||||
protected GeniusTools geniusToolsInstance;
|
||||
|
||||
public GeniusEndpointTool(GeniusTools geniusTools) {
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package me.neurodock.genius.endpoints;
|
||||
|
||||
import me.neurodock.ollama.OllamaFunctionArgument;
|
||||
import me.neurodock.ollama.OllamaPerameter;
|
||||
import me.neurodock.ollama.OllamaToolResponse;
|
||||
import me.neurodock.ollama.exceptions.OllamaToolErrorException;
|
||||
import me.neurodock.genius.GeniusEndpoint;
|
||||
import me.neurodock.genius.GeniusEndpointTool;
|
||||
import me.neurodock.genius.GeniusTools;
|
||||
import me.neurodock.llm.tools.ToolArguments;
|
||||
import me.neurodock.llm.tools.ToolParameters;
|
||||
import me.neurodock.llm.tools.ToolResponse;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.json.JSONObject;
|
||||
import org.jsoup.Jsoup;
|
||||
@@ -49,20 +48,16 @@ public class GetLyrics extends GeniusEndpointTool {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaPerameter parameters() {
|
||||
return OllamaPerameter.builder()
|
||||
.addProperty("song_id", OllamaPerameter.OllamaPerameterBuilder.Type.INT, "The ID of the song to get lyrics for.", true)
|
||||
public @NotNull ToolParameters parameters() {
|
||||
return ToolParameters.builder()
|
||||
.addProperty("song_id", ToolParameters.ToolParametersBuilder.Type.INT, "The ID of the song to get lyrics for.", true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OllamaToolResponse function(OllamaFunctionArgument... args) {
|
||||
public @NotNull ToolResponse function(ToolArguments 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());
|
||||
String lyricsStr = geniusToolsInstance.hasCache(args.getArgument("song_id", Integer.class));
|
||||
|
||||
if(lyricsStr != null)
|
||||
{
|
||||
|
||||
+2
-2
@@ -2,6 +2,6 @@
|
||||
useAPI = true
|
||||
javaVersion = 25
|
||||
|
||||
coreVersion = 1.10.1
|
||||
pluginAPIVersion = 0.1.4.1
|
||||
coreVersion = 2.1.5
|
||||
pluginAPIVersion = 1.1.4.1
|
||||
APIVersion = 1.0-SNAPSHOT
|
||||
Reference in New Issue
Block a user