From 3a792090e26150496962860413d8849c9152fb76 Mon Sep 17 00:00:00 2001
From: Sven Strickroth
Date: Mon, 10 Mar 2025 14:39:54 +0100
Subject: [PATCH 01/20] Support bearer token
May be use as follows:
```
ollamaAPI.setBasicAuth(new BasicAuth() {
@Override
public String getBasicAuthHeaderValue() { return "Bearer [sometext]"; }
});
```
Signed-off-by: Sven Strickroth
---
.../java/io/github/ollama4j/OllamaAPI.java | 16 +++------
.../ollama4j/models/request/BasicAuth.java | 12 +++++++
.../models/request/OllamaEndpointCaller.java | 36 ++++---------------
3 files changed, 24 insertions(+), 40 deletions(-)
diff --git a/src/main/java/io/github/ollama4j/OllamaAPI.java b/src/main/java/io/github/ollama4j/OllamaAPI.java
index 76af4c8..d16bcad 100644
--- a/src/main/java/io/github/ollama4j/OllamaAPI.java
+++ b/src/main/java/io/github/ollama4j/OllamaAPI.java
@@ -109,6 +109,10 @@ public class OllamaAPI {
this.basicAuth = new BasicAuth(username, password);
}
+ public void setBasicAuth(BasicAuth basicAuth) {
+ this.basicAuth = basicAuth;
+ }
+
/**
* API to check the reachability of Ollama server.
*
@@ -1083,21 +1087,11 @@ public class OllamaAPI {
private HttpRequest.Builder getRequestBuilderDefault(URI uri) {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(uri).header("Content-Type", "application/json").timeout(Duration.ofSeconds(requestTimeoutSeconds));
if (isBasicAuthCredentialsSet()) {
- requestBuilder.header("Authorization", getBasicAuthHeaderValue());
+ requestBuilder.header("Authorization", basicAuth.getBasicAuthHeaderValue());
}
return requestBuilder;
}
- /**
- * Get basic authentication header value.
- *
- * @return basic authentication header value (encoded credentials)
- */
- private String getBasicAuthHeaderValue() {
- String credentialsToEncode = basicAuth.getUsername() + ":" + basicAuth.getPassword();
- return "Basic " + Base64.getEncoder().encodeToString(credentialsToEncode.getBytes());
- }
-
/**
* Check if Basic Auth credentials set.
*
diff --git a/src/main/java/io/github/ollama4j/models/request/BasicAuth.java b/src/main/java/io/github/ollama4j/models/request/BasicAuth.java
index f3372a9..683ed38 100644
--- a/src/main/java/io/github/ollama4j/models/request/BasicAuth.java
+++ b/src/main/java/io/github/ollama4j/models/request/BasicAuth.java
@@ -1,5 +1,7 @@
package io.github.ollama4j.models.request;
+import java.util.Base64;
+
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -10,4 +12,14 @@ import lombok.NoArgsConstructor;
public class BasicAuth {
private String username;
private String password;
+
+ /**
+ * Get basic authentication header value.
+ *
+ * @return basic authentication header value (encoded credentials)
+ */
+ public String getBasicAuthHeaderValue() {
+ final String credentialsToEncode = this.getUsername() + ":" + this.getPassword();
+ return "Basic " + Base64.getEncoder().encodeToString(credentialsToEncode.getBytes());
+ }
}
diff --git a/src/main/java/io/github/ollama4j/models/request/OllamaEndpointCaller.java b/src/main/java/io/github/ollama4j/models/request/OllamaEndpointCaller.java
index e9d0e0d..a9a9a8b 100644
--- a/src/main/java/io/github/ollama4j/models/request/OllamaEndpointCaller.java
+++ b/src/main/java/io/github/ollama4j/models/request/OllamaEndpointCaller.java
@@ -1,26 +1,14 @@
package io.github.ollama4j.models.request;
-import io.github.ollama4j.OllamaAPI;
-import io.github.ollama4j.exceptions.OllamaBaseException;
-import io.github.ollama4j.models.response.OllamaErrorResponse;
-import io.github.ollama4j.models.response.OllamaResult;
-import io.github.ollama4j.utils.OllamaRequestBody;
-import io.github.ollama4j.utils.Utils;
-import lombok.Getter;
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.time.Duration;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-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 io.github.ollama4j.OllamaAPI;
+import lombok.Getter;
/**
* Abstract helperclass to call the ollama api server.
@@ -59,21 +47,11 @@ public abstract class OllamaEndpointCaller {
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(this.requestTimeoutSeconds));
if (isBasicAuthCredentialsSet()) {
- requestBuilder.header("Authorization", getBasicAuthHeaderValue());
+ requestBuilder.header("Authorization", this.basicAuth.getBasicAuthHeaderValue());
}
return requestBuilder;
}
- /**
- * Get basic authentication header value.
- *
- * @return basic authentication header value (encoded credentials)
- */
- protected String getBasicAuthHeaderValue() {
- String credentialsToEncode = this.basicAuth.getUsername() + ":" + this.basicAuth.getPassword();
- return "Basic " + Base64.getEncoder().encodeToString(credentialsToEncode.getBytes());
- }
-
/**
* Check if Basic Auth credentials set.
*
From 138497b30fec62046fdf7e4747cad219ae012a79 Mon Sep 17 00:00:00 2001
From: Sven Strickroth
Date: Mon, 10 Mar 2025 14:51:37 +0100
Subject: [PATCH 02/20] Introduce BearerAuth class
Signed-off-by: Sven Strickroth
---
.../java/io/github/ollama4j/OllamaAPI.java | 21 ++++++++++++-------
.../github/ollama4j/models/request/Auth.java | 10 +++++++++
.../ollama4j/models/request/BasicAuth.java | 5 ++---
.../ollama4j/models/request/BearerAuth.java | 19 +++++++++++++++++
.../request/OllamaChatEndpointCaller.java | 4 ++--
.../models/request/OllamaEndpointCaller.java | 18 ++++++++--------
.../request/OllamaGenerateEndpointCaller.java | 2 +-
7 files changed, 56 insertions(+), 23 deletions(-)
create mode 100644 src/main/java/io/github/ollama4j/models/request/Auth.java
create mode 100644 src/main/java/io/github/ollama4j/models/request/BearerAuth.java
diff --git a/src/main/java/io/github/ollama4j/OllamaAPI.java b/src/main/java/io/github/ollama4j/OllamaAPI.java
index d16bcad..4a4872d 100644
--- a/src/main/java/io/github/ollama4j/OllamaAPI.java
+++ b/src/main/java/io/github/ollama4j/OllamaAPI.java
@@ -72,7 +72,7 @@ public class OllamaAPI {
@Setter
private int maxChatToolCallRetries = 3;
- private BasicAuth basicAuth;
+ private Auth auth;
private final ToolRegistry toolRegistry = new ToolRegistry();
@@ -106,11 +106,16 @@ public class OllamaAPI {
* @param password the password
*/
public void setBasicAuth(String username, String password) {
- this.basicAuth = new BasicAuth(username, password);
+ this.auth = new BasicAuth(username, password);
}
- public void setBasicAuth(BasicAuth basicAuth) {
- this.basicAuth = basicAuth;
+ /**
+ * Set Bearer authentication for accessing Ollama server that's behind a reverse-proxy/gateway.
+ *
+ * @param bearerToken the Bearer authentication token to provide
+ */
+ public void setBearerAuth(String bearerToken) {
+ this.auth = new BearerAuth(bearerToken);
}
/**
@@ -860,7 +865,7 @@ public class OllamaAPI {
* @throws InterruptedException if the operation is interrupted
*/
public OllamaChatResult chatStreaming(OllamaChatRequest request, OllamaTokenHandler tokenHandler) throws OllamaBaseException, IOException, InterruptedException {
- OllamaChatEndpointCaller requestCaller = new OllamaChatEndpointCaller(host, basicAuth, requestTimeoutSeconds, verbose);
+ OllamaChatEndpointCaller requestCaller = new OllamaChatEndpointCaller(host, auth, requestTimeoutSeconds, verbose);
OllamaChatResult result;
// add all registered tools to Request
@@ -1066,7 +1071,7 @@ public class OllamaAPI {
* @throws InterruptedException if the thread is interrupted during the request.
*/
private OllamaResult generateSyncForOllamaRequestModel(OllamaGenerateRequest ollamaRequestModel, OllamaStreamHandler streamHandler) throws OllamaBaseException, IOException, InterruptedException {
- OllamaGenerateEndpointCaller requestCaller = new OllamaGenerateEndpointCaller(host, basicAuth, requestTimeoutSeconds, verbose);
+ OllamaGenerateEndpointCaller requestCaller = new OllamaGenerateEndpointCaller(host, auth, requestTimeoutSeconds, verbose);
OllamaResult result;
if (streamHandler != null) {
ollamaRequestModel.setStream(true);
@@ -1087,7 +1092,7 @@ public class OllamaAPI {
private HttpRequest.Builder getRequestBuilderDefault(URI uri) {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(uri).header("Content-Type", "application/json").timeout(Duration.ofSeconds(requestTimeoutSeconds));
if (isBasicAuthCredentialsSet()) {
- requestBuilder.header("Authorization", basicAuth.getBasicAuthHeaderValue());
+ requestBuilder.header("Authorization", auth.getAuthHeaderValue());
}
return requestBuilder;
}
@@ -1098,7 +1103,7 @@ public class OllamaAPI {
* @return true when Basic Auth credentials set
*/
private boolean isBasicAuthCredentialsSet() {
- return basicAuth != null;
+ return auth != null;
}
private Object invokeTool(ToolFunctionCallSpec toolFunctionCallSpec) throws ToolInvocationException {
diff --git a/src/main/java/io/github/ollama4j/models/request/Auth.java b/src/main/java/io/github/ollama4j/models/request/Auth.java
new file mode 100644
index 0000000..70c9c1b
--- /dev/null
+++ b/src/main/java/io/github/ollama4j/models/request/Auth.java
@@ -0,0 +1,10 @@
+package io.github.ollama4j.models.request;
+
+public abstract class Auth {
+ /**
+ * Get authentication header value.
+ *
+ * @return authentication header value
+ */
+ public abstract String getAuthHeaderValue();
+}
diff --git a/src/main/java/io/github/ollama4j/models/request/BasicAuth.java b/src/main/java/io/github/ollama4j/models/request/BasicAuth.java
index 683ed38..c58b240 100644
--- a/src/main/java/io/github/ollama4j/models/request/BasicAuth.java
+++ b/src/main/java/io/github/ollama4j/models/request/BasicAuth.java
@@ -7,9 +7,8 @@ import lombok.Data;
import lombok.NoArgsConstructor;
@Data
-@NoArgsConstructor
@AllArgsConstructor
-public class BasicAuth {
+public class BasicAuth extends Auth {
private String username;
private String password;
@@ -18,7 +17,7 @@ public class BasicAuth {
*
* @return basic authentication header value (encoded credentials)
*/
- public String getBasicAuthHeaderValue() {
+ public String getAuthHeaderValue() {
final String credentialsToEncode = this.getUsername() + ":" + this.getPassword();
return "Basic " + Base64.getEncoder().encodeToString(credentialsToEncode.getBytes());
}
diff --git a/src/main/java/io/github/ollama4j/models/request/BearerAuth.java b/src/main/java/io/github/ollama4j/models/request/BearerAuth.java
new file mode 100644
index 0000000..8236042
--- /dev/null
+++ b/src/main/java/io/github/ollama4j/models/request/BearerAuth.java
@@ -0,0 +1,19 @@
+package io.github.ollama4j.models.request;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class BearerAuth extends Auth {
+ private String bearerToken;
+
+ /**
+ * Get authentication header value.
+ *
+ * @return authentication header value with bearer token
+ */
+ public String getAuthHeaderValue() {
+ return "Bearer "+ bearerToken;
+ }
+}
diff --git a/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java b/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java
index a1a6216..09a3870 100644
--- a/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java
+++ b/src/main/java/io/github/ollama4j/models/request/OllamaChatEndpointCaller.java
@@ -30,8 +30,8 @@ public class OllamaChatEndpointCaller extends OllamaEndpointCaller {
private OllamaTokenHandler tokenHandler;
- public OllamaChatEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) {
- super(host, basicAuth, requestTimeoutSeconds, verbose);
+ public OllamaChatEndpointCaller(String host, Auth auth, long requestTimeoutSeconds, boolean verbose) {
+ super(host, auth, requestTimeoutSeconds, verbose);
}
@Override
diff --git a/src/main/java/io/github/ollama4j/models/request/OllamaEndpointCaller.java b/src/main/java/io/github/ollama4j/models/request/OllamaEndpointCaller.java
index a9a9a8b..1f42ef8 100644
--- a/src/main/java/io/github/ollama4j/models/request/OllamaEndpointCaller.java
+++ b/src/main/java/io/github/ollama4j/models/request/OllamaEndpointCaller.java
@@ -19,13 +19,13 @@ public abstract class OllamaEndpointCaller {
private static final Logger LOG = LoggerFactory.getLogger(OllamaAPI.class);
private final String host;
- private final BasicAuth basicAuth;
+ private final Auth auth;
private final long requestTimeoutSeconds;
private final boolean verbose;
- public OllamaEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) {
+ public OllamaEndpointCaller(String host, Auth auth, long requestTimeoutSeconds, boolean verbose) {
this.host = host;
- this.basicAuth = basicAuth;
+ this.auth = auth;
this.requestTimeoutSeconds = requestTimeoutSeconds;
this.verbose = verbose;
}
@@ -46,19 +46,19 @@ public abstract class OllamaEndpointCaller {
HttpRequest.newBuilder(uri)
.header("Content-Type", "application/json")
.timeout(Duration.ofSeconds(this.requestTimeoutSeconds));
- if (isBasicAuthCredentialsSet()) {
- requestBuilder.header("Authorization", this.basicAuth.getBasicAuthHeaderValue());
+ if (isAuthCredentialsSet()) {
+ requestBuilder.header("Authorization", this.auth.getAuthHeaderValue());
}
return requestBuilder;
}
/**
- * Check if Basic Auth credentials set.
+ * Check if Auth credentials set.
*
- * @return true when Basic Auth credentials set
+ * @return true when Auth credentials set
*/
- protected boolean isBasicAuthCredentialsSet() {
- return this.basicAuth != null;
+ protected boolean isAuthCredentialsSet() {
+ return this.auth != null;
}
}
diff --git a/src/main/java/io/github/ollama4j/models/request/OllamaGenerateEndpointCaller.java b/src/main/java/io/github/ollama4j/models/request/OllamaGenerateEndpointCaller.java
index 00b2b12..461ec75 100644
--- a/src/main/java/io/github/ollama4j/models/request/OllamaGenerateEndpointCaller.java
+++ b/src/main/java/io/github/ollama4j/models/request/OllamaGenerateEndpointCaller.java
@@ -28,7 +28,7 @@ public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller {
private OllamaGenerateStreamObserver streamObserver;
- public OllamaGenerateEndpointCaller(String host, BasicAuth basicAuth, long requestTimeoutSeconds, boolean verbose) {
+ public OllamaGenerateEndpointCaller(String host, Auth basicAuth, long requestTimeoutSeconds, boolean verbose) {
super(host, basicAuth, requestTimeoutSeconds, verbose);
}
From fe5078891fe64baf38fd15c1d7e05fd268de750d Mon Sep 17 00:00:00 2001
From: amithkoujalgi
Date: Tue, 11 Mar 2025 11:41:51 +0530
Subject: [PATCH 03/20] Remove TestRealAPIs and enhance
OllamaAPIIntegrationTest
---
.../OllamaAPIIntegrationTest.java | 380 ++++++++++-
.../integrationtests/TestRealAPIs.java | 622 ------------------
2 files changed, 370 insertions(+), 632 deletions(-)
delete mode 100644 src/test/java/io/github/ollama4j/integrationtests/TestRealAPIs.java
diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
index 4ccfadd..e26609e 100644
--- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
+++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
@@ -2,32 +2,41 @@ package io.github.ollama4j.integrationtests;
import io.github.ollama4j.OllamaAPI;
import io.github.ollama4j.exceptions.OllamaBaseException;
-import io.github.ollama4j.models.chat.OllamaChatMessageRole;
-import io.github.ollama4j.models.chat.OllamaChatRequest;
-import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
-import io.github.ollama4j.models.chat.OllamaChatResult;
+import io.github.ollama4j.models.chat.*;
import io.github.ollama4j.models.embeddings.OllamaEmbedResponseModel;
import io.github.ollama4j.models.response.LibraryModel;
import io.github.ollama4j.models.response.Model;
import io.github.ollama4j.models.response.ModelDetail;
import io.github.ollama4j.models.response.OllamaResult;
+import io.github.ollama4j.samples.AnnotatedTool;
+import io.github.ollama4j.tools.OllamaToolCallsFunction;
+import io.github.ollama4j.tools.ToolFunction;
+import io.github.ollama4j.tools.Tools;
+import io.github.ollama4j.tools.annotations.OllamaToolService;
import io.github.ollama4j.utils.OptionsBuilder;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.testcontainers.ollama.OllamaContainer;
+import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
+@OllamaToolService(providers = {AnnotatedTool.class})
+@TestMethodOrder(OrderAnnotation.class)
+
@SuppressWarnings("HttpUrlsUsage")
public class OllamaAPIIntegrationTest {
+ private static final Logger LOG = LoggerFactory.getLogger(OllamaAPIIntegrationTest.class);
private static OllamaContainer ollama;
private static OllamaAPI api;
@@ -45,6 +54,7 @@ public class OllamaAPIIntegrationTest {
ollama.start();
api = new OllamaAPI("http://" + ollama.getHost() + ":" + ollama.getMappedPort(internalPort));
api.setRequestTimeoutSeconds(60);
+ api.setVerbose(true);
}
@Test
@@ -102,7 +112,7 @@ public class OllamaAPIIntegrationTest {
@Test
@Order(5)
- public void testGenerateEmbeddings() throws Exception {
+ public void testEmbeddings() throws Exception {
String embeddingModelMinilm = "all-minilm";
api.pullModel(embeddingModelMinilm);
OllamaEmbedResponseModel embeddings = api.embed(embeddingModelMinilm, Arrays.asList("Why is the sky blue?", "Why is the grass green?"));
@@ -136,9 +146,9 @@ public class OllamaAPIIntegrationTest {
"What is the capital of France? And what's France's connection with Mona Lisa?",
false,
new OptionsBuilder().build(), (s) -> {
- System.out.println(s);
+ LOG.info(s);
String substring = s.substring(sb.toString().length(), s.length());
- System.out.println(substring);
+ LOG.info(substring);
sb.append(substring);
});
@@ -236,4 +246,354 @@ public class OllamaAPIIntegrationTest {
assertTrue(chatResult.getChatHistory().size() > 2, "Chat history should contain more than two messages");
assertTrue(chatResult.getChatHistory().get(chatResult.getChatHistory().size() - 1).getContent().contains("river"), "Response should be related to river");
}
+
+
+ @Test
+ @Order(11)
+ void testChatWithExplicitToolDefinition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+
+ final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder()
+ .functionName("get-employee-details")
+ .functionDescription("Get employee details from the database")
+ .toolPrompt(
+ Tools.PromptFuncDefinition.builder().type("function").function(
+ Tools.PromptFuncDefinition.PromptFuncSpec.builder()
+ .name("get-employee-details")
+ .description("Get employee details from the database")
+ .parameters(
+ Tools.PromptFuncDefinition.Parameters.builder()
+ .type("object")
+ .properties(
+ new Tools.PropsBuilder()
+ .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build())
+ .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build())
+ .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build())
+ .build()
+ )
+ .required(List.of("employee-name"))
+ .build()
+ ).build()
+ ).build()
+ )
+ .toolFunction(arguments -> {
+ // perform DB operations here
+ return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name"), arguments.get("employee-address"), arguments.get("employee-phone"));
+ })
+ .build();
+
+ api.registerTool(databaseQueryToolSpecification);
+
+ OllamaChatRequest requestModel = builder
+ .withMessage(OllamaChatMessageRole.USER,
+ "Give me the ID of the employee named 'Rahul Kumar'?")
+ .build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(), chatResult.getResponseModel().getMessage().getRole().getRoleName());
+ List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
+ assertEquals(1, toolCalls.size());
+ OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
+ assertEquals("get-employee-details", function.getName());
+ assertEquals(1, function.getArguments().size());
+ Object employeeName = function.getArguments().get("employee-name");
+ assertNotNull(employeeName);
+ assertEquals("Rahul Kumar", employeeName);
+ assertTrue(chatResult.getChatHistory().size() > 2);
+ List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
+ assertNull(finalToolCalls);
+ }
+
+ @Test
+ @Order(12)
+ void testChatWithAnnotatedToolsAndSingleParam() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+
+ api.registerAnnotatedTools();
+
+ OllamaChatRequest requestModel = builder
+ .withMessage(OllamaChatMessageRole.USER,
+ "Compute the most important constant in the world using 5 digits")
+ .build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(), chatResult.getResponseModel().getMessage().getRole().getRoleName());
+ List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
+ assertEquals(1, toolCalls.size());
+ OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
+ assertEquals("computeImportantConstant", function.getName());
+ assertEquals(1, function.getArguments().size());
+ Object noOfDigits = function.getArguments().get("noOfDigits");
+ assertNotNull(noOfDigits);
+ assertEquals("5", noOfDigits.toString());
+ assertTrue(chatResult.getChatHistory().size() > 2);
+ List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
+ assertNull(finalToolCalls);
+ }
+
+ @Test
+ @Order(13)
+ void testChatWithAnnotatedToolsAndMultipleParams() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+
+ api.registerAnnotatedTools(new AnnotatedTool());
+
+ OllamaChatRequest requestModel = builder
+ .withMessage(OllamaChatMessageRole.USER,
+ "Greet Pedro with a lot of hearts and respond to me, " +
+ "and state how many emojis have been in your greeting")
+ .build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(), chatResult.getResponseModel().getMessage().getRole().getRoleName());
+ List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
+ assertEquals(1, toolCalls.size());
+ OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
+ assertEquals("sayHello", function.getName());
+ assertEquals(2, function.getArguments().size());
+ Object name = function.getArguments().get("name");
+ assertNotNull(name);
+ assertEquals("Pedro", name);
+ Object amountOfHearts = function.getArguments().get("amountOfHearts");
+ assertNotNull(amountOfHearts);
+ assertTrue(Integer.parseInt(amountOfHearts.toString()) > 1);
+ assertTrue(chatResult.getChatHistory().size() > 2);
+ List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
+ assertNull(finalToolCalls);
+ }
+
+ @Test
+ @Order(14)
+ void testChatWithToolsAndStream() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+ final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder()
+ .functionName("get-employee-details")
+ .functionDescription("Get employee details from the database")
+ .toolPrompt(
+ Tools.PromptFuncDefinition.builder().type("function").function(
+ Tools.PromptFuncDefinition.PromptFuncSpec.builder()
+ .name("get-employee-details")
+ .description("Get employee details from the database")
+ .parameters(
+ Tools.PromptFuncDefinition.Parameters.builder()
+ .type("object")
+ .properties(
+ new Tools.PropsBuilder()
+ .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build())
+ .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build())
+ .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build())
+ .build()
+ )
+ .required(List.of("employee-name"))
+ .build()
+ ).build()
+ ).build()
+ )
+ .toolFunction(new ToolFunction() {
+ @Override
+ public Object apply(Map arguments) {
+ // perform DB operations here
+ return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name"), arguments.get("employee-address"), arguments.get("employee-phone"));
+ }
+ })
+ .build();
+
+ api.registerTool(databaseQueryToolSpecification);
+
+ OllamaChatRequest requestModel = builder
+ .withMessage(OllamaChatMessageRole.USER,
+ "Give me the ID of the employee named 'Rahul Kumar'?")
+ .build();
+
+ StringBuffer sb = new StringBuffer();
+
+ OllamaChatResult chatResult = api.chat(requestModel, (s) -> {
+ LOG.info(s);
+ String substring = s.substring(sb.toString().length());
+ LOG.info(substring);
+ sb.append(substring);
+ });
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertNotNull(chatResult.getResponseModel().getMessage().getContent());
+ assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim());
+ }
+
+ @Test
+ @Order(15)
+ void testChatWithStream() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+ OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER,
+ "What is the capital of France? And what's France's connection with Mona Lisa?")
+ .build();
+
+ StringBuffer sb = new StringBuffer();
+
+ OllamaChatResult chatResult = api.chat(requestModel, (s) -> {
+ LOG.info(s);
+ String substring = s.substring(sb.toString().length(), s.length());
+ LOG.info(substring);
+ sb.append(substring);
+ });
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertNotNull(chatResult.getResponseModel().getMessage().getContent());
+ assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim());
+ }
+
+ @Test
+ @Order(16)
+ void testChatWithImageFromURL() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(imageModel);
+ OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(),
+ "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg")
+ .build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ }
+
+ @Test
+ @Order(17)
+ void testAskModelWithOptionsAndImageURLs() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+
+ OllamaResult result =
+ api.generateWithImageURLs(
+ imageModel,
+ "What is in this image?",
+ List.of(
+ "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg"),
+ new OptionsBuilder().build());
+ assertNotNull(result);
+ assertNotNull(result.getResponse());
+ assertFalse(result.getResponse().isEmpty());
+ }
+
+ @Test
+ @Order(18)
+ void testAskModelWithOptionsAndImageFiles() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+ File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg");
+ try {
+ OllamaResult result =
+ api.generateWithImageFiles(
+ imageModel,
+ "What is in this image?",
+ List.of(imageFile),
+ new OptionsBuilder().build());
+ assertNotNull(result);
+ assertNotNull(result.getResponse());
+ assertFalse(result.getResponse().isEmpty());
+ } catch (IOException | OllamaBaseException | InterruptedException e) {
+ fail(e);
+ }
+ }
+
+ @Test
+ @Order(19)
+ void testChatWithImageFromFileWithHistoryRecognition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+ OllamaChatRequestBuilder builder =
+ OllamaChatRequestBuilder.getInstance(imageModel);
+ OllamaChatRequest requestModel =
+ builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(),
+ List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+
+ builder.reset();
+
+ requestModel =
+ builder.withMessages(chatResult.getChatHistory())
+ .withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build();
+
+ chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ }
+
+ @Test
+ @Order(20)
+ void testAskModelWithOptionsAndImageFilesStreamed() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+
+ File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg");
+
+ StringBuffer sb = new StringBuffer();
+
+ OllamaResult result = api.generateWithImageFiles(imageModel,
+ "What is in this image?", List.of(imageFile), new OptionsBuilder().build(), (s) -> {
+ LOG.info(s);
+ String substring = s.substring(sb.toString().length(), s.length());
+ LOG.info(substring);
+ sb.append(substring);
+ });
+ assertNotNull(result);
+ assertNotNull(result.getResponse());
+ assertFalse(result.getResponse().isEmpty());
+ assertEquals(sb.toString().trim(), result.getResponse().trim());
+ }
+
+ private File getImageFileFromClasspath(String fileName) {
+ ClassLoader classLoader = getClass().getClassLoader();
+ return new File(Objects.requireNonNull(classLoader.getResource(fileName)).getFile());
+ }
}
+//
+//@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);
+// }
+// }
+//}
diff --git a/src/test/java/io/github/ollama4j/integrationtests/TestRealAPIs.java b/src/test/java/io/github/ollama4j/integrationtests/TestRealAPIs.java
deleted file mode 100644
index c6fc1b2..0000000
--- a/src/test/java/io/github/ollama4j/integrationtests/TestRealAPIs.java
+++ /dev/null
@@ -1,622 +0,0 @@
-//package io.github.ollama4j.integrationtests;
-//
-//import io.github.ollama4j.OllamaAPI;
-//import io.github.ollama4j.exceptions.OllamaBaseException;
-//import io.github.ollama4j.models.chat.*;
-//import io.github.ollama4j.models.response.ModelDetail;
-//import io.github.ollama4j.models.response.OllamaResult;
-//import io.github.ollama4j.models.embeddings.OllamaEmbeddingsRequestBuilder;
-//import io.github.ollama4j.models.embeddings.OllamaEmbeddingsRequestModel;
-//import io.github.ollama4j.samples.AnnotatedTool;
-//import io.github.ollama4j.tools.OllamaToolCallsFunction;
-//import io.github.ollama4j.tools.ToolFunction;
-//import io.github.ollama4j.tools.Tools;
-//import io.github.ollama4j.tools.annotations.OllamaToolService;
-//import io.github.ollama4j.utils.OptionsBuilder;
-//import lombok.Data;
-//import org.junit.jupiter.api.BeforeEach;
-//import org.junit.jupiter.api.Order;
-//import org.junit.jupiter.api.Test;
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//
-//import java.io.File;
-//import java.io.IOException;
-//import java.io.InputStream;
-//import java.net.ConnectException;
-//import java.net.URISyntaxException;
-//import java.net.http.HttpConnectTimeoutException;
-//import java.util.*;
-//
-//import static org.junit.jupiter.api.Assertions.*;
-//
-//@OllamaToolService(providers = {AnnotatedTool.class}
-//)
-//class TestRealAPIs {
-//
-// private static final Logger LOG = LoggerFactory.getLogger(TestRealAPIs.class);
-//
-// OllamaAPI ollamaAPI;
-// Config config;
-//
-// private File getImageFileFromClasspath(String fileName) {
-// ClassLoader classLoader = getClass().getClassLoader();
-// return new File(Objects.requireNonNull(classLoader.getResource(fileName)).getFile());
-// }
-//
-// @BeforeEach
-// void setUp() {
-// config = new Config();
-// ollamaAPI = new OllamaAPI(config.getOllamaURL());
-// ollamaAPI.setRequestTimeoutSeconds(config.getRequestTimeoutSeconds());
-// ollamaAPI.setVerbose(true);
-// }
-//
-// @Test
-// @Order(1)
-// void testWrongEndpoint() {
-// OllamaAPI ollamaAPI = new OllamaAPI("http://wrong-host:11434");
-// assertThrows(ConnectException.class, ollamaAPI::listModels);
-// }
-//
-// @Test
-// @Order(1)
-// void testEndpointReachability() {
-// try {
-// assertNotNull(ollamaAPI.listModels());
-// } catch (HttpConnectTimeoutException e) {
-// fail(e.getMessage());
-// } catch (Exception e) {
-// fail(e);
-// }
-// }
-//
-//// @Test
-//// @Order(2)
-//// void testListModels() {
-//// testEndpointReachability();
-//// try {
-//// assertNotNull(ollamaAPI.listModels());
-//// ollamaAPI.listModels().forEach(System.out::println);
-//// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(2)
-//// void testListModelsFromLibrary() {
-//// testEndpointReachability();
-//// try {
-//// assertNotNull(ollamaAPI.listModelsFromLibrary());
-//// ollamaAPI.listModelsFromLibrary().forEach(System.out::println);
-//// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(2)
-//// void testPullModel() {
-//// testEndpointReachability();
-//// try {
-//// ollamaAPI.pullModel(config.getModel());
-//// boolean found =
-//// ollamaAPI.listModels().stream()
-//// .anyMatch(model -> model.getModel().equalsIgnoreCase(config.getModel()));
-//// assertTrue(found);
-//// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testListDtails() {
-//// testEndpointReachability();
-//// try {
-//// ModelDetail modelDetails = ollamaAPI.getModelDetails(config.getModel());
-//// assertNotNull(modelDetails);
-//// System.out.println(modelDetails);
-//// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testAskModelWithDefaultOptions() {
-//// testEndpointReachability();
-//// try {
-//// OllamaResult result =
-//// ollamaAPI.generate(
-//// config.getModel(),
-//// "What is the capital of France? And what's France's connection with Mona Lisa?",
-//// false,
-//// new OptionsBuilder().build());
-//// assertNotNull(result);
-//// assertNotNull(result.getResponse());
-//// assertFalse(result.getResponse().isEmpty());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testAskModelWithDefaultOptionsStreamed() {
-//// testEndpointReachability();
-//// try {
-//// StringBuffer sb = new StringBuffer("");
-//// OllamaResult result = ollamaAPI.generate(config.getModel(),
-//// "What is the capital of France? And what's France's connection with Mona Lisa?",
-//// false,
-//// new OptionsBuilder().build(), (s) -> {
-//// LOG.info(s);
-//// String substring = s.substring(sb.toString().length(), s.length());
-//// LOG.info(substring);
-//// sb.append(substring);
-//// });
-////
-//// assertNotNull(result);
-//// assertNotNull(result.getResponse());
-//// assertFalse(result.getResponse().isEmpty());
-//// assertEquals(sb.toString().trim(), result.getResponse().trim());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testAskModelWithOptions() {
-//// testEndpointReachability();
-//// try {
-//// OllamaResult result =
-//// ollamaAPI.generate(
-//// config.getModel(),
-//// "What is the capital of France? And what's France's connection with Mona Lisa?",
-//// true,
-//// new OptionsBuilder().setTemperature(0.9f).build());
-//// assertNotNull(result);
-//// assertNotNull(result.getResponse());
-//// assertFalse(result.getResponse().isEmpty());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testChat() {
-//// testEndpointReachability();
-//// try {
-//// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//// OllamaChatRequest 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);
-//// assertNotNull(chatResult.getResponseModel());
-//// assertNotNull(chatResult.getResponseModel().getMessage());
-//// assertFalse(chatResult.getResponseModel().getMessage().getContent().isBlank());
-//// assertEquals(4, chatResult.getChatHistory().size());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testChatWithSystemPrompt() {
-//// testEndpointReachability();
-//// try {
-//// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//// OllamaChatRequest 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);
-//// assertNotNull(chatResult.getResponseModel());
-//// assertNotNull(chatResult.getResponseModel().getMessage());
-//// assertFalse(chatResult.getResponseModel().getMessage().getContent().isBlank());
-//// assertTrue(chatResult.getResponseModel().getMessage().getContent().startsWith("NI"));
-//// assertEquals(3, chatResult.getChatHistory().size());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-//
-// @Test
-// @Order(3)
-// void testChatWithExplicitToolDefinition() {
-// testEndpointReachability();
-// try {
-// ollamaAPI.setVerbose(true);
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//
-// final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder()
-// .functionName("get-employee-details")
-// .functionDescription("Get employee details from the database")
-// .toolPrompt(
-// Tools.PromptFuncDefinition.builder().type("function").function(
-// Tools.PromptFuncDefinition.PromptFuncSpec.builder()
-// .name("get-employee-details")
-// .description("Get employee details from the database")
-// .parameters(
-// Tools.PromptFuncDefinition.Parameters.builder()
-// .type("object")
-// .properties(
-// new Tools.PropsBuilder()
-// .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build())
-// .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build())
-// .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build())
-// .build()
-// )
-// .required(List.of("employee-name"))
-// .build()
-// ).build()
-// ).build()
-// )
-// .toolFunction(new DBQueryFunction())
-// .build();
-//
-// ollamaAPI.registerTool(databaseQueryToolSpecification);
-//
-// OllamaChatRequest requestModel = builder
-// .withMessage(OllamaChatMessageRole.USER,
-// "Give me the ID of the employee named 'Rahul Kumar'?")
-// .build();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(),chatResult.getResponseModel().getMessage().getRole().getRoleName());
-// List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
-// assertEquals(1, toolCalls.size());
-// OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
-// assertEquals("get-employee-details", function.getName());
-// assertEquals(1, function.getArguments().size());
-// Object employeeName = function.getArguments().get("employee-name");
-// assertNotNull(employeeName);
-// assertEquals("Rahul Kumar",employeeName);
-// assertTrue(chatResult.getChatHistory().size()>2);
-// List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
-// assertNull(finalToolCalls);
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithAnnotatedToolsAndSingleParam() {
-// testEndpointReachability();
-// try {
-// ollamaAPI.setVerbose(true);
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//
-// ollamaAPI.registerAnnotatedTools();
-//
-// OllamaChatRequest requestModel = builder
-// .withMessage(OllamaChatMessageRole.USER,
-// "Compute the most important constant in the world using 5 digits")
-// .build();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(),chatResult.getResponseModel().getMessage().getRole().getRoleName());
-// List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
-// assertEquals(1, toolCalls.size());
-// OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
-// assertEquals("computeImportantConstant", function.getName());
-// assertEquals(1, function.getArguments().size());
-// Object noOfDigits = function.getArguments().get("noOfDigits");
-// assertNotNull(noOfDigits);
-// assertEquals("5", noOfDigits.toString());
-// assertTrue(chatResult.getChatHistory().size()>2);
-// List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
-// assertNull(finalToolCalls);
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithAnnotatedToolsAndMultipleParams() {
-// testEndpointReachability();
-// try {
-// ollamaAPI.setVerbose(true);
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//
-// ollamaAPI.registerAnnotatedTools(new AnnotatedTool());
-//
-// OllamaChatRequest requestModel = builder
-// .withMessage(OllamaChatMessageRole.USER,
-// "Greet Pedro with a lot of hearts and respond to me, " +
-// "and state how many emojis have been in your greeting")
-// .build();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(),chatResult.getResponseModel().getMessage().getRole().getRoleName());
-// List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
-// assertEquals(1, toolCalls.size());
-// OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
-// assertEquals("sayHello", function.getName());
-// assertEquals(2, function.getArguments().size());
-// Object name = function.getArguments().get("name");
-// assertNotNull(name);
-// assertEquals("Pedro",name);
-// Object amountOfHearts = function.getArguments().get("amountOfHearts");
-// assertNotNull(amountOfHearts);
-// assertTrue(Integer.parseInt(amountOfHearts.toString()) > 1);
-// assertTrue(chatResult.getChatHistory().size()>2);
-// List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
-// assertNull(finalToolCalls);
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithToolsAndStream() {
-// testEndpointReachability();
-// try {
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-// final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder()
-// .functionName("get-employee-details")
-// .functionDescription("Get employee details from the database")
-// .toolPrompt(
-// Tools.PromptFuncDefinition.builder().type("function").function(
-// Tools.PromptFuncDefinition.PromptFuncSpec.builder()
-// .name("get-employee-details")
-// .description("Get employee details from the database")
-// .parameters(
-// Tools.PromptFuncDefinition.Parameters.builder()
-// .type("object")
-// .properties(
-// new Tools.PropsBuilder()
-// .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build())
-// .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build())
-// .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build())
-// .build()
-// )
-// .required(List.of("employee-name"))
-// .build()
-// ).build()
-// ).build()
-// )
-// .toolFunction(new DBQueryFunction())
-// .build();
-//
-// ollamaAPI.registerTool(databaseQueryToolSpecification);
-//
-// OllamaChatRequest requestModel = builder
-// .withMessage(OllamaChatMessageRole.USER,
-// "Give me the ID of the employee named 'Rahul Kumar'?")
-// .build();
-//
-// StringBuffer sb = new StringBuffer();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel, (s) -> {
-// LOG.info(s);
-// String substring = s.substring(sb.toString().length());
-// LOG.info(substring);
-// sb.append(substring);
-// });
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertNotNull(chatResult.getResponseModel().getMessage().getContent());
-// assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithStream() {
-// testEndpointReachability();
-// try {
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-// OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER,
-// "What is the capital of France? And what's France's connection with Mona Lisa?")
-// .build();
-//
-// StringBuffer sb = new StringBuffer("");
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel, (s) -> {
-// LOG.info(s);
-// String substring = s.substring(sb.toString().length(), s.length());
-// LOG.info(substring);
-// sb.append(substring);
-// });
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertNotNull(chatResult.getResponseModel().getMessage().getContent());
-// assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithImageFromFileWithHistoryRecognition() {
-// testEndpointReachability();
-// try {
-// OllamaChatRequestBuilder builder =
-// OllamaChatRequestBuilder.getInstance(config.getImageModel());
-// OllamaChatRequest requestModel =
-// builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?",Collections.emptyList(),
-// List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-//
-// builder.reset();
-//
-// requestModel =
-// builder.withMessages(chatResult.getChatHistory())
-// .withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build();
-//
-// chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-//
-//
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithImageFromURL() {
-// testEndpointReachability();
-// try {
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getImageModel());
-// OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?",Collections.emptyList(),
-// "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) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testAskModelWithOptionsAndImageFiles() {
-// testEndpointReachability();
-// File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg");
-// try {
-// OllamaResult result =
-// ollamaAPI.generateWithImageFiles(
-// config.getImageModel(),
-// "What is in this image?",
-// List.of(imageFile),
-// new OptionsBuilder().build());
-// assertNotNull(result);
-// assertNotNull(result.getResponse());
-// assertFalse(result.getResponse().isEmpty());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testAskModelWithOptionsAndImageFilesStreamed() {
-// testEndpointReachability();
-// File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg");
-// try {
-// StringBuffer sb = new StringBuffer("");
-//
-// OllamaResult result = ollamaAPI.generateWithImageFiles(config.getImageModel(),
-// "What is in this image?", List.of(imageFile), new OptionsBuilder().build(), (s) -> {
-// LOG.info(s);
-// String substring = s.substring(sb.toString().length(), s.length());
-// LOG.info(substring);
-// sb.append(substring);
-// });
-// assertNotNull(result);
-// assertNotNull(result.getResponse());
-// assertFalse(result.getResponse().isEmpty());
-// assertEquals(sb.toString().trim(), result.getResponse().trim());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testAskModelWithOptionsAndImageURLs() {
-// testEndpointReachability();
-// try {
-// OllamaResult result =
-// 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"),
-// new OptionsBuilder().build());
-// assertNotNull(result);
-// assertNotNull(result.getResponse());
-// assertFalse(result.getResponse().isEmpty());
-// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// public void testEmbedding() {
-// testEndpointReachability();
-// try {
-// OllamaEmbeddingsRequestModel request = OllamaEmbeddingsRequestBuilder
-// .getInstance(config.getModel(), "What is the capital of France?").build();
-//
-// List embeddings = ollamaAPI.generateEmbeddings(request);
-//
-// assertNotNull(embeddings);
-// assertFalse(embeddings.isEmpty());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//}
-//
-//class DBQueryFunction implements ToolFunction {
-// @Override
-// public Object apply(Map arguments) {
-// // perform DB operations here
-// return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name"), arguments.get("employee-address"), arguments.get("employee-phone"));
-// }
-//}
-//
-//@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);
-// }
-// }
-//
-//
-//}
From b638b981c9d1eed402a74f724969d526f4176884 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 11 Mar 2025 12:05:11 +0530
Subject: [PATCH 04/20] Remove unnecessary blank lines from pom.xml
Cleaned up redundant blank lines at the end of the pom.xml file to ensure consistent formatting. This helps improve code readability and adheres to standard practices.
---
pom.xml | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/pom.xml b/pom.xml
index 6121533..fa21640 100644
--- a/pom.xml
+++ b/pom.xml
@@ -407,3 +407,8 @@
+
+
+
+
+
From 983a3617f05311fb9586c85d8b9973d2687fd036 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 11 Mar 2025 12:15:19 +0530
Subject: [PATCH 05/20] Add dev setup instructions and update pre-commit config
---
.pre-commit-config.yaml | 2 +-
Makefile | 7 +++++++
README.md | 35 ++++++++++++++++++++++++++++-------
3 files changed, 36 insertions(+), 8 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 35c91c3..5de9f01 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -21,7 +21,7 @@ repos:
# for commit message formatting
- repo: https://github.com/commitizen-tools/commitizen
- rev: v4.1.1
+ rev: v4.4.1
hooks:
- id: commitizen
stages: [commit-msg]
diff --git a/Makefile b/Makefile
index 8202309..953c764 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,10 @@
+dev:
+ @echo "Setting up dev environment..."
+ @command -v pre-commit >/dev/null 2>&1 || { echo "Error: pre-commit is not installed. Please install it first."; exit 1; }
+ pre-commit install
+ pre-commit autoupdate
+ pre-commit install --install-hooks
+
build:
mvn -B clean install
diff --git a/README.md b/README.md
index b9e472d..a832a0c 100644
--- a/README.md
+++ b/README.md
@@ -191,33 +191,54 @@ dependencies {
> [!TIP]
> Find the full API specifications on the [website](https://ollama4j.github.io/ollama4j/).
-#### Development
+### Development
-Build:
+Make sure you have `pre-commit` installed.
+
+With `brew`:
+
+```shell
+brew install pre-commit
+```
+
+With `pip`:
+
+```shell
+pip install pre-commit
+```
+
+#### Setup dev environment
+
+```shell
+make dev
+```
+
+#### Build
```shell
make build
```
-Run unit tests:
+#### Run unit tests
```shell
make unit-tests
```
-Run integration tests:
+#### Run integration tests
```shell
make integration-tests
```
-#### Releases
+### Releases
Newer artifacts are published via GitHub Actions CI workflow when a new release is created from `main` branch.
## Examples
-The `ollama4j-examples` repository contains examples for using the Ollama4j library. You can explore it [here](https://github.com/ollama4j/ollama4j-examples).
+The `ollama4j-examples` repository contains examples for using the Ollama4j library. You can explore
+it [here](https://github.com/ollama4j/ollama4j-examples).
## â Give us a Star!
@@ -236,7 +257,7 @@ If you like or are using this project to build your own, please give us a star.
| 7 | Katie Backend | An open-source AI-based question-answering platform for accessing private domain knowledge | [GitHub](https://github.com/wyona/katie-backend) |
| 8 | TeleLlama3 Bot | A question-answering Telegram bot | [Repo](https://git.hiast.edu.sy/mohamadbashar.disoki/telellama3-bot) |
| 9 | moqui-wechat | A moqui-wechat component | [GitHub](https://github.com/heguangyong/moqui-wechat) |
-| 10 | B4X | A set of simple and powerful RAD tool for Desktop and Server development | [Website](https://www.b4x.com/android/forum/threads/ollama4j-library-pnd_ollama4j-your-local-offline-llm-like-chatgpt.165003/) |
+| 10 | B4X | A set of simple and powerful RAD tool for Desktop and Server development | [Website](https://www.b4x.com/android/forum/threads/ollama4j-library-pnd_ollama4j-your-local-offline-llm-like-chatgpt.165003/) |
| 11 | Research Article | Article: `Large language model based mutations in genetic improvement` - published on National Library of Medicine (National Center for Biotechnology Information) | [Website](https://pmc.ncbi.nlm.nih.gov/articles/PMC11750896/) |
## Traction
From 469a0fe491b44f922630111a8287c729d00d1e6b Mon Sep 17 00:00:00 2001
From: amithkoujalgi
Date: Tue, 11 Mar 2025 11:41:51 +0530
Subject: [PATCH 06/20] Refactor
- Remove TestRealAPIs and enhance OllamaAPIIntegrationTest
- Add dev setup instruction
---
.pre-commit-config.yaml | 2 +-
Makefile | 7 +
README.md | 35 +-
pom.xml | 5 +
.../OllamaAPIIntegrationTest.java | 380 ++++++++++-
.../integrationtests/TestRealAPIs.java | 622 ------------------
6 files changed, 411 insertions(+), 640 deletions(-)
delete mode 100644 src/test/java/io/github/ollama4j/integrationtests/TestRealAPIs.java
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 35c91c3..5de9f01 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -21,7 +21,7 @@ repos:
# for commit message formatting
- repo: https://github.com/commitizen-tools/commitizen
- rev: v4.1.1
+ rev: v4.4.1
hooks:
- id: commitizen
stages: [commit-msg]
diff --git a/Makefile b/Makefile
index 8202309..953c764 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,10 @@
+dev:
+ @echo "Setting up dev environment..."
+ @command -v pre-commit >/dev/null 2>&1 || { echo "Error: pre-commit is not installed. Please install it first."; exit 1; }
+ pre-commit install
+ pre-commit autoupdate
+ pre-commit install --install-hooks
+
build:
mvn -B clean install
diff --git a/README.md b/README.md
index b9e472d..a832a0c 100644
--- a/README.md
+++ b/README.md
@@ -191,33 +191,54 @@ dependencies {
> [!TIP]
> Find the full API specifications on the [website](https://ollama4j.github.io/ollama4j/).
-#### Development
+### Development
-Build:
+Make sure you have `pre-commit` installed.
+
+With `brew`:
+
+```shell
+brew install pre-commit
+```
+
+With `pip`:
+
+```shell
+pip install pre-commit
+```
+
+#### Setup dev environment
+
+```shell
+make dev
+```
+
+#### Build
```shell
make build
```
-Run unit tests:
+#### Run unit tests
```shell
make unit-tests
```
-Run integration tests:
+#### Run integration tests
```shell
make integration-tests
```
-#### Releases
+### Releases
Newer artifacts are published via GitHub Actions CI workflow when a new release is created from `main` branch.
## Examples
-The `ollama4j-examples` repository contains examples for using the Ollama4j library. You can explore it [here](https://github.com/ollama4j/ollama4j-examples).
+The `ollama4j-examples` repository contains examples for using the Ollama4j library. You can explore
+it [here](https://github.com/ollama4j/ollama4j-examples).
## â Give us a Star!
@@ -236,7 +257,7 @@ If you like or are using this project to build your own, please give us a star.
| 7 | Katie Backend | An open-source AI-based question-answering platform for accessing private domain knowledge | [GitHub](https://github.com/wyona/katie-backend) |
| 8 | TeleLlama3 Bot | A question-answering Telegram bot | [Repo](https://git.hiast.edu.sy/mohamadbashar.disoki/telellama3-bot) |
| 9 | moqui-wechat | A moqui-wechat component | [GitHub](https://github.com/heguangyong/moqui-wechat) |
-| 10 | B4X | A set of simple and powerful RAD tool for Desktop and Server development | [Website](https://www.b4x.com/android/forum/threads/ollama4j-library-pnd_ollama4j-your-local-offline-llm-like-chatgpt.165003/) |
+| 10 | B4X | A set of simple and powerful RAD tool for Desktop and Server development | [Website](https://www.b4x.com/android/forum/threads/ollama4j-library-pnd_ollama4j-your-local-offline-llm-like-chatgpt.165003/) |
| 11 | Research Article | Article: `Large language model based mutations in genetic improvement` - published on National Library of Medicine (National Center for Biotechnology Information) | [Website](https://pmc.ncbi.nlm.nih.gov/articles/PMC11750896/) |
## Traction
diff --git a/pom.xml b/pom.xml
index 6121533..fa21640 100644
--- a/pom.xml
+++ b/pom.xml
@@ -407,3 +407,8 @@
+
+
+
+
+
diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
index 4ccfadd..e26609e 100644
--- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
+++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
@@ -2,32 +2,41 @@ package io.github.ollama4j.integrationtests;
import io.github.ollama4j.OllamaAPI;
import io.github.ollama4j.exceptions.OllamaBaseException;
-import io.github.ollama4j.models.chat.OllamaChatMessageRole;
-import io.github.ollama4j.models.chat.OllamaChatRequest;
-import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
-import io.github.ollama4j.models.chat.OllamaChatResult;
+import io.github.ollama4j.models.chat.*;
import io.github.ollama4j.models.embeddings.OllamaEmbedResponseModel;
import io.github.ollama4j.models.response.LibraryModel;
import io.github.ollama4j.models.response.Model;
import io.github.ollama4j.models.response.ModelDetail;
import io.github.ollama4j.models.response.OllamaResult;
+import io.github.ollama4j.samples.AnnotatedTool;
+import io.github.ollama4j.tools.OllamaToolCallsFunction;
+import io.github.ollama4j.tools.ToolFunction;
+import io.github.ollama4j.tools.Tools;
+import io.github.ollama4j.tools.annotations.OllamaToolService;
import io.github.ollama4j.utils.OptionsBuilder;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.testcontainers.ollama.OllamaContainer;
+import java.io.File;
import java.io.IOException;
import java.net.ConnectException;
import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
+@OllamaToolService(providers = {AnnotatedTool.class})
+@TestMethodOrder(OrderAnnotation.class)
+
@SuppressWarnings("HttpUrlsUsage")
public class OllamaAPIIntegrationTest {
+ private static final Logger LOG = LoggerFactory.getLogger(OllamaAPIIntegrationTest.class);
private static OllamaContainer ollama;
private static OllamaAPI api;
@@ -45,6 +54,7 @@ public class OllamaAPIIntegrationTest {
ollama.start();
api = new OllamaAPI("http://" + ollama.getHost() + ":" + ollama.getMappedPort(internalPort));
api.setRequestTimeoutSeconds(60);
+ api.setVerbose(true);
}
@Test
@@ -102,7 +112,7 @@ public class OllamaAPIIntegrationTest {
@Test
@Order(5)
- public void testGenerateEmbeddings() throws Exception {
+ public void testEmbeddings() throws Exception {
String embeddingModelMinilm = "all-minilm";
api.pullModel(embeddingModelMinilm);
OllamaEmbedResponseModel embeddings = api.embed(embeddingModelMinilm, Arrays.asList("Why is the sky blue?", "Why is the grass green?"));
@@ -136,9 +146,9 @@ public class OllamaAPIIntegrationTest {
"What is the capital of France? And what's France's connection with Mona Lisa?",
false,
new OptionsBuilder().build(), (s) -> {
- System.out.println(s);
+ LOG.info(s);
String substring = s.substring(sb.toString().length(), s.length());
- System.out.println(substring);
+ LOG.info(substring);
sb.append(substring);
});
@@ -236,4 +246,354 @@ public class OllamaAPIIntegrationTest {
assertTrue(chatResult.getChatHistory().size() > 2, "Chat history should contain more than two messages");
assertTrue(chatResult.getChatHistory().get(chatResult.getChatHistory().size() - 1).getContent().contains("river"), "Response should be related to river");
}
+
+
+ @Test
+ @Order(11)
+ void testChatWithExplicitToolDefinition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+
+ final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder()
+ .functionName("get-employee-details")
+ .functionDescription("Get employee details from the database")
+ .toolPrompt(
+ Tools.PromptFuncDefinition.builder().type("function").function(
+ Tools.PromptFuncDefinition.PromptFuncSpec.builder()
+ .name("get-employee-details")
+ .description("Get employee details from the database")
+ .parameters(
+ Tools.PromptFuncDefinition.Parameters.builder()
+ .type("object")
+ .properties(
+ new Tools.PropsBuilder()
+ .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build())
+ .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build())
+ .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build())
+ .build()
+ )
+ .required(List.of("employee-name"))
+ .build()
+ ).build()
+ ).build()
+ )
+ .toolFunction(arguments -> {
+ // perform DB operations here
+ return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name"), arguments.get("employee-address"), arguments.get("employee-phone"));
+ })
+ .build();
+
+ api.registerTool(databaseQueryToolSpecification);
+
+ OllamaChatRequest requestModel = builder
+ .withMessage(OllamaChatMessageRole.USER,
+ "Give me the ID of the employee named 'Rahul Kumar'?")
+ .build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(), chatResult.getResponseModel().getMessage().getRole().getRoleName());
+ List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
+ assertEquals(1, toolCalls.size());
+ OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
+ assertEquals("get-employee-details", function.getName());
+ assertEquals(1, function.getArguments().size());
+ Object employeeName = function.getArguments().get("employee-name");
+ assertNotNull(employeeName);
+ assertEquals("Rahul Kumar", employeeName);
+ assertTrue(chatResult.getChatHistory().size() > 2);
+ List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
+ assertNull(finalToolCalls);
+ }
+
+ @Test
+ @Order(12)
+ void testChatWithAnnotatedToolsAndSingleParam() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+
+ api.registerAnnotatedTools();
+
+ OllamaChatRequest requestModel = builder
+ .withMessage(OllamaChatMessageRole.USER,
+ "Compute the most important constant in the world using 5 digits")
+ .build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(), chatResult.getResponseModel().getMessage().getRole().getRoleName());
+ List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
+ assertEquals(1, toolCalls.size());
+ OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
+ assertEquals("computeImportantConstant", function.getName());
+ assertEquals(1, function.getArguments().size());
+ Object noOfDigits = function.getArguments().get("noOfDigits");
+ assertNotNull(noOfDigits);
+ assertEquals("5", noOfDigits.toString());
+ assertTrue(chatResult.getChatHistory().size() > 2);
+ List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
+ assertNull(finalToolCalls);
+ }
+
+ @Test
+ @Order(13)
+ void testChatWithAnnotatedToolsAndMultipleParams() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+
+ api.registerAnnotatedTools(new AnnotatedTool());
+
+ OllamaChatRequest requestModel = builder
+ .withMessage(OllamaChatMessageRole.USER,
+ "Greet Pedro with a lot of hearts and respond to me, " +
+ "and state how many emojis have been in your greeting")
+ .build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(), chatResult.getResponseModel().getMessage().getRole().getRoleName());
+ List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
+ assertEquals(1, toolCalls.size());
+ OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
+ assertEquals("sayHello", function.getName());
+ assertEquals(2, function.getArguments().size());
+ Object name = function.getArguments().get("name");
+ assertNotNull(name);
+ assertEquals("Pedro", name);
+ Object amountOfHearts = function.getArguments().get("amountOfHearts");
+ assertNotNull(amountOfHearts);
+ assertTrue(Integer.parseInt(amountOfHearts.toString()) > 1);
+ assertTrue(chatResult.getChatHistory().size() > 2);
+ List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
+ assertNull(finalToolCalls);
+ }
+
+ @Test
+ @Order(14)
+ void testChatWithToolsAndStream() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+ final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder()
+ .functionName("get-employee-details")
+ .functionDescription("Get employee details from the database")
+ .toolPrompt(
+ Tools.PromptFuncDefinition.builder().type("function").function(
+ Tools.PromptFuncDefinition.PromptFuncSpec.builder()
+ .name("get-employee-details")
+ .description("Get employee details from the database")
+ .parameters(
+ Tools.PromptFuncDefinition.Parameters.builder()
+ .type("object")
+ .properties(
+ new Tools.PropsBuilder()
+ .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build())
+ .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build())
+ .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build())
+ .build()
+ )
+ .required(List.of("employee-name"))
+ .build()
+ ).build()
+ ).build()
+ )
+ .toolFunction(new ToolFunction() {
+ @Override
+ public Object apply(Map arguments) {
+ // perform DB operations here
+ return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name"), arguments.get("employee-address"), arguments.get("employee-phone"));
+ }
+ })
+ .build();
+
+ api.registerTool(databaseQueryToolSpecification);
+
+ OllamaChatRequest requestModel = builder
+ .withMessage(OllamaChatMessageRole.USER,
+ "Give me the ID of the employee named 'Rahul Kumar'?")
+ .build();
+
+ StringBuffer sb = new StringBuffer();
+
+ OllamaChatResult chatResult = api.chat(requestModel, (s) -> {
+ LOG.info(s);
+ String substring = s.substring(sb.toString().length());
+ LOG.info(substring);
+ sb.append(substring);
+ });
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertNotNull(chatResult.getResponseModel().getMessage().getContent());
+ assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim());
+ }
+
+ @Test
+ @Order(15)
+ void testChatWithStream() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String chatModel = "llama3.2:1b";
+ api.pullModel(chatModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
+ OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER,
+ "What is the capital of France? And what's France's connection with Mona Lisa?")
+ .build();
+
+ StringBuffer sb = new StringBuffer();
+
+ OllamaChatResult chatResult = api.chat(requestModel, (s) -> {
+ LOG.info(s);
+ String substring = s.substring(sb.toString().length(), s.length());
+ LOG.info(substring);
+ sb.append(substring);
+ });
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ assertNotNull(chatResult.getResponseModel().getMessage());
+ assertNotNull(chatResult.getResponseModel().getMessage().getContent());
+ assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim());
+ }
+
+ @Test
+ @Order(16)
+ void testChatWithImageFromURL() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(imageModel);
+ OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(),
+ "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg")
+ .build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ }
+
+ @Test
+ @Order(17)
+ void testAskModelWithOptionsAndImageURLs() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+
+ OllamaResult result =
+ api.generateWithImageURLs(
+ imageModel,
+ "What is in this image?",
+ List.of(
+ "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg"),
+ new OptionsBuilder().build());
+ assertNotNull(result);
+ assertNotNull(result.getResponse());
+ assertFalse(result.getResponse().isEmpty());
+ }
+
+ @Test
+ @Order(18)
+ void testAskModelWithOptionsAndImageFiles() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+ File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg");
+ try {
+ OllamaResult result =
+ api.generateWithImageFiles(
+ imageModel,
+ "What is in this image?",
+ List.of(imageFile),
+ new OptionsBuilder().build());
+ assertNotNull(result);
+ assertNotNull(result.getResponse());
+ assertFalse(result.getResponse().isEmpty());
+ } catch (IOException | OllamaBaseException | InterruptedException e) {
+ fail(e);
+ }
+ }
+
+ @Test
+ @Order(19)
+ void testChatWithImageFromFileWithHistoryRecognition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+ OllamaChatRequestBuilder builder =
+ OllamaChatRequestBuilder.getInstance(imageModel);
+ OllamaChatRequest requestModel =
+ builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(),
+ List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+
+ builder.reset();
+
+ requestModel =
+ builder.withMessages(chatResult.getChatHistory())
+ .withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build();
+
+ chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ }
+
+ @Test
+ @Order(20)
+ void testAskModelWithOptionsAndImageFilesStreamed() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+
+ File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg");
+
+ StringBuffer sb = new StringBuffer();
+
+ OllamaResult result = api.generateWithImageFiles(imageModel,
+ "What is in this image?", List.of(imageFile), new OptionsBuilder().build(), (s) -> {
+ LOG.info(s);
+ String substring = s.substring(sb.toString().length(), s.length());
+ LOG.info(substring);
+ sb.append(substring);
+ });
+ assertNotNull(result);
+ assertNotNull(result.getResponse());
+ assertFalse(result.getResponse().isEmpty());
+ assertEquals(sb.toString().trim(), result.getResponse().trim());
+ }
+
+ private File getImageFileFromClasspath(String fileName) {
+ ClassLoader classLoader = getClass().getClassLoader();
+ return new File(Objects.requireNonNull(classLoader.getResource(fileName)).getFile());
+ }
}
+//
+//@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);
+// }
+// }
+//}
diff --git a/src/test/java/io/github/ollama4j/integrationtests/TestRealAPIs.java b/src/test/java/io/github/ollama4j/integrationtests/TestRealAPIs.java
deleted file mode 100644
index c6fc1b2..0000000
--- a/src/test/java/io/github/ollama4j/integrationtests/TestRealAPIs.java
+++ /dev/null
@@ -1,622 +0,0 @@
-//package io.github.ollama4j.integrationtests;
-//
-//import io.github.ollama4j.OllamaAPI;
-//import io.github.ollama4j.exceptions.OllamaBaseException;
-//import io.github.ollama4j.models.chat.*;
-//import io.github.ollama4j.models.response.ModelDetail;
-//import io.github.ollama4j.models.response.OllamaResult;
-//import io.github.ollama4j.models.embeddings.OllamaEmbeddingsRequestBuilder;
-//import io.github.ollama4j.models.embeddings.OllamaEmbeddingsRequestModel;
-//import io.github.ollama4j.samples.AnnotatedTool;
-//import io.github.ollama4j.tools.OllamaToolCallsFunction;
-//import io.github.ollama4j.tools.ToolFunction;
-//import io.github.ollama4j.tools.Tools;
-//import io.github.ollama4j.tools.annotations.OllamaToolService;
-//import io.github.ollama4j.utils.OptionsBuilder;
-//import lombok.Data;
-//import org.junit.jupiter.api.BeforeEach;
-//import org.junit.jupiter.api.Order;
-//import org.junit.jupiter.api.Test;
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//
-//import java.io.File;
-//import java.io.IOException;
-//import java.io.InputStream;
-//import java.net.ConnectException;
-//import java.net.URISyntaxException;
-//import java.net.http.HttpConnectTimeoutException;
-//import java.util.*;
-//
-//import static org.junit.jupiter.api.Assertions.*;
-//
-//@OllamaToolService(providers = {AnnotatedTool.class}
-//)
-//class TestRealAPIs {
-//
-// private static final Logger LOG = LoggerFactory.getLogger(TestRealAPIs.class);
-//
-// OllamaAPI ollamaAPI;
-// Config config;
-//
-// private File getImageFileFromClasspath(String fileName) {
-// ClassLoader classLoader = getClass().getClassLoader();
-// return new File(Objects.requireNonNull(classLoader.getResource(fileName)).getFile());
-// }
-//
-// @BeforeEach
-// void setUp() {
-// config = new Config();
-// ollamaAPI = new OllamaAPI(config.getOllamaURL());
-// ollamaAPI.setRequestTimeoutSeconds(config.getRequestTimeoutSeconds());
-// ollamaAPI.setVerbose(true);
-// }
-//
-// @Test
-// @Order(1)
-// void testWrongEndpoint() {
-// OllamaAPI ollamaAPI = new OllamaAPI("http://wrong-host:11434");
-// assertThrows(ConnectException.class, ollamaAPI::listModels);
-// }
-//
-// @Test
-// @Order(1)
-// void testEndpointReachability() {
-// try {
-// assertNotNull(ollamaAPI.listModels());
-// } catch (HttpConnectTimeoutException e) {
-// fail(e.getMessage());
-// } catch (Exception e) {
-// fail(e);
-// }
-// }
-//
-//// @Test
-//// @Order(2)
-//// void testListModels() {
-//// testEndpointReachability();
-//// try {
-//// assertNotNull(ollamaAPI.listModels());
-//// ollamaAPI.listModels().forEach(System.out::println);
-//// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(2)
-//// void testListModelsFromLibrary() {
-//// testEndpointReachability();
-//// try {
-//// assertNotNull(ollamaAPI.listModelsFromLibrary());
-//// ollamaAPI.listModelsFromLibrary().forEach(System.out::println);
-//// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(2)
-//// void testPullModel() {
-//// testEndpointReachability();
-//// try {
-//// ollamaAPI.pullModel(config.getModel());
-//// boolean found =
-//// ollamaAPI.listModels().stream()
-//// .anyMatch(model -> model.getModel().equalsIgnoreCase(config.getModel()));
-//// assertTrue(found);
-//// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testListDtails() {
-//// testEndpointReachability();
-//// try {
-//// ModelDetail modelDetails = ollamaAPI.getModelDetails(config.getModel());
-//// assertNotNull(modelDetails);
-//// System.out.println(modelDetails);
-//// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testAskModelWithDefaultOptions() {
-//// testEndpointReachability();
-//// try {
-//// OllamaResult result =
-//// ollamaAPI.generate(
-//// config.getModel(),
-//// "What is the capital of France? And what's France's connection with Mona Lisa?",
-//// false,
-//// new OptionsBuilder().build());
-//// assertNotNull(result);
-//// assertNotNull(result.getResponse());
-//// assertFalse(result.getResponse().isEmpty());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testAskModelWithDefaultOptionsStreamed() {
-//// testEndpointReachability();
-//// try {
-//// StringBuffer sb = new StringBuffer("");
-//// OllamaResult result = ollamaAPI.generate(config.getModel(),
-//// "What is the capital of France? And what's France's connection with Mona Lisa?",
-//// false,
-//// new OptionsBuilder().build(), (s) -> {
-//// LOG.info(s);
-//// String substring = s.substring(sb.toString().length(), s.length());
-//// LOG.info(substring);
-//// sb.append(substring);
-//// });
-////
-//// assertNotNull(result);
-//// assertNotNull(result.getResponse());
-//// assertFalse(result.getResponse().isEmpty());
-//// assertEquals(sb.toString().trim(), result.getResponse().trim());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testAskModelWithOptions() {
-//// testEndpointReachability();
-//// try {
-//// OllamaResult result =
-//// ollamaAPI.generate(
-//// config.getModel(),
-//// "What is the capital of France? And what's France's connection with Mona Lisa?",
-//// true,
-//// new OptionsBuilder().setTemperature(0.9f).build());
-//// assertNotNull(result);
-//// assertNotNull(result.getResponse());
-//// assertFalse(result.getResponse().isEmpty());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testChat() {
-//// testEndpointReachability();
-//// try {
-//// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//// OllamaChatRequest 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);
-//// assertNotNull(chatResult.getResponseModel());
-//// assertNotNull(chatResult.getResponseModel().getMessage());
-//// assertFalse(chatResult.getResponseModel().getMessage().getContent().isBlank());
-//// assertEquals(4, chatResult.getChatHistory().size());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-////
-//// @Test
-//// @Order(3)
-//// void testChatWithSystemPrompt() {
-//// testEndpointReachability();
-//// try {
-//// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//// OllamaChatRequest 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);
-//// assertNotNull(chatResult.getResponseModel());
-//// assertNotNull(chatResult.getResponseModel().getMessage());
-//// assertFalse(chatResult.getResponseModel().getMessage().getContent().isBlank());
-//// assertTrue(chatResult.getResponseModel().getMessage().getContent().startsWith("NI"));
-//// assertEquals(3, chatResult.getChatHistory().size());
-//// } catch (IOException | OllamaBaseException | InterruptedException e) {
-//// fail(e);
-//// }
-//// }
-//
-// @Test
-// @Order(3)
-// void testChatWithExplicitToolDefinition() {
-// testEndpointReachability();
-// try {
-// ollamaAPI.setVerbose(true);
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//
-// final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder()
-// .functionName("get-employee-details")
-// .functionDescription("Get employee details from the database")
-// .toolPrompt(
-// Tools.PromptFuncDefinition.builder().type("function").function(
-// Tools.PromptFuncDefinition.PromptFuncSpec.builder()
-// .name("get-employee-details")
-// .description("Get employee details from the database")
-// .parameters(
-// Tools.PromptFuncDefinition.Parameters.builder()
-// .type("object")
-// .properties(
-// new Tools.PropsBuilder()
-// .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build())
-// .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build())
-// .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build())
-// .build()
-// )
-// .required(List.of("employee-name"))
-// .build()
-// ).build()
-// ).build()
-// )
-// .toolFunction(new DBQueryFunction())
-// .build();
-//
-// ollamaAPI.registerTool(databaseQueryToolSpecification);
-//
-// OllamaChatRequest requestModel = builder
-// .withMessage(OllamaChatMessageRole.USER,
-// "Give me the ID of the employee named 'Rahul Kumar'?")
-// .build();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(),chatResult.getResponseModel().getMessage().getRole().getRoleName());
-// List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
-// assertEquals(1, toolCalls.size());
-// OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
-// assertEquals("get-employee-details", function.getName());
-// assertEquals(1, function.getArguments().size());
-// Object employeeName = function.getArguments().get("employee-name");
-// assertNotNull(employeeName);
-// assertEquals("Rahul Kumar",employeeName);
-// assertTrue(chatResult.getChatHistory().size()>2);
-// List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
-// assertNull(finalToolCalls);
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithAnnotatedToolsAndSingleParam() {
-// testEndpointReachability();
-// try {
-// ollamaAPI.setVerbose(true);
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//
-// ollamaAPI.registerAnnotatedTools();
-//
-// OllamaChatRequest requestModel = builder
-// .withMessage(OllamaChatMessageRole.USER,
-// "Compute the most important constant in the world using 5 digits")
-// .build();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(),chatResult.getResponseModel().getMessage().getRole().getRoleName());
-// List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
-// assertEquals(1, toolCalls.size());
-// OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
-// assertEquals("computeImportantConstant", function.getName());
-// assertEquals(1, function.getArguments().size());
-// Object noOfDigits = function.getArguments().get("noOfDigits");
-// assertNotNull(noOfDigits);
-// assertEquals("5", noOfDigits.toString());
-// assertTrue(chatResult.getChatHistory().size()>2);
-// List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
-// assertNull(finalToolCalls);
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithAnnotatedToolsAndMultipleParams() {
-// testEndpointReachability();
-// try {
-// ollamaAPI.setVerbose(true);
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-//
-// ollamaAPI.registerAnnotatedTools(new AnnotatedTool());
-//
-// OllamaChatRequest requestModel = builder
-// .withMessage(OllamaChatMessageRole.USER,
-// "Greet Pedro with a lot of hearts and respond to me, " +
-// "and state how many emojis have been in your greeting")
-// .build();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertEquals(OllamaChatMessageRole.ASSISTANT.getRoleName(),chatResult.getResponseModel().getMessage().getRole().getRoleName());
-// List toolCalls = chatResult.getChatHistory().get(1).getToolCalls();
-// assertEquals(1, toolCalls.size());
-// OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
-// assertEquals("sayHello", function.getName());
-// assertEquals(2, function.getArguments().size());
-// Object name = function.getArguments().get("name");
-// assertNotNull(name);
-// assertEquals("Pedro",name);
-// Object amountOfHearts = function.getArguments().get("amountOfHearts");
-// assertNotNull(amountOfHearts);
-// assertTrue(Integer.parseInt(amountOfHearts.toString()) > 1);
-// assertTrue(chatResult.getChatHistory().size()>2);
-// List finalToolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
-// assertNull(finalToolCalls);
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithToolsAndStream() {
-// testEndpointReachability();
-// try {
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-// final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder()
-// .functionName("get-employee-details")
-// .functionDescription("Get employee details from the database")
-// .toolPrompt(
-// Tools.PromptFuncDefinition.builder().type("function").function(
-// Tools.PromptFuncDefinition.PromptFuncSpec.builder()
-// .name("get-employee-details")
-// .description("Get employee details from the database")
-// .parameters(
-// Tools.PromptFuncDefinition.Parameters.builder()
-// .type("object")
-// .properties(
-// new Tools.PropsBuilder()
-// .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build())
-// .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build())
-// .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build())
-// .build()
-// )
-// .required(List.of("employee-name"))
-// .build()
-// ).build()
-// ).build()
-// )
-// .toolFunction(new DBQueryFunction())
-// .build();
-//
-// ollamaAPI.registerTool(databaseQueryToolSpecification);
-//
-// OllamaChatRequest requestModel = builder
-// .withMessage(OllamaChatMessageRole.USER,
-// "Give me the ID of the employee named 'Rahul Kumar'?")
-// .build();
-//
-// StringBuffer sb = new StringBuffer();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel, (s) -> {
-// LOG.info(s);
-// String substring = s.substring(sb.toString().length());
-// LOG.info(substring);
-// sb.append(substring);
-// });
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertNotNull(chatResult.getResponseModel().getMessage().getContent());
-// assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithStream() {
-// testEndpointReachability();
-// try {
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getModel());
-// OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER,
-// "What is the capital of France? And what's France's connection with Mona Lisa?")
-// .build();
-//
-// StringBuffer sb = new StringBuffer("");
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel, (s) -> {
-// LOG.info(s);
-// String substring = s.substring(sb.toString().length(), s.length());
-// LOG.info(substring);
-// sb.append(substring);
-// });
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-// assertNotNull(chatResult.getResponseModel().getMessage());
-// assertNotNull(chatResult.getResponseModel().getMessage().getContent());
-// assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithImageFromFileWithHistoryRecognition() {
-// testEndpointReachability();
-// try {
-// OllamaChatRequestBuilder builder =
-// OllamaChatRequestBuilder.getInstance(config.getImageModel());
-// OllamaChatRequest requestModel =
-// builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?",Collections.emptyList(),
-// List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build();
-//
-// OllamaChatResult chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-//
-// builder.reset();
-//
-// requestModel =
-// builder.withMessages(chatResult.getChatHistory())
-// .withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build();
-//
-// chatResult = ollamaAPI.chat(requestModel);
-// assertNotNull(chatResult);
-// assertNotNull(chatResult.getResponseModel());
-//
-//
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testChatWithImageFromURL() {
-// testEndpointReachability();
-// try {
-// OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(config.getImageModel());
-// OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?",Collections.emptyList(),
-// "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) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testAskModelWithOptionsAndImageFiles() {
-// testEndpointReachability();
-// File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg");
-// try {
-// OllamaResult result =
-// ollamaAPI.generateWithImageFiles(
-// config.getImageModel(),
-// "What is in this image?",
-// List.of(imageFile),
-// new OptionsBuilder().build());
-// assertNotNull(result);
-// assertNotNull(result.getResponse());
-// assertFalse(result.getResponse().isEmpty());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testAskModelWithOptionsAndImageFilesStreamed() {
-// testEndpointReachability();
-// File imageFile = getImageFileFromClasspath("dog-on-a-boat.jpg");
-// try {
-// StringBuffer sb = new StringBuffer("");
-//
-// OllamaResult result = ollamaAPI.generateWithImageFiles(config.getImageModel(),
-// "What is in this image?", List.of(imageFile), new OptionsBuilder().build(), (s) -> {
-// LOG.info(s);
-// String substring = s.substring(sb.toString().length(), s.length());
-// LOG.info(substring);
-// sb.append(substring);
-// });
-// assertNotNull(result);
-// assertNotNull(result.getResponse());
-// assertFalse(result.getResponse().isEmpty());
-// assertEquals(sb.toString().trim(), result.getResponse().trim());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// void testAskModelWithOptionsAndImageURLs() {
-// testEndpointReachability();
-// try {
-// OllamaResult result =
-// 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"),
-// new OptionsBuilder().build());
-// assertNotNull(result);
-// assertNotNull(result.getResponse());
-// assertFalse(result.getResponse().isEmpty());
-// } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
-// fail(e);
-// }
-// }
-//
-// @Test
-// @Order(3)
-// public void testEmbedding() {
-// testEndpointReachability();
-// try {
-// OllamaEmbeddingsRequestModel request = OllamaEmbeddingsRequestBuilder
-// .getInstance(config.getModel(), "What is the capital of France?").build();
-//
-// List embeddings = ollamaAPI.generateEmbeddings(request);
-//
-// assertNotNull(embeddings);
-// assertFalse(embeddings.isEmpty());
-// } catch (IOException | OllamaBaseException | InterruptedException e) {
-// fail(e);
-// }
-// }
-//}
-//
-//class DBQueryFunction implements ToolFunction {
-// @Override
-// public Object apply(Map arguments) {
-// // perform DB operations here
-// return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name"), arguments.get("employee-address"), arguments.get("employee-phone"));
-// }
-//}
-//
-//@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);
-// }
-// }
-//
-//
-//}
From 2b0238b9e80b3c3d90e0fb34933a9cf45ed57b46 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 11 Mar 2025 12:26:35 +0530
Subject: [PATCH 07/20] Ensure Docker availability in dev setup and integration
tests
Updated `README.md` to include Docker as a prerequisite for running integration tests using Testcontainers. Modified the `Makefile` to check for Docker installation during the dev environment setup.
---
Makefile | 1 +
README.md | 5 ++++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 953c764..34972dd 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,7 @@
dev:
@echo "Setting up dev environment..."
@command -v pre-commit >/dev/null 2>&1 || { echo "Error: pre-commit is not installed. Please install it first."; exit 1; }
+ @command -v docker >/dev/null 2>&1 || { echo "Error: docker is not installed. Please install it first."; exit 1; }
pre-commit install
pre-commit autoupdate
pre-commit install --install-hooks
diff --git a/README.md b/README.md
index a832a0c..33ac281 100644
--- a/README.md
+++ b/README.md
@@ -227,6 +227,9 @@ make unit-tests
#### Run integration tests
+Make sure you have Docker running as this uses [testcontainers](https://testcontainers.com/) to run the integration
+tests on Ollama Docker container.
+
```shell
make integration-tests
```
@@ -325,7 +328,7 @@ project.
-### Appreciate my work?
+### Appreciate the work?
From ac3f505aa6a9dba8dd1fd256e5dbd17f4eeee229 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 11 Mar 2025 13:12:55 +0530
Subject: [PATCH 08/20] Switch image model to "moondream" in integration test
---
.../ollama4j/integrationtests/OllamaAPIIntegrationTest.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
index e26609e..4c74e97 100644
--- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
+++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
@@ -520,7 +520,7 @@ public class OllamaAPIIntegrationTest {
@Test
@Order(19)
void testChatWithImageFromFileWithHistoryRecognition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
- String imageModel = "llava";
+ String imageModel = "moondream";
api.pullModel(imageModel);
OllamaChatRequestBuilder builder =
OllamaChatRequestBuilder.getInstance(imageModel);
@@ -531,7 +531,6 @@ public class OllamaAPIIntegrationTest {
OllamaChatResult chatResult = api.chat(requestModel);
assertNotNull(chatResult);
assertNotNull(chatResult.getResponseModel());
-
builder.reset();
requestModel =
From 8aa6e3b0668ca214232a5f185a93b900cd9caa1a Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 18 Mar 2025 21:41:20 +0530
Subject: [PATCH 09/20] Updated integration tests
---
.../OllamaAPIIntegrationTest.java | 84 ++++++++++---------
1 file changed, 43 insertions(+), 41 deletions(-)
diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
index 4c74e97..12d60f4 100644
--- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
+++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
@@ -43,10 +43,10 @@ public class OllamaAPIIntegrationTest {
@BeforeAll
public static void setUp() {
- String version = "0.5.13";
+ String ollamaVersion = "0.6.1";
int internalPort = 11434;
int mappedPort = 11435;
- ollama = new OllamaContainer("ollama/ollama:" + version);
+ ollama = new OllamaContainer("ollama/ollama:" + ollamaVersion);
ollama.addExposedPort(internalPort);
List portBindings = new ArrayList<>();
portBindings.add(mappedPort + ":" + internalPort);
@@ -94,7 +94,8 @@ public class OllamaAPIIntegrationTest {
@Test
@Order(3)
public void testPullModelAPI() throws URISyntaxException, IOException, OllamaBaseException, InterruptedException {
- api.pullModel("all-minilm");
+ String embeddingModelMinilm = "all-minilm";
+ api.pullModel(embeddingModelMinilm);
List models = api.listModels();
assertNotNull(models, "Models should not be null");
assertFalse(models.isEmpty(), "Models list should contain elements");
@@ -247,7 +248,45 @@ public class OllamaAPIIntegrationTest {
assertTrue(chatResult.getChatHistory().get(chatResult.getChatHistory().size() - 1).getContent().contains("river"), "Response should be related to river");
}
+ @Test
+ @Order(10)
+ void testChatWithImageFromURL() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
+ String imageModel = "llava";
+ api.pullModel(imageModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(imageModel);
+ OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(),
+ "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg")
+ .build();
+ api.registerAnnotatedTools(new OllamaAPIIntegrationTest());
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ }
+ @Test
+ @Order(10)
+ void testChatWithImageFromFileWithHistoryRecognition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
+ String imageModel = "moondream";
+ api.pullModel(imageModel);
+ OllamaChatRequestBuilder builder =
+ OllamaChatRequestBuilder.getInstance(imageModel);
+ OllamaChatRequest requestModel =
+ builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(),
+ List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build();
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ builder.reset();
+
+ requestModel =
+ builder.withMessages(chatResult.getChatHistory())
+ .withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build();
+
+ chatResult = api.chat(requestModel);
+ assertNotNull(chatResult);
+ assertNotNull(chatResult.getResponseModel());
+ }
@Test
@Order(11)
void testChatWithExplicitToolDefinition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
@@ -300,7 +339,7 @@ public class OllamaAPIIntegrationTest {
assertEquals(1, toolCalls.size());
OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
assertEquals("get-employee-details", function.getName());
- assertEquals(1, function.getArguments().size());
+ assert !function.getArguments().isEmpty();
Object employeeName = function.getArguments().get("employee-name");
assertNotNull(employeeName);
assertEquals("Rahul Kumar", employeeName);
@@ -463,20 +502,6 @@ public class OllamaAPIIntegrationTest {
assertEquals(sb.toString().trim(), chatResult.getResponseModel().getMessage().getContent().trim());
}
- @Test
- @Order(16)
- void testChatWithImageFromURL() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
- String imageModel = "llava";
- api.pullModel(imageModel);
-
- OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(imageModel);
- OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(),
- "https://t3.ftcdn.net/jpg/02/96/63/80/360_F_296638053_0gUVA4WVBKceGsIr7LNqRWSnkusi07dq.jpg")
- .build();
-
- OllamaChatResult chatResult = api.chat(requestModel);
- assertNotNull(chatResult);
- }
@Test
@Order(17)
@@ -517,30 +542,7 @@ public class OllamaAPIIntegrationTest {
}
}
- @Test
- @Order(19)
- void testChatWithImageFromFileWithHistoryRecognition() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
- String imageModel = "moondream";
- api.pullModel(imageModel);
- OllamaChatRequestBuilder builder =
- OllamaChatRequestBuilder.getInstance(imageModel);
- OllamaChatRequest requestModel =
- builder.withMessage(OllamaChatMessageRole.USER, "What's in the picture?", Collections.emptyList(),
- List.of(getImageFileFromClasspath("dog-on-a-boat.jpg"))).build();
- OllamaChatResult chatResult = api.chat(requestModel);
- assertNotNull(chatResult);
- assertNotNull(chatResult.getResponseModel());
- builder.reset();
-
- requestModel =
- builder.withMessages(chatResult.getChatHistory())
- .withMessage(OllamaChatMessageRole.USER, "What's the dogs breed?").build();
-
- chatResult = api.chat(requestModel);
- assertNotNull(chatResult);
- assertNotNull(chatResult.getResponseModel());
- }
@Test
@Order(20)
From bee09aa626b3dfc8863ef684c71d47006dc7f486 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 18 Mar 2025 22:03:04 +0530
Subject: [PATCH 10/20] Updated integration tests
---
.../ollama4j/integrationtests/OllamaAPIIntegrationTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
index 12d60f4..9aa1579 100644
--- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
+++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
@@ -53,7 +53,7 @@ public class OllamaAPIIntegrationTest {
ollama.setPortBindings(portBindings);
ollama.start();
api = new OllamaAPI("http://" + ollama.getHost() + ":" + ollama.getMappedPort(internalPort));
- api.setRequestTimeoutSeconds(60);
+ api.setRequestTimeoutSeconds(120);
api.setVerbose(true);
}
From bbafc95577aa13123903f87879406cea00c9e89e Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 18 Mar 2025 22:13:39 +0530
Subject: [PATCH 11/20] Updated GH workflow
---
.github/workflows/build-and-test-on-pr-open.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build-and-test-on-pr-open.yml b/.github/workflows/build-and-test-on-pr-open.yml
index dbf54c7..e5e892d 100644
--- a/.github/workflows/build-and-test-on-pr-open.yml
+++ b/.github/workflows/build-and-test-on-pr-open.yml
@@ -5,9 +5,12 @@ name: Build and Test on PR Open
on:
pull_request:
- types: [ opened, reopened ]
+# types: [ opened, reopened ]
branches: [ "main" ]
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
jobs:
build:
From 26bb2f9babd90b9941be56670965c0448ef1540f Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 18 Mar 2025 22:15:58 +0530
Subject: [PATCH 12/20] Updated GH workflow
---
.github/workflows/build-and-test-on-pr-open.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build-and-test-on-pr-open.yml b/.github/workflows/build-and-test-on-pr-open.yml
index e5e892d..d5ffbaf 100644
--- a/.github/workflows/build-and-test-on-pr-open.yml
+++ b/.github/workflows/build-and-test-on-pr-open.yml
@@ -1,7 +1,7 @@
# This workflow will build a package using Maven and then publish it to GitHub packages when a release is created
# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path
-name: Build and Test on PR Open
+name: Run Tests
on:
pull_request:
@@ -13,7 +13,7 @@ concurrency:
cancel-in-progress: true
jobs:
- build:
+ run-tests:
runs-on: ubuntu-latest
permissions:
From c33c1c8315a624ff623612ce620d7e8bf85c333e Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 18 Mar 2025 22:23:06 +0530
Subject: [PATCH 13/20] test
---
pom.xml | 5 -----
1 file changed, 5 deletions(-)
diff --git a/pom.xml b/pom.xml
index fa21640..6121533 100644
--- a/pom.xml
+++ b/pom.xml
@@ -407,8 +407,3 @@
-
-
-
-
-
From 590364dd53005baa6c04314ccb1763545d4c2384 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 18 Mar 2025 22:33:29 +0530
Subject: [PATCH 14/20] test
---
.github/workflows/build-and-test-on-pr-open.yml | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/build-and-test-on-pr-open.yml b/.github/workflows/build-and-test-on-pr-open.yml
index d5ffbaf..bd9ce71 100644
--- a/.github/workflows/build-and-test-on-pr-open.yml
+++ b/.github/workflows/build-and-test-on-pr-open.yml
@@ -1,11 +1,8 @@
-# This workflow will build a package using Maven and then publish it to GitHub packages when a release is created
-# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path
-
name: Run Tests
on:
pull_request:
-# types: [ opened, reopened ]
+# types: [opened, reopened, synchronize, edited]
branches: [ "main" ]
concurrency:
@@ -36,4 +33,4 @@ jobs:
run: mvn --file pom.xml -U clean test -Punit-tests
- name: Run integration tests
- run: mvn --file pom.xml -U clean verify -Pintegration-tests
\ No newline at end of file
+ run: mvn --file pom.xml -U clean verify -Pintegration-tests
From e245d9633fda5ef628a8bdab6225788c0e768728 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 18 Mar 2025 22:54:56 +0530
Subject: [PATCH 15/20] Updated integration tests
---
.../OllamaAPIIntegrationTest.java | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
index 9aa1579..086f151 100644
--- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
+++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
@@ -208,7 +208,7 @@ public class OllamaAPIIntegrationTest {
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
// Create the initial user question
- OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What is the capital of France?")
+ OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What is 1+1?")
.build();
// Start conversation with model
@@ -216,13 +216,13 @@ public class OllamaAPIIntegrationTest {
assertTrue(
chatResult.getChatHistory().stream()
- .anyMatch(chat -> chat.getContent().contains("Paris")),
- "Expected chat history to contain 'Paris'"
+ .anyMatch(chat -> chat.getContent().contains("2")),
+ "Expected chat history to contain '2'"
);
// Create the next user question: second largest city
requestModel = builder.withMessages(chatResult.getChatHistory())
- .withMessage(OllamaChatMessageRole.USER, "And what is its official language?")
+ .withMessage(OllamaChatMessageRole.USER, "And what is its squared value?")
.build();
// Continue conversation with model
@@ -230,13 +230,13 @@ public class OllamaAPIIntegrationTest {
assertTrue(
chatResult.getChatHistory().stream()
- .anyMatch(chat -> chat.getContent().contains("French")),
- "Expected chat history to contain 'French'"
+ .anyMatch(chat -> chat.getContent().contains("4")),
+ "Expected chat history to contain '4'"
);
// Create the next user question: the third question
requestModel = builder.withMessages(chatResult.getChatHistory())
- .withMessage(OllamaChatMessageRole.USER, "What is the largest river in France?")
+ .withMessage(OllamaChatMessageRole.USER, "What is the largest value between 2, 4 and 6?")
.build();
// Continue conversation with the model for the third question
@@ -245,7 +245,7 @@ public class OllamaAPIIntegrationTest {
// verify the result
assertNotNull(chatResult, "Chat result should not be null");
assertTrue(chatResult.getChatHistory().size() > 2, "Chat history should contain more than two messages");
- assertTrue(chatResult.getChatHistory().get(chatResult.getChatHistory().size() - 1).getContent().contains("river"), "Response should be related to river");
+ assertTrue(chatResult.getChatHistory().get(chatResult.getChatHistory().size() - 1).getContent().contains("6"), "Response should contain '6'");
}
@Test
From 681a692ca98c66d2a0b1f9a58fc25c82f799db61 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Tue, 18 Mar 2025 23:18:42 +0530
Subject: [PATCH 16/20] Updated integration tests
---
.../ollama4j/integrationtests/OllamaAPIIntegrationTest.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
index 086f151..cbdecbe 100644
--- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
+++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
@@ -203,12 +203,12 @@ public class OllamaAPIIntegrationTest {
@Test
@Order(10)
public void testChat() throws Exception {
- String chatModel = "qwen2.5:0.5b";
+ String chatModel = "llama3";
api.pullModel(chatModel);
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(chatModel);
// Create the initial user question
- OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What is 1+1?")
+ OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, "What is 1+1? Answer only in numbers.")
.build();
// Start conversation with model
From e74ef7115c7e9f369bb7d116789a997169ff4cf7 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Wed, 19 Mar 2025 08:08:39 +0530
Subject: [PATCH 17/20] Add blog post about tooling with couchbase
---
docs/blog/2025-03-08-blog/index.md | 713 +++++++++++++++++++++++++++++
1 file changed, 713 insertions(+)
create mode 100644 docs/blog/2025-03-08-blog/index.md
diff --git a/docs/blog/2025-03-08-blog/index.md b/docs/blog/2025-03-08-blog/index.md
new file mode 100644
index 0000000..c52e71a
--- /dev/null
+++ b/docs/blog/2025-03-08-blog/index.md
@@ -0,0 +1,713 @@
+---
+slug: talk-to-your-data-on-couchbase-via-ollama4j
+title: "Talk to Your Data Using Natural Language "
+authors: [ amith ]
+tags: [ Java, AI, LLM, GenAI, GenerativeAI, Generative AI Tools, Ollama, Ollama4J, OpenSource, Developers,
+]
+---
+
+Talk to Your Data Using Natural Language: A Guide to Interacting with Couchbase via Ollama4j
+
+Sometime back, I created a small wrapper called Ollama4j to interact with the Ollama server over the REST API in Java as
+a side project and made the [repository](https://github.com/ollama4j/ollama4j) public on GitHub. Over time, the project
+gained traction, with many fellow Java
+developers contributing, and it now boasts over _300 stars_! đ
+
+Weâve consistently introduced new features, and when we added the tool-calling capability, the library became incredibly
+powerful, opening up so many possibilities. With this addition, we could automate numerous tasks using natural language!
+I wanted to share how to make the most of this functionality.
+
+In this article, weâll explore how to use Ollama4j, a Java SDK for interacting with Ollama-hosted models, to leverage
+tool-calling models like Mistral for querying a Couchbase database. The goal is to create a system where you can query
+your database using natural, conversational language â just like interacting with a virtual assistant. Weâll walk you
+through the code, explain the key components, and show you how to set up your environment to ensure everything runs
+smoothly.
+
+### Overview of the Technologies Involved
+
+Before diving into the implementation, letâs understand the core technologies weâre using:
+
+- **Ollama4j**: A Java SDK that interacts with hosted AI models through a convenient API. Ollama allows you to interact
+ with
+ pre-trained models (like Mistral) and access additional tools that can be applied to real-world tasks.
+- **Mistral**: A powerful, language-based model that can be used for a variety of tasks, including answering questions,
+ text
+ generation, and data retrieval from external sources. While Iâve used Mistral in this instance, you can easily replace
+ it with [any other model](https://ollama.com/search?c=tools) that supports tool-calling capabilities.
+- **Couchbase**: A NoSQL database that provides a flexible and scalable data model. In this example, weâll query a
+ Couchbase
+ database to retrieve airline information.
+
+The magic happens when we combine these technologies to allow the model to query the database in a more intuitive and
+human-like way, acting as an interface between the userâs natural language and Couchbaseâs structured data.
+
+
+> Oh, by the way, you can either set up
+> a [Couchbase server](https://www.couchbase.com/downloads/?family=couchbase-server) on your own or, if you prefer a
+> more
+> effortless approach like
+> I
+> do, give [Couchbase Capella](https://www.couchbase.com/products/capella/) a spin. Itâs a fully managed
+> Database-as-a-Service (DBaaS) with a free tier đ thatâs so
+> easy
+> to set up, youâll be querying your data in no time. Itâs perfect for developers who want to dive in without any
+> hassle â
+> itâs like having your own cloud database, minus the headache!
+
+
+In the following section, we will walk you through the simple steps to create your free Couchbase Capella database
+cluster. If youâd prefer to set up your own Couchbase server elsewhere, feel free to skip this section and go directly
+to the [Code Environment Setup](#setting-up-the-environment-for-code) section.
+
+Sign up for a free database cluster on Couchbase Capella
+Head over to https://cloud.couchbase.com/sign-in and sign up for an account.
+
+
+
+Once youâre in, you will be able to create a new database cluster. Click on the _**Operational**_ tab and click on the *
+*_Create
+Cluster_** button.
+
+
+
+Select the default project named **_My First Project_** and click on the **_Continue_** button.
+
+
+
+Youâll now see the available cluster options. Go ahead and select the **_Free_** option! đ
+Next, choose your preferred cloud provider (you can select any provider or stick with the default AWS provider).
+Pick a region (or leave it set to the default).
+Finally, click on the Create Cluster button to proceed.
+
+
+
+Give it a couple of minutes, and let the magic happen as your cluster gets deployed.
+
+
+
+Once your cluster is deployed, youâll see the status of your cluster as **_Healthy_**.
+
+
+
+Click on the listed cluster to open its details. Here, you can view the version of the deployed Couchbase server, the
+enabled services, as well as the cloud provider and region.
+
+
+
+Click on **_Explore Data_** button. Notice that a default bucket called **_travel-sample_** with some sample data has
+been created
+for you.
+
+
+
+Browse through the collection to explore the pre-created buckets, scopes and collections available to you.
+
+
+
+Open up a sample document from the **_travel-sample_** (bucket) > **_inventory_** (scope) > **_airline_** (collection)
+to see the contents
+of the document.
+
+The document shown in the image below is about an airline named **_Astraeus_**, whose call sign (a unique name or code
+used to
+identify an airline or aircraft in communication) is **_FLYSTAR_**.
+
+
+
+Navigate to the **_Connect_** tab, and you will see a **_Public Connection String_** that allows you to access the
+Capella cluster
+endpoint from your client application, which looks like the following URL:
+
+```
+couchbases://cb.uniqueclusteridentifer.cloud.couchbase.com
+```
+
+
+
+To access this cluster endpoint, you need to allow the IP addresses that are permitted to connect. Click on the *
+*_Settings_**
+tab, which will take you to the **_Cluster Settings_** view. Then, click on **_Allowed IP Addresses_** in the left pane
+under
+**_Networking_**, where you can add allowed IP addresses. Then, click on the **_Add Allowed IP_** button.
+
+
+
+You can either click on the **_Add Current IP Address_** button to limit access to your cluster to your IP address
+alone, or
+if youâd like to allow access from anywhere, click on the **_Allow Access from Anywhere_** button.
+
+
+
+Confirm that you want to allow the IP addresses.
+
+
+
+The IP addresses have now been added to the allow list, and the networking is set up.
+
+
+
+Now that youâve allowed IP addresses, itâs time to create credentials for accessing the cluster using a username and
+password. Click on the **_Cluster Access_** tab in the left pane, then click on the **_Create Cluster Access_** button.
+
+
+
+Enter a username of your choice in the **_Cluster Access Name_** text field, and then enter a password of your choice in
+the
+**_Password_** text field.
+
+Next, select the bucket, scope, and the read/write permissions you want these credentials to have access to. In this
+example, Iâve granted access to all buckets and scopes with both read and write permissions.
+
+
+
+Alright, your cluster access is now set up.
+
+
+
+One last step: you just need to select the **_Cluster Access Credentials_** that you want to allow to connect to your
+Capella
+cluster. Head over to the **_Connect_** tab, then click on the **_SDKs_** tab in the left pane. Under Choose the *
+*_Cluster Access
+Credentials you want to use to connect to your Capella cluster_**, select the cluster credentials you just created.
+
+
+
+Awesome! Your cluster access is all set up, and youâre ready to connect to your Capella cluster using a Couchbase
+client. Thatâs it â youâre all set and good to go!
+
+### Setting Up the Environment For Code
+
+Before you begin, ensure you have the following components setup.
+
+**Java**: Make sure you have Java 11+ installed on your system. Set it up
+from [here](https://www.oracle.com/in/java/technologies/downloads/). Verify it by running the following
+command in your terminal.
+
+```shell
+java --version
+```
+
+Maven: Make sure you have the Maven build system set up. Set it up from [here](https://maven.apache.org/download.cgi).
+Verify it by running the following command
+in your terminal.
+
+```
+mvn --version
+```
+
+**Ollama Server**: Make sure you have installed the latest version of [Ollama server](https://ollama.com/) and it is up
+and running. Verify it by
+running the following command in your terminal.
+
+```shell
+ollama --version
+```
+
+Model: Youâll need [tool-calling model](https://ollama.com/search?c=tools) (such as Mistral) downloaded and ready to
+serve from your Ollama server.
+
+To download/pull the model into your Ollama server, run the following command in your terminal.
+
+```shell
+ollama pull mistral
+
+```
+
+You can list the models available on your model server by running the following command in your terminal.
+
+```shell
+ollama list
+```
+
+Once you have these, you can start setting up the application.
+
+Setup `pom.xml` for your Maven project.
+
+```xml
+
+
+ 4.0.0
+
+ io.github.ollama4j.couchbase
+ ollama4j-couchbase
+ 0.0.1
+ Ollama4j Couchbase
+ Talk to your data in Couchbase over Ollama4j
+ jar
+
+
+ 11
+ UTF-8
+ 1.18.30
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.3.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.11.2
+
+
+ all,-missing
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+
+
+
+ maven-compiler-plugin
+ 3.14.0
+
+
+
+
+
+
+
+ io.github.ollama4j
+ ollama4j
+ ollama4j-revision
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.0
+
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.4.12
+
+
+
+ com.couchbase.client
+ java-client
+ 3.7.8
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.30
+ provided
+
+
+
+```
+
+### Code Walkthrough
+
+Hereâs the main part of the implementation in the Java code.
+
+```java
+package io.github.ollama4j.examples;
+
+
+import com.couchbase.client.java.Bucket;
+import com.couchbase.client.java.Cluster;
+import com.couchbase.client.java.ClusterOptions;
+import com.couchbase.client.java.Scope;
+import com.couchbase.client.java.json.JsonObject;
+import com.couchbase.client.java.query.QueryResult;
+import io.github.ollama4j.OllamaAPI;
+import io.github.ollama4j.exceptions.OllamaBaseException;
+import io.github.ollama4j.exceptions.ToolInvocationException;
+import io.github.ollama4j.tools.OllamaToolsResult;
+import io.github.ollama4j.tools.ToolFunction;
+import io.github.ollama4j.tools.Tools;
+import io.github.ollama4j.utils.OptionsBuilder;
+import io.github.ollama4j.utils.Utilities;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.Map;
+
+
+public class CouchbaseToolCallingExample {
+
+ public static void main(String[] args) throws IOException, ToolInvocationException, OllamaBaseException, InterruptedException {
+ String connectionString = Utilities.getFromEnvVar("CB_CLUSTER_URL");
+ String username = Utilities.getFromEnvVar("CB_CLUSTER_USERNAME");
+ String password = Utilities.getFromEnvVar("CB_CLUSTER_PASSWORD");
+ String bucketName = "travel-sample";
+
+ Cluster cluster = Cluster.connect(
+ connectionString,
+ ClusterOptions.clusterOptions(username, password).environment(env -> {
+ env.applyProfile("wan-development");
+ })
+ );
+
+ String host = Utilities.getFromConfig("host");
+ String modelName = Utilities.getFromConfig("tools_model_mistral");
+
+ OllamaAPI ollamaAPI = new OllamaAPI(host);
+ ollamaAPI.setVerbose(false);
+ ollamaAPI.setRequestTimeoutSeconds(60);
+
+ Tools.ToolSpecification callSignFinderToolSpec = getCallSignFinderToolSpec(cluster, bucketName);
+ Tools.ToolSpecification callSignUpdaterToolSpec = getCallSignUpdaterToolSpec(cluster, bucketName);
+
+ ollamaAPI.registerTool(callSignFinderToolSpec);
+ ollamaAPI.registerTool(callSignUpdaterToolSpec);
+
+ String prompt1 = "What is the call-sign of Astraeus?";
+ for (OllamaToolsResult.ToolResult r : ollamaAPI.generateWithTools(modelName, new Tools.PromptBuilder()
+ .withToolSpecification(callSignFinderToolSpec)
+ .withPrompt(prompt1)
+ .build(), new OptionsBuilder().build()).getToolResults()) {
+ AirlineDetail airlineDetail = (AirlineDetail) r.getResult();
+ System.out.println(String.format("[Result of tool '%s']: Call-sign of %s is '%s'! âď¸", r.getFunctionName(), airlineDetail.getName(), airlineDetail.getCallsign()));
+ }
+
+ String prompt2 = "I want to code name Astraeus as STARBOUND";
+ for (OllamaToolsResult.ToolResult r : ollamaAPI.generateWithTools(modelName, new Tools.PromptBuilder()
+ .withToolSpecification(callSignUpdaterToolSpec)
+ .withPrompt(prompt2)
+ .build(), new OptionsBuilder().build()).getToolResults()) {
+ Boolean updated = (Boolean) r.getResult();
+ System.out.println(String.format("[Result of tool '%s']: Call-sign is %s! âď¸", r.getFunctionName(), updated ? "updated" : "not updated"));
+ }
+
+ String prompt3 = "What is the call-sign of Astraeus?";
+ for (OllamaToolsResult.ToolResult r : ollamaAPI.generateWithTools(modelName, new Tools.PromptBuilder()
+ .withToolSpecification(callSignFinderToolSpec)
+ .withPrompt(prompt3)
+ .build(), new OptionsBuilder().build()).getToolResults()) {
+ AirlineDetail airlineDetail = (AirlineDetail) r.getResult();
+ System.out.println(String.format("[Result of tool '%s']: Call-sign of %s is '%s'! âď¸", r.getFunctionName(), airlineDetail.getName(), airlineDetail.getCallsign()));
+ }
+ }
+
+ public static Tools.ToolSpecification getCallSignFinderToolSpec(Cluster cluster, String bucketName) {
+ return Tools.ToolSpecification.builder()
+ .functionName("airline-lookup")
+ .functionDescription("You are a tool who finds only the airline name and do not worry about any other parameters. You simply find the airline name and ignore the rest of the parameters. Do not validate airline names as I want to use fake/fictitious airline names as well.")
+ .toolFunction(new AirlineCallsignQueryToolFunction(bucketName, cluster))
+ .toolPrompt(
+ Tools.PromptFuncDefinition.builder()
+ .type("prompt")
+ .function(
+ Tools.PromptFuncDefinition.PromptFuncSpec.builder()
+ .name("get-airline-name")
+ .description("Get the airline name")
+ .parameters(
+ Tools.PromptFuncDefinition.Parameters.builder()
+ .type("object")
+ .properties(
+ Map.of(
+ "airlineName", Tools.PromptFuncDefinition.Property.builder()
+ .type("string")
+ .description("The name of the airline. e.g. Emirates")
+ .required(true)
+ .build()
+ )
+ )
+ .required(java.util.List.of("airline-name"))
+ .build()
+ )
+ .build()
+ )
+ .build()
+ )
+ .build();
+ }
+
+ public static Tools.ToolSpecification getCallSignUpdaterToolSpec(Cluster cluster, String bucketName) {
+ return Tools.ToolSpecification.builder()
+ .functionName("airline-update")
+ .functionDescription("You are a tool who finds the airline name and its callsign and do not worry about any validations. You simply find the airline name and its callsign. Do not validate airline names as I want to use fake/fictitious airline names as well.")
+ .toolFunction(new AirlineCallsignUpdateToolFunction(bucketName, cluster))
+ .toolPrompt(
+ Tools.PromptFuncDefinition.builder()
+ .type("prompt")
+ .function(
+ Tools.PromptFuncDefinition.PromptFuncSpec.builder()
+ .name("get-airline-name-and-callsign")
+ .description("Get the airline name and callsign")
+ .parameters(
+ Tools.PromptFuncDefinition.Parameters.builder()
+ .type("object")
+ .properties(
+ Map.of(
+ "airlineName", Tools.PromptFuncDefinition.Property.builder()
+ .type("string")
+ .description("The name of the airline. e.g. Emirates")
+ .required(true)
+ .build(),
+ "airlineCallsign", Tools.PromptFuncDefinition.Property.builder()
+ .type("string")
+ .description("The callsign of the airline. e.g. Maverick")
+ .enumValues(Arrays.asList("petrol", "diesel"))
+ .required(true)
+ .build()
+ )
+ )
+ .required(java.util.List.of("airlineName", "airlineCallsign"))
+ .build()
+ )
+ .build()
+ )
+ .build()
+ )
+ .build();
+ }
+}
+
+class AirlineCallsignQueryToolFunction implements ToolFunction {
+ private final String bucketName;
+ private final Cluster cluster;
+
+ public AirlineCallsignQueryToolFunction(String bucketName, Cluster cluster) {
+ this.bucketName = bucketName;
+ this.cluster = cluster;
+ }
+
+ @Override
+ public AirlineDetail apply(Map arguments) {
+ String airlineName = arguments.get("airlineName").toString();
+
+ Bucket bucket = cluster.bucket(bucketName);
+ bucket.waitUntilReady(Duration.ofSeconds(10));
+
+ Scope inventoryScope = bucket.scope("inventory");
+ QueryResult result = inventoryScope.query(String.format("SELECT * FROM airline WHERE name = '%s';", airlineName));
+
+ JsonObject row = (JsonObject) result.rowsAsObject().get(0).get("airline");
+ return new AirlineDetail(row.getString("callsign"), row.getString("name"), row.getString("country"));
+ }
+}
+
+class AirlineCallsignUpdateToolFunction implements ToolFunction {
+ private final String bucketName;
+ private final Cluster cluster;
+
+ public AirlineCallsignUpdateToolFunction(String bucketName, Cluster cluster) {
+ this.bucketName = bucketName;
+ this.cluster = cluster;
+ }
+
+
+ @Override
+ public Boolean apply(Map arguments) {
+ String airlineName = arguments.get("airlineName").toString();
+ String airlineNewCallsign = arguments.get("airlineCallsign").toString();
+
+ Bucket bucket = cluster.bucket(bucketName);
+ bucket.waitUntilReady(Duration.ofSeconds(10));
+
+ Scope inventoryScope = bucket.scope("inventory");
+ String query = String.format("SELECT * FROM airline WHERE name = '%s';", airlineName);
+
+ QueryResult result;
+ try {
+ result = inventoryScope.query(query);
+ } catch (Exception e) {
+ throw new RuntimeException("Error executing query", e);
+ }
+
+ if (result.rowsAsObject().isEmpty()) {
+ throw new RuntimeException("Airline not found with name: " + airlineName);
+ }
+
+ JsonObject row = (JsonObject) result.rowsAsObject().get(0).get("airline");
+
+ if (row == null) {
+ throw new RuntimeException("Airline data is missing or corrupted.");
+ }
+
+ String currentCallsign = row.getString("callsign");
+
+ if (!airlineNewCallsign.equals(currentCallsign)) {
+ JsonObject updateQuery = JsonObject.create()
+ .put("callsign", airlineNewCallsign);
+
+ inventoryScope.query(String.format(
+ "UPDATE airline SET callsign = '%s' WHERE name = '%s';",
+ airlineNewCallsign, airlineName
+ ));
+ return true;
+ }
+ return false;
+ }
+}
+
+@SuppressWarnings("ALL")
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+class AirlineDetail {
+ private String callsign;
+ private String name;
+ private String country;
+}
+```
+
+### Key Concepts
+
+#### 1. Ollama API Client Setup
+
+```javascript
+OllamaAPI ollamaAPI = new OllamaAPI(host);
+
+ollamaAPI.setRequestTimeoutSeconds(60);
+```
+
+Here, we initialize the Ollama API client and configure it with the host of the Ollama server, where the model is hosted
+and can handle API requests. Additionally, we set the request timeout to 60 seconds to ensure that even if the model
+takes longer to respond, the request will still be processed.
+
+#### 2. Tool Specification
+
+The ToolSpecification class defines how the model will interact with the Couchbase database. We define a function that
+queries the database for airline details based on the airline name.
+
+```javascript
+Tools.ToolSpecification callSignFinderToolSpec = getCallSignFinderToolSpec(cluster, bucketName);
+
+ollamaAPI.registerTool(callSignFinderToolSpec);
+```
+
+This step registers custom tools with Ollama that allows the tool-calling model to invoke database queries.
+
+#### 3. Query Execution
+
+The tool will execute a Couchbase N1QL query to retrieve the airline details:
+
+```javascript
+QueryResult result = inventoryScope.query(String.format("SELECT * FROM airline WHERE name = '%s';", airlineName));
+```
+
+The result is processed and returned as an AirlineDetail object.
+
+#### 4. Set up your prompt (question)
+
+```javascript
+String prompt = "What is the call-sign of Astraeus?";
+```
+
+#### 5. Generating Results with Tools
+
+```javascript
+for (OllamaToolsResult.ToolResult r : ollamaAPI.generateWithTools(modelName, new Tools.PromptBuilder()
+ .withToolSpecification(callSignFinderToolSpec)
+ .withPrompt(prompt)
+ .build(), new OptionsBuilder().build()).getToolResults()) {
+ AirlineDetail airlineDetail = (AirlineDetail) r.getResult();
+ System.out.printf("[Result of tool '%s']: Call-sign of %s is '%s'! âď¸", r.getFunctionName(), airlineDetail.getName(), airlineDetail.getCallsign());
+}
+```
+
+This invokes the tool-calling model (Mistral in this case) with the provided prompt and uses the registered tool to
+query the database. The result is returned and printed to the console.
+
+So, we ask the following question to the model.
+
+> **What is the call-sign of Astraeus?**
+
+And, hereâs what the model responds:
+
+> **Call-sign of Astraeus is âFLYSTARâ! âď¸**
+
+Isnât that amazing? Now, letâs enhance it further by adding a function that allows us to update an airlineâs call sign
+using natural language.
+
+Letâs define another `ToolSpecificationclass` that defines how the model will interact with the Couchbase database to
+update the database. We define a function that queries the database for airline details based on the airline name and
+then update the airlineâs callsign.
+
+```javascript
+Tools.ToolSpecification callSignUpdaterToolSpec = getCallSignUpdaterToolSpec(cluster, bucketName);
+
+ollamaAPI.registerTool(callSignUpdaterToolSpec);
+```
+
+The tool will execute a Couchbase N1QL query to update the airlineâs callsign.
+
+```javascript
+inventoryScope.query(String.format(
+ "UPDATE airline SET callsign = '%s' WHERE name = '%s';",
+ airlineNewCallsign, airlineName
+));
+```
+
+Setup the prompt to instruct the model to update the airlineâs callsign.
+
+```javascript
+String prompt = "I want to code name Astraeus as STARBOUND";
+
+```
+
+And then we invoke the model with the new prompt.
+
+```javascript
+String prompt = "I want to code name Astraeus as STARBOUND";
+for (OllamaToolsResult.ToolResult r : ollamaAPI.generateWithTools(modelName, new Tools.PromptBuilder()
+ .withToolSpecification(callSignUpdaterToolSpec)
+ .withPrompt(prompt)
+ .build(), new OptionsBuilder().build()).getToolResults()) {
+ Boolean updated = (Boolean) r.getResult();
+ System.out.println(String.format("[Result of tool '%s']: Call-sign is %s! âď¸", r.getFunctionName(), updated ? "updated" : "not updated"));
+}
+```
+
+This invokes the tool-calling model (Mistral in this case) with the new prompt and uses the registered tool to update
+the database.
+
+So, we ask the following question to the model.
+
+> **I want to code name Astraeus as STARBOUND.**
+
+And, hereâs what the model responds:
+
+> **Call-sign is updated! âď¸**
+
+How amazing is that? The possibilities for interacting with your data using natural language are endless. You could
+integrate features like checking flight availability, booking tickets, retrieving ticket details, and so much more!
+
+Feel free to extend this example further by adding more sophisticated capabilities! đ
+
+### Conclusion
+
+With the code above, you can use Ollamaâs hosted models (like Mistral) to query a Couchbase database using natural
+language prompts. This makes it possible to interact with databases in a more intuitive and human-like way.
+
+By leveraging Ollama4j, you can connect AI models to real-world applications and build powerful tools that can automate
+complex tasks or simply make querying your data more conversational.
+
+You can find the full code and more such examples from
+the [ollama4j-examples](https://github.com/ollama4j/ollama4j-examples) GitHub repository.
+
+Credit to Couchbase, Ollama, and all the model teams for providing us with such amazing software!
From 6f1da25f7e14635308dc8ed95bcf13caf6e0f183 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Wed, 19 Mar 2025 08:20:09 +0530
Subject: [PATCH 18/20] Updated GH action
---
.github/workflows/build-and-test-on-pr-open.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/build-and-test-on-pr-open.yml b/.github/workflows/build-and-test-on-pr-open.yml
index bd9ce71..4f564bf 100644
--- a/.github/workflows/build-and-test-on-pr-open.yml
+++ b/.github/workflows/build-and-test-on-pr-open.yml
@@ -2,8 +2,11 @@ name: Run Tests
on:
pull_request:
-# types: [opened, reopened, synchronize, edited]
+ # types: [opened, reopened, synchronize, edited]
branches: [ "main" ]
+ paths:
+ - 'src/**' # Run if changes occur in the 'src' folder
+ - 'pom.xml' # Run if changes occur in the 'pom.xml' file
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
From f6a29842b58e76b2d9f7032be2ce135ed8f18863 Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Wed, 19 Mar 2025 08:31:10 +0530
Subject: [PATCH 19/20] Updated docs
---
docs/blog/2025-03-08-blog/index.md | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/docs/blog/2025-03-08-blog/index.md b/docs/blog/2025-03-08-blog/index.md
index c52e71a..bb0524c 100644
--- a/docs/blog/2025-03-08-blog/index.md
+++ b/docs/blog/2025-03-08-blog/index.md
@@ -64,9 +64,8 @@ Head over to https://cloud.couchbase.com/sign-in and sign up for an account.
-Once youâre in, you will be able to create a new database cluster. Click on the _**Operational**_ tab and click on the *
-*_Create
-Cluster_** button.
+Once youâre in, you will be able to create a new database cluster. Click on the _**Operational**_ tab and click on the
+**_Create Cluster_** button.
@@ -124,8 +123,8 @@ couchbases://cb.uniqueclusteridentifer.cloud.couchbase.com
-To access this cluster endpoint, you need to allow the IP addresses that are permitted to connect. Click on the *
-*_Settings_**
+To access this cluster endpoint, you need to allow the IP addresses that are permitted to connect. Click on the
+**_Settings_**
tab, which will take you to the **_Cluster Settings_** view. Then, click on **_Allowed IP Addresses_** in the left pane
under
**_Networking_**, where you can add allowed IP addresses. Then, click on the **_Add Allowed IP_** button.
@@ -166,9 +165,9 @@ Alright, your cluster access is now set up.
One last step: you just need to select the **_Cluster Access Credentials_** that you want to allow to connect to your
Capella
-cluster. Head over to the **_Connect_** tab, then click on the **_SDKs_** tab in the left pane. Under Choose the *
-*_Cluster Access
-Credentials you want to use to connect to your Capella cluster_**, select the cluster credentials you just created.
+cluster. Head over to the **_Connect_** tab, then click on the **_SDKs_** tab in the left pane. Under Choose the
+**_Cluster Access Credentials you want to use to connect to your Capella cluster_**, select the cluster credentials you
+just created.
@@ -187,7 +186,7 @@ command in your terminal.
java --version
```
-Maven: Make sure you have the Maven build system set up. Set it up from [here](https://maven.apache.org/download.cgi).
+**Maven**: Make sure you have the Maven build system set up. Set it up from [here](https://maven.apache.org/download.cgi).
Verify it by running the following command
in your terminal.
@@ -203,7 +202,7 @@ running the following command in your terminal.
ollama --version
```
-Model: Youâll need [tool-calling model](https://ollama.com/search?c=tools) (such as Mistral) downloaded and ready to
+**Model**: Youâll need [tool-calling model](https://ollama.com/search?c=tools) (such as Mistral) downloaded and ready to
serve from your Ollama server.
To download/pull the model into your Ollama server, run the following command in your terminal.
@@ -667,7 +666,6 @@ Setup the prompt to instruct the model to update the airlineâs callsign.
```javascript
String prompt = "I want to code name Astraeus as STARBOUND";
-
```
And then we invoke the model with the new prompt.
From 11bf20c405b0ed150737e444aa96918d63f5288d Mon Sep 17 00:00:00 2001
From: Amith Koujalgi
Date: Wed, 19 Mar 2025 08:49:58 +0530
Subject: [PATCH 20/20] Updated docs
---
docs/blog/2025-03-08-blog/index.md | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/docs/blog/2025-03-08-blog/index.md b/docs/blog/2025-03-08-blog/index.md
index bb0524c..28c05ef 100644
--- a/docs/blog/2025-03-08-blog/index.md
+++ b/docs/blog/2025-03-08-blog/index.md
@@ -1,13 +1,11 @@
---
slug: talk-to-your-data-on-couchbase-via-ollama4j
-title: "Talk to Your Data Using Natural Language "
+title: "Talk to Your Data Using Natural Language: A Guide to Interacting with Couchbase via Ollama4j"
authors: [ amith ]
tags: [ Java, AI, LLM, GenAI, GenerativeAI, Generative AI Tools, Ollama, Ollama4J, OpenSource, Developers,
]
---
-Talk to Your Data Using Natural Language: A Guide to Interacting with Couchbase via Ollama4j
-
Sometime back, I created a small wrapper called Ollama4j to interact with the Ollama server over the REST API in Java as
a side project and made the [repository](https://github.com/ollama4j/ollama4j) public on GitHub. Over time, the project
gained traction, with many fellow Java