Compare commits

..

60 Commits

Author SHA1 Message Date
amithkoujalgi
2c52f4d0bb [maven-release-plugin] prepare release v1.0.52 2024-02-14 13:44:29 +00:00
Amith Koujalgi
32c4231eb5 Merge pull request #25 from AgentSchmecker/feature/chat_messages_with_images
Adds image capability to chat API
2024-02-14 19:13:22 +05:30
Markus Klenke
1aeb555a53 Adds documentation for chat with images use case 2024-02-13 10:22:13 +00:00
Markus Klenke
9aff3ec5d9 Extends chat with image integration test to use history 2024-02-13 10:20:37 +00:00
Markus Klenke
b4eaf0cfb5 Fixes ChatRequestBuilder 2024-02-13 10:20:06 +00:00
Markus Klenke
199cb6082d Cleans up unused imports 2024-02-12 22:09:55 +00:00
Markus Klenke
37bfe26a6d Adds integrationtest for chatImagesByURL and formats test cases 2024-02-12 22:08:53 +00:00
Markus Klenke
3769386539 Changes images property of ChatMessage to type byte[] 2024-02-12 22:08:10 +00:00
Markus Klenke
84a6e57f42 Refactors loadImageBytesFromUrl to Utils class 2024-02-12 22:07:15 +00:00
Markus Klenke
14d2474ee9 Adds integrationtestcase for chat message with image content 2024-02-12 21:35:43 +00:00
Markus Klenke
ca613ed80a Adds base64 encoding for chat-API request messages 2024-02-12 21:32:07 +00:00
Markus Klenke
bbcd458849 Adds sync-response interpreter for responseCode 400 2024-02-12 21:30:55 +00:00
Markus Klenke
bc885894f8 Extends OllamaChatResponseModel with error property 2024-02-12 21:29:27 +00:00
amithkoujalgi
bc83df6971 [maven-release-plugin] prepare for next development iteration 2024-02-12 16:34:11 +00:00
amithkoujalgi
43f43c9f81 [maven-release-plugin] prepare release v1.0.51 2024-02-12 16:34:10 +00:00
Amith Koujalgi
65f00defcf Updates to javadoc 2024-02-12 22:03:01 +05:30
Amith Koujalgi
d716b81342 Merge pull request #23 from AgentSchmecker/feature/chat-request-model
Adds feature for /api/chat access via OllamaAPI
2024-02-12 21:36:33 +05:30
Markus Klenke
272ba445f6 Moves documentation to end of sidebar group 2024-02-10 23:23:01 +00:00
Markus Klenke
d9816d8869 Removes unused import 2024-02-10 23:13:11 +00:00
Markus Klenke
874736eb16 adds documentation for chat API 2024-02-10 22:58:50 +00:00
Markus Klenke
9c16ccbf81 Changes test texts for chat integration tests 2024-02-10 22:58:29 +00:00
Markus Klenke
40a3aa31dc Adds helper reset method to OllamaChatRequestBuilder 2024-02-10 22:51:47 +00:00
Markus Klenke
90669b611b Changes implementation of withMessages in ChatRequestBuilder 2024-02-10 22:28:50 +00:00
Markus Klenke
f10c7ac725 Merge remote-tracking branch 'upstream/main' into feature/chat-request-model 2024-02-10 22:09:59 +00:00
amithkoujalgi
38dca3cd0d [maven-release-plugin] prepare for next development iteration 2024-02-10 05:13:11 +00:00
amithkoujalgi
44bb35b168 [maven-release-plugin] prepare release v1.0.50 2024-02-10 05:13:09 +00:00
Amith Koujalgi
9832caf503 Update intg test class 2024-02-10 10:41:53 +05:30
Markus Klenke
0c4e8e306e Organizes imports 2024-02-10 00:26:25 +00:00
Markus Klenke
075416eb9c Adds Integration tests for chat API 2024-02-10 00:25:09 +00:00
Markus Klenke
4260fbbc32 Fixes OllamaAPI after Renaming 2024-02-10 00:24:12 +00:00
Markus Klenke
0bec697a86 Specifies OllamaChatResponseModel 2024-02-10 00:23:26 +00:00
Markus Klenke
4ca6eef8fd Fixes JSON Binding for OllamaChatMessageRole 2024-02-10 00:22:55 +00:00
Markus Klenke
a635dd9be2 Renames request caller to endpoint caller 2024-02-10 00:22:21 +00:00
Markus Klenke
14982011d9 Adds chat-API with OllamaChatResult 2024-02-09 22:49:28 +00:00
Markus Klenke
65d852fdc9 Creates OllamaChatResult extension class 2024-02-09 22:48:48 +00:00
Markus Klenke
d483c23c81 Adds documentation to OllamaServerCaller 2024-02-09 22:48:19 +00:00
Markus Klenke
273b1e47ca Removes unneccessary override of OllamaRequestBody method 2024-02-09 21:58:17 +00:00
Markus Klenke
5c5cdba4cd Merge remote-tracking branch 'origin/main' into feature/chat-request-model 2024-02-09 21:56:10 +00:00
amithkoujalgi
24674ea483 [maven-release-plugin] prepare for next development iteration 2024-02-09 18:19:37 +00:00
amithkoujalgi
5d3a975e4c [maven-release-plugin] prepare release v1.0.49 2024-02-09 18:19:36 +00:00
Amith Koujalgi
ad670c3c62 Merge pull request #22 from AgentSchmecker/bugfix/add_model_property_to_model_api
Adds model property to Model Type
2024-02-09 23:48:36 +05:30
Markus Klenke
f9063484f3 Fixes recursive JacksonMarshalling on BodyPublisher 2024-02-09 17:07:18 +00:00
Markus Klenke
5e2a07ad41 Adds model property to Model Type 2024-02-09 16:54:09 +00:00
Markus Klenke
00a3e51a93 Extends OllamaAPI by Chat methods and refactors synchronous Generate API Methods 2024-02-09 16:32:52 +00:00
Markus Klenke
bc20468f28 Applies OllamaRequestBody to OllamaRequestModel 2024-02-09 16:30:12 +00:00
Markus Klenke
c7ac50a805 Defines technical Request Caller Object 2024-02-09 16:29:35 +00:00
Markus Klenke
f8cd7bc013 Adds model for Chat Requests to Ollama-API 2024-02-09 16:28:27 +00:00
Markus Klenke
3469bf314b Removes unused Imports 2024-02-09 13:36:49 +00:00
amithkoujalgi
9636807819 [maven-release-plugin] prepare for next development iteration 2024-02-09 06:39:34 +00:00
amithkoujalgi
455251d1d4 [maven-release-plugin] prepare release v1.0.48 2024-02-09 06:39:33 +00:00
Amith Koujalgi
ec00ffae7f Merge pull request #21 from AgentSchmecker/main
Correction of Documentation for ask-async
2024-02-09 12:08:33 +05:30
Markus Klenke
d969c7ad46 Fixes currently not working code snippet for ask-async doc 2024-02-08 22:52:41 +00:00
amithkoujalgi
02bf769188 [maven-release-plugin] prepare for next development iteration 2024-02-01 14:39:21 +00:00
amithkoujalgi
1c8a6b4f2a [maven-release-plugin] prepare release v1.0.47 2024-02-01 14:39:20 +00:00
Amith Koujalgi
60fe5d6ffb Update APIs from ask to generate 2024-02-01 20:08:01 +05:30
amithkoujalgi
327ae7437f [maven-release-plugin] prepare for next development iteration 2024-01-23 19:43:40 +00:00
amithkoujalgi
795b9f2b9b [maven-release-plugin] prepare release v1.0.46 2024-01-23 19:43:39 +00:00
Amith Koujalgi
54da069e68 Merge remote-tracking branch 'origin/main' 2024-01-24 01:12:32 +05:30
Amith Koujalgi
bfc5cebac1 clean up 2024-01-24 01:12:21 +05:30
amithkoujalgi
d46b1d48d8 [maven-release-plugin] prepare for next development iteration 2024-01-23 19:39:05 +00:00
28 changed files with 969 additions and 141 deletions

View File

@@ -67,7 +67,7 @@ In your Maven project, add this dependency:
<dependency>
<groupId>io.github.amithkoujalgi</groupId>
<artifactId>ollama4j</artifactId>
<version>1.0.29</version>
<version>1.0.47</version>
</dependency>
```

View File

@@ -2,7 +2,7 @@
sidebar_position: 2
---
# Ask - Async
# Generate - Async
This API lets you ask questions to the LLMs in a asynchronous way.
These APIs correlate to
@@ -19,13 +19,13 @@ public class Main {
String prompt = "Who are you?";
OllamaAsyncResultCallback callback = ollamaAPI.askAsync(OllamaModelType.LLAMA2, prompt);
OllamaAsyncResultCallback callback = ollamaAPI.generateAsync(OllamaModelType.LLAMA2, prompt);
while (!callback.isComplete() || !callback.getStream().isEmpty()) {
// poll for data from the response stream
String result = callback.getStream().poll();
if (response != null) {
System.out.print(result.getResponse());
if (result != null) {
System.out.print(result);
}
Thread.sleep(100);
}

View File

@@ -2,7 +2,7 @@
sidebar_position: 3
---
# Ask - With Image Files
# Generate - With Image Files
This API lets you ask questions along with the image files to the LLMs.
These APIs correlate to
@@ -15,7 +15,7 @@ recommended.
:::
## Ask (Sync)
## Synchronous mode
If you have this image downloaded and you pass the path to the downloaded image to the following code:
@@ -29,7 +29,7 @@ public class Main {
OllamaAPI ollamaAPI = new OllamaAPI(host);
ollamaAPI.setRequestTimeoutSeconds(10);
OllamaResult result = ollamaAPI.askWithImageFiles(OllamaModelType.LLAVA,
OllamaResult result = ollamaAPI.generateWithImageFiles(OllamaModelType.LLAVA,
"What's in this image?",
List.of(
new File("/path/to/image")));

View File

@@ -2,7 +2,7 @@
sidebar_position: 4
---
# Ask - With Image URLs
# Generate - With Image URLs
This API lets you ask questions along with the image files to the LLMs.
These APIs correlate to
@@ -29,7 +29,7 @@ public class Main {
OllamaAPI ollamaAPI = new OllamaAPI(host);
ollamaAPI.setRequestTimeoutSeconds(10);
OllamaResult result = ollamaAPI.askWithImageURLs(OllamaModelType.LLAVA,
OllamaResult result = ollamaAPI.generateWithImageURLs(OllamaModelType.LLAVA,
"What's in this image?",
List.of(
"https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg"));

View File

@@ -2,7 +2,7 @@
sidebar_position: 1
---
# Ask - Sync
# Generate - Sync
This API lets you ask questions to the LLMs in a synchronous way.
These APIs correlate to
@@ -25,7 +25,7 @@ public class Main {
OllamaAPI ollamaAPI = new OllamaAPI(host);
OllamaResult result =
ollamaAPI.ask(OllamaModelType.LLAMA2, "Who are you?", new OptionsBuilder().build());
ollamaAPI.generate(OllamaModelType.LLAMA2, "Who are you?", new OptionsBuilder().build());
System.out.println(result.getResponse());
}
@@ -55,7 +55,7 @@ public class Main {
String prompt = "List all cricket world cup teams of 2019.";
OllamaResult result =
ollamaAPI.ask(OllamaModelType.LLAMA2, prompt, new OptionsBuilder().build());
ollamaAPI.generate(OllamaModelType.LLAMA2, prompt, new OptionsBuilder().build());
System.out.println(result.getResponse());
}
@@ -97,7 +97,7 @@ public class Main {
SamplePrompts.getSampleDatabasePromptWithQuestion(
"List all customer names who have bought one or more products");
OllamaResult result =
ollamaAPI.ask(OllamaModelType.SQLCODER, prompt, new OptionsBuilder().build());
ollamaAPI.generate(OllamaModelType.SQLCODER, prompt, new OptionsBuilder().build());
System.out.println(result.getResponse());
}
}

137
docs/docs/apis-ask/chat.md Normal file
View File

@@ -0,0 +1,137 @@
---
sidebar_position: 7
---
# 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.
## Create a conversation about an image (requires model with image recognition skills)
```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.LLAVA);
// Load Image from File and attach to user message (alternatively images could also be added via URL)
OllamaChatRequestModel requestModel =
builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?",
List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build();
OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
System.out.println("First answer: " + chatResult.getResponse());
builder.reset();
// Use history to ask further questions about the image or assistant answer
requestModel =
builder.withMessages(chatResult.getChatHistory())
.withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build();
chatResult = ollamaAPI.chat(requestModel);
System.out.println("Second answer: " + chatResult.getResponse());
}
}
```
You will get a response similar to:
> First Answer: The image shows a dog sitting on the bow of a boat that is docked in calm water. The boat has two levels, with the lower level containing seating and what appears to be an engine cover. The dog seems relaxed and comfortable on the boat, looking out over the water. The background suggests it might be late afternoon or early evening, given the warm lighting and the low position of the sun in the sky.
>
> Second Answer: Based on the image, it's difficult to definitively determine the breed of the dog. However, the dog appears to be medium-sized with a short coat and a brown coloration, which might suggest that it is a Golden Retriever or a similar breed. Without more details like ear shape and tail length, it's not possible to identify the exact breed confidently.

View File

@@ -42,7 +42,7 @@ public class AskPhi {
.addSeparator()
.add("How do I read a file in Go and print its contents to stdout?");
OllamaResult response = ollamaAPI.ask(model, promptBuilder.build());
OllamaResult response = ollamaAPI.generate(model, promptBuilder.build());
System.out.println(response.getResponse());
}
}

View File

@@ -79,6 +79,7 @@ const config = {
label: 'Docs',
},
{to: 'https://amithkoujalgi.github.io/ollama4j/apidocs/', label: 'Javadoc', position: 'left'},
{to: 'https://amithkoujalgi.github.io/ollama4j/doxygen/html/', label: 'Doxygen', position: 'left'},
{to: '/blog', label: 'Blog', position: 'left'},
{
href: 'https://github.com/amithkoujalgi/ollama4j',

View File

@@ -4,7 +4,7 @@
<groupId>io.github.amithkoujalgi</groupId>
<artifactId>ollama4j</artifactId>
<version>1.0.45</version>
<version>1.0.52</version>
<name>Ollama4j</name>
<description>Java library for interacting with Ollama API.</description>
@@ -39,7 +39,7 @@
<connection>scm:git:git@github.com:amithkoujalgi/ollama4j.git</connection>
<developerConnection>scm:git:https://github.com/amithkoujalgi/ollama4j.git</developerConnection>
<url>https://github.com/amithkoujalgi/ollama4j</url>
<tag>v1.0.45</tag>
<tag>v1.0.52</tag>
</scm>
<build>

View File

@@ -2,21 +2,25 @@ 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.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.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.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;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpConnectTimeoutException;
import java.net.http.HttpRequest;
@@ -329,7 +333,8 @@ public class OllamaAPI {
}
/**
* Ask a question to a model running on Ollama server. This is a sync/blocking call.
* Generate response for a question to a model running on Ollama server. This is a sync/blocking
* call.
*
* @param model the ollama model to ask the question to
* @param prompt the prompt/question text
@@ -338,23 +343,23 @@ public class OllamaAPI {
* details on the options</a>
* @return OllamaResult that includes response text and time taken for response
*/
public OllamaResult ask(String model, String prompt, Options options)
public OllamaResult generate(String model, String prompt, Options options)
throws OllamaBaseException, IOException, InterruptedException {
OllamaRequestModel ollamaRequestModel = new OllamaRequestModel(model, prompt);
ollamaRequestModel.setOptions(options.getOptionsMap());
return askSync(ollamaRequestModel);
return generateSyncForOllamaRequestModel(ollamaRequestModel);
}
/**
* Ask a question to a model running on Ollama server and get a callback handle that can be used
* to check for status and get the response from the model later. This would be an
* async/non-blocking call.
* Generate response for a question to a model running on Ollama server and get a callback handle
* that can be used to check for status and get the response from the model later. This would be
* an async/non-blocking call.
*
* @param model the ollama model to ask the question to
* @param prompt the prompt/question text
* @return the ollama async result callback handle
*/
public OllamaAsyncResultCallback askAsync(String model, String prompt) {
public OllamaAsyncResultCallback generateAsync(String model, String prompt) {
OllamaRequestModel ollamaRequestModel = new OllamaRequestModel(model, prompt);
URI uri = URI.create(this.host + "/api/generate");
@@ -377,7 +382,7 @@ public class OllamaAPI {
* details on the options</a>
* @return OllamaResult that includes response text and time taken for response
*/
public OllamaResult askWithImageFiles(
public OllamaResult generateWithImageFiles(
String model, String prompt, List<File> imageFiles, Options options)
throws OllamaBaseException, IOException, InterruptedException {
List<String> images = new ArrayList<>();
@@ -386,7 +391,7 @@ public class OllamaAPI {
}
OllamaRequestModel ollamaRequestModel = new OllamaRequestModel(model, prompt, images);
ollamaRequestModel.setOptions(options.getOptionsMap());
return askSync(ollamaRequestModel);
return generateSyncForOllamaRequestModel(ollamaRequestModel);
}
/**
@@ -401,18 +406,59 @@ public class OllamaAPI {
* details on the options</a>
* @return OllamaResult that includes response text and time taken for response
*/
public OllamaResult askWithImageURLs(
public OllamaResult generateWithImageURLs(
String model, String prompt, List<String> imageURLs, Options options)
throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
List<String> images = new ArrayList<>();
for (String imageURL : imageURLs) {
images.add(encodeByteArrayToBase64(loadImageBytesFromUrl(imageURL)));
images.add(encodeByteArrayToBase64(Utils.loadImageBytesFromUrl(imageURL)));
}
OllamaRequestModel ollamaRequestModel = new OllamaRequestModel(model, prompt, images);
ollamaRequestModel.setOptions(options.getOptionsMap());
return askSync(ollamaRequestModel);
return generateSyncForOllamaRequestModel(ollamaRequestModel);
}
/**
* 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<OllamaChatMessage> messages) throws OllamaBaseException, IOException, InterruptedException{
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(model);
return chat(builder.withMessages(messages).build());
}
/**
* Ask a question to a model using an {@link OllamaChatRequestModel}. This can be constructed using an {@link OllamaChatRequestBuilder}.
*
* Hint: the 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{
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");
}
OllamaResult result = requestCaller.generateSync(request);
return new OllamaChatResult(result.getResponse(), result.getResponseTime(), result.getHttpStatusCode(), request.getMessages());
}
// technical private methods //
private static String encodeFileToBase64(File file) throws IOException {
return Base64.getEncoder().encodeToString(Files.readAllBytes(file.toPath()));
}
@@ -421,72 +467,10 @@ public class OllamaAPI {
return Base64.getEncoder().encodeToString(bytes);
}
private static byte[] loadImageBytesFromUrl(String imageUrl)
throws IOException, URISyntaxException {
URL url = new URI(imageUrl).toURL();
try (InputStream in = url.openStream();
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
return out.toByteArray();
}
}
private OllamaResult askSync(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<InputStream> 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;
}
OllamaGenerateEndpointCaller requestCaller = new OllamaGenerateEndpointCaller(host, basicAuth, requestTimeoutSeconds, verbose);
return requestCaller.generateSync(ollamaRequestModel);
}
/**

View File

@@ -7,6 +7,7 @@ import lombok.Data;
public class Model {
private String name;
private String model;
@JsonProperty("modified_at")
private String modifiedAt;
private String digest;

View File

@@ -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

View File

@@ -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;

View File

@@ -0,0 +1,45 @@
package io.github.amithkoujalgi.ollama4j.core.models.chat;
import static io.github.amithkoujalgi.ollama4j.core.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.github.amithkoujalgi.ollama4j.core.utils.FileToBase64Serializer;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
/**
* Defines a single Message to be used inside a chat request against the ollama /api/chat endpoint.
*
* @see <a href="https://github.com/ollama/ollama/blob/main/docs/api.md#generate-a-chat-completion">Generate chat completion</a>
*/
@Data
@AllArgsConstructor
@RequiredArgsConstructor
@NoArgsConstructor
public class OllamaChatMessage {
@NonNull
private OllamaChatMessageRole role;
@NonNull
private String content;
@JsonSerialize(using = FileToBase64Serializer.class)
private List<byte[]> images;
@Override
public String toString() {
try {
return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,19 @@
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("assistant");
@JsonValue
private String roleName;
private OllamaChatMessageRole(String roleName){
this.roleName = roleName;
}
}

View File

@@ -0,0 +1,110 @@
package io.github.amithkoujalgi.ollama4j.core.models.chat;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.amithkoujalgi.ollama4j.core.utils.Options;
import io.github.amithkoujalgi.ollama4j.core.utils.Utils;
/**
* Helper class for creating {@link OllamaChatRequestModel} objects using the builder-pattern.
*/
public class OllamaChatRequestBuilder {
private static final Logger LOG = LoggerFactory.getLogger(OllamaChatRequestBuilder.class);
private OllamaChatRequestBuilder(String model, List<OllamaChatMessage> 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 void reset(){
request = new OllamaChatRequestModel(request.getModel(), new ArrayList<>());
}
public OllamaChatRequestBuilder withMessage(OllamaChatMessageRole role, String content, List<File> images){
List<OllamaChatMessage> messages = this.request.getMessages();
List<byte[]> binaryImages = images.stream().map(file -> {
try {
return Files.readAllBytes(file.toPath());
} catch (IOException e) {
LOG.warn(String.format("File '%s' could not be accessed, will not add to message!",file.toPath()), e);
return new byte[0];
}
}).collect(Collectors.toList());
messages.add(new OllamaChatMessage(role,content,binaryImages));
return this;
}
public OllamaChatRequestBuilder withMessage(OllamaChatMessageRole role, String content, String... imageUrls){
List<OllamaChatMessage> messages = this.request.getMessages();
List<byte[]> binaryImages = null;
if(imageUrls.length>0){
binaryImages = new ArrayList<>();
for (String imageUrl : imageUrls) {
try{
binaryImages.add(Utils.loadImageBytesFromUrl(imageUrl));
}
catch (URISyntaxException e){
LOG.warn(String.format("URL '%s' could not be accessed, will not add to message!",imageUrl), e);
}
catch (IOException e){
LOG.warn(String.format("Content of URL '%s' could not be read, will not add to message!",imageUrl), e);
}
}
}
messages.add(new OllamaChatMessage(role,content,binaryImages));
return this;
}
public OllamaChatRequestBuilder withMessages(List<OllamaChatMessage> messages){
this.request.getMessages().addAll(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;
}
}

View File

@@ -0,0 +1,47 @@
package io.github.amithkoujalgi.ollama4j.core.models.chat;
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 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 <a
* href="https://github.com/ollama/ollama/blob/main/docs/api.md#generate-a-chat-completion">Generate
* Chat Completion</a>
*/
@Data
@AllArgsConstructor
@RequiredArgsConstructor
public class OllamaChatRequestModel implements OllamaRequestBody {
@NonNull private String model;
@NonNull private List<OllamaChatMessage> 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);
}
}
}

View File

@@ -0,0 +1,22 @@
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 String error;
private List<Integer> 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;
}

View File

@@ -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<OllamaChatMessage> chatHistory;
public OllamaChatResult(String response, long responseTime, int httpStatusCode,
List<OllamaChatMessage> chatHistory) {
super(response, responseTime, httpStatusCode);
this.chatHistory = chatHistory;
appendAnswerToChatHistory(response);
}
public List<OllamaChatMessage> getChatHistory() {
return chatHistory;
}
private void appendAnswerToChatHistory(String answer){
OllamaChatMessage assistantMessage = new OllamaChatMessage(OllamaChatMessageRole.ASSISTANT, answer);
this.chatHistory.add(assistantMessage);
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,155 @@
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.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 OllamaEndpointCaller {
private static final Logger LOG = LoggerFactory.getLogger(OllamaAPI.class);
private String host;
private BasicAuth basicAuth;
private long requestTimeoutSeconds;
private boolean verbose;
public OllamaEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) {
this.host = host;
this.basicAuth = basicAuth;
this.requestTimeoutSeconds = requestTimeoutSeconds;
this.verbose = verbose;
}
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.
*
* @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
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.toString());
HttpResponse<InputStream> 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 if (statusCode == 400) {
LOG.warn("Status code: 400 (Bad Request)");
OllamaErrorResponseModel ollamaResponseModel = Utils.getObjectMapper().readValue(line,
OllamaErrorResponseModel.class);
responseBuffer.append(ollamaResponseModel.getError());
} else {
boolean finished = parseResponseAndAddToBuffer(line,responseBuffer);
if (finished) {
break;
}
}
}
}
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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -0,0 +1,30 @@
package io.github.amithkoujalgi.ollama4j.core.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;
import java.util.Collection;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
public class FileToBase64Serializer extends JsonSerializer<Collection<byte[]>> {
@Override
public void serialize(Collection<byte[]> value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException {
jsonGenerator.writeStartArray();
for (byte[] file : value) {
jsonGenerator.writeString(Base64.getEncoder().encodeToString(file));
}
jsonGenerator.writeEndArray();
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(obj);
return out.toByteArray();
}
}

View File

@@ -0,0 +1,28 @@
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;
/**
* 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
*/
@JsonIgnore
default BodyPublisher getBodyPublisher(){
try {
return BodyPublishers.ofString(
Utils.getObjectMapper().writeValueAsString(this));
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("Request not Body convertible.",e);
}
}
}

View File

@@ -1,9 +1,30 @@
package io.github.amithkoujalgi.ollama4j.core.utils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Utils {
public static ObjectMapper getObjectMapper() {
return new ObjectMapper();
}
public static byte[] loadImageBytesFromUrl(String imageUrl)
throws IOException, URISyntaxException {
URL url = new URI(imageUrl).toURL();
try (InputStream in = url.openStream();
ByteArrayOutputStream out = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
return out.toByteArray();
}
}
}

View File

@@ -5,7 +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.types.OllamaModelType;
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.utils.OptionsBuilder;
import java.io.File;
import java.io.IOException;
@@ -16,26 +19,14 @@ import java.net.http.HttpConnectTimeoutException;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import lombok.Data;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
class TestRealAPIs {
OllamaAPI ollamaAPI;
private Properties loadProperties() {
Properties properties = new Properties();
try (InputStream input =
getClass().getClassLoader().getResourceAsStream("test-config.properties")) {
if (input == null) {
throw new RuntimeException("Sorry, unable to find test-config.properties");
}
properties.load(input);
return properties;
} catch (IOException e) {
throw new RuntimeException("Error loading properties", e);
}
}
Config config;
private File getImageFileFromClasspath(String fileName) {
ClassLoader classLoader = getClass().getClassLoader();
@@ -44,9 +35,9 @@ class TestRealAPIs {
@BeforeEach
void setUp() {
Properties properties = loadProperties();
ollamaAPI = new OllamaAPI(properties.getProperty("ollama.api.url"));
ollamaAPI.setRequestTimeoutSeconds(20);
config = new Config();
ollamaAPI = new OllamaAPI(config.getOllamaURL());
ollamaAPI.setRequestTimeoutSeconds(config.getRequestTimeoutSeconds());
}
@Test
@@ -85,10 +76,10 @@ class TestRealAPIs {
void testPullModel() {
testEndpointReachability();
try {
ollamaAPI.pullModel(OllamaModelType.LLAMA2);
ollamaAPI.pullModel(config.getModel());
boolean found =
ollamaAPI.listModels().stream()
.anyMatch(model -> model.getModelName().equals(OllamaModelType.LLAMA2));
.anyMatch(model -> model.getModel().equalsIgnoreCase(config.getModel()));
assertTrue(found);
} catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
throw new RuntimeException(e);
@@ -101,8 +92,8 @@ class TestRealAPIs {
testEndpointReachability();
try {
OllamaResult result =
ollamaAPI.ask(
OllamaModelType.LLAMA2,
ollamaAPI.generate(
config.getModel(),
"What is the capital of France? And what's France's connection with Mona Lisa?",
new OptionsBuilder().build());
assertNotNull(result);
@@ -119,8 +110,8 @@ class TestRealAPIs {
testEndpointReachability();
try {
OllamaResult result =
ollamaAPI.ask(
OllamaModelType.LLAMA2,
ollamaAPI.generate(
config.getModel(),
"What is the capital of France? And what's France's connection with Mona Lisa?",
new OptionsBuilder().setTemperature(0.9f).build());
assertNotNull(result);
@@ -131,6 +122,96 @@ class TestRealAPIs {
}
}
@Test
@Order(3)
void testChat() {
testEndpointReachability();
try {
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
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);
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(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,
"What is the capital of France? And what's France's connection with Mona Lisa?")
.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 testChatWithImageFromFileWithHistoryRecognition() {
testEndpointReachability();
try {
OllamaChatRequestBuilder builder =
OllamaChatRequestBuilder.getInstance(config.getImageModel());
OllamaChatRequestModel requestModel =
builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?",
List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build();
OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
assertNotNull(chatResult);
assertNotNull(chatResult.getResponse());
builder.reset();
requestModel =
builder.withMessages(chatResult.getChatHistory())
.withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build();
chatResult = ollamaAPI.chat(requestModel);
assertNotNull(chatResult);
assertNotNull(chatResult.getResponse());
} catch (IOException | OllamaBaseException | InterruptedException e) {
throw new RuntimeException(e);
}
}
@Test
@Order(3)
void testChatWithImageFromURL() {
testEndpointReachability();
try {
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getImageModel());
OllamaChatRequestModel requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?",
"https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg")
.build();
OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
assertNotNull(chatResult);
} catch (IOException | OllamaBaseException | InterruptedException e) {
throw new RuntimeException(e);
}
}
@Test
@Order(3)
void testAskModelWithOptionsAndImageFiles() {
@@ -138,8 +219,8 @@ class TestRealAPIs {
File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg");
try {
OllamaResult result =
ollamaAPI.askWithImageFiles(
OllamaModelType.LLAVA,
ollamaAPI.generateWithImageFiles(
config.getImageModel(),
"What is in this image?",
List.of(imageFile),
new OptionsBuilder().build());
@@ -157,8 +238,8 @@ class TestRealAPIs {
testEndpointReachability();
try {
OllamaResult result =
ollamaAPI.askWithImageURLs(
OllamaModelType.LLAVA,
ollamaAPI.generateWithImageURLs(
config.getImageModel(),
"What is in this image?",
List.of(
"https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg"),
@@ -171,3 +252,29 @@ class TestRealAPIs {
}
}
}
@Data
class Config {
private String ollamaURL;
private String model;
private String imageModel;
private int requestTimeoutSeconds;
public Config() {
Properties properties = new Properties();
try (InputStream input =
getClass().getClassLoader().getResourceAsStream("test-config.properties")) {
if (input == null) {
throw new RuntimeException("Sorry, unable to find test-config.properties");
}
properties.load(input);
this.ollamaURL = properties.getProperty("ollama.url");
this.model = properties.getProperty("ollama.model");
this.imageModel = properties.getProperty("ollama.model.image");
this.requestTimeoutSeconds =
Integer.parseInt(properties.getProperty("ollama.request-timeout-seconds"));
} catch (IOException e) {
throw new RuntimeException("Error loading properties", e);
}
}
}

View File

@@ -103,10 +103,10 @@ class TestMockedAPIs {
String prompt = "some prompt text";
OptionsBuilder optionsBuilder = new OptionsBuilder();
try {
when(ollamaAPI.ask(model, prompt, optionsBuilder.build()))
when(ollamaAPI.generate(model, prompt, optionsBuilder.build()))
.thenReturn(new OllamaResult("", 0, 200));
ollamaAPI.ask(model, prompt, optionsBuilder.build());
verify(ollamaAPI, times(1)).ask(model, prompt, optionsBuilder.build());
ollamaAPI.generate(model, prompt, optionsBuilder.build());
verify(ollamaAPI, times(1)).generate(model, prompt, optionsBuilder.build());
} catch (IOException | OllamaBaseException | InterruptedException e) {
throw new RuntimeException(e);
}
@@ -118,13 +118,14 @@ class TestMockedAPIs {
String model = OllamaModelType.LLAMA2;
String prompt = "some prompt text";
try {
when(ollamaAPI.askWithImageFiles(
when(ollamaAPI.generateWithImageFiles(
model, prompt, Collections.emptyList(), new OptionsBuilder().build()))
.thenReturn(new OllamaResult("", 0, 200));
ollamaAPI.askWithImageFiles(
ollamaAPI.generateWithImageFiles(
model, prompt, Collections.emptyList(), new OptionsBuilder().build());
verify(ollamaAPI, times(1))
.askWithImageFiles(model, prompt, Collections.emptyList(), new OptionsBuilder().build());
.generateWithImageFiles(
model, prompt, Collections.emptyList(), new OptionsBuilder().build());
} catch (IOException | OllamaBaseException | InterruptedException e) {
throw new RuntimeException(e);
}
@@ -136,13 +137,14 @@ class TestMockedAPIs {
String model = OllamaModelType.LLAMA2;
String prompt = "some prompt text";
try {
when(ollamaAPI.askWithImageURLs(
when(ollamaAPI.generateWithImageURLs(
model, prompt, Collections.emptyList(), new OptionsBuilder().build()))
.thenReturn(new OllamaResult("", 0, 200));
ollamaAPI.askWithImageURLs(
ollamaAPI.generateWithImageURLs(
model, prompt, Collections.emptyList(), new OptionsBuilder().build());
verify(ollamaAPI, times(1))
.askWithImageURLs(model, prompt, Collections.emptyList(), new OptionsBuilder().build());
.generateWithImageURLs(
model, prompt, Collections.emptyList(), new OptionsBuilder().build());
} catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
throw new RuntimeException(e);
}
@@ -153,9 +155,9 @@ class TestMockedAPIs {
OllamaAPI ollamaAPI = Mockito.mock(OllamaAPI.class);
String model = OllamaModelType.LLAMA2;
String prompt = "some prompt text";
when(ollamaAPI.askAsync(model, prompt))
when(ollamaAPI.generateAsync(model, prompt))
.thenReturn(new OllamaAsyncResultCallback(null, null, 3));
ollamaAPI.askAsync(model, prompt);
verify(ollamaAPI, times(1)).askAsync(model, prompt);
ollamaAPI.generateAsync(model, prompt);
verify(ollamaAPI, times(1)).generateAsync(model, prompt);
}
}

View File

@@ -1,2 +1,4 @@
ollama.api.url=http://192.168.29.223:11434
ollama.model=llava
ollama.url=http://localhost:11434
ollama.model=qwen:0.5b
ollama.model.image=llava
ollama.request-timeout-seconds=120