From f0c48f2b136af41fb3322490759a4010a09a0ec2 Mon Sep 17 00:00:00 2001 From: Simon Oberzier Date: Wed, 12 Aug 2026 13:26:07 +0200 Subject: [PATCH] Added support for more Ollama operations --- README.md | 66 +++++++++- pom.xml | 17 ++- src/main/java/de/asedem/Ollama.java | 63 +++++++++- .../java/de/asedem/model/ChatRequest.java | 23 ++++ .../java/de/asedem/model/ChatResponse.java | 26 ++++ .../java/de/asedem/model/CreateRequest.java | 28 +++++ .../java/de/asedem/model/CreateResponse.java | 6 + .../java/de/asedem/model/EmbedRequest.java | 21 ++++ .../java/de/asedem/model/EmbedResponse.java | 17 +++ src/main/java/de/asedem/model/Message.java | 17 +++ src/main/java/de/asedem/model/ModelInfo.java | 11 +- .../java/de/asedem/model/ProcessModel.java | 16 +++ .../java/de/asedem/model/PullRequest.java | 15 +++ .../java/de/asedem/model/PullResponse.java | 6 + .../java/de/asedem/model/PushRequest.java | 15 +++ .../java/de/asedem/model/PushResponse.java | 6 + .../asedem/model/RunningModelsResponse.java | 8 ++ .../java/de/asedem/model/ShowInfoRequest.java | 7 ++ src/main/java/de/asedem/model/Tool.java | 16 +++ src/main/java/de/asedem/model/ToolCall.java | 14 +++ .../java/de/asedem/model/VersionResponse.java | 6 + src/main/java/de/asedem/rest/HttpMethode.java | 26 ++++ src/main/java/de/asedem/rest/Rest.java | 118 ++++++++++++++++++ .../java/de/asedem/rest/RestResponse.java | 49 ++++++++ .../java/de/asedem/service/ChatService.java | 30 +++++ .../java/de/asedem/service/CreateService.java | 30 +++++ .../java/de/asedem/service/DeleteService.java | 2 +- .../java/de/asedem/service/EmbedService.java | 30 +++++ .../java/de/asedem/service/PullService.java | 30 +++++ .../java/de/asedem/service/PushService.java | 30 +++++ .../asedem/service/RunningModelsService.java | 31 +++++ .../de/asedem/service/ShowInfoService.java | 9 +- .../de/asedem/service/VersionService.java | 28 +++++ .../de/asedem/service/ChatServiceTest.java | 77 ++++++++++++ .../de/asedem/service/CreateServiceTest.java | 55 ++++++++ .../de/asedem/service/DeleteServiceTest.java | 6 +- .../de/asedem/service/EmbedServiceTest.java | 65 ++++++++++ .../de/asedem/service/PullServiceTest.java | 55 ++++++++ .../de/asedem/service/PushServiceTest.java | 55 ++++++++ .../service/RunningModelsServiceTest.java | 92 ++++++++++++++ .../asedem/service/ShowInfoServiceTest.java | 11 +- .../de/asedem/service/VersionServiceTest.java | 50 ++++++++ 42 files changed, 1263 insertions(+), 20 deletions(-) create mode 100644 src/main/java/de/asedem/model/ChatRequest.java create mode 100644 src/main/java/de/asedem/model/ChatResponse.java create mode 100644 src/main/java/de/asedem/model/CreateRequest.java create mode 100644 src/main/java/de/asedem/model/CreateResponse.java create mode 100644 src/main/java/de/asedem/model/EmbedRequest.java create mode 100644 src/main/java/de/asedem/model/EmbedResponse.java create mode 100644 src/main/java/de/asedem/model/Message.java create mode 100644 src/main/java/de/asedem/model/ProcessModel.java create mode 100644 src/main/java/de/asedem/model/PullRequest.java create mode 100644 src/main/java/de/asedem/model/PullResponse.java create mode 100644 src/main/java/de/asedem/model/PushRequest.java create mode 100644 src/main/java/de/asedem/model/PushResponse.java create mode 100644 src/main/java/de/asedem/model/RunningModelsResponse.java create mode 100644 src/main/java/de/asedem/model/ShowInfoRequest.java create mode 100644 src/main/java/de/asedem/model/Tool.java create mode 100644 src/main/java/de/asedem/model/ToolCall.java create mode 100644 src/main/java/de/asedem/model/VersionResponse.java create mode 100644 src/main/java/de/asedem/rest/HttpMethode.java create mode 100644 src/main/java/de/asedem/rest/Rest.java create mode 100644 src/main/java/de/asedem/rest/RestResponse.java create mode 100644 src/main/java/de/asedem/service/ChatService.java create mode 100644 src/main/java/de/asedem/service/CreateService.java create mode 100644 src/main/java/de/asedem/service/EmbedService.java create mode 100644 src/main/java/de/asedem/service/PullService.java create mode 100644 src/main/java/de/asedem/service/PushService.java create mode 100644 src/main/java/de/asedem/service/RunningModelsService.java create mode 100644 src/main/java/de/asedem/service/VersionService.java create mode 100644 src/test/java/de/asedem/service/ChatServiceTest.java create mode 100644 src/test/java/de/asedem/service/CreateServiceTest.java create mode 100644 src/test/java/de/asedem/service/EmbedServiceTest.java create mode 100644 src/test/java/de/asedem/service/PullServiceTest.java create mode 100644 src/test/java/de/asedem/service/PushServiceTest.java create mode 100644 src/test/java/de/asedem/service/RunningModelsServiceTest.java create mode 100644 src/test/java/de/asedem/service/VersionServiceTest.java diff --git a/README.md b/README.md index 5381b97..7f679b0 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ This API is by far not finished and many features are missing by now... ## 1 Features - Intuitive API client: Set up and interact with Ollama in just a few lines of code. `✓` -- Support for various Ollama operations: Including streaming completions (chatting), listing local models, pulling new - models, show model information, creating new models, copying models, deleting models, pushing models, and generating - embeddings. `✗` +- Support for various Ollama operations: Including completions, chatting, listing local models, listing running models, + showing model information, creating new models, copying models, deleting models, pulling models, pushing models, + generating embeddings, and retrieving the server version. `✓` - Real-time streaming: Stream responses directly to your application. `✗` - Progress reporting: Get real-time progress feedback on tasks like model pulling. `✗` @@ -83,9 +83,7 @@ String prompt = "Why is the sky blue?"; GenerationResponse response = ollama.generate(new GenerationRequest(model, prompt)); -System.out. - -println(response.response()); +System.out.println(response.response()); ``` **OUTPUTS:** The sky appears blue because of a phenomenon called Rayleigh scattering... @@ -122,6 +120,62 @@ boolean success = ollama.delete("llama2-backup"); *Returns* `true` *if the deletion was successful.* +### 2.7 Chat with a model + +```java +import de.asedem.model.*; + +List messages = List.of(new Message("user", "Why is the sky blue?", null, null, null, null)); +ChatResponse response = ollama.chat(new ChatRequest("llama3.2", messages)); + +System.out.println(response.message().content()); +``` + +### 2.8 Show model information + +```java +// with optional verbose flag +ModelInfo modelInfo = ollama.showInfo("llava", true); +``` + +### 2.9 Create a model + +```java +CreateResponse response = ollama.create(new CreateRequest("mario", "llama3.2")); +``` + +### 2.10 Pull a model + +```java +PullResponse response = ollama.pull(new PullRequest("llama3.2")); +``` + +### 2.11 Push a model + +```java +PushResponse response = ollama.push(new PushRequest("mattw/pygmalion:latest")); +``` + +### 2.12 Generate embeddings + +```java +EmbedResponse response = ollama.embed(new EmbedRequest("all-minilm", "Why is the sky blue?")); +``` + +*The* `input` *may also be a* `List` *for multiple inputs.* + +### 2.13 List running models + +```java +List models = ollama.runningModels(); +``` + +### 2.14 Server version + +```java +String version = ollama.version().version(); +``` + ## 4 Credits Structure of the readme is inspired from [Ollama Sharp](https://github.com/awaescher/OllamaSharp) diff --git a/pom.xml b/pom.xml index b0c1566..7439d11 100644 --- a/pom.xml +++ b/pom.xml @@ -22,9 +22,14 @@ - com.github.Asedem - ConnectionAPI - 5ffcfef027 + com.fasterxml.jackson.core + jackson-databind + 2.14.2 + + + org.jetbrains + annotations + 24.0.1 @@ -48,10 +53,14 @@ maven-compiler-plugin 3.13.0 + org.apache.maven.plugins maven-surefire-plugin 3.5.0 + + @{argLine} -Dnet.bytebuddy.experimental=true + @@ -73,7 +82,7 @@ check - test + verify check diff --git a/src/main/java/de/asedem/Ollama.java b/src/main/java/de/asedem/Ollama.java index b9a5762..1811f83 100644 --- a/src/main/java/de/asedem/Ollama.java +++ b/src/main/java/de/asedem/Ollama.java @@ -1,9 +1,21 @@ package de.asedem; +import de.asedem.model.ChatRequest; +import de.asedem.model.ChatResponse; +import de.asedem.model.CreateRequest; +import de.asedem.model.CreateResponse; +import de.asedem.model.EmbedRequest; +import de.asedem.model.EmbedResponse; import de.asedem.model.GenerationRequest; import de.asedem.model.GenerationResponse; import de.asedem.model.Model; import de.asedem.model.ModelInfo; +import de.asedem.model.ProcessModel; +import de.asedem.model.PullRequest; +import de.asedem.model.PullResponse; +import de.asedem.model.PushRequest; +import de.asedem.model.PushResponse; +import de.asedem.model.VersionResponse; import de.asedem.service.*; import org.jetbrains.annotations.NotNull; @@ -20,7 +32,14 @@ public record Ollama( GenerateService, ShowInfoService, CopyService, - DeleteService { + DeleteService, + ChatService, + CreateService, + PullService, + PushService, + EmbedService, + RunningModelsService, + VersionService { public static Ollama initDefault() { return new Ollama("http://127.0.0.1", 11434); @@ -61,4 +80,46 @@ public boolean copy(@NotNull String source, @NotNull String destination) { public boolean delete(@NotNull String modelName) { return DeleteService.super.delete(this, modelName); } + + @NotNull + @Override + public ChatResponse chat(@NotNull ChatRequest request) { + return ChatService.super.chat(this, request); + } + + @NotNull + @Override + public CreateResponse create(@NotNull CreateRequest request) { + return CreateService.super.create(this, request); + } + + @NotNull + @Override + public PullResponse pull(@NotNull PullRequest request) { + return PullService.super.pull(this, request); + } + + @NotNull + @Override + public PushResponse push(@NotNull PushRequest request) { + return PushService.super.push(this, request); + } + + @NotNull + @Override + public EmbedResponse embed(@NotNull EmbedRequest request) { + return EmbedService.super.embed(this, request); + } + + @NotNull + @Override + public List runningModels() { + return RunningModelsService.super.runningModels(this); + } + + @NotNull + @Override + public VersionResponse version() { + return VersionService.super.version(this); + } } diff --git a/src/main/java/de/asedem/model/ChatRequest.java b/src/main/java/de/asedem/model/ChatRequest.java new file mode 100644 index 0000000..5dc5805 --- /dev/null +++ b/src/main/java/de/asedem/model/ChatRequest.java @@ -0,0 +1,23 @@ +package de.asedem.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record ChatRequest( + String model, + List messages, + boolean stream, + Object format, + Map options, + Object think, + Object keep_alive, + List tools +) { + + public ChatRequest(String model, List messages) { + this(model, messages, false, null, null, null, null, null); + } +} diff --git a/src/main/java/de/asedem/model/ChatResponse.java b/src/main/java/de/asedem/model/ChatResponse.java new file mode 100644 index 0000000..135dd42 --- /dev/null +++ b/src/main/java/de/asedem/model/ChatResponse.java @@ -0,0 +1,26 @@ +package de.asedem.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ChatResponse( + String model, + String created_at, + Message message, + boolean done, + @JsonProperty("done_reason") + String doneReason, + int[] context, + @JsonProperty("total_duration") + long totalDuration, + @JsonProperty("load_duration") + long loadDuration, + @JsonProperty("prompt_eval_count") + long promptEvalCount, + @JsonProperty("prompt_eval_duration") + long promptEvalDuration, + @JsonProperty("eval_count") + long evalCount, + @JsonProperty("eval_duration") + long evalDuration +) { +} diff --git a/src/main/java/de/asedem/model/CreateRequest.java b/src/main/java/de/asedem/model/CreateRequest.java new file mode 100644 index 0000000..62c6398 --- /dev/null +++ b/src/main/java/de/asedem/model/CreateRequest.java @@ -0,0 +1,28 @@ +package de.asedem.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record CreateRequest( + String model, + String from, + Map files, + Map adapters, + String template, + String renderer, + String parser, + Object license, + String system, + Map parameters, + List messages, + boolean stream, + String quantize +) { + + public CreateRequest(String model, String from) { + this(model, from, null, null, null, null, null, null, null, null, null, false, null); + } +} diff --git a/src/main/java/de/asedem/model/CreateResponse.java b/src/main/java/de/asedem/model/CreateResponse.java new file mode 100644 index 0000000..aa9f5f5 --- /dev/null +++ b/src/main/java/de/asedem/model/CreateResponse.java @@ -0,0 +1,6 @@ +package de.asedem.model; + +public record CreateResponse( + String status +) { +} diff --git a/src/main/java/de/asedem/model/EmbedRequest.java b/src/main/java/de/asedem/model/EmbedRequest.java new file mode 100644 index 0000000..e6dc3b0 --- /dev/null +++ b/src/main/java/de/asedem/model/EmbedRequest.java @@ -0,0 +1,21 @@ +package de.asedem.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record EmbedRequest( + String model, + Object input, + Boolean truncate, + Integer dimensions, + Object keep_alive, + Map options +) { + + public EmbedRequest(String model, Object input) { + this(model, input, null, null, null, null); + } +} diff --git a/src/main/java/de/asedem/model/EmbedResponse.java b/src/main/java/de/asedem/model/EmbedResponse.java new file mode 100644 index 0000000..a7632ee --- /dev/null +++ b/src/main/java/de/asedem/model/EmbedResponse.java @@ -0,0 +1,17 @@ +package de.asedem.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +public record EmbedResponse( + String model, + List> embeddings, + @JsonProperty("total_duration") + long totalDuration, + @JsonProperty("load_duration") + long loadDuration, + @JsonProperty("prompt_eval_count") + long promptEvalCount +) { +} diff --git a/src/main/java/de/asedem/model/Message.java b/src/main/java/de/asedem/model/Message.java new file mode 100644 index 0000000..c6306b8 --- /dev/null +++ b/src/main/java/de/asedem/model/Message.java @@ -0,0 +1,17 @@ +package de.asedem.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.List; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record Message( + String role, + String content, + List images, + String thinking, + List tool_calls, + String tool_name +) { +} diff --git a/src/main/java/de/asedem/model/ModelInfo.java b/src/main/java/de/asedem/model/ModelInfo.java index 2d0792d..53686ed 100644 --- a/src/main/java/de/asedem/model/ModelInfo.java +++ b/src/main/java/de/asedem/model/ModelInfo.java @@ -2,11 +2,20 @@ import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.Map; + public record ModelInfo( String license, @JsonProperty("modelfile") String modelFile, String parameters, - String template + String template, + Model.ModelDetails details, + @JsonProperty("model_info") + Map modelInfo, + List capabilities, + @JsonProperty("projector_info") + Map projectorInfo ) { } diff --git a/src/main/java/de/asedem/model/ProcessModel.java b/src/main/java/de/asedem/model/ProcessModel.java new file mode 100644 index 0000000..808420c --- /dev/null +++ b/src/main/java/de/asedem/model/ProcessModel.java @@ -0,0 +1,16 @@ +package de.asedem.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record ProcessModel( + String name, + String model, + long size, + String digest, + Model.ModelDetails details, + @JsonProperty("expires_at") + String expiresAt, + @JsonProperty("size_vram") + long sizeVram +) { +} diff --git a/src/main/java/de/asedem/model/PullRequest.java b/src/main/java/de/asedem/model/PullRequest.java new file mode 100644 index 0000000..2b3769a --- /dev/null +++ b/src/main/java/de/asedem/model/PullRequest.java @@ -0,0 +1,15 @@ +package de.asedem.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record PullRequest( + String model, + Boolean insecure, + boolean stream +) { + + public PullRequest(String model) { + this(model, null, false); + } +} diff --git a/src/main/java/de/asedem/model/PullResponse.java b/src/main/java/de/asedem/model/PullResponse.java new file mode 100644 index 0000000..a2141e5 --- /dev/null +++ b/src/main/java/de/asedem/model/PullResponse.java @@ -0,0 +1,6 @@ +package de.asedem.model; + +public record PullResponse( + String status +) { +} diff --git a/src/main/java/de/asedem/model/PushRequest.java b/src/main/java/de/asedem/model/PushRequest.java new file mode 100644 index 0000000..2a96a70 --- /dev/null +++ b/src/main/java/de/asedem/model/PushRequest.java @@ -0,0 +1,15 @@ +package de.asedem.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public record PushRequest( + String model, + Boolean insecure, + boolean stream +) { + + public PushRequest(String model) { + this(model, null, false); + } +} diff --git a/src/main/java/de/asedem/model/PushResponse.java b/src/main/java/de/asedem/model/PushResponse.java new file mode 100644 index 0000000..e837a9c --- /dev/null +++ b/src/main/java/de/asedem/model/PushResponse.java @@ -0,0 +1,6 @@ +package de.asedem.model; + +public record PushResponse( + String status +) { +} diff --git a/src/main/java/de/asedem/model/RunningModelsResponse.java b/src/main/java/de/asedem/model/RunningModelsResponse.java new file mode 100644 index 0000000..2d3fde5 --- /dev/null +++ b/src/main/java/de/asedem/model/RunningModelsResponse.java @@ -0,0 +1,8 @@ +package de.asedem.model; + +import java.util.List; + +public record RunningModelsResponse( + List models +) { +} diff --git a/src/main/java/de/asedem/model/ShowInfoRequest.java b/src/main/java/de/asedem/model/ShowInfoRequest.java new file mode 100644 index 0000000..0a0aedf --- /dev/null +++ b/src/main/java/de/asedem/model/ShowInfoRequest.java @@ -0,0 +1,7 @@ +package de.asedem.model; + +public record ShowInfoRequest( + String model, + boolean verbose +) { +} diff --git a/src/main/java/de/asedem/model/Tool.java b/src/main/java/de/asedem/model/Tool.java new file mode 100644 index 0000000..b7ea41b --- /dev/null +++ b/src/main/java/de/asedem/model/Tool.java @@ -0,0 +1,16 @@ +package de.asedem.model; + +import java.util.Map; + +public record Tool( + String type, + ToolDefinition function +) { +} + +record ToolDefinition( + String name, + String description, + Map parameters +) { +} diff --git a/src/main/java/de/asedem/model/ToolCall.java b/src/main/java/de/asedem/model/ToolCall.java new file mode 100644 index 0000000..6cfe433 --- /dev/null +++ b/src/main/java/de/asedem/model/ToolCall.java @@ -0,0 +1,14 @@ +package de.asedem.model; + +import java.util.Map; + +public record ToolCall( + ToolFunction function +) { +} + +record ToolFunction( + String name, + Map arguments +) { +} diff --git a/src/main/java/de/asedem/model/VersionResponse.java b/src/main/java/de/asedem/model/VersionResponse.java new file mode 100644 index 0000000..34ac81f --- /dev/null +++ b/src/main/java/de/asedem/model/VersionResponse.java @@ -0,0 +1,6 @@ +package de.asedem.model; + +public record VersionResponse( + String version +) { +} diff --git a/src/main/java/de/asedem/rest/HttpMethode.java b/src/main/java/de/asedem/rest/HttpMethode.java new file mode 100644 index 0000000..d93c79f --- /dev/null +++ b/src/main/java/de/asedem/rest/HttpMethode.java @@ -0,0 +1,26 @@ +package de.asedem.rest; + +import org.jetbrains.annotations.NotNull; + +public enum HttpMethode { + + GET("GET"), + POST("POST"), + DELETE("DELETE"); + + private final String methode; + + HttpMethode(@NotNull final String methode) { + this.methode = methode; + } + + /** + * Get the string from the request methode + * + * @return the string from the request methode + */ + @NotNull + public String get() { + return methode; + } +} diff --git a/src/main/java/de/asedem/rest/Rest.java b/src/main/java/de/asedem/rest/Rest.java new file mode 100644 index 0000000..47b261c --- /dev/null +++ b/src/main/java/de/asedem/rest/Rest.java @@ -0,0 +1,118 @@ +package de.asedem.rest; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +public class Rest { + + private Rest() { + } + + /** + * Send a Rest request to an url sync + * + * @param url the url to send to + * @param httpMethode the http method to use + * @return a response from the request + * @throws IOException if something went wrong + */ + @NotNull + public static RestResponse requestSync(@NotNull final URL url, @NotNull final HttpMethode httpMethode) throws IOException { + + return Rest.requestSync(url, httpMethode, 10000, 10000); + } + + /** + * Send a Rest request to an url sync + * + * @param url the url to send to + * @param httpMethode the http method to use + * @param connectionTimeout the max connection time + * @param readTimeout the max read time + * @return a response from the request + * @throws IOException if something went wrong + */ + @NotNull + public static RestResponse requestSync(@NotNull final URL url, @NotNull final HttpMethode httpMethode, final int connectionTimeout, final int readTimeout) throws IOException { + + return Rest.requestSync(url, httpMethode, null, connectionTimeout, readTimeout); + } + + /** + * Send a Rest request to an url sync + * + * @param url the url to send to + * @param httpMethode the http method to use + * @param jsonObject the json which should be sent + * @return a response from the request + * @throws IOException if something went wrong + */ + @NotNull + public static RestResponse requestSync(@NotNull final URL url, @NotNull final HttpMethode httpMethode, @Nullable final T jsonObject) throws IOException { + + return Rest.requestSync(url, httpMethode, jsonObject, 10000, 10000); + } + + /** + * Send a Rest request to an url sync + * + * @param url the url to send to + * @param httpMethode the http method to use + * @param jsonObject the json which should be sent + * @param connectionTimeout the max connection time + * @param readTimeout the max read time + * @return a response from the request + * @throws IOException if something went wrong + */ + @NotNull + public static RestResponse requestSync(@NotNull final URL url, @NotNull final HttpMethode httpMethode, @Nullable final T jsonObject, final int connectionTimeout, final int readTimeout) throws IOException { + + final BufferedReader bufferedReader; + final StringBuilder responseContent = new StringBuilder(); + + String line; + + final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + if (jsonObject != null) { + connection.addRequestProperty("Content-Type", "application/json"); + connection.addRequestProperty("User-Agent", "request"); + connection.setDoOutput(true); + } + connection.setRequestMethod(httpMethode.get()); + connection.setConnectTimeout(connectionTimeout); + connection.setReadTimeout(readTimeout); + + if (jsonObject != null) { + final OutputStream stream = connection.getOutputStream(); + final String json = RestResponse.mapper.writeValueAsString(jsonObject); + stream.write(json.getBytes()); + stream.flush(); + stream.close(); + } + + final int responseCode = connection.getResponseCode(); + + if (responseCode > 299) { + bufferedReader = new BufferedReader(new InputStreamReader(connection.getErrorStream())); + while ((line = bufferedReader.readLine()) != null) responseContent.append(line); + bufferedReader.close(); + return new RestResponse(responseCode, null); + } + + bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + while ((line = bufferedReader.readLine()) != null) responseContent.append(line); + connection.getInputStream().close(); + bufferedReader.close(); + + connection.disconnect(); + + return new RestResponse(responseCode, responseContent.toString()); + } +} diff --git a/src/main/java/de/asedem/rest/RestResponse.java b/src/main/java/de/asedem/rest/RestResponse.java new file mode 100644 index 0000000..62af9f0 --- /dev/null +++ b/src/main/java/de/asedem/rest/RestResponse.java @@ -0,0 +1,49 @@ +package de.asedem.rest; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class RestResponse { + + private final int statusCode; + private final String value; + static final ObjectMapper mapper = new ObjectMapper(); + + public RestResponse(int statusCode, @Nullable String value) { + this.statusCode = statusCode; + this.value = value; + } + + /** + * Gets the statusCode of the response + * + * @return the statusCode + */ + public int getStatusCode() { + return this.statusCode; + } + + /** + * Get the JSON value the is saved in the response as a String + * + * @return the JSON value the is saved in the response as a String + */ + public String asValueString() { + return this.value; + } + + /** + * Get the JSON value the is saved in the response as a java object + * + * @param targetClass The class of the java object + * @param The class where the response should be mapped to + * @return The java object if the response can be mapped + * @throws JsonProcessingException if the response can't be mapped to the class + */ + public T asJavaObject(Class targetClass) throws JsonProcessingException { + + return this.value == null ? null : mapper.readValue(this.value, targetClass); + } +} diff --git a/src/main/java/de/asedem/service/ChatService.java b/src/main/java/de/asedem/service/ChatService.java new file mode 100644 index 0000000..a705e99 --- /dev/null +++ b/src/main/java/de/asedem/service/ChatService.java @@ -0,0 +1,30 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.ChatRequest; +import de.asedem.model.ChatResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public interface ChatService { + + @NotNull + ChatResponse chat(@NotNull ChatRequest request); + + @NotNull + default ChatResponse chat(@NotNull Ollama ollama, @NotNull ChatRequest request) { + final ChatResponse response; + try { + response = Rest.requestSync(ollama.buildUrl("/api/chat"), + HttpMethode.POST, request, 10000, 30000) + .asJavaObject(ChatResponse.class); + } catch (IOException exception) { + throw new OllamaConnectionException(exception); + } + return response; + } +} diff --git a/src/main/java/de/asedem/service/CreateService.java b/src/main/java/de/asedem/service/CreateService.java new file mode 100644 index 0000000..bc1c124 --- /dev/null +++ b/src/main/java/de/asedem/service/CreateService.java @@ -0,0 +1,30 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.CreateRequest; +import de.asedem.model.CreateResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public interface CreateService { + + @NotNull + CreateResponse create(@NotNull CreateRequest request); + + @NotNull + default CreateResponse create(@NotNull Ollama ollama, @NotNull CreateRequest request) { + final CreateResponse response; + try { + response = Rest.requestSync(ollama.buildUrl("/api/create"), + HttpMethode.POST, request, 10000, 30000) + .asJavaObject(CreateResponse.class); + } catch (IOException exception) { + throw new OllamaConnectionException(exception); + } + return response; + } +} diff --git a/src/main/java/de/asedem/service/DeleteService.java b/src/main/java/de/asedem/service/DeleteService.java index 3f34fa1..376bcd5 100644 --- a/src/main/java/de/asedem/service/DeleteService.java +++ b/src/main/java/de/asedem/service/DeleteService.java @@ -16,7 +16,7 @@ default boolean delete(@NotNull Ollama ollama, @NotNull String modelName) { final int statusCode; try { statusCode = Rest.requestSync(ollama.buildUrl("/api/delete"), - HttpMethode.POST, new DeleteRequest(modelName)) + HttpMethode.DELETE, new DeleteRequest(modelName)) .getStatusCode(); } catch (IOException exception) { throw new OllamaConnectionException(exception); diff --git a/src/main/java/de/asedem/service/EmbedService.java b/src/main/java/de/asedem/service/EmbedService.java new file mode 100644 index 0000000..c085996 --- /dev/null +++ b/src/main/java/de/asedem/service/EmbedService.java @@ -0,0 +1,30 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.EmbedRequest; +import de.asedem.model.EmbedResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public interface EmbedService { + + @NotNull + EmbedResponse embed(@NotNull EmbedRequest request); + + @NotNull + default EmbedResponse embed(@NotNull Ollama ollama, @NotNull EmbedRequest request) { + final EmbedResponse response; + try { + response = Rest.requestSync(ollama.buildUrl("/api/embed"), + HttpMethode.POST, request, 10000, 30000) + .asJavaObject(EmbedResponse.class); + } catch (IOException exception) { + throw new OllamaConnectionException(exception); + } + return response; + } +} diff --git a/src/main/java/de/asedem/service/PullService.java b/src/main/java/de/asedem/service/PullService.java new file mode 100644 index 0000000..f38a85c --- /dev/null +++ b/src/main/java/de/asedem/service/PullService.java @@ -0,0 +1,30 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.PullRequest; +import de.asedem.model.PullResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public interface PullService { + + @NotNull + PullResponse pull(@NotNull PullRequest request); + + @NotNull + default PullResponse pull(@NotNull Ollama ollama, @NotNull PullRequest request) { + final PullResponse response; + try { + response = Rest.requestSync(ollama.buildUrl("/api/pull"), + HttpMethode.POST, request, 10000, 30000) + .asJavaObject(PullResponse.class); + } catch (IOException exception) { + throw new OllamaConnectionException(exception); + } + return response; + } +} diff --git a/src/main/java/de/asedem/service/PushService.java b/src/main/java/de/asedem/service/PushService.java new file mode 100644 index 0000000..226a34a --- /dev/null +++ b/src/main/java/de/asedem/service/PushService.java @@ -0,0 +1,30 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.PushRequest; +import de.asedem.model.PushResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public interface PushService { + + @NotNull + PushResponse push(@NotNull PushRequest request); + + @NotNull + default PushResponse push(@NotNull Ollama ollama, @NotNull PushRequest request) { + final PushResponse response; + try { + response = Rest.requestSync(ollama.buildUrl("/api/push"), + HttpMethode.POST, request, 10000, 30000) + .asJavaObject(PushResponse.class); + } catch (IOException exception) { + throw new OllamaConnectionException(exception); + } + return response; + } +} diff --git a/src/main/java/de/asedem/service/RunningModelsService.java b/src/main/java/de/asedem/service/RunningModelsService.java new file mode 100644 index 0000000..1355683 --- /dev/null +++ b/src/main/java/de/asedem/service/RunningModelsService.java @@ -0,0 +1,31 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.ProcessModel; +import de.asedem.model.RunningModelsResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +public interface RunningModelsService { + + List runningModels(); + + @NotNull + default List runningModels(@NotNull Ollama ollama) { + final RunningModelsResponse response; + try { + response = Rest.requestSync(ollama.buildUrl("/api/ps"), HttpMethode.GET) + .asJavaObject(RunningModelsResponse.class); + } catch (IOException exception) { + throw new OllamaConnectionException(exception); + } + if (response == null || response.models() == null) return Collections.emptyList(); + return response.models(); + } +} diff --git a/src/main/java/de/asedem/service/ShowInfoService.java b/src/main/java/de/asedem/service/ShowInfoService.java index 4c49fe6..4bfff92 100644 --- a/src/main/java/de/asedem/service/ShowInfoService.java +++ b/src/main/java/de/asedem/service/ShowInfoService.java @@ -3,6 +3,7 @@ import de.asedem.Ollama; import de.asedem.exception.OllamaConnectionException; import de.asedem.model.ModelInfo; +import de.asedem.model.ShowInfoRequest; import de.asedem.rest.HttpMethode; import de.asedem.rest.Rest; import org.jetbrains.annotations.NotNull; @@ -16,9 +17,15 @@ public interface ShowInfoService { @NotNull default ModelInfo showInfo(@NotNull Ollama ollama, @NotNull String modelName) { + return showInfo(ollama, modelName, false); + } + + @NotNull + default ModelInfo showInfo(@NotNull Ollama ollama, @NotNull String modelName, boolean verbose) { final ModelInfo modelInfo; try { - modelInfo = Rest.requestSync(ollama.buildUrl("/api/show"), HttpMethode.POST, modelName) + modelInfo = Rest.requestSync(ollama.buildUrl("/api/show"), + HttpMethode.POST, new ShowInfoRequest(modelName, verbose)) .asJavaObject(ModelInfo.class); } catch (IOException exception) { throw new OllamaConnectionException(exception); diff --git a/src/main/java/de/asedem/service/VersionService.java b/src/main/java/de/asedem/service/VersionService.java new file mode 100644 index 0000000..3406690 --- /dev/null +++ b/src/main/java/de/asedem/service/VersionService.java @@ -0,0 +1,28 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.VersionResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; + +public interface VersionService { + + @NotNull + VersionResponse version(); + + @NotNull + default VersionResponse version(@NotNull Ollama ollama) { + final VersionResponse response; + try { + response = Rest.requestSync(ollama.buildUrl("/api/version"), HttpMethode.GET) + .asJavaObject(VersionResponse.class); + } catch (IOException exception) { + throw new OllamaConnectionException(exception); + } + return response; + } +} diff --git a/src/test/java/de/asedem/service/ChatServiceTest.java b/src/test/java/de/asedem/service/ChatServiceTest.java new file mode 100644 index 0000000..eaafb8c --- /dev/null +++ b/src/test/java/de/asedem/service/ChatServiceTest.java @@ -0,0 +1,77 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.ChatRequest; +import de.asedem.model.ChatResponse; +import de.asedem.model.Message; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import de.asedem.rest.RestResponse; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class ChatServiceTest { + + private final ChatRequest request = new ChatRequest( + "llama3.2", + List.of(new Message("user", "Why is the sky blue?", null, null, null, null)) + ); + + @Test + void testMethodCall() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/chat"), + HttpMethode.POST, request, 10000, 30000)) + .thenReturn(new RestResponse(200, """ + { + "model": "llama3.2", + "created_at": "2023-12-12T14:13:43.416799Z", + "message": { + "role": "assistant", + "content": "Hello! How are you today?" + }, + "done": true, + "total_duration": 5191566416, + "load_duration": 2154458, + "prompt_eval_count": 26, + "prompt_eval_duration": 383809000, + "eval_count": 298, + "eval_duration": 4799921000 + } + """)); + + final ChatResponse response = ollama.chat(request); + + assertEquals("llama3.2", response.model()); + assertEquals("assistant", response.message().role()); + assertEquals("Hello! How are you today?", response.message().content()); + assertTrue(response.done()); + assertEquals(5191566416L, response.totalDuration()); + assertEquals(4799921000L, response.evalDuration()); + } + } + + @Test + void testException() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/chat"), + HttpMethode.POST, request, 10000, 30000)) + .thenThrow(new IOException()); + + assertThrows(OllamaConnectionException.class, () -> ollama.chat(request)); + } + } +} diff --git a/src/test/java/de/asedem/service/CreateServiceTest.java b/src/test/java/de/asedem/service/CreateServiceTest.java new file mode 100644 index 0000000..fe818d5 --- /dev/null +++ b/src/test/java/de/asedem/service/CreateServiceTest.java @@ -0,0 +1,55 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.CreateRequest; +import de.asedem.model.CreateResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import de.asedem.rest.RestResponse; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +class CreateServiceTest { + + private final CreateRequest request = new CreateRequest("mario", "llama3.2"); + + @Test + void testMethodCall() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/create"), + HttpMethode.POST, request, 10000, 30000)) + .thenReturn(new RestResponse(200, """ + { + "status": "success" + } + """)); + + final CreateResponse response = ollama.create(request); + + assertEquals("success", response.status()); + } + } + + @Test + void testException() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/create"), + HttpMethode.POST, request, 10000, 30000)) + .thenThrow(new IOException()); + + assertThrows(OllamaConnectionException.class, () -> ollama.create(request)); + } + } +} diff --git a/src/test/java/de/asedem/service/DeleteServiceTest.java b/src/test/java/de/asedem/service/DeleteServiceTest.java index 3d6399a..8187ca8 100644 --- a/src/test/java/de/asedem/service/DeleteServiceTest.java +++ b/src/test/java/de/asedem/service/DeleteServiceTest.java @@ -22,7 +22,7 @@ void testMethodCall() { try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/delete"), - HttpMethode.POST, new DeleteService.DeleteRequest("llama2:latest"))) + HttpMethode.DELETE, new DeleteService.DeleteRequest("llama2:latest"))) .thenReturn(new RestResponse(200, "")); assertTrue(ollama.delete("llama2:latest")); @@ -36,7 +36,7 @@ void testFalseIfNotSuccessful() { try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/delete"), - HttpMethode.POST, new DeleteService.DeleteRequest("llama2:latest"))) + HttpMethode.DELETE, new DeleteService.DeleteRequest("llama2:latest"))) .thenReturn(new RestResponse(404, "")); assertFalse(ollama.delete("llama2:latest")); @@ -50,7 +50,7 @@ void testException() { try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/delete"), - HttpMethode.POST, new DeleteService.DeleteRequest("llama2:latest"))) + HttpMethode.DELETE, new DeleteService.DeleteRequest("llama2:latest"))) .thenThrow(new IOException()); assertThrows(OllamaConnectionException.class, () -> ollama.delete("llama2:latest")); diff --git a/src/test/java/de/asedem/service/EmbedServiceTest.java b/src/test/java/de/asedem/service/EmbedServiceTest.java new file mode 100644 index 0000000..5b6c956 --- /dev/null +++ b/src/test/java/de/asedem/service/EmbedServiceTest.java @@ -0,0 +1,65 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.EmbedRequest; +import de.asedem.model.EmbedResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import de.asedem.rest.RestResponse; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +class EmbedServiceTest { + + private final EmbedRequest request = new EmbedRequest("all-minilm", "Why is the sky blue?"); + + @Test + void testMethodCall() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/embed"), + HttpMethode.POST, request, 10000, 30000)) + .thenReturn(new RestResponse(200, """ + { + "model": "all-minilm", + "embeddings": [ + [0.010071029, -0.0017594862, 0.05007221] + ], + "total_duration": 14143917, + "load_duration": 1019500, + "prompt_eval_count": 8 + } + """)); + + final EmbedResponse response = ollama.embed(request); + + assertEquals("all-minilm", response.model()); + assertEquals(1, response.embeddings().size()); + assertEquals(3, response.embeddings().getFirst().size()); + assertEquals(14143917L, response.totalDuration()); + assertEquals(8L, response.promptEvalCount()); + } + } + + @Test + void testException() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/embed"), + HttpMethode.POST, request, 10000, 30000)) + .thenThrow(new IOException()); + + assertThrows(OllamaConnectionException.class, () -> ollama.embed(request)); + } + } +} diff --git a/src/test/java/de/asedem/service/PullServiceTest.java b/src/test/java/de/asedem/service/PullServiceTest.java new file mode 100644 index 0000000..e6b2600 --- /dev/null +++ b/src/test/java/de/asedem/service/PullServiceTest.java @@ -0,0 +1,55 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.PullRequest; +import de.asedem.model.PullResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import de.asedem.rest.RestResponse; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +class PullServiceTest { + + private final PullRequest request = new PullRequest("llama3.2"); + + @Test + void testMethodCall() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/pull"), + HttpMethode.POST, request, 10000, 30000)) + .thenReturn(new RestResponse(200, """ + { + "status": "success" + } + """)); + + final PullResponse response = ollama.pull(request); + + assertEquals("success", response.status()); + } + } + + @Test + void testException() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/pull"), + HttpMethode.POST, request, 10000, 30000)) + .thenThrow(new IOException()); + + assertThrows(OllamaConnectionException.class, () -> ollama.pull(request)); + } + } +} diff --git a/src/test/java/de/asedem/service/PushServiceTest.java b/src/test/java/de/asedem/service/PushServiceTest.java new file mode 100644 index 0000000..52c6937 --- /dev/null +++ b/src/test/java/de/asedem/service/PushServiceTest.java @@ -0,0 +1,55 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.PushRequest; +import de.asedem.model.PushResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import de.asedem.rest.RestResponse; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +class PushServiceTest { + + private final PushRequest request = new PushRequest("mattw/pygmalion:latest"); + + @Test + void testMethodCall() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/push"), + HttpMethode.POST, request, 10000, 30000)) + .thenReturn(new RestResponse(200, """ + { + "status": "success" + } + """)); + + final PushResponse response = ollama.push(request); + + assertEquals("success", response.status()); + } + } + + @Test + void testException() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/push"), + HttpMethode.POST, request, 10000, 30000)) + .thenThrow(new IOException()); + + assertThrows(OllamaConnectionException.class, () -> ollama.push(request)); + } + } +} diff --git a/src/test/java/de/asedem/service/RunningModelsServiceTest.java b/src/test/java/de/asedem/service/RunningModelsServiceTest.java new file mode 100644 index 0000000..767f881 --- /dev/null +++ b/src/test/java/de/asedem/service/RunningModelsServiceTest.java @@ -0,0 +1,92 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.ProcessModel; +import de.asedem.model.RunningModelsResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import de.asedem.rest.RestResponse; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class RunningModelsServiceTest { + + @Test + void testMethodCall() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/ps"), HttpMethode.GET)) + .thenReturn(new RestResponse(200, """ + { + "models": [ + { + "name": "mistral:latest", + "model": "mistral:latest", + "size": 5137025024, + "digest": "2ae6f6dd7a3dd734790bbbf58b8909a606e0e7e97e94b7604e0aa7ae4490e6d8", + "details": { + "parent_model": "", + "format": "gguf", + "family": "llama", + "families": ["llama"], + "parameter_size": "7.2B", + "quantization_level": "Q4_0" + }, + "expires_at": "2024-06-04T14:38:31.83753-07:00", + "size_vram": 5137025024 + } + ] + } + """)); + + final List models = ollama.runningModels(); + + assertEquals(1, models.size()); + assertEquals("mistral:latest", models.getFirst().name()); + assertEquals(5137025024L, models.getFirst().sizeVram()); + assertNotNull(models.getFirst().details()); + assertEquals("Q4_0", models.getFirst().details().quantizationLevel()); + } + } + + @Test + void testEmptyListIfNoModelRunning() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/ps"), HttpMethode.GET)) + .thenReturn(new RestResponse(200, """ + { + "models": [] + } + """)); + + final List models = ollama.runningModels(); + + assertEquals(0, models.size()); + } + } + + @Test + void testException() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/ps"), HttpMethode.GET)) + .thenThrow(new IOException()); + + assertThrows(OllamaConnectionException.class, ollama::runningModels); + } + } +} diff --git a/src/test/java/de/asedem/service/ShowInfoServiceTest.java b/src/test/java/de/asedem/service/ShowInfoServiceTest.java index 498942d..c4d5d0d 100644 --- a/src/test/java/de/asedem/service/ShowInfoServiceTest.java +++ b/src/test/java/de/asedem/service/ShowInfoServiceTest.java @@ -3,6 +3,7 @@ import de.asedem.Ollama; import de.asedem.exception.OllamaConnectionException; import de.asedem.model.ModelInfo; +import de.asedem.model.ShowInfoRequest; import de.asedem.rest.HttpMethode; import de.asedem.rest.Rest; import de.asedem.rest.RestResponse; @@ -22,13 +23,15 @@ void testMethodCall() { final Ollama ollama = Ollama.initDefault(); try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { - utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/show"), HttpMethode.POST, "llama2:latest")) + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/show"), + HttpMethode.POST, new ShowInfoRequest("llama2:latest", false))) .thenReturn(new RestResponse(200, """ { "license": "MIT", "modelfile": "# Modelfile", "parameters": "num_ctx 4096", - "template": "{{ .Prompt }}" + "template": "{{ .Prompt }}", + "capabilities": ["completion", "vision"] } """)); @@ -38,6 +41,7 @@ void testMethodCall() { assertEquals("# Modelfile", modelInfo.modelFile()); assertEquals("num_ctx 4096", modelInfo.parameters()); assertEquals("{{ .Prompt }}", modelInfo.template()); + assertEquals(2, modelInfo.capabilities().size()); } } @@ -47,7 +51,8 @@ void testException() { final Ollama ollama = Ollama.initDefault(); try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { - utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/show"), HttpMethode.POST, "llama2:latest")) + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/show"), + HttpMethode.POST, new ShowInfoRequest("llama2:latest", false))) .thenThrow(new IOException()); assertThrows(OllamaConnectionException.class, () -> ollama.showInfo("llama2:latest")); diff --git a/src/test/java/de/asedem/service/VersionServiceTest.java b/src/test/java/de/asedem/service/VersionServiceTest.java new file mode 100644 index 0000000..2be7395 --- /dev/null +++ b/src/test/java/de/asedem/service/VersionServiceTest.java @@ -0,0 +1,50 @@ +package de.asedem.service; + +import de.asedem.Ollama; +import de.asedem.exception.OllamaConnectionException; +import de.asedem.model.VersionResponse; +import de.asedem.rest.HttpMethode; +import de.asedem.rest.Rest; +import de.asedem.rest.RestResponse; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; + +class VersionServiceTest { + + @Test + void testMethodCall() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/version"), HttpMethode.GET)) + .thenReturn(new RestResponse(200, """ + { + "version": "0.5.1" + } + """)); + + final VersionResponse response = ollama.version(); + + assertEquals("0.5.1", response.version()); + } + } + + @Test + void testException() { + + final Ollama ollama = Ollama.initDefault(); + + try (MockedStatic utilities = Mockito.mockStatic(Rest.class)) { + utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/version"), HttpMethode.GET)) + .thenThrow(new IOException()); + + assertThrows(OllamaConnectionException.class, ollama::version); + } + } +}