From 3469bf314bb440e1c4c6536536ffb5fd6132c662 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 13:36:49 +0000 Subject: [PATCH 01/22] Removes unused Imports --- .../ollama4j/core/models/OllamaErrorResponseModel.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/OllamaErrorResponseModel.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/OllamaErrorResponseModel.java index 26fc82b..be3d8e4 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/OllamaErrorResponseModel.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/OllamaErrorResponseModel.java @@ -1,8 +1,6 @@ package io.github.amithkoujalgi.ollama4j.core.models; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.List; import lombok.Data; @Data From f8cd7bc0132482b42d13da65bdddfc1f40daec8a Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 16:28:27 +0000 Subject: [PATCH 02/22] Adds model for Chat Requests to Ollama-API --- .../core/models/chat/OllamaChatMessage.java | 40 ++++++++++++ .../models/chat/OllamaChatMessageRole.java | 16 +++++ .../models/chat/OllamaChatRequestBuilder.java | 64 +++++++++++++++++++ .../models/chat/OllamaChatRequestModel.java | 61 ++++++++++++++++++ .../core/utils/OllamaRequestBody.java | 26 ++++++++ 5 files changed, 207 insertions(+) create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessage.java create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestModel.java create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/utils/OllamaRequestBody.java diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessage.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessage.java new file mode 100644 index 0000000..cd0becd --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessage.java @@ -0,0 +1,40 @@ +package io.github.amithkoujalgi.ollama4j.core.models.chat; + +import com.fasterxml.jackson.core.JsonProcessingException; +import static io.github.amithkoujalgi.ollama4j.core.utils.Utils.getObjectMapper; + +import java.io.File; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Defines a single Message to be used inside a chat request against the ollama /api/chat endpoint. + * + * @see https://github.com/ollama/ollama/blob/main/docs/api.md#generate-a-chat-completion + */ +@Data +@AllArgsConstructor +@RequiredArgsConstructor +public class OllamaChatMessage { + + @NonNull + private OllamaChatMessageRole role; + + @NonNull + private String content; + + private List images; + + @Override + public String toString() { + try { + return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java new file mode 100644 index 0000000..ec7f9be --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java @@ -0,0 +1,16 @@ +package io.github.amithkoujalgi.ollama4j.core.models.chat; + +/** + * Defines the possible Chat Message roles. + */ +public enum OllamaChatMessageRole { + SYSTEM("system"), + USER("user"), + ASSISTANT("assisstant"); + + private String roleName; + + private OllamaChatMessageRole(String roleName){ + this.roleName = roleName; + } +} diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java new file mode 100644 index 0000000..bcd710c --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java @@ -0,0 +1,64 @@ +package io.github.amithkoujalgi.ollama4j.core.models.chat; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import io.github.amithkoujalgi.ollama4j.core.utils.Options; + +/** + * Helper class for creating {@link OllamaChatRequestModel} objects using the builder-pattern. + */ +public class OllamaChatRequestBuilder { + + private OllamaChatRequestBuilder(String model, List messages){ + request = new OllamaChatRequestModel(model, messages); + } + + private OllamaChatRequestModel request; + + public static OllamaChatRequestBuilder getInstance(String model){ + return new OllamaChatRequestBuilder(model, new ArrayList<>()); + } + + public OllamaChatRequestModel build(){ + return request; + } + + public OllamaChatRequestBuilder withMessage(OllamaChatMessageRole role, String content, File... images){ + List messages = this.request.getMessages(); + messages.add(new OllamaChatMessage(role,content,List.of(images))); + return this; + } + + public OllamaChatRequestBuilder withMessages(List messages){ + this.request.setMessages(messages); + return this; + } + + public OllamaChatRequestBuilder withOptions(Options options){ + this.request.setOptions(options); + return this; + } + + public OllamaChatRequestBuilder withFormat(String format){ + this.request.setFormat(format); + return this; + } + + public OllamaChatRequestBuilder withTemplate(String template){ + this.request.setTemplate(template); + return this; + } + + public OllamaChatRequestBuilder withStreaming(){ + this.request.setStream(true); + return this; + } + + public OllamaChatRequestBuilder withKeepAlive(String keepAlive){ + this.request.setKeepAlive(keepAlive); + return this; + } + +} diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestModel.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestModel.java new file mode 100644 index 0000000..6ada64d --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestModel.java @@ -0,0 +1,61 @@ +package io.github.amithkoujalgi.ollama4j.core.models.chat; + +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublisher; +import java.util.List; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import io.github.amithkoujalgi.ollama4j.core.utils.OllamaRequestBody; +import io.github.amithkoujalgi.ollama4j.core.utils.Options; +import io.github.amithkoujalgi.ollama4j.core.utils.Utils; + +import static io.github.amithkoujalgi.ollama4j.core.utils.Utils.getObjectMapper; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +/** + * Defines a Request to use against the ollama /api/chat endpoint. + * + * @see https://github.com/ollama/ollama/blob/main/docs/api.md#generate-a-chat-completion + */ +@Data +@AllArgsConstructor +@RequiredArgsConstructor +public class OllamaChatRequestModel implements OllamaRequestBody{ + + @NonNull + private String model; + + @NonNull + private List messages; + + private String format; + private Options options; + private String template; + private boolean stream; + private String keepAlive; + + @Override + public String toString() { + try { + return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Override + public BodyPublisher getBodyPublisher() { + try { + return HttpRequest.BodyPublishers.ofString( + Utils.getObjectMapper().writeValueAsString(this)); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Request not Body convertible.",e); + } + } + +} \ No newline at end of file diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/utils/OllamaRequestBody.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/utils/OllamaRequestBody.java new file mode 100644 index 0000000..1c4d533 --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/utils/OllamaRequestBody.java @@ -0,0 +1,26 @@ +package io.github.amithkoujalgi.ollama4j.core.utils; + +import java.net.http.HttpRequest.BodyPublisher; +import java.net.http.HttpRequest.BodyPublishers; + +import com.fasterxml.jackson.core.JsonProcessingException; + +/** + * Interface to represent a OllamaRequest as HTTP-Request Body via {@link BodyPublishers}. + */ +public interface OllamaRequestBody { + + /** + * Transforms the OllamaRequest Object to a JSON Object via Jackson. + * + * @return JSON representation of a OllamaRequest + */ + default BodyPublisher getBodyPublisher(){ + try { + return BodyPublishers.ofString( + Utils.getObjectMapper().writeValueAsString(this)); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Request not Body convertible.",e); + } + } +} From c7ac50a805467c3243dffcbe41d92e8935969ebe Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 16:29:35 +0000 Subject: [PATCH 03/22] Defines technical Request Caller Object --- .../request/OllamaChatRequestCaller.java | 16 ++ .../request/OllamaGenerateRequestCaller.java | 18 +++ .../models/request/OllamaServerCaller.java | 137 ++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatRequestCaller.java create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateRequestCaller.java create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatRequestCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatRequestCaller.java new file mode 100644 index 0000000..b08c507 --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatRequestCaller.java @@ -0,0 +1,16 @@ +package io.github.amithkoujalgi.ollama4j.core.models.request; + +import io.github.amithkoujalgi.ollama4j.core.models.BasicAuth; + +public class OllamaChatRequestCaller extends OllamaServerCaller{ + + public OllamaChatRequestCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { + super(host, basicAuth, requestTimeoutSeconds, verbose); + } + + @Override + protected String getEndpointSuffix() { + return "/api/generate"; + } + +} diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateRequestCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateRequestCaller.java new file mode 100644 index 0000000..cc316db --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateRequestCaller.java @@ -0,0 +1,18 @@ +package io.github.amithkoujalgi.ollama4j.core.models.request; + +import io.github.amithkoujalgi.ollama4j.core.models.BasicAuth; + +public class OllamaGenerateRequestCaller extends OllamaServerCaller{ + + public OllamaGenerateRequestCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { + super(host, basicAuth, requestTimeoutSeconds, verbose); + } + + @Override + protected String getEndpointSuffix() { + return "/api/generate"; + } + + + +} diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java new file mode 100644 index 0000000..47b8a34 --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java @@ -0,0 +1,137 @@ +package io.github.amithkoujalgi.ollama4j.core.models.request; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Base64; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.github.amithkoujalgi.ollama4j.core.OllamaAPI; +import io.github.amithkoujalgi.ollama4j.core.exceptions.OllamaBaseException; +import io.github.amithkoujalgi.ollama4j.core.models.BasicAuth; +import io.github.amithkoujalgi.ollama4j.core.models.OllamaErrorResponseModel; +import io.github.amithkoujalgi.ollama4j.core.models.OllamaResponseModel; +import io.github.amithkoujalgi.ollama4j.core.models.OllamaResult; +import io.github.amithkoujalgi.ollama4j.core.utils.OllamaRequestBody; +import io.github.amithkoujalgi.ollama4j.core.utils.Utils; + +public abstract class OllamaServerCaller { + + private static final Logger LOG = LoggerFactory.getLogger(OllamaAPI.class); + + private String host; + private BasicAuth basicAuth; + private long requestTimeoutSeconds; + private boolean verbose; + + public OllamaServerCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { + this.host = host; + this.basicAuth = basicAuth; + this.requestTimeoutSeconds = requestTimeoutSeconds; + this.verbose = verbose; + } + + protected abstract String getEndpointSuffix(); + + public OllamaResult generateSync(OllamaRequestBody body) throws OllamaBaseException, IOException, InterruptedException{ + + // Create Request + long startTime = System.currentTimeMillis(); + HttpClient httpClient = HttpClient.newHttpClient(); + URI uri = URI.create(this.host + getEndpointSuffix()); + HttpRequest.Builder requestBuilder = + getRequestBuilderDefault(uri) + .POST( + body.getBodyPublisher()); + HttpRequest request = requestBuilder.build(); + if (this.verbose) LOG.info("Asking model: " + body.getBodyPublisher()); + HttpResponse response = + httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); + + + int statusCode = response.statusCode(); + InputStream responseBodyStream = response.body(); + StringBuilder responseBuffer = new StringBuilder(); + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + if (statusCode == 404) { + LOG.warn("Status code: 404 (Not Found)"); + OllamaErrorResponseModel ollamaResponseModel = + Utils.getObjectMapper().readValue(line, OllamaErrorResponseModel.class); + responseBuffer.append(ollamaResponseModel.getError()); + } else if (statusCode == 401) { + LOG.warn("Status code: 401 (Unauthorized)"); + OllamaErrorResponseModel ollamaResponseModel = + Utils.getObjectMapper() + .readValue("{\"error\":\"Unauthorized\"}", OllamaErrorResponseModel.class); + responseBuffer.append(ollamaResponseModel.getError()); + } else { + OllamaResponseModel ollamaResponseModel = + Utils.getObjectMapper().readValue(line, OllamaResponseModel.class); + if (!ollamaResponseModel.isDone()) { + responseBuffer.append(ollamaResponseModel.getResponse()); + } + } + } + } + + if (statusCode != 200) { + LOG.error("Status code " + statusCode); + throw new OllamaBaseException(responseBuffer.toString()); + } else { + long endTime = System.currentTimeMillis(); + OllamaResult ollamaResult = + new OllamaResult(responseBuffer.toString().trim(), endTime - startTime, statusCode); + if (verbose) LOG.info("Model response: " + ollamaResult); + return ollamaResult; + } + } + + /** + * Get default request builder. + * + * @param uri URI to get a HttpRequest.Builder + * @return HttpRequest.Builder + */ + private HttpRequest.Builder getRequestBuilderDefault(URI uri) { + HttpRequest.Builder requestBuilder = + HttpRequest.newBuilder(uri) + .header("Content-Type", "application/json") + .timeout(Duration.ofSeconds(this.requestTimeoutSeconds)); + if (isBasicAuthCredentialsSet()) { + requestBuilder.header("Authorization", getBasicAuthHeaderValue()); + } + return requestBuilder; + } + + /** + * Get basic authentication header value. + * + * @return basic authentication header value (encoded credentials) + */ + private String getBasicAuthHeaderValue() { + String credentialsToEncode = this.basicAuth.getUsername() + ":" + this.basicAuth.getPassword(); + return "Basic " + Base64.getEncoder().encodeToString(credentialsToEncode.getBytes()); + } + + /** + * Check if Basic Auth credentials set. + * + * @return true when Basic Auth credentials set + */ + private boolean isBasicAuthCredentialsSet() { + return this.basicAuth != null; + } + +} From bc20468f28228e11fbd7673d59cdb99b10f80bc5 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 16:30:12 +0000 Subject: [PATCH 04/22] Applies OllamaRequestBody to OllamaRequestModel --- .../ollama4j/core/models/OllamaRequestModel.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/OllamaRequestModel.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/OllamaRequestModel.java index a2507a6..9c88698 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/OllamaRequestModel.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/OllamaRequestModel.java @@ -3,12 +3,15 @@ package io.github.amithkoujalgi.ollama4j.core.models; import static io.github.amithkoujalgi.ollama4j.core.utils.Utils.getObjectMapper; import com.fasterxml.jackson.core.JsonProcessingException; + +import io.github.amithkoujalgi.ollama4j.core.utils.OllamaRequestBody; + import java.util.List; import java.util.Map; import lombok.Data; @Data -public class OllamaRequestModel { +public class OllamaRequestModel implements OllamaRequestBody{ private String model; private String prompt; From 00a3e51a93dd8f3482926b0679f393e739de0913 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 16:32:52 +0000 Subject: [PATCH 05/22] Extends OllamaAPI by Chat methods and refactors synchronous Generate API Methods --- .../ollama4j/core/OllamaAPI.java | 79 ++++++------------- 1 file changed, 25 insertions(+), 54 deletions(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java index 17fddc0..831e09e 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java @@ -2,10 +2,15 @@ package io.github.amithkoujalgi.ollama4j.core; import io.github.amithkoujalgi.ollama4j.core.exceptions.OllamaBaseException; import io.github.amithkoujalgi.ollama4j.core.models.*; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatMessage; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestModel; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestBuilder; import io.github.amithkoujalgi.ollama4j.core.models.request.CustomModelFileContentsRequest; import io.github.amithkoujalgi.ollama4j.core.models.request.CustomModelFilePathRequest; import io.github.amithkoujalgi.ollama4j.core.models.request.ModelEmbeddingsRequest; import io.github.amithkoujalgi.ollama4j.core.models.request.ModelRequest; +import io.github.amithkoujalgi.ollama4j.core.models.request.OllamaChatRequestCaller; +import io.github.amithkoujalgi.ollama4j.core.models.request.OllamaGenerateRequestCaller; import io.github.amithkoujalgi.ollama4j.core.utils.Options; import io.github.amithkoujalgi.ollama4j.core.utils.Utils; import java.io.BufferedReader; @@ -343,7 +348,7 @@ public class OllamaAPI { throws OllamaBaseException, IOException, InterruptedException { OllamaRequestModel ollamaRequestModel = new OllamaRequestModel(model, prompt); ollamaRequestModel.setOptions(options.getOptionsMap()); - return generateSync(ollamaRequestModel); + return generateSyncForOllamaRequestModel(ollamaRequestModel); } /** @@ -387,7 +392,7 @@ public class OllamaAPI { } OllamaRequestModel ollamaRequestModel = new OllamaRequestModel(model, prompt, images); ollamaRequestModel.setOptions(options.getOptionsMap()); - return generateSync(ollamaRequestModel); + return generateSyncForOllamaRequestModel(ollamaRequestModel); } /** @@ -411,9 +416,23 @@ public class OllamaAPI { } OllamaRequestModel ollamaRequestModel = new OllamaRequestModel(model, prompt, images); ollamaRequestModel.setOptions(options.getOptionsMap()); - return generateSync(ollamaRequestModel); + return generateSyncForOllamaRequestModel(ollamaRequestModel); } + + + public OllamaResult chat(String model, List messages) throws OllamaBaseException, IOException, InterruptedException, URISyntaxException{ + OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(model); + return chat(builder.withMessages(messages).build()); + } + + public OllamaResult chat(OllamaChatRequestModel request) throws OllamaBaseException, IOException, InterruptedException, URISyntaxException{ + OllamaChatRequestCaller requestCaller = new OllamaChatRequestCaller(host, basicAuth, requestTimeoutSeconds, verbose); + return requestCaller.generateSync(request); + } + + // technical private methods // + private static String encodeFileToBase64(File file) throws IOException { return Base64.getEncoder().encodeToString(Files.readAllBytes(file.toPath())); } @@ -436,58 +455,10 @@ public class OllamaAPI { } } - private OllamaResult generateSync(OllamaRequestModel ollamaRequestModel) + private OllamaResult generateSyncForOllamaRequestModel(OllamaRequestModel ollamaRequestModel) throws OllamaBaseException, IOException, InterruptedException { - long startTime = System.currentTimeMillis(); - HttpClient httpClient = HttpClient.newHttpClient(); - URI uri = URI.create(this.host + "/api/generate"); - HttpRequest.Builder requestBuilder = - getRequestBuilderDefault(uri) - .POST( - HttpRequest.BodyPublishers.ofString( - Utils.getObjectMapper().writeValueAsString(ollamaRequestModel))); - HttpRequest request = requestBuilder.build(); - if (verbose) logger.info("Asking model: " + ollamaRequestModel); - HttpResponse response = - httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); - int statusCode = response.statusCode(); - InputStream responseBodyStream = response.body(); - StringBuilder responseBuffer = new StringBuilder(); - try (BufferedReader reader = - new BufferedReader(new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) { - String line; - while ((line = reader.readLine()) != null) { - if (statusCode == 404) { - logger.warn("Status code: 404 (Not Found)"); - OllamaErrorResponseModel ollamaResponseModel = - Utils.getObjectMapper().readValue(line, OllamaErrorResponseModel.class); - responseBuffer.append(ollamaResponseModel.getError()); - } else if (statusCode == 401) { - logger.warn("Status code: 401 (Unauthorized)"); - OllamaErrorResponseModel ollamaResponseModel = - Utils.getObjectMapper() - .readValue("{\"error\":\"Unauthorized\"}", OllamaErrorResponseModel.class); - responseBuffer.append(ollamaResponseModel.getError()); - } else { - OllamaResponseModel ollamaResponseModel = - Utils.getObjectMapper().readValue(line, OllamaResponseModel.class); - if (!ollamaResponseModel.isDone()) { - responseBuffer.append(ollamaResponseModel.getResponse()); - } - } - } - } - - if (statusCode != 200) { - logger.error("Status code " + statusCode); - throw new OllamaBaseException(responseBuffer.toString()); - } else { - long endTime = System.currentTimeMillis(); - OllamaResult ollamaResult = - new OllamaResult(responseBuffer.toString().trim(), endTime - startTime, statusCode); - if (verbose) logger.info("Model response: " + ollamaResult); - return ollamaResult; - } + OllamaGenerateRequestCaller requestCaller = new OllamaGenerateRequestCaller(host, basicAuth, requestTimeoutSeconds, verbose); + return requestCaller.generateSync(ollamaRequestModel); } /** From f9063484f3beb78ccc6a1c9b00af4bacbfb1459c Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 17:07:18 +0000 Subject: [PATCH 06/22] Fixes recursive JacksonMarshalling on BodyPublisher --- .../amithkoujalgi/ollama4j/core/utils/OllamaRequestBody.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/utils/OllamaRequestBody.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/utils/OllamaRequestBody.java index 1c4d533..f787cee 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/utils/OllamaRequestBody.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/utils/OllamaRequestBody.java @@ -3,6 +3,7 @@ package io.github.amithkoujalgi.ollama4j.core.utils; import java.net.http.HttpRequest.BodyPublisher; import java.net.http.HttpRequest.BodyPublishers; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; /** @@ -15,6 +16,7 @@ public interface OllamaRequestBody { * * @return JSON representation of a OllamaRequest */ + @JsonIgnore default BodyPublisher getBodyPublisher(){ try { return BodyPublishers.ofString( From 273b1e47ca891cede1134b9c0f1fad5d71c15e5b Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 21:58:17 +0000 Subject: [PATCH 07/22] Removes unneccessary override of OllamaRequestBody method --- .../core/models/chat/OllamaChatRequestModel.java | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestModel.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestModel.java index 6ada64d..516e00b 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestModel.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestModel.java @@ -1,14 +1,11 @@ package io.github.amithkoujalgi.ollama4j.core.models.chat; -import java.net.http.HttpRequest; -import java.net.http.HttpRequest.BodyPublisher; import java.util.List; import com.fasterxml.jackson.core.JsonProcessingException; import io.github.amithkoujalgi.ollama4j.core.utils.OllamaRequestBody; import io.github.amithkoujalgi.ollama4j.core.utils.Options; -import io.github.amithkoujalgi.ollama4j.core.utils.Utils; import static io.github.amithkoujalgi.ollama4j.core.utils.Utils.getObjectMapper; @@ -48,14 +45,4 @@ public class OllamaChatRequestModel implements OllamaRequestBody{ } } - @Override - public BodyPublisher getBodyPublisher() { - try { - return HttpRequest.BodyPublishers.ofString( - Utils.getObjectMapper().writeValueAsString(this)); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException("Request not Body convertible.",e); - } - } - } \ No newline at end of file From d483c23c81ae7c4da1cde9b2b6cbde2c8572fe5d Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 22:48:19 +0000 Subject: [PATCH 08/22] Adds documentation to OllamaServerCaller --- .../core/models/request/OllamaServerCaller.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java index 47b8a34..6f9f27b 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java @@ -24,6 +24,9 @@ import io.github.amithkoujalgi.ollama4j.core.models.OllamaResult; import io.github.amithkoujalgi.ollama4j.core.utils.OllamaRequestBody; import io.github.amithkoujalgi.ollama4j.core.utils.Utils; +/** + * Abstract helperclass to call the ollama api server. + */ public abstract class OllamaServerCaller { private static final Logger LOG = LoggerFactory.getLogger(OllamaAPI.class); @@ -42,6 +45,15 @@ public abstract class OllamaServerCaller { protected abstract String getEndpointSuffix(); + /** + * Calls the api server on the given host and endpoint suffix asynchronously, aka waiting for the response. + * + * @param body POST body payload + * @return result answer given by the assistant + * @throws OllamaBaseException any response code than 200 has been returned + * @throws IOException in case the responseStream can not be read + * @throws InterruptedException in case the server is not reachable or network issues happen + */ public OllamaResult generateSync(OllamaRequestBody body) throws OllamaBaseException, IOException, InterruptedException{ // Create Request @@ -53,7 +65,7 @@ public abstract class OllamaServerCaller { .POST( body.getBodyPublisher()); HttpRequest request = requestBuilder.build(); - if (this.verbose) LOG.info("Asking model: " + body.getBodyPublisher()); + if (this.verbose) LOG.info("Asking model: " + body.toString()); HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); From 65d852fdc90c4ec2bdde5b98cb7ac64816b28dfc Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 22:48:48 +0000 Subject: [PATCH 09/22] Creates OllamaChatResult extension class --- .../core/models/chat/OllamaChatResult.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatResult.java diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatResult.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatResult.java new file mode 100644 index 0000000..6ac6578 --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatResult.java @@ -0,0 +1,32 @@ +package io.github.amithkoujalgi.ollama4j.core.models.chat; + +import java.util.List; + +import io.github.amithkoujalgi.ollama4j.core.models.OllamaResult; + +/** + * Specific chat-API result that contains the chat history sent to the model and appends the answer as {@link OllamaChatResult} given by the + * {@link OllamaChatMessageRole#ASSISTANT} role. + */ +public class OllamaChatResult extends OllamaResult{ + + private List chatHistory; + + public OllamaChatResult(String response, long responseTime, int httpStatusCode, + List chatHistory) { + super(response, responseTime, httpStatusCode); + this.chatHistory = chatHistory; + appendAnswerToChatHistory(response); + } + + public List getChatHistory() { + return chatHistory; + } + + private void appendAnswerToChatHistory(String answer){ + OllamaChatMessage assistantMessage = new OllamaChatMessage(OllamaChatMessageRole.ASSISTANT, answer); + this.chatHistory.add(assistantMessage); + } + + +} From 14982011d9fb0df6ecc570f38d90707bfc0461cf Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Fri, 9 Feb 2024 22:49:28 +0000 Subject: [PATCH 10/22] Adds chat-API with OllamaChatResult --- .../ollama4j/core/OllamaAPI.java | 34 +++++++++++++++++-- .../models/chat/OllamaChatMessageRole.java | 1 + 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java index 831e09e..af39c66 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java @@ -4,6 +4,7 @@ import io.github.amithkoujalgi.ollama4j.core.exceptions.OllamaBaseException; import io.github.amithkoujalgi.ollama4j.core.models.*; import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatMessage; import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestModel; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatResult; import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestBuilder; import io.github.amithkoujalgi.ollama4j.core.models.request.CustomModelFileContentsRequest; import io.github.amithkoujalgi.ollama4j.core.models.request.CustomModelFilePathRequest; @@ -421,14 +422,41 @@ public class OllamaAPI { - public OllamaResult chat(String model, List messages) throws OllamaBaseException, IOException, InterruptedException, URISyntaxException{ + /** + * Ask a question to a model based on a given message stack (i.e. a chat history). Creates a synchronous call to the api + * 'api/chat'. + * + * @param model the ollama model to ask the question to + * @param messages chat history / message stack to send to the model + * @return {@link OllamaChatResult} containing the api response and the message history including the newly aqcuired assistant response. + * @throws OllamaBaseException any response code than 200 has been returned + * @throws IOException in case the responseStream can not be read + * @throws InterruptedException in case the server is not reachable or network issues happen + */ + public OllamaChatResult chat(String model, List messages) throws OllamaBaseException, IOException, InterruptedException{ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(model); return chat(builder.withMessages(messages).build()); } - public OllamaResult chat(OllamaChatRequestModel request) throws OllamaBaseException, IOException, InterruptedException, URISyntaxException{ + /** + * Ask a question to a model using an {@link OllamaChatRequestModel}. This can be constructed using an {@link OllamaChatRequestBuilder}. + * + * Hint: the {@link OllamaChatRequestModel#getStream()} property is not implemented + * + * @param request request object to be sent to the server + * @return + * @throws OllamaBaseException any response code than 200 has been returned + * @throws IOException in case the responseStream can not be read + * @throws InterruptedException in case the server is not reachable or network issues happen + */ + public OllamaChatResult chat(OllamaChatRequestModel request) throws OllamaBaseException, IOException, InterruptedException{ OllamaChatRequestCaller requestCaller = new OllamaChatRequestCaller(host, basicAuth, requestTimeoutSeconds, verbose); - return requestCaller.generateSync(request); + //TODO: implement async way + if(request.isStream()){ + throw new UnsupportedOperationException("Streamed chat responses are not implemented yet"); + } + OllamaResult result = requestCaller.generateSync(request); + return new OllamaChatResult(result.getResponse(), result.getResponseTime(), result.getHttpStatusCode(), request.getMessages()); } // technical private methods // diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java index ec7f9be..ad0665f 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java @@ -8,6 +8,7 @@ public enum OllamaChatMessageRole { USER("user"), ASSISTANT("assisstant"); + @SuppressWarnings("unused") private String roleName; private OllamaChatMessageRole(String roleName){ From a635dd9be22fc59d363fa1680ad2d89979cdf1cb Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 00:22:21 +0000 Subject: [PATCH 11/22] Renames request caller to endpoint caller --- .../request/OllamaChatEndpointCaller.java | 44 +++++++++++++++++++ .../request/OllamaChatRequestCaller.java | 16 ------- ...rCaller.java => OllamaEndpointCaller.java} | 16 ++++--- .../request/OllamaGenerateEndpointCaller.java | 40 +++++++++++++++++ .../request/OllamaGenerateRequestCaller.java | 18 -------- 5 files changed, 93 insertions(+), 41 deletions(-) create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatEndpointCaller.java delete mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatRequestCaller.java rename src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/{OllamaServerCaller.java => OllamaEndpointCaller.java} (92%) create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateEndpointCaller.java delete mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateRequestCaller.java diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatEndpointCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatEndpointCaller.java new file mode 100644 index 0000000..eb06c37 --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatEndpointCaller.java @@ -0,0 +1,44 @@ +package io.github.amithkoujalgi.ollama4j.core.models.request; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import io.github.amithkoujalgi.ollama4j.core.models.BasicAuth; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatResponseModel; +import io.github.amithkoujalgi.ollama4j.core.utils.Utils; + +/** + * Specialization class for requests + */ +public class OllamaChatEndpointCaller extends OllamaEndpointCaller{ + + private static final Logger LOG = LoggerFactory.getLogger(OllamaChatEndpointCaller.class); + + public OllamaChatEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { + super(host, basicAuth, requestTimeoutSeconds, verbose); + } + + @Override + protected String getEndpointSuffix() { + return "/api/chat"; + } + + @Override + protected boolean parseResponseAndAddToBuffer(String line, StringBuilder responseBuffer) { + try { + OllamaChatResponseModel ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaChatResponseModel.class); + responseBuffer.append(ollamaResponseModel.getMessage().getContent()); + return ollamaResponseModel.isDone(); + } catch (JsonProcessingException e) { + LOG.error("Error parsing the Ollama chat response!",e); + return true; + } + } + + + + + +} diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatRequestCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatRequestCaller.java deleted file mode 100644 index b08c507..0000000 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaChatRequestCaller.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.github.amithkoujalgi.ollama4j.core.models.request; - -import io.github.amithkoujalgi.ollama4j.core.models.BasicAuth; - -public class OllamaChatRequestCaller extends OllamaServerCaller{ - - public OllamaChatRequestCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { - super(host, basicAuth, requestTimeoutSeconds, verbose); - } - - @Override - protected String getEndpointSuffix() { - return "/api/generate"; - } - -} diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaEndpointCaller.java similarity index 92% rename from src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java rename to src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaEndpointCaller.java index 6f9f27b..d99499f 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaServerCaller.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaEndpointCaller.java @@ -27,7 +27,7 @@ import io.github.amithkoujalgi.ollama4j.core.utils.Utils; /** * Abstract helperclass to call the ollama api server. */ -public abstract class OllamaServerCaller { +public abstract class OllamaEndpointCaller { private static final Logger LOG = LoggerFactory.getLogger(OllamaAPI.class); @@ -36,7 +36,7 @@ public abstract class OllamaServerCaller { private long requestTimeoutSeconds; private boolean verbose; - public OllamaServerCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { + public OllamaEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { this.host = host; this.basicAuth = basicAuth; this.requestTimeoutSeconds = requestTimeoutSeconds; @@ -44,6 +44,9 @@ public abstract class OllamaServerCaller { } protected abstract String getEndpointSuffix(); + + protected abstract boolean parseResponseAndAddToBuffer(String line, StringBuilder responseBuffer); + /** * Calls the api server on the given host and endpoint suffix asynchronously, aka waiting for the response. @@ -89,11 +92,10 @@ public abstract class OllamaServerCaller { .readValue("{\"error\":\"Unauthorized\"}", OllamaErrorResponseModel.class); responseBuffer.append(ollamaResponseModel.getError()); } else { - OllamaResponseModel ollamaResponseModel = - Utils.getObjectMapper().readValue(line, OllamaResponseModel.class); - if (!ollamaResponseModel.isDone()) { - responseBuffer.append(ollamaResponseModel.getResponse()); - } + boolean finished = parseResponseAndAddToBuffer(line,responseBuffer); + if (finished) { + break; + } } } } diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateEndpointCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateEndpointCaller.java new file mode 100644 index 0000000..8d54db3 --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateEndpointCaller.java @@ -0,0 +1,40 @@ +package io.github.amithkoujalgi.ollama4j.core.models.request; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; + +import io.github.amithkoujalgi.ollama4j.core.models.BasicAuth; +import io.github.amithkoujalgi.ollama4j.core.models.OllamaResponseModel; +import io.github.amithkoujalgi.ollama4j.core.utils.Utils; + +public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller{ + + private static final Logger LOG = LoggerFactory.getLogger(OllamaGenerateEndpointCaller.class); + + public OllamaGenerateEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { + super(host, basicAuth, requestTimeoutSeconds, verbose); + } + + @Override + protected String getEndpointSuffix() { + return "/api/generate"; + } + + @Override + protected boolean parseResponseAndAddToBuffer(String line, StringBuilder responseBuffer) { + try { + OllamaResponseModel ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaResponseModel.class); + responseBuffer.append(ollamaResponseModel.getResponse()); + return ollamaResponseModel.isDone(); + } catch (JsonProcessingException e) { + LOG.error("Error parsing the Ollama chat response!",e); + return true; + } + } + + + + +} diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateRequestCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateRequestCaller.java deleted file mode 100644 index cc316db..0000000 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaGenerateRequestCaller.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.github.amithkoujalgi.ollama4j.core.models.request; - -import io.github.amithkoujalgi.ollama4j.core.models.BasicAuth; - -public class OllamaGenerateRequestCaller extends OllamaServerCaller{ - - public OllamaGenerateRequestCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) { - super(host, basicAuth, requestTimeoutSeconds, verbose); - } - - @Override - protected String getEndpointSuffix() { - return "/api/generate"; - } - - - -} From 4ca6eef8fd44329854af05421c63d8fe3738a322 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 00:22:55 +0000 Subject: [PATCH 12/22] Fixes JSON Binding for OllamaChatMessageRole --- .../ollama4j/core/models/chat/OllamaChatMessageRole.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java index ad0665f..cbecb00 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessageRole.java @@ -1,14 +1,16 @@ package io.github.amithkoujalgi.ollama4j.core.models.chat; +import com.fasterxml.jackson.annotation.JsonValue; + /** * Defines the possible Chat Message roles. */ public enum OllamaChatMessageRole { SYSTEM("system"), USER("user"), - ASSISTANT("assisstant"); + ASSISTANT("assistant"); - @SuppressWarnings("unused") + @JsonValue private String roleName; private OllamaChatMessageRole(String roleName){ From 0bec697a86f0e2c2ef8350686a63f86d5eaa246f Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 00:23:26 +0000 Subject: [PATCH 13/22] Specifies OllamaChatResponseModel --- .../models/chat/OllamaChatResponseModel.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatResponseModel.java diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatResponseModel.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatResponseModel.java new file mode 100644 index 0000000..da69dfe --- /dev/null +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatResponseModel.java @@ -0,0 +1,21 @@ +package io.github.amithkoujalgi.ollama4j.core.models.chat; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; +import lombok.Data; + +@Data +public class OllamaChatResponseModel { + private String model; + private @JsonProperty("created_at") String createdAt; + private OllamaChatMessage message; + private boolean done; + private List context; + private @JsonProperty("total_duration") Long totalDuration; + private @JsonProperty("load_duration") Long loadDuration; + private @JsonProperty("prompt_eval_duration") Long promptEvalDuration; + private @JsonProperty("eval_duration") Long evalDuration; + private @JsonProperty("prompt_eval_count") Integer promptEvalCount; + private @JsonProperty("eval_count") Integer evalCount; +} From 4260fbbc32b7fcf34a53cac7898b56218bf812af Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 00:24:12 +0000 Subject: [PATCH 14/22] Fixes OllamaAPI after Renaming --- .../io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java | 8 ++++---- .../ollama4j/core/models/chat/OllamaChatMessage.java | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java index af39c66..a0212d7 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/OllamaAPI.java @@ -10,8 +10,8 @@ import io.github.amithkoujalgi.ollama4j.core.models.request.CustomModelFileConte import io.github.amithkoujalgi.ollama4j.core.models.request.CustomModelFilePathRequest; import io.github.amithkoujalgi.ollama4j.core.models.request.ModelEmbeddingsRequest; import io.github.amithkoujalgi.ollama4j.core.models.request.ModelRequest; -import io.github.amithkoujalgi.ollama4j.core.models.request.OllamaChatRequestCaller; -import io.github.amithkoujalgi.ollama4j.core.models.request.OllamaGenerateRequestCaller; +import io.github.amithkoujalgi.ollama4j.core.models.request.OllamaChatEndpointCaller; +import io.github.amithkoujalgi.ollama4j.core.models.request.OllamaGenerateEndpointCaller; import io.github.amithkoujalgi.ollama4j.core.utils.Options; import io.github.amithkoujalgi.ollama4j.core.utils.Utils; import java.io.BufferedReader; @@ -450,7 +450,7 @@ public class OllamaAPI { * @throws InterruptedException in case the server is not reachable or network issues happen */ public OllamaChatResult chat(OllamaChatRequestModel request) throws OllamaBaseException, IOException, InterruptedException{ - OllamaChatRequestCaller requestCaller = new OllamaChatRequestCaller(host, basicAuth, requestTimeoutSeconds, verbose); + OllamaChatEndpointCaller requestCaller = new OllamaChatEndpointCaller(host, basicAuth, requestTimeoutSeconds, verbose); //TODO: implement async way if(request.isStream()){ throw new UnsupportedOperationException("Streamed chat responses are not implemented yet"); @@ -485,7 +485,7 @@ public class OllamaAPI { private OllamaResult generateSyncForOllamaRequestModel(OllamaRequestModel ollamaRequestModel) throws OllamaBaseException, IOException, InterruptedException { - OllamaGenerateRequestCaller requestCaller = new OllamaGenerateRequestCaller(host, basicAuth, requestTimeoutSeconds, verbose); + OllamaGenerateEndpointCaller requestCaller = new OllamaGenerateEndpointCaller(host, basicAuth, requestTimeoutSeconds, verbose); return requestCaller.generateSync(ollamaRequestModel); } diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessage.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessage.java index cd0becd..b877ddf 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessage.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatMessage.java @@ -8,6 +8,7 @@ import java.util.List; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -19,6 +20,7 @@ import lombok.RequiredArgsConstructor; @Data @AllArgsConstructor @RequiredArgsConstructor +@NoArgsConstructor public class OllamaChatMessage { @NonNull From 075416eb9c04265485759f4d26438eab88fc90b8 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 00:25:09 +0000 Subject: [PATCH 15/22] Adds Integration tests for chat API --- .../integrationtests/TestRealAPIs.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java b/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java index 0218ae9..846b12a 100644 --- a/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java +++ b/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java @@ -5,6 +5,10 @@ import static org.junit.jupiter.api.Assertions.*; import io.github.amithkoujalgi.ollama4j.core.OllamaAPI; import io.github.amithkoujalgi.ollama4j.core.exceptions.OllamaBaseException; import io.github.amithkoujalgi.ollama4j.core.models.OllamaResult; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatMessageRole; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestBuilder; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestModel; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatResult; import io.github.amithkoujalgi.ollama4j.core.types.OllamaModelType; import io.github.amithkoujalgi.ollama4j.core.utils.OptionsBuilder; import java.io.File; @@ -131,6 +135,46 @@ class TestRealAPIs { } } + @Test + @Order(3) + void testChat() { + testEndpointReachability(); + try { + OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(OllamaModelType.LLAMA2); + OllamaChatRequestModel requestModel = builder.withMessage(OllamaChatMessageRole.USER, "Say hello to my little friend") + .withMessage(OllamaChatMessageRole.ASSISTANT, "Seems to be a Tony Montana montage!") + .withMessage(OllamaChatMessageRole.USER,"We need a montage!") + .build(); + + OllamaChatResult chatResult = ollamaAPI.chat(requestModel); + assertNotNull(chatResult); + assertFalse(chatResult.getResponse().isBlank()); + assertEquals(4,chatResult.getChatHistory().size()); + } catch (IOException | OllamaBaseException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(3) + void testChatWithSystemPrompt() { + testEndpointReachability(); + try { + OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(OllamaModelType.LLAMA2); + OllamaChatRequestModel requestModel = builder.withMessage(OllamaChatMessageRole.SYSTEM, "You are a silent bot that only says 'NI'. Do not say anything else under any circumstances!") + .withMessage(OllamaChatMessageRole.USER,"We need a montage!") + .build(); + + OllamaChatResult chatResult = ollamaAPI.chat(requestModel); + assertNotNull(chatResult); + assertFalse(chatResult.getResponse().isBlank()); + assertTrue(chatResult.getResponse().startsWith("NI")); + assertEquals(3,chatResult.getChatHistory().size()); + } catch (IOException | OllamaBaseException | InterruptedException e) { + throw new RuntimeException(e); + } + } + @Test @Order(3) void testAskModelWithOptionsAndImageFiles() { From 0c4e8e306ea2f54364200241d848c2ab2d569ad6 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 00:26:25 +0000 Subject: [PATCH 16/22] Organizes imports --- .../ollama4j/core/models/request/OllamaEndpointCaller.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaEndpointCaller.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaEndpointCaller.java index d99499f..134056f 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaEndpointCaller.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/request/OllamaEndpointCaller.java @@ -19,7 +19,6 @@ import io.github.amithkoujalgi.ollama4j.core.OllamaAPI; import io.github.amithkoujalgi.ollama4j.core.exceptions.OllamaBaseException; import io.github.amithkoujalgi.ollama4j.core.models.BasicAuth; import io.github.amithkoujalgi.ollama4j.core.models.OllamaErrorResponseModel; -import io.github.amithkoujalgi.ollama4j.core.models.OllamaResponseModel; import io.github.amithkoujalgi.ollama4j.core.models.OllamaResult; import io.github.amithkoujalgi.ollama4j.core.utils.OllamaRequestBody; import io.github.amithkoujalgi.ollama4j.core.utils.Utils; From 90669b611b0b2fd587c803c58b241531240ade38 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 22:28:50 +0000 Subject: [PATCH 17/22] Changes implementation of withMessages in ChatRequestBuilder --- .../ollama4j/core/models/chat/OllamaChatRequestBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java index bcd710c..69ded1b 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java @@ -32,7 +32,7 @@ public class OllamaChatRequestBuilder { } public OllamaChatRequestBuilder withMessages(List messages){ - this.request.setMessages(messages); + this.request.getMessages().addAll(messages); return this; } From 40a3aa31dc960b4ded67a43b70a3fbe2420e0538 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 22:51:47 +0000 Subject: [PATCH 18/22] Adds helper reset method to OllamaChatRequestBuilder --- .../ollama4j/core/models/chat/OllamaChatRequestBuilder.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java index 69ded1b..af28b7b 100644 --- a/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java +++ b/src/main/java/io/github/amithkoujalgi/ollama4j/core/models/chat/OllamaChatRequestBuilder.java @@ -25,6 +25,10 @@ public class OllamaChatRequestBuilder { return request; } + public void reset(){ + request = new OllamaChatRequestModel(request.getModel(), new ArrayList<>()); + } + public OllamaChatRequestBuilder withMessage(OllamaChatMessageRole role, String content, File... images){ List messages = this.request.getMessages(); messages.add(new OllamaChatMessage(role,content,List.of(images))); From 9c16ccbf816f864a2771e9fb50d874b6b704a9c2 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 22:58:29 +0000 Subject: [PATCH 19/22] Changes test texts for chat integration tests --- .../ollama4j/integrationtests/TestRealAPIs.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java b/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java index 0ca633f..0ebb404 100644 --- a/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java +++ b/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.*; import io.github.amithkoujalgi.ollama4j.core.OllamaAPI; import io.github.amithkoujalgi.ollama4j.core.exceptions.OllamaBaseException; import io.github.amithkoujalgi.ollama4j.core.models.OllamaResult; +import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatMessage; import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatMessageRole; import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestBuilder; import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestModel; @@ -128,9 +129,9 @@ class TestRealAPIs { testEndpointReachability(); try { OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel()); - OllamaChatRequestModel requestModel = builder.withMessage(OllamaChatMessageRole.USER, "Say hello to my little friend") - .withMessage(OllamaChatMessageRole.ASSISTANT, "Seems to be a Tony Montana montage!") - .withMessage(OllamaChatMessageRole.USER,"We need a montage!") + OllamaChatRequestModel requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What is the capital of France?") + .withMessage(OllamaChatMessageRole.ASSISTANT, "Should be Paris!") + .withMessage(OllamaChatMessageRole.USER,"And what is the second larges city?") .build(); OllamaChatResult chatResult = ollamaAPI.chat(requestModel); @@ -149,7 +150,7 @@ class TestRealAPIs { try { OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel()); OllamaChatRequestModel requestModel = builder.withMessage(OllamaChatMessageRole.SYSTEM, "You are a silent bot that only says 'NI'. Do not say anything else under any circumstances!") - .withMessage(OllamaChatMessageRole.USER,"We need a montage!") + .withMessage(OllamaChatMessageRole.USER,"What is the capital of France? And what's France's connection with Mona Lisa?") .build(); OllamaChatResult chatResult = ollamaAPI.chat(requestModel); From 874736eb162c490058d945a83a14b2fd7a2171c6 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 22:58:50 +0000 Subject: [PATCH 20/22] adds documentation for chat API --- docs/docs/apis-ask/chat.md | 98 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 docs/docs/apis-ask/chat.md diff --git a/docs/docs/apis-ask/chat.md b/docs/docs/apis-ask/chat.md new file mode 100644 index 0000000..751aba2 --- /dev/null +++ b/docs/docs/apis-ask/chat.md @@ -0,0 +1,98 @@ +--- +sidebar_position: 5 +--- + +# Chat + +This API lets you create a conversation with LLMs. Using this API enables you to ask questions to the model including +information using the history of already asked questions and the respective answers. + +## Create a new conversation and use chat history to augment follow up questions + +```java +public class Main { + + public static void main(String[] args) { + + String host = "http://localhost:11434/"; + + OllamaAPI ollamaAPI = new OllamaAPI(host); + OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(OllamaModelType.LLAMA2); + + // create first user question + OllamaChatRequestModel requestModel = builder.withMessage(OllamaChatMessageRole.USER,"What is the capital of France?") + .build(); + + // start conversation with model + OllamaChatResult chatResult = ollamaAPI.chat(requestModel); + + System.out.println("First answer: " + chatResult.getResponse()); + + // create next userQuestion + requestModel = builder.withMessages(chatResult.getChatHistory()).withMessage(OllamaChatMessageRole.USER,"And what is the second largest city?").build(); + + // "continue" conversation with model + chatResult = ollamaAPI.chat(requestModel); + + System.out.println("Second answer: " + chatResult.getResponse()); + + System.out.println("Chat History: " + chatResult.getChatHistory()); + } +} + +``` +You will get a response similar to: + +> First answer: Should be Paris! +> +> Second answer: Marseille. +> +> Chat History: + +```json +[ { + "role" : "user", + "content" : "What is the capital of France?", + "images" : [ ] + }, { + "role" : "assistant", + "content" : "Should be Paris!", + "images" : [ ] + }, { + "role" : "user", + "content" : "And what is the second largest city?", + "images" : [ ] + }, { + "role" : "assistant", + "content" : "Marseille.", + "images" : [ ] + } ] +``` + +## Create a new conversation with individual system prompt +```java +public class Main { + + public static void main(String[] args) { + + String host = "http://localhost:11434/"; + + OllamaAPI ollamaAPI = new OllamaAPI(host); + OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(OllamaModelType.LLAMA2); + + // create request with system-prompt (overriding the model defaults) and user question + OllamaChatRequestModel requestModel = builder.withMessage(OllamaChatMessageRole.SYSTEM, "You are a silent bot that only says 'NI'. Do not say anything else under any circumstances!") + .withMessage(OllamaChatMessageRole.USER,"What is the capital of France? And what's France's connection with Mona Lisa?") + .build(); + + // start conversation with model + OllamaChatResult chatResult = ollamaAPI.chat(requestModel); + + System.out.println(chatResult.getResponse()); + } +} + +``` +You will get a response similar to: + +> NI. \ No newline at end of file From d9816d88695f23c562bd463011226f153031189b Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 23:13:11 +0000 Subject: [PATCH 21/22] Removes unused import --- .../amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java b/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java index 0ebb404..284a52d 100644 --- a/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java +++ b/src/test/java/io/github/amithkoujalgi/ollama4j/integrationtests/TestRealAPIs.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.*; import io.github.amithkoujalgi.ollama4j.core.OllamaAPI; import io.github.amithkoujalgi.ollama4j.core.exceptions.OllamaBaseException; import io.github.amithkoujalgi.ollama4j.core.models.OllamaResult; -import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatMessage; import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatMessageRole; import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestBuilder; import io.github.amithkoujalgi.ollama4j.core.models.chat.OllamaChatRequestModel; From 272ba445f6bbd4817c89f379a1aa1d9559afeb81 Mon Sep 17 00:00:00 2001 From: Markus Klenke Date: Sat, 10 Feb 2024 23:23:01 +0000 Subject: [PATCH 22/22] Moves documentation to end of sidebar group --- docs/docs/apis-ask/chat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/apis-ask/chat.md b/docs/docs/apis-ask/chat.md index 751aba2..00cda08 100644 --- a/docs/docs/apis-ask/chat.md +++ b/docs/docs/apis-ask/chat.md @@ -1,5 +1,5 @@ --- -sidebar_position: 5 +sidebar_position: 7 --- # Chat