diff --git a/.github/workflows/build-and-test-on-pr-open.yml b/.github/workflows/build-and-test-on-pr-open.yml index dbf54c7..d5ffbaf 100644 --- a/.github/workflows/build-and-test-on-pr-open.yml +++ b/.github/workflows/build-and-test-on-pr-open.yml @@ -1,16 +1,19 @@ # 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: - 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: + run-tests: runs-on: ubuntu-latest permissions: 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..34972dd 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,11 @@ +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 + build: mvn -B clean install diff --git a/README.md b/README.md index b9e472d..33ac281 100644 --- a/README.md +++ b/README.md @@ -191,33 +191,57 @@ 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 + +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 ``` -#### 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 +260,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 @@ -304,7 +328,7 @@ project.

-### Appreciate my work? +### Appreciate the work?

Buy Me A Coffee 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..9aa1579 100644 --- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java +++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java @@ -2,49 +2,59 @@ 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; @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); ollama.setPortBindings(portBindings); ollama.start(); api = new OllamaAPI("http://" + ollama.getHost() + ":" + ollama.getMappedPort(internalPort)); - api.setRequestTimeoutSeconds(60); + api.setRequestTimeoutSeconds(120); + api.setVerbose(true); } @Test @@ -84,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"); @@ -102,7 +113,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 +147,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 +247,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(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 { + 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()); + assert !function.getArguments().isEmpty(); + 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(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(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); -// } -// } -// -// -//}