Add Spotless plugin and update copyright headers

Introduced the Spotless Maven plugin for code formatting in pom.xml. Updated copyright headers to include 'and contributors' and changed the year to 2025 in all main source files. Minor formatting and import order improvements applied throughout the codebase.
This commit is contained in:
Amith Koujalgi 2025-09-17 11:10:55 +05:30
parent 329381b1ee
commit ac92766c6c
98 changed files with 2469 additions and 1005 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2023 Amith Koujalgi Copyright (c) 2023 Amith Koujalgi and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

65
pom.xml
View File

@ -163,6 +163,71 @@
<dateFormatTimeZone>Etc/UTC</dateFormatTimeZone> <dateFormatTimeZone>Etc/UTC</dateFormatTimeZone>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
<version>2.46.1</version>
<configuration>
<!-- &lt;!&ndash; optional: limit format enforcement to just the files changed by this feature branch &ndash;&gt;-->
<!-- <ratchetFrom>origin/main</ratchetFrom>-->
<formats>
<!-- you can define as many formats as you want, each is independent -->
<format>
<!-- define the files to apply to -->
<includes>
<include>.gitattributes</include>
<include>.gitignore</include>
</includes>
<!-- define the steps to apply to those files -->
<trimTrailingWhitespace/>
<endWithNewline/>
<indent>
<tabs>true</tabs>
<spacesPerTab>4</spacesPerTab>
</indent>
</format>
</formats>
<!-- define a language-specific format -->
<java>
<!-- no need to specify files, inferred automatically, but you can if you want -->
<!-- apply a specific flavor of google-java-format and reflow long strings -->
<googleJavaFormat>
<version>1.28.0</version>
<style>AOSP</style>
<reflowLongStrings>true</reflowLongStrings>
<formatJavadoc>false</formatJavadoc>
</googleJavaFormat>
<!-- make sure every file has the following copyright header.
optionally, Spotless can set copyright years by digging
through git history (see "license" section below) -->
<licenseHeader>
<content>
<![CDATA[
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) $YEAR Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
]]>
</content> <!-- or <file>${project.basedir}/license-header</file> -->
</licenseHeader>
</java>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
</plugins> </plugins>
<pluginManagement> <pluginManagement>

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j; package io.github.ollama4j;
import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParseException;
@ -22,10 +30,6 @@ import io.github.ollama4j.tools.annotations.ToolSpec;
import io.github.ollama4j.utils.Constants; import io.github.ollama4j.utils.Constants;
import io.github.ollama4j.utils.Options; import io.github.ollama4j.utils.Options;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*; import java.io.*;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -41,6 +45,9 @@ import java.nio.file.Files;
import java.time.Duration; import java.time.Duration;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* The base Ollama API class. * The base Ollama API class.
@ -61,14 +68,12 @@ public class OllamaAPI {
* for a response * for a response
* from the Ollama server before timing out. * from the Ollama server before timing out.
*/ */
@Setter @Setter private long requestTimeoutSeconds = 10;
private long requestTimeoutSeconds = 10;
@Setter @Setter private int imageURLReadTimeoutSeconds = 10;
private int imageURLReadTimeoutSeconds = 10;
@Setter private int imageURLConnectTimeoutSeconds = 10;
@Setter
private int imageURLConnectTimeoutSeconds = 10;
/** /**
* The maximum number of retries for tool calls during chat interactions. * The maximum number of retries for tool calls during chat interactions.
* <p> * <p>
@ -76,8 +81,7 @@ public class OllamaAPI {
* event of a failure. * event of a failure.
* Default is 3. * Default is 3.
*/ */
@Setter @Setter private int maxChatToolCallRetries = 3;
private int maxChatToolCallRetries = 3;
/** /**
* The number of retries to attempt when pulling a model from the Ollama server. * The number of retries to attempt when pulling a model from the Ollama server.
@ -98,8 +102,7 @@ public class OllamaAPI {
* <p> * <p>
* Default is false for backward compatibility. * Default is false for backward compatibility.
*/ */
@Setter @Setter private boolean clientHandlesTools = false;
private boolean clientHandlesTools = false;
/** /**
* Instantiates the Ollama API with default Ollama host: * Instantiates the Ollama API with default Ollama host:
@ -154,9 +157,14 @@ public class OllamaAPI {
HttpClient httpClient = HttpClient.newHttpClient(); HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest httpRequest; HttpRequest httpRequest;
try { try {
httpRequest = getRequestBuilderDefault(new URI(url)) httpRequest =
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) getRequestBuilderDefault(new URI(url))
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON) .header(
Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.GET() .GET()
.build(); .build();
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
@ -183,15 +191,22 @@ public class OllamaAPI {
* @throws InterruptedException if the operation is interrupted * @throws InterruptedException if the operation is interrupted
* @throws OllamaBaseException if the response indicates an error status * @throws OllamaBaseException if the response indicates an error status
*/ */
public ModelsProcessResponse ps() throws IOException, InterruptedException, OllamaBaseException { public ModelsProcessResponse ps()
throws IOException, InterruptedException, OllamaBaseException {
String url = this.host + "/api/ps"; String url = this.host + "/api/ps";
HttpClient httpClient = HttpClient.newHttpClient(); HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest httpRequest = null; HttpRequest httpRequest = null;
try { try {
httpRequest = getRequestBuilderDefault(new URI(url)) httpRequest =
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) getRequestBuilderDefault(new URI(url))
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON) .header(
.GET().build(); Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.GET()
.build();
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -215,18 +230,28 @@ public class OllamaAPI {
* @throws InterruptedException if the operation is interrupted * @throws InterruptedException if the operation is interrupted
* @throws URISyntaxException if the URI for the request is malformed * @throws URISyntaxException if the URI for the request is malformed
*/ */
public List<Model> listModels() throws OllamaBaseException, IOException, InterruptedException, URISyntaxException { public List<Model> listModels()
throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
String url = this.host + "/api/tags"; String url = this.host + "/api/tags";
HttpClient httpClient = HttpClient.newHttpClient(); HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest httpRequest = getRequestBuilderDefault(new URI(url)) HttpRequest httpRequest =
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) getRequestBuilderDefault(new URI(url))
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON).GET() .header(
Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.GET()
.build(); .build();
HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response =
httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
String responseString = response.body(); String responseString = response.body();
if (statusCode == 200) { if (statusCode == 200) {
return Utils.getObjectMapper().readValue(responseString, ListModelsResponse.class).getModels(); return Utils.getObjectMapper()
.readValue(responseString, ListModelsResponse.class)
.getModels();
} else { } else {
throw new OllamaBaseException(statusCode + " - " + responseString); throw new OllamaBaseException(statusCode + " - " + responseString);
} }
@ -266,24 +291,34 @@ public class OllamaAPI {
this.doPullModel(modelName); this.doPullModel(modelName);
return; return;
} catch (OllamaBaseException e) { } catch (OllamaBaseException e) {
handlePullRetry(modelName, numberOfRetries, numberOfRetriesForModelPull, baseDelayMillis); handlePullRetry(
modelName, numberOfRetries, numberOfRetriesForModelPull, baseDelayMillis);
numberOfRetries++; numberOfRetries++;
} }
} }
throw new OllamaBaseException( throw new OllamaBaseException(
"Failed to pull model " + modelName + " after " + numberOfRetriesForModelPull + " retries"); "Failed to pull model "
+ modelName
+ " after "
+ numberOfRetriesForModelPull
+ " retries");
} }
/** /**
* Handles retry backoff for pullModel. * Handles retry backoff for pullModel.
*/ */
private void handlePullRetry(String modelName, int currentRetry, int maxRetries, long baseDelayMillis) private void handlePullRetry(
String modelName, int currentRetry, int maxRetries, long baseDelayMillis)
throws InterruptedException { throws InterruptedException {
int attempt = currentRetry + 1; int attempt = currentRetry + 1;
if (attempt < maxRetries) { if (attempt < maxRetries) {
long backoffMillis = baseDelayMillis * (1L << currentRetry); long backoffMillis = baseDelayMillis * (1L << currentRetry);
LOG.error("Failed to pull model {}, retrying in {}s... (attempt {}/{})", LOG.error(
modelName, backoffMillis / 1000, attempt, maxRetries); "Failed to pull model {}, retrying in {}s... (attempt {}/{})",
modelName,
backoffMillis / 1000,
attempt,
maxRetries);
try { try {
Thread.sleep(backoffMillis); Thread.sleep(backoffMillis);
} catch (InterruptedException ie) { } catch (InterruptedException ie) {
@ -291,7 +326,10 @@ public class OllamaAPI {
throw ie; throw ie;
} }
} else { } else {
LOG.error("Failed to pull model {} after {} attempts, no more retries.", modelName, maxRetries); LOG.error(
"Failed to pull model {} after {} attempts, no more retries.",
modelName,
maxRetries);
} }
} }
@ -299,25 +337,36 @@ public class OllamaAPI {
throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
String url = this.host + "/api/pull"; String url = this.host + "/api/pull";
String jsonData = new ModelRequest(modelName).toString(); String jsonData = new ModelRequest(modelName).toString();
HttpRequest request = getRequestBuilderDefault(new URI(url)).POST(HttpRequest.BodyPublishers.ofString(jsonData)) HttpRequest request =
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) getRequestBuilderDefault(new URI(url))
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON) .POST(HttpRequest.BodyPublishers.ofString(jsonData))
.header(
Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.build(); .build();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpResponse<InputStream> response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); HttpResponse<InputStream> response =
client.send(request, HttpResponse.BodyHandlers.ofInputStream());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
InputStream responseBodyStream = response.body(); InputStream responseBodyStream = response.body();
String responseString = ""; String responseString = "";
boolean success = false; // Flag to check the pull success. boolean success = false; // Flag to check the pull success.
try (BufferedReader reader = new BufferedReader( try (BufferedReader reader =
new BufferedReader(
new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) { new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
ModelPullResponse modelPullResponse = Utils.getObjectMapper().readValue(line, ModelPullResponse.class); ModelPullResponse modelPullResponse =
Utils.getObjectMapper().readValue(line, ModelPullResponse.class);
if (modelPullResponse != null) { if (modelPullResponse != null) {
// Check for error in response body first // Check for error in response body first
if (modelPullResponse.getError() != null && !modelPullResponse.getError().trim().isEmpty()) { if (modelPullResponse.getError() != null
throw new OllamaBaseException("Model pull failed: " + modelPullResponse.getError()); && !modelPullResponse.getError().trim().isEmpty()) {
throw new OllamaBaseException(
"Model pull failed: " + modelPullResponse.getError());
} }
if (modelPullResponse.getStatus() != null) { if (modelPullResponse.getStatus() != null) {
@ -341,18 +390,28 @@ public class OllamaAPI {
} }
} }
public String getVersion() throws URISyntaxException, IOException, InterruptedException, OllamaBaseException { public String getVersion()
throws URISyntaxException, IOException, InterruptedException, OllamaBaseException {
String url = this.host + "/api/version"; String url = this.host + "/api/version";
HttpClient httpClient = HttpClient.newHttpClient(); HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest httpRequest = getRequestBuilderDefault(new URI(url)) HttpRequest httpRequest =
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) getRequestBuilderDefault(new URI(url))
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON).GET() .header(
Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.GET()
.build(); .build();
HttpResponse<String> response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response =
httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
String responseString = response.body(); String responseString = response.body();
if (statusCode == 200) { if (statusCode == 200) {
return Utils.getObjectMapper().readValue(responseString, OllamaVersion.class).getVersion(); return Utils.getObjectMapper()
.readValue(responseString, OllamaVersion.class)
.getVersion();
} else { } else {
throw new OllamaBaseException(statusCode + " - " + responseString); throw new OllamaBaseException(statusCode + " - " + responseString);
} }
@ -374,7 +433,8 @@ public class OllamaAPI {
*/ */
public void pullModel(LibraryModelTag libraryModelTag) public void pullModel(LibraryModelTag libraryModelTag)
throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
String tagToPull = String.format("%s:%s", libraryModelTag.getName(), libraryModelTag.getTag()); String tagToPull =
String.format("%s:%s", libraryModelTag.getName(), libraryModelTag.getTag());
pullModel(tagToPull); pullModel(tagToPull);
} }
@ -392,10 +452,16 @@ public class OllamaAPI {
throws IOException, OllamaBaseException, InterruptedException, URISyntaxException { throws IOException, OllamaBaseException, InterruptedException, URISyntaxException {
String url = this.host + "/api/show"; String url = this.host + "/api/show";
String jsonData = new ModelRequest(modelName).toString(); String jsonData = new ModelRequest(modelName).toString();
HttpRequest request = getRequestBuilderDefault(new URI(url)) HttpRequest request =
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) getRequestBuilderDefault(new URI(url))
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON) .header(
.POST(HttpRequest.BodyPublishers.ofString(jsonData)).build(); Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
@ -422,10 +488,16 @@ public class OllamaAPI {
throws IOException, InterruptedException, OllamaBaseException, URISyntaxException { throws IOException, InterruptedException, OllamaBaseException, URISyntaxException {
String url = this.host + "/api/create"; String url = this.host + "/api/create";
String jsonData = customModelRequest.toString(); String jsonData = customModelRequest.toString();
HttpRequest request = getRequestBuilderDefault(new URI(url)) HttpRequest request =
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) getRequestBuilderDefault(new URI(url))
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON) .header(
.POST(HttpRequest.BodyPublishers.ofString(jsonData, StandardCharsets.UTF_8)).build(); Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.POST(HttpRequest.BodyPublishers.ofString(jsonData, StandardCharsets.UTF_8))
.build();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
@ -454,16 +526,26 @@ public class OllamaAPI {
throws IOException, InterruptedException, OllamaBaseException, URISyntaxException { throws IOException, InterruptedException, OllamaBaseException, URISyntaxException {
String url = this.host + "/api/delete"; String url = this.host + "/api/delete";
String jsonData = new ModelRequest(modelName).toString(); String jsonData = new ModelRequest(modelName).toString();
HttpRequest request = getRequestBuilderDefault(new URI(url)) HttpRequest request =
.method("DELETE", HttpRequest.BodyPublishers.ofString(jsonData, StandardCharsets.UTF_8)) getRequestBuilderDefault(new URI(url))
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) .method(
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON) "DELETE",
HttpRequest.BodyPublishers.ofString(
jsonData, StandardCharsets.UTF_8))
.header(
Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.build(); .build();
HttpClient client = HttpClient.newHttpClient(); HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
String responseBody = response.body(); String responseBody = response.body();
if (statusCode == 404 && responseBody.contains("model") && responseBody.contains("not found")) { if (statusCode == 404
&& responseBody.contains("model")
&& responseBody.contains("not found")) {
return; return;
} }
if (statusCode != 200) { if (statusCode != 200) {
@ -486,11 +568,16 @@ public class OllamaAPI {
String jsonData = Utils.getObjectMapper().writeValueAsString(modelRequest); String jsonData = Utils.getObjectMapper().writeValueAsString(modelRequest);
HttpClient httpClient = HttpClient.newHttpClient(); HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(uri) HttpRequest request =
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) HttpRequest.newBuilder(uri)
.POST(HttpRequest.BodyPublishers.ofString(jsonData)).build(); .header(
Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
String responseBody = response.body(); String responseBody = response.body();
@ -527,8 +614,13 @@ public class OllamaAPI {
* @throws IOException if an I/O error occurs during the HTTP request * @throws IOException if an I/O error occurs during the HTTP request
* @throws InterruptedException if the operation is interrupted * @throws InterruptedException if the operation is interrupted
*/ */
public OllamaResult generate(String model, String prompt, boolean raw, Options options, public OllamaResult generate(
OllamaStreamHandler responseStreamHandler) throws OllamaBaseException, IOException, InterruptedException { String model,
String prompt,
boolean raw,
Options options,
OllamaStreamHandler responseStreamHandler)
throws OllamaBaseException, IOException, InterruptedException {
OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt); OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt);
ollamaRequestModel.setRaw(raw); ollamaRequestModel.setRaw(raw);
ollamaRequestModel.setThink(false); ollamaRequestModel.setThink(false);
@ -563,14 +655,20 @@ public class OllamaAPI {
* @throws IOException if an I/O error occurs during the HTTP request * @throws IOException if an I/O error occurs during the HTTP request
* @throws InterruptedException if the operation is interrupted * @throws InterruptedException if the operation is interrupted
*/ */
public OllamaResult generate(String model, String prompt, boolean raw, Options options, public OllamaResult generate(
OllamaStreamHandler thinkingStreamHandler, OllamaStreamHandler responseStreamHandler) String model,
String prompt,
boolean raw,
Options options,
OllamaStreamHandler thinkingStreamHandler,
OllamaStreamHandler responseStreamHandler)
throws OllamaBaseException, IOException, InterruptedException { throws OllamaBaseException, IOException, InterruptedException {
OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt); OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt);
ollamaRequestModel.setRaw(raw); ollamaRequestModel.setRaw(raw);
ollamaRequestModel.setThink(true); ollamaRequestModel.setThink(true);
ollamaRequestModel.setOptions(options.getOptionsMap()); ollamaRequestModel.setOptions(options.getOptionsMap());
return generateSyncForOllamaRequestModel(ollamaRequestModel, thinkingStreamHandler, responseStreamHandler); return generateSyncForOllamaRequestModel(
ollamaRequestModel, thinkingStreamHandler, responseStreamHandler);
} }
/** /**
@ -596,7 +694,8 @@ public class OllamaAPI {
* @throws IOException if an I/O error occurs during the HTTP request * @throws IOException if an I/O error occurs during the HTTP request
* @throws InterruptedException if the operation is interrupted * @throws InterruptedException if the operation is interrupted
*/ */
public OllamaResult generate(String model, String prompt, boolean raw, boolean think, Options options) public OllamaResult generate(
String model, String prompt, boolean raw, boolean think, Options options)
throws OllamaBaseException, IOException, InterruptedException { throws OllamaBaseException, IOException, InterruptedException {
if (think) { if (think) {
return generate(model, prompt, raw, options, null, null); return generate(model, prompt, raw, options, null, null);
@ -635,27 +734,41 @@ public class OllamaAPI {
String jsonData = Utils.getObjectMapper().writeValueAsString(requestBody); String jsonData = Utils.getObjectMapper().writeValueAsString(requestBody);
HttpClient httpClient = HttpClient.newHttpClient(); HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = getRequestBuilderDefault(uri) HttpRequest request =
.header(Constants.HttpConstants.HEADER_KEY_ACCEPT, Constants.HttpConstants.APPLICATION_JSON) getRequestBuilderDefault(uri)
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON) .header(
.POST(HttpRequest.BodyPublishers.ofString(jsonData)).build(); Constants.HttpConstants.HEADER_KEY_ACCEPT,
Constants.HttpConstants.APPLICATION_JSON)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.POST(HttpRequest.BodyPublishers.ofString(jsonData))
.build();
try { try {
String prettyJson = Utils.getObjectMapper().writerWithDefaultPrettyPrinter() String prettyJson =
.writeValueAsString(Utils.getObjectMapper().readValue(jsonData, Object.class)); Utils.getObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(
Utils.getObjectMapper().readValue(jsonData, Object.class));
LOG.debug("Asking model:\n{}", prettyJson); LOG.debug("Asking model:\n{}", prettyJson);
} catch (Exception e) { } catch (Exception e) {
LOG.debug("Asking model: {}", jsonData); LOG.debug("Asking model: {}", jsonData);
} }
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); HttpResponse<String> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofString());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
String responseBody = response.body(); String responseBody = response.body();
if (statusCode == 200) { if (statusCode == 200) {
OllamaStructuredResult structuredResult = Utils.getObjectMapper().readValue(responseBody, OllamaStructuredResult structuredResult =
OllamaStructuredResult.class); Utils.getObjectMapper().readValue(responseBody, OllamaStructuredResult.class);
OllamaResult ollamaResult = new OllamaResult(structuredResult.getResponse(), structuredResult.getThinking(), OllamaResult ollamaResult =
structuredResult.getResponseTime(), statusCode); new OllamaResult(
structuredResult.getResponse(),
structuredResult.getThinking(),
structuredResult.getResponseTime(),
statusCode);
ollamaResult.setModel(structuredResult.getModel()); ollamaResult.setModel(structuredResult.getModel());
ollamaResult.setCreatedAt(structuredResult.getCreatedAt()); ollamaResult.setCreatedAt(structuredResult.getCreatedAt());
@ -671,8 +784,11 @@ public class OllamaAPI {
LOG.debug("Model response:\n{}", ollamaResult); LOG.debug("Model response:\n{}", ollamaResult);
return ollamaResult; return ollamaResult;
} else { } else {
LOG.debug("Model response:\n{}", LOG.debug(
Utils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(responseBody)); "Model response:\n{}",
Utils.getObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(responseBody));
throw new OllamaBaseException(statusCode + " - " + responseBody); throw new OllamaBaseException(statusCode + " - " + responseBody);
} }
} }
@ -688,7 +804,6 @@ public class OllamaAPI {
* using the registered tool implementations, and their results are collected. * using the registered tool implementations, and their results are collected.
* </p> * </p>
* *
* <p>
* <b>Typical usage:</b> * <b>Typical usage:</b>
* <pre>{@code * <pre>{@code
* OllamaToolsResult result = ollamaAPI.generateWithTools( * OllamaToolsResult result = ollamaAPI.generateWithTools(
@ -700,7 +815,6 @@ public class OllamaAPI {
* String modelResponse = result.getModelResult().getResponse(); * String modelResponse = result.getModelResult().getResponse();
* Map<ToolFunctionCallSpec, Object> toolResults = result.getToolResults(); * Map<ToolFunctionCallSpec, Object> toolResults = result.getToolResults();
* }</pre> * }</pre>
* </p>
* *
* @param model the name or identifier of the AI model to use for generating the response * @param model the name or identifier of the AI model to use for generating the response
* @param prompt the input text or prompt to provide to the AI model * @param prompt the input text or prompt to provide to the AI model
@ -713,7 +827,8 @@ public class OllamaAPI {
* @throws InterruptedException if the operation is interrupted * @throws InterruptedException if the operation is interrupted
* @throws ToolInvocationException if a tool call fails to execute * @throws ToolInvocationException if a tool call fails to execute
*/ */
public OllamaToolsResult generateWithTools(String model, String prompt, Options options, OllamaStreamHandler streamHandler) public OllamaToolsResult generateWithTools(
String model, String prompt, Options options, OllamaStreamHandler streamHandler)
throws OllamaBaseException, IOException, InterruptedException, ToolInvocationException { throws OllamaBaseException, IOException, InterruptedException, ToolInvocationException {
boolean raw = true; boolean raw = true;
OllamaToolsResult toolResult = new OllamaToolsResult(); OllamaToolsResult toolResult = new OllamaToolsResult();
@ -744,11 +859,18 @@ public class OllamaAPI {
// Try to parse the string to see if it's a valid JSON // Try to parse the string to see if it's a valid JSON
objectMapper.readTree(toolsResponse); objectMapper.readTree(toolsResponse);
} catch (JsonParseException e) { } catch (JsonParseException e) {
LOG.warn("Response from model does not contain any tool calls. Returning the response as is."); LOG.warn(
"Response from model does not contain any tool calls. Returning the"
+ " response as is.");
return toolResult; return toolResult;
} }
toolFunctionCallSpecs = objectMapper.readValue(toolsResponse, toolFunctionCallSpecs =
objectMapper.getTypeFactory().constructCollectionType(List.class, ToolFunctionCallSpec.class)); objectMapper.readValue(
toolsResponse,
objectMapper
.getTypeFactory()
.constructCollectionType(
List.class, ToolFunctionCallSpec.class));
} }
for (ToolFunctionCallSpec toolFunctionCallSpec : toolFunctionCallSpecs) { for (ToolFunctionCallSpec toolFunctionCallSpec : toolFunctionCallSpecs) {
toolResults.put(toolFunctionCallSpec, invokeTool(toolFunctionCallSpec)); toolResults.put(toolFunctionCallSpec, invokeTool(toolFunctionCallSpec));
@ -795,12 +917,14 @@ public class OllamaAPI {
* @return an {@link OllamaAsyncResultStreamer} handle for polling and * @return an {@link OllamaAsyncResultStreamer} handle for polling and
* retrieving streamed results * retrieving streamed results
*/ */
public OllamaAsyncResultStreamer generate(String model, String prompt, boolean raw, boolean think) { public OllamaAsyncResultStreamer generate(
String model, String prompt, boolean raw, boolean think) {
OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt); OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt);
ollamaRequestModel.setRaw(raw); ollamaRequestModel.setRaw(raw);
ollamaRequestModel.setThink(think); ollamaRequestModel.setThink(think);
URI uri = URI.create(this.host + "/api/generate"); URI uri = URI.create(this.host + "/api/generate");
OllamaAsyncResultStreamer ollamaAsyncResultStreamer = new OllamaAsyncResultStreamer( OllamaAsyncResultStreamer ollamaAsyncResultStreamer =
new OllamaAsyncResultStreamer(
getRequestBuilderDefault(uri), ollamaRequestModel, requestTimeoutSeconds); getRequestBuilderDefault(uri), ollamaRequestModel, requestTimeoutSeconds);
ollamaAsyncResultStreamer.start(); ollamaAsyncResultStreamer.start();
return ollamaAsyncResultStreamer; return ollamaAsyncResultStreamer;
@ -833,8 +957,14 @@ public class OllamaAPI {
* @throws InterruptedException if the operation is interrupted * @throws InterruptedException if the operation is interrupted
* @throws URISyntaxException if an image URL is malformed * @throws URISyntaxException if an image URL is malformed
*/ */
public OllamaResult generateWithImages(String model, String prompt, List<Object> images, Options options, Map<String, Object> format, public OllamaResult generateWithImages(
OllamaStreamHandler streamHandler) throws OllamaBaseException, IOException, InterruptedException, URISyntaxException { String model,
String prompt,
List<Object> images,
Options options,
Map<String, Object> format,
OllamaStreamHandler streamHandler)
throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
List<String> encodedImages = new ArrayList<>(); List<String> encodedImages = new ArrayList<>();
for (Object image : images) { for (Object image : images) {
if (image instanceof File) { if (image instanceof File) {
@ -845,12 +975,19 @@ public class OllamaAPI {
encodedImages.add(encodeByteArrayToBase64((byte[]) image)); encodedImages.add(encodeByteArrayToBase64((byte[]) image));
} else if (image instanceof String) { } else if (image instanceof String) {
LOG.debug("Using image URL: {}", image); LOG.debug("Using image URL: {}", image);
encodedImages.add(encodeByteArrayToBase64(Utils.loadImageBytesFromUrl((String) image, imageURLConnectTimeoutSeconds, imageURLReadTimeoutSeconds))); encodedImages.add(
encodeByteArrayToBase64(
Utils.loadImageBytesFromUrl(
(String) image,
imageURLConnectTimeoutSeconds,
imageURLReadTimeoutSeconds)));
} else { } else {
throw new OllamaBaseException("Unsupported image type. Please provide a File, byte[], or a URL String."); throw new OllamaBaseException(
"Unsupported image type. Please provide a File, byte[], or a URL String.");
} }
} }
OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt, encodedImages); OllamaGenerateRequest ollamaRequestModel =
new OllamaGenerateRequest(model, prompt, encodedImages);
if (format != null) { if (format != null) {
ollamaRequestModel.setFormat(format); ollamaRequestModel.setFormat(format);
} }
@ -879,11 +1016,14 @@ public class OllamaAPI {
*/ */
public OllamaChatResult chat(OllamaChatRequest request, OllamaTokenHandler tokenHandler) public OllamaChatResult chat(OllamaChatRequest request, OllamaTokenHandler tokenHandler)
throws OllamaBaseException, IOException, InterruptedException, ToolInvocationException { throws OllamaBaseException, IOException, InterruptedException, ToolInvocationException {
OllamaChatEndpointCaller requestCaller = new OllamaChatEndpointCaller(host, auth, requestTimeoutSeconds); OllamaChatEndpointCaller requestCaller =
new OllamaChatEndpointCaller(host, auth, requestTimeoutSeconds);
OllamaChatResult result; OllamaChatResult result;
// add all registered tools to Request // add all registered tools to Request
request.setTools(toolRegistry.getRegisteredSpecs().stream().map(Tools.ToolSpecification::getToolPrompt) request.setTools(
toolRegistry.getRegisteredSpecs().stream()
.map(Tools.ToolSpecification::getToolPrompt)
.collect(Collectors.toList())); .collect(Collectors.toList()));
if (tokenHandler != null) { if (tokenHandler != null) {
@ -900,7 +1040,9 @@ public class OllamaAPI {
// check if toolCallIsWanted // check if toolCallIsWanted
List<OllamaChatToolCalls> toolCalls = result.getResponseModel().getMessage().getToolCalls(); List<OllamaChatToolCalls> toolCalls = result.getResponseModel().getMessage().getToolCalls();
int toolCallTries = 0; int toolCallTries = 0;
while (toolCalls != null && !toolCalls.isEmpty() && toolCallTries < maxChatToolCallRetries) { while (toolCalls != null
&& !toolCalls.isEmpty()
&& toolCallTries < maxChatToolCallRetries) {
for (OllamaChatToolCalls toolCall : toolCalls) { for (OllamaChatToolCalls toolCall : toolCalls) {
String toolName = toolCall.getFunction().getName(); String toolName = toolCall.getFunction().getName();
ToolFunction toolFunction = toolRegistry.getToolFunction(toolName); ToolFunction toolFunction = toolRegistry.getToolFunction(toolName);
@ -909,11 +1051,21 @@ public class OllamaAPI {
} }
Map<String, Object> arguments = toolCall.getFunction().getArguments(); Map<String, Object> arguments = toolCall.getFunction().getArguments();
Object res = toolFunction.apply(arguments); Object res = toolFunction.apply(arguments);
String argumentKeys = arguments.keySet().stream() String argumentKeys =
arguments.keySet().stream()
.map(Object::toString) .map(Object::toString)
.collect(Collectors.joining(", ")); .collect(Collectors.joining(", "));
request.getMessages().add(new OllamaChatMessage(OllamaChatMessageRole.TOOL, request.getMessages()
"[TOOL_RESULTS] " + toolName + "(" + argumentKeys + "): " + res + " [/TOOL_RESULTS]")); .add(
new OllamaChatMessage(
OllamaChatMessageRole.TOOL,
"[TOOL_RESULTS] "
+ toolName
+ "("
+ argumentKeys
+ "): "
+ res
+ " [/TOOL_RESULTS]"));
} }
if (tokenHandler != null) { if (tokenHandler != null) {
@ -982,21 +1134,26 @@ public class OllamaAPI {
try { try {
Class<?> callerClass = null; Class<?> callerClass = null;
try { try {
callerClass = Class.forName(Thread.currentThread().getStackTrace()[2].getClassName()); callerClass =
Class.forName(Thread.currentThread().getStackTrace()[2].getClassName());
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
OllamaToolService ollamaToolServiceAnnotation = callerClass.getDeclaredAnnotation(OllamaToolService.class); OllamaToolService ollamaToolServiceAnnotation =
callerClass.getDeclaredAnnotation(OllamaToolService.class);
if (ollamaToolServiceAnnotation == null) { if (ollamaToolServiceAnnotation == null) {
throw new IllegalStateException(callerClass + " is not annotated as " + OllamaToolService.class); throw new IllegalStateException(
callerClass + " is not annotated as " + OllamaToolService.class);
} }
Class<?>[] providers = ollamaToolServiceAnnotation.providers(); Class<?>[] providers = ollamaToolServiceAnnotation.providers();
for (Class<?> provider : providers) { for (Class<?> provider : providers) {
registerAnnotatedTools(provider.getDeclaredConstructor().newInstance()); registerAnnotatedTools(provider.getDeclaredConstructor().newInstance());
} }
} catch (InstantiationException | NoSuchMethodException | IllegalAccessException } catch (InstantiationException
| NoSuchMethodException
| IllegalAccessException
| InvocationTargetException e) { | InvocationTargetException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -1029,36 +1186,61 @@ public class OllamaAPI {
final Tools.PropsBuilder propsBuilder = new Tools.PropsBuilder(); final Tools.PropsBuilder propsBuilder = new Tools.PropsBuilder();
LinkedHashMap<String, String> methodParams = new LinkedHashMap<>(); LinkedHashMap<String, String> methodParams = new LinkedHashMap<>();
for (Parameter parameter : m.getParameters()) { for (Parameter parameter : m.getParameters()) {
final ToolProperty toolPropertyAnn = parameter.getDeclaredAnnotation(ToolProperty.class); final ToolProperty toolPropertyAnn =
parameter.getDeclaredAnnotation(ToolProperty.class);
String propType = parameter.getType().getTypeName(); String propType = parameter.getType().getTypeName();
if (toolPropertyAnn == null) { if (toolPropertyAnn == null) {
methodParams.put(parameter.getName(), null); methodParams.put(parameter.getName(), null);
continue; continue;
} }
String propName = !toolPropertyAnn.name().isBlank() ? toolPropertyAnn.name() : parameter.getName(); String propName =
!toolPropertyAnn.name().isBlank()
? toolPropertyAnn.name()
: parameter.getName();
methodParams.put(propName, propType); methodParams.put(propName, propType);
propsBuilder.withProperty(propName, Tools.PromptFuncDefinition.Property.builder().type(propType) propsBuilder.withProperty(
.description(toolPropertyAnn.desc()).required(toolPropertyAnn.required()).build()); propName,
Tools.PromptFuncDefinition.Property.builder()
.type(propType)
.description(toolPropertyAnn.desc())
.required(toolPropertyAnn.required())
.build());
} }
final Map<String, Tools.PromptFuncDefinition.Property> params = propsBuilder.build(); final Map<String, Tools.PromptFuncDefinition.Property> params = propsBuilder.build();
List<String> reqProps = params.entrySet().stream().filter(e -> e.getValue().isRequired()) List<String> reqProps =
.map(Map.Entry::getKey).collect(Collectors.toList()); params.entrySet().stream()
.filter(e -> e.getValue().isRequired())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
Tools.ToolSpecification toolSpecification = Tools.ToolSpecification.builder().functionName(operationName) Tools.ToolSpecification toolSpecification =
Tools.ToolSpecification.builder()
.functionName(operationName)
.functionDescription(operationDesc) .functionDescription(operationDesc)
.toolPrompt(Tools.PromptFuncDefinition.builder().type("function") .toolPrompt(
.function(Tools.PromptFuncDefinition.PromptFuncSpec.builder().name(operationName) Tools.PromptFuncDefinition.builder()
.description(operationDesc).parameters(Tools.PromptFuncDefinition.Parameters .type("function")
.builder().type("object").properties(params).required(reqProps).build()) .function(
Tools.PromptFuncDefinition.PromptFuncSpec
.builder()
.name(operationName)
.description(operationDesc)
.parameters(
Tools.PromptFuncDefinition
.Parameters.builder()
.type("object")
.properties(params)
.required(reqProps)
.build())
.build()) .build())
.build()) .build())
.build(); .build();
ReflectionalToolFunction reflectionalToolFunction = new ReflectionalToolFunction(object, m, methodParams); ReflectionalToolFunction reflectionalToolFunction =
new ReflectionalToolFunction(object, m, methodParams);
toolSpecification.setToolFunction(reflectionalToolFunction); toolSpecification.setToolFunction(reflectionalToolFunction);
toolRegistry.addTool(toolSpecification.getFunctionName(), toolSpecification); toolRegistry.addTool(toolSpecification.getFunctionName(), toolSpecification);
} }
} }
/** /**
@ -1135,14 +1317,19 @@ public class OllamaAPI {
* process. * process.
* @throws InterruptedException if the thread is interrupted during the request. * @throws InterruptedException if the thread is interrupted during the request.
*/ */
private OllamaResult generateSyncForOllamaRequestModel(OllamaGenerateRequest ollamaRequestModel, private OllamaResult generateSyncForOllamaRequestModel(
OllamaStreamHandler thinkingStreamHandler, OllamaStreamHandler responseStreamHandler) OllamaGenerateRequest ollamaRequestModel,
OllamaStreamHandler thinkingStreamHandler,
OllamaStreamHandler responseStreamHandler)
throws OllamaBaseException, IOException, InterruptedException { throws OllamaBaseException, IOException, InterruptedException {
OllamaGenerateEndpointCaller requestCaller = new OllamaGenerateEndpointCaller(host, auth, requestTimeoutSeconds); OllamaGenerateEndpointCaller requestCaller =
new OllamaGenerateEndpointCaller(host, auth, requestTimeoutSeconds);
OllamaResult result; OllamaResult result;
if (responseStreamHandler != null) { if (responseStreamHandler != null) {
ollamaRequestModel.setStream(true); ollamaRequestModel.setStream(true);
result = requestCaller.call(ollamaRequestModel, thinkingStreamHandler, responseStreamHandler); result =
requestCaller.call(
ollamaRequestModel, thinkingStreamHandler, responseStreamHandler);
} else { } else {
result = requestCaller.callSync(ollamaRequestModel); result = requestCaller.callSync(ollamaRequestModel);
} }
@ -1156,8 +1343,11 @@ public class OllamaAPI {
* @return HttpRequest.Builder * @return HttpRequest.Builder
*/ */
private HttpRequest.Builder getRequestBuilderDefault(URI uri) { private HttpRequest.Builder getRequestBuilderDefault(URI uri) {
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(uri) HttpRequest.Builder requestBuilder =
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON) HttpRequest.newBuilder(uri)
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.timeout(Duration.ofSeconds(requestTimeoutSeconds)); .timeout(Duration.ofSeconds(requestTimeoutSeconds));
if (isAuthSet()) { if (isAuthSet()) {
requestBuilder.header("Authorization", auth.getAuthHeaderValue()); requestBuilder.header("Authorization", auth.getAuthHeaderValue());
@ -1174,7 +1364,8 @@ public class OllamaAPI {
return auth != null; return auth != null;
} }
private Object invokeTool(ToolFunctionCallSpec toolFunctionCallSpec) throws ToolInvocationException { private Object invokeTool(ToolFunctionCallSpec toolFunctionCallSpec)
throws ToolInvocationException {
try { try {
String methodName = toolFunctionCallSpec.getName(); String methodName = toolFunctionCallSpec.getName();
Map<String, Object> arguments = toolFunctionCallSpec.getArguments(); Map<String, Object> arguments = toolFunctionCallSpec.getArguments();
@ -1182,11 +1373,14 @@ public class OllamaAPI {
LOG.debug("Invoking function {} with arguments {}", methodName, arguments); LOG.debug("Invoking function {} with arguments {}", methodName, arguments);
if (function == null) { if (function == null) {
throw new ToolNotFoundException( throw new ToolNotFoundException(
"No such tool: " + methodName + ". Please register the tool before invoking it."); "No such tool: "
+ methodName
+ ". Please register the tool before invoking it.");
} }
return function.apply(arguments); return function.apply(arguments);
} catch (Exception e) { } catch (Exception e) {
throw new ToolInvocationException("Failed to invoke tool: " + toolFunctionCallSpec.getName(), e); throw new ToolInvocationException(
"Failed to invoke tool: " + toolFunctionCallSpec.getName(), e);
} }
} }
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.exceptions; package io.github.ollama4j.exceptions;
public class OllamaBaseException extends Exception { public class OllamaBaseException extends Exception {

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.exceptions; package io.github.ollama4j.exceptions;
public class RoleNotFoundException extends Exception { public class RoleNotFoundException extends Exception {

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.exceptions; package io.github.ollama4j.exceptions;
public class ToolInvocationException extends Exception { public class ToolInvocationException extends Exception {

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.exceptions; package io.github.ollama4j.exceptions;
public class ToolNotFoundException extends Exception { public class ToolNotFoundException extends Exception {

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.impl; package io.github.ollama4j.impl;
import io.github.ollama4j.models.generate.OllamaStreamHandler; import io.github.ollama4j.models.generate.OllamaStreamHandler;

View File

@ -1,15 +1,22 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.chat; package io.github.ollama4j.models.chat;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.github.ollama4j.utils.FileToBase64Serializer; import io.github.ollama4j.utils.FileToBase64Serializer;
import lombok.*;
import java.util.List; import java.util.List;
import lombok.*;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
/** /**
* Defines a single Message to be used inside a chat request against the ollama /api/chat endpoint. * Defines a single Message to be used inside a chat request against the ollama /api/chat endpoint.
@ -23,11 +30,9 @@ import static io.github.ollama4j.utils.Utils.getObjectMapper;
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class OllamaChatMessage { public class OllamaChatMessage {
@NonNull @NonNull private OllamaChatMessageRole role;
private OllamaChatMessageRole role;
@NonNull @NonNull private String content;
private String content;
private String thinking; private String thinking;

View File

@ -1,11 +1,18 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.chat; package io.github.ollama4j.models.chat;
import com.fasterxml.jackson.annotation.JsonValue; import com.fasterxml.jackson.annotation.JsonValue;
import io.github.ollama4j.exceptions.RoleNotFoundException; import io.github.ollama4j.exceptions.RoleNotFoundException;
import lombok.Getter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Getter;
/** /**
* Defines the possible Chat Message roles. * Defines the possible Chat Message roles.
@ -19,8 +26,7 @@ public class OllamaChatMessageRole {
public static final OllamaChatMessageRole ASSISTANT = new OllamaChatMessageRole("assistant"); public static final OllamaChatMessageRole ASSISTANT = new OllamaChatMessageRole("assistant");
public static final OllamaChatMessageRole TOOL = new OllamaChatMessageRole("tool"); public static final OllamaChatMessageRole TOOL = new OllamaChatMessageRole("tool");
@JsonValue @JsonValue private final String roleName;
private final String roleName;
private OllamaChatMessageRole(String roleName) { private OllamaChatMessageRole(String roleName) {
this.roleName = roleName; this.roleName = roleName;

View File

@ -1,13 +1,20 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.chat; package io.github.ollama4j.models.chat;
import io.github.ollama4j.models.request.OllamaCommonRequest; import io.github.ollama4j.models.request.OllamaCommonRequest;
import io.github.ollama4j.tools.Tools; import io.github.ollama4j.tools.Tools;
import io.github.ollama4j.utils.OllamaRequestBody; import io.github.ollama4j.utils.OllamaRequestBody;
import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
/** /**
* Defines a Request to use against the ollama /api/chat endpoint. * Defines a Request to use against the ollama /api/chat endpoint.
* *
@ -25,8 +32,7 @@ public class OllamaChatRequest extends OllamaCommonRequest implements OllamaRequ
private boolean think; private boolean think;
public OllamaChatRequest() { public OllamaChatRequest() {}
}
public OllamaChatRequest(String model, boolean think, List<OllamaChatMessage> messages) { public OllamaChatRequest(String model, boolean think, List<OllamaChatMessage> messages) {
this.model = model; this.model = model;
@ -42,5 +48,4 @@ public class OllamaChatRequest extends OllamaCommonRequest implements OllamaRequ
return this.toString().equals(o.toString()); return this.toString().equals(o.toString());
} }
} }

View File

@ -1,10 +1,15 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.chat; package io.github.ollama4j.models.chat;
import io.github.ollama4j.utils.Options; import io.github.ollama4j.utils.Options;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -12,6 +17,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Helper class for creating {@link OllamaChatRequest} objects using the builder-pattern. * Helper class for creating {@link OllamaChatRequest} objects using the builder-pattern.
@ -23,7 +30,8 @@ public class OllamaChatRequestBuilder {
private int imageURLConnectTimeoutSeconds = 10; private int imageURLConnectTimeoutSeconds = 10;
private int imageURLReadTimeoutSeconds = 10; private int imageURLReadTimeoutSeconds = 10;
public OllamaChatRequestBuilder withImageURLConnectTimeoutSeconds(int imageURLConnectTimeoutSeconds) { public OllamaChatRequestBuilder withImageURLConnectTimeoutSeconds(
int imageURLConnectTimeoutSeconds) {
this.imageURLConnectTimeoutSeconds = imageURLConnectTimeoutSeconds; this.imageURLConnectTimeoutSeconds = imageURLConnectTimeoutSeconds;
return this; return this;
} }
@ -55,40 +63,67 @@ public class OllamaChatRequestBuilder {
return withMessage(role, content, Collections.emptyList()); return withMessage(role, content, Collections.emptyList());
} }
public OllamaChatRequestBuilder withMessage(OllamaChatMessageRole role, String content, List<OllamaChatToolCalls> toolCalls) { public OllamaChatRequestBuilder withMessage(
OllamaChatMessageRole role, String content, List<OllamaChatToolCalls> toolCalls) {
List<OllamaChatMessage> messages = this.request.getMessages(); List<OllamaChatMessage> messages = this.request.getMessages();
messages.add(new OllamaChatMessage(role, content, null, toolCalls, null)); messages.add(new OllamaChatMessage(role, content, null, toolCalls, null));
return this; return this;
} }
public OllamaChatRequestBuilder withMessage(OllamaChatMessageRole role, String content, List<OllamaChatToolCalls> toolCalls, List<File> images) { public OllamaChatRequestBuilder withMessage(
OllamaChatMessageRole role,
String content,
List<OllamaChatToolCalls> toolCalls,
List<File> images) {
List<OllamaChatMessage> messages = this.request.getMessages(); List<OllamaChatMessage> messages = this.request.getMessages();
List<byte[]> binaryImages = images.stream().map(file -> { List<byte[]> binaryImages =
images.stream()
.map(
file -> {
try { try {
return Files.readAllBytes(file.toPath()); return Files.readAllBytes(file.toPath());
} catch (IOException e) { } catch (IOException e) {
LOG.warn("File '{}' could not be accessed, will not add to message!", file.toPath(), e); LOG.warn(
"File '{}' could not be accessed, will not add to"
+ " message!",
file.toPath(),
e);
return new byte[0]; return new byte[0];
} }
}).collect(Collectors.toList()); })
.collect(Collectors.toList());
messages.add(new OllamaChatMessage(role, content, null, toolCalls, binaryImages)); messages.add(new OllamaChatMessage(role, content, null, toolCalls, binaryImages));
return this; return this;
} }
public OllamaChatRequestBuilder withMessage(OllamaChatMessageRole role, String content, List<OllamaChatToolCalls> toolCalls, String... imageUrls) { public OllamaChatRequestBuilder withMessage(
OllamaChatMessageRole role,
String content,
List<OllamaChatToolCalls> toolCalls,
String... imageUrls) {
List<OllamaChatMessage> messages = this.request.getMessages(); List<OllamaChatMessage> messages = this.request.getMessages();
List<byte[]> binaryImages = null; List<byte[]> binaryImages = null;
if (imageUrls.length > 0) { if (imageUrls.length > 0) {
binaryImages = new ArrayList<>(); binaryImages = new ArrayList<>();
for (String imageUrl : imageUrls) { for (String imageUrl : imageUrls) {
try { try {
binaryImages.add(Utils.loadImageBytesFromUrl(imageUrl, imageURLConnectTimeoutSeconds, imageURLReadTimeoutSeconds)); binaryImages.add(
Utils.loadImageBytesFromUrl(
imageUrl,
imageURLConnectTimeoutSeconds,
imageURLReadTimeoutSeconds));
} catch (IOException e) { } catch (IOException e) {
LOG.warn("Content of URL '{}' could not be read, will not add to message!", imageUrl, e); LOG.warn(
"Content of URL '{}' could not be read, will not add to message!",
imageUrl,
e);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.warn("Loading image from URL '{}' was interrupted, will not add to message!", imageUrl, e); LOG.warn(
"Loading image from URL '{}' was interrupted, will not add to message!",
imageUrl,
e);
} }
} }
} }

View File

@ -1,9 +1,16 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.chat; package io.github.ollama4j.models.chat;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List; import java.util.List;
import lombok.Data;
@Data @Data
public class OllamaChatResponseModel { public class OllamaChatResponseModel {

View File

@ -1,12 +1,19 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.chat; package io.github.ollama4j.models.chat;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.Getter;
import java.util.List;
import static io.github.ollama4j.utils.Utils.getObjectMapper; import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.List;
import lombok.Getter;
/** /**
* Specific chat-API result that contains the chat history sent to the model and appends the answer as {@link OllamaChatResult} given by the * Specific chat-API result that contains the chat history sent to the model and appends the answer as {@link OllamaChatResult} given by the
* {@link OllamaChatMessageRole#ASSISTANT} role. * {@link OllamaChatMessageRole#ASSISTANT} role.
@ -18,7 +25,8 @@ public class OllamaChatResult {
private final OllamaChatResponseModel responseModel; private final OllamaChatResponseModel responseModel;
public OllamaChatResult(OllamaChatResponseModel responseModel, List<OllamaChatMessage> chatHistory) { public OllamaChatResult(
OllamaChatResponseModel responseModel, List<OllamaChatMessage> chatHistory) {
this.chatHistory = chatHistory; this.chatHistory = chatHistory;
this.responseModel = responseModel; this.responseModel = responseModel;
appendAnswerToChatHistory(responseModel); appendAnswerToChatHistory(responseModel);

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.chat; package io.github.ollama4j.models.chat;
import io.github.ollama4j.models.generate.OllamaStreamHandler; import io.github.ollama4j.models.generate.OllamaStreamHandler;
@ -33,7 +41,6 @@ public class OllamaChatStreamObserver implements OllamaTokenHandler {
// //
// responseStreamHandler.accept(message); // responseStreamHandler.accept(message);
if (!hasContent && hasThinking && thinkingStreamHandler != null) { if (!hasContent && hasThinking && thinkingStreamHandler != null) {
// message = message + thinking; // message = message + thinking;

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.chat; package io.github.ollama4j.models.chat;
import io.github.ollama4j.tools.OllamaToolCallsFunction; import io.github.ollama4j.tools.OllamaToolCallsFunction;
@ -11,6 +19,4 @@ import lombok.NoArgsConstructor;
public class OllamaChatToolCalls { public class OllamaChatToolCalls {
private OllamaToolCallsFunction function; private OllamaToolCallsFunction function;
} }

View File

@ -1,7 +1,14 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.embeddings; package io.github.ollama4j.models.embeddings;
import io.github.ollama4j.utils.Options; import io.github.ollama4j.utils.Options;
import java.util.List; import java.util.List;
/** /**

View File

@ -1,26 +1,31 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.embeddings; package io.github.ollama4j.models.embeddings;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.List;
import java.util.Map;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import java.util.List;
import java.util.Map;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
@Data @Data
@RequiredArgsConstructor @RequiredArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class OllamaEmbedRequestModel { public class OllamaEmbedRequestModel {
@NonNull @NonNull private String model;
private String model;
@NonNull @NonNull private List<String> input;
private List<String> input;
private Map<String, Object> options; private Map<String, Object> options;

View File

@ -1,9 +1,16 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.embeddings; package io.github.ollama4j.models.embeddings;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List; import java.util.List;
import lombok.Data;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Data @Data

View File

@ -1,9 +1,16 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.embeddings; package io.github.ollama4j.models.embeddings;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List; import java.util.List;
import lombok.Data;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Data @Data

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.embeddings; package io.github.ollama4j.models.embeddings;
import io.github.ollama4j.utils.Options; import io.github.ollama4j.utils.Options;
@ -28,5 +36,4 @@ public class OllamaEmbeddingsRequestBuilder {
this.request.setKeepAlive(keepAlive); this.request.setKeepAlive(keepAlive);
return this; return this;
} }
} }

View File

@ -1,27 +1,33 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.embeddings; package io.github.ollama4j.models.embeddings;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Map;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import java.util.Map;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
@Data @Data
@RequiredArgsConstructor @RequiredArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Deprecated(since = "1.0.90") @Deprecated(since = "1.0.90")
public class OllamaEmbeddingsRequestModel { public class OllamaEmbeddingsRequestModel {
@NonNull @NonNull private String model;
private String model; @NonNull private String prompt;
@NonNull
private String prompt;
protected Map<String, Object> options; protected Map<String, Object> options;
@JsonProperty(value = "keep_alive") @JsonProperty(value = "keep_alive")
private String keepAlive; private String keepAlive;

View File

@ -1,13 +1,19 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.generate; package io.github.ollama4j.models.generate;
import io.github.ollama4j.models.request.OllamaCommonRequest; import io.github.ollama4j.models.request.OllamaCommonRequest;
import io.github.ollama4j.utils.OllamaRequestBody; import io.github.ollama4j.utils.OllamaRequestBody;
import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
@Getter @Getter
@Setter @Setter
public class OllamaGenerateRequest extends OllamaCommonRequest implements OllamaRequestBody { public class OllamaGenerateRequest extends OllamaCommonRequest implements OllamaRequestBody {
@ -19,8 +25,7 @@ public class OllamaGenerateRequest extends OllamaCommonRequest implements Ollama
private boolean raw; private boolean raw;
private boolean think; private boolean think;
public OllamaGenerateRequest() { public OllamaGenerateRequest() {}
}
public OllamaGenerateRequest(String model, String prompt) { public OllamaGenerateRequest(String model, String prompt) {
this.model = model; this.model = model;

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.generate; package io.github.ollama4j.models.generate;
import io.github.ollama4j.utils.Options; import io.github.ollama4j.utils.Options;
@ -51,5 +59,4 @@ public class OllamaGenerateRequestBuilder {
this.request.setKeepAlive(keepAlive); this.request.setKeepAlive(keepAlive);
return this; return this;
} }
} }

View File

@ -1,10 +1,17 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.generate; package io.github.ollama4j.models.generate;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List; import java.util.List;
import lombok.Data;
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.generate; package io.github.ollama4j.models.generate;
import java.util.ArrayList; import java.util.ArrayList;
@ -12,7 +20,8 @@ public class OllamaGenerateStreamObserver {
private String message = ""; private String message = "";
public OllamaGenerateStreamObserver(OllamaStreamHandler thinkingStreamHandler, OllamaStreamHandler responseStreamHandler) { public OllamaGenerateStreamObserver(
OllamaStreamHandler thinkingStreamHandler, OllamaStreamHandler responseStreamHandler) {
this.responseStreamHandler = responseStreamHandler; this.responseStreamHandler = responseStreamHandler;
this.thinkingStreamHandler = thinkingStreamHandler; this.thinkingStreamHandler = thinkingStreamHandler;
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.generate; package io.github.ollama4j.models.generate;
import java.util.function.Consumer; import java.util.function.Consumer;

View File

@ -1,8 +1,14 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.generate; package io.github.ollama4j.models.generate;
import io.github.ollama4j.models.chat.OllamaChatResponseModel; import io.github.ollama4j.models.chat.OllamaChatResponseModel;
import java.util.function.Consumer; import java.util.function.Consumer;
public interface OllamaTokenHandler extends Consumer<OllamaChatResponseModel> { public interface OllamaTokenHandler extends Consumer<OllamaChatResponseModel> {}
}

View File

@ -1,12 +1,19 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.ps; package io.github.ollama4j.models.ps;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
public abstract class Auth { public abstract class Auth {

View File

@ -1,11 +1,18 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import java.util.Base64;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.util.Base64;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(callSuper = false) @EqualsAndHashCode(callSuper = false)

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;

View File

@ -1,11 +1,19 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class CustomModelFileContentsRequest { public class CustomModelFileContentsRequest {

View File

@ -1,11 +1,19 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class CustomModelFilePathRequest { public class CustomModelFilePathRequest {

View File

@ -1,15 +1,21 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import java.util.List;
import java.util.Map;
import static io.github.ollama4j.utils.Utils.getObjectMapper; import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.List;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@Data @Data
@AllArgsConstructor @AllArgsConstructor

View File

@ -1,11 +1,19 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class ModelRequest { public class ModelRequest {

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@ -7,9 +15,6 @@ import io.github.ollama4j.models.chat.*;
import io.github.ollama4j.models.generate.OllamaTokenHandler; import io.github.ollama4j.models.generate.OllamaTokenHandler;
import io.github.ollama4j.models.response.OllamaErrorResponse; import io.github.ollama4j.models.response.OllamaErrorResponse;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -20,6 +25,8 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** /**
* Specialization class for requests * Specialization class for requests
@ -52,11 +59,15 @@ public class OllamaChatEndpointCaller extends OllamaEndpointCaller {
* @return TRUE, if ollama-Response has 'done' state * @return TRUE, if ollama-Response has 'done' state
*/ */
@Override @Override
protected boolean parseResponseAndAddToBuffer(String line, StringBuilder responseBuffer, StringBuilder thinkingBuffer) { protected boolean parseResponseAndAddToBuffer(
String line, StringBuilder responseBuffer, StringBuilder thinkingBuffer) {
try { try {
OllamaChatResponseModel ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaChatResponseModel.class); OllamaChatResponseModel ollamaResponseModel =
// it seems that under heavy load ollama responds with an empty chat message part in the streamed response Utils.getObjectMapper().readValue(line, OllamaChatResponseModel.class);
// thus, we null check the message and hope that the next streamed response has some message content again // it seems that under heavy load ollama responds with an empty chat message part in the
// streamed response
// thus, we null check the message and hope that the next streamed response has some
// message content again
OllamaChatMessage message = ollamaResponseModel.getMessage(); OllamaChatMessage message = ollamaResponseModel.getMessage();
if (message != null) { if (message != null) {
if (message.getThinking() != null) { if (message.getThinking() != null) {
@ -81,14 +92,13 @@ public class OllamaChatEndpointCaller extends OllamaEndpointCaller {
return callSync(body); return callSync(body);
} }
public OllamaChatResult callSync(OllamaChatRequest body) throws OllamaBaseException, IOException, InterruptedException { public OllamaChatResult callSync(OllamaChatRequest body)
throws OllamaBaseException, IOException, InterruptedException {
// Create Request // Create Request
HttpClient httpClient = HttpClient.newHttpClient(); HttpClient httpClient = HttpClient.newHttpClient();
URI uri = URI.create(getHost() + getEndpointSuffix()); URI uri = URI.create(getHost() + getEndpointSuffix());
HttpRequest.Builder requestBuilder = HttpRequest.Builder requestBuilder =
getRequestBuilderDefault(uri) getRequestBuilderDefault(uri).POST(body.getBodyPublisher());
.POST(
body.getBodyPublisher());
HttpRequest request = requestBuilder.build(); HttpRequest request = requestBuilder.build();
LOG.debug("Asking model: {}", body); LOG.debug("Asking model: {}", body);
HttpResponse<InputStream> response = HttpResponse<InputStream> response =
@ -101,7 +111,8 @@ public class OllamaChatEndpointCaller extends OllamaEndpointCaller {
OllamaChatResponseModel ollamaChatResponseModel = null; OllamaChatResponseModel ollamaChatResponseModel = null;
List<OllamaChatToolCalls> wantedToolsForStream = null; List<OllamaChatToolCalls> wantedToolsForStream = null;
try (BufferedReader reader = try (BufferedReader reader =
new BufferedReader(new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) { new BufferedReader(
new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
@ -114,22 +125,27 @@ public class OllamaChatEndpointCaller extends OllamaEndpointCaller {
LOG.warn("Status code: 401 (Unauthorized)"); LOG.warn("Status code: 401 (Unauthorized)");
OllamaErrorResponse ollamaResponseModel = OllamaErrorResponse ollamaResponseModel =
Utils.getObjectMapper() Utils.getObjectMapper()
.readValue("{\"error\":\"Unauthorized\"}", OllamaErrorResponse.class); .readValue(
"{\"error\":\"Unauthorized\"}",
OllamaErrorResponse.class);
responseBuffer.append(ollamaResponseModel.getError()); responseBuffer.append(ollamaResponseModel.getError());
} else if (statusCode == 400) { } else if (statusCode == 400) {
LOG.warn("Status code: 400 (Bad Request)"); LOG.warn("Status code: 400 (Bad Request)");
OllamaErrorResponse ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaErrorResponse ollamaResponseModel =
OllamaErrorResponse.class); Utils.getObjectMapper().readValue(line, OllamaErrorResponse.class);
responseBuffer.append(ollamaResponseModel.getError()); responseBuffer.append(ollamaResponseModel.getError());
} else if (statusCode == 500) { } else if (statusCode == 500) {
LOG.warn("Status code: 500 (Internal Server Error)"); LOG.warn("Status code: 500 (Internal Server Error)");
OllamaErrorResponse ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaErrorResponse ollamaResponseModel =
OllamaErrorResponse.class); Utils.getObjectMapper().readValue(line, OllamaErrorResponse.class);
responseBuffer.append(ollamaResponseModel.getError()); responseBuffer.append(ollamaResponseModel.getError());
} else { } else {
boolean finished = parseResponseAndAddToBuffer(line, responseBuffer, thinkingBuffer); boolean finished =
ollamaChatResponseModel = Utils.getObjectMapper().readValue(line, OllamaChatResponseModel.class); parseResponseAndAddToBuffer(line, responseBuffer, thinkingBuffer);
if (body.stream && ollamaChatResponseModel.getMessage().getToolCalls() != null) { ollamaChatResponseModel =
Utils.getObjectMapper().readValue(line, OllamaChatResponseModel.class);
if (body.stream
&& ollamaChatResponseModel.getMessage().getToolCalls() != null) {
wantedToolsForStream = ollamaChatResponseModel.getMessage().getToolCalls(); wantedToolsForStream = ollamaChatResponseModel.getMessage().getToolCalls();
} }
if (finished && body.stream) { if (finished && body.stream) {

View File

@ -1,32 +1,43 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import lombok.Data;
import java.util.Map; import java.util.Map;
import lombok.Data;
@Data @Data
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class OllamaCommonRequest { public abstract class OllamaCommonRequest {
protected String model; protected String model;
// @JsonSerialize(using = BooleanToJsonFormatFlagSerializer.class) // @JsonSerialize(using = BooleanToJsonFormatFlagSerializer.class)
// this can either be set to format=json or format={"key1": "val1", "key2": "val2"} // this can either be set to format=json or format={"key1": "val1", "key2": "val2"}
@JsonProperty(value = "format", required = false, defaultValue = "json") @JsonProperty(value = "format", required = false, defaultValue = "json")
protected Object format; protected Object format;
protected Map<String, Object> options; protected Map<String, Object> options;
protected String template; protected String template;
protected boolean stream; protected boolean stream;
@JsonProperty(value = "keep_alive") @JsonProperty(value = "keep_alive")
protected String keepAlive; protected String keepAlive;
public String toString() { public String toString() {
try { try {
return Utils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this); return Utils.getObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(this);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -1,11 +1,18 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import io.github.ollama4j.utils.Constants; import io.github.ollama4j.utils.Constants;
import lombok.Getter;
import java.net.URI; import java.net.URI;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.time.Duration; import java.time.Duration;
import lombok.Getter;
/** /**
* Abstract helperclass to call the ollama api server. * Abstract helperclass to call the ollama api server.
@ -25,8 +32,8 @@ public abstract class OllamaEndpointCaller {
protected abstract String getEndpointSuffix(); protected abstract String getEndpointSuffix();
protected abstract boolean parseResponseAndAddToBuffer(String line, StringBuilder responseBuffer, StringBuilder thinkingBuffer); protected abstract boolean parseResponseAndAddToBuffer(
String line, StringBuilder responseBuffer, StringBuilder thinkingBuffer);
/** /**
* Get default request builder. * Get default request builder.
@ -37,7 +44,9 @@ public abstract class OllamaEndpointCaller {
protected HttpRequest.Builder getRequestBuilderDefault(URI uri) { protected HttpRequest.Builder getRequestBuilderDefault(URI uri) {
HttpRequest.Builder requestBuilder = HttpRequest.Builder requestBuilder =
HttpRequest.newBuilder(uri) HttpRequest.newBuilder(uri)
.header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON) .header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.timeout(Duration.ofSeconds(this.requestTimeoutSeconds)); .timeout(Duration.ofSeconds(this.requestTimeoutSeconds));
if (isAuthCredentialsSet()) { if (isAuthCredentialsSet()) {
requestBuilder.header("Authorization", this.auth.getAuthHeaderValue()); requestBuilder.header("Authorization", this.auth.getAuthHeaderValue());
@ -53,5 +62,4 @@ public abstract class OllamaEndpointCaller {
protected boolean isAuthCredentialsSet() { protected boolean isAuthCredentialsSet() {
return this.auth != null; return this.auth != null;
} }
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.request; package io.github.ollama4j.models.request;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@ -9,9 +17,6 @@ import io.github.ollama4j.models.response.OllamaErrorResponse;
import io.github.ollama4j.models.response.OllamaResult; import io.github.ollama4j.models.response.OllamaResult;
import io.github.ollama4j.utils.OllamaRequestBody; import io.github.ollama4j.utils.OllamaRequestBody;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -21,6 +26,8 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("resource") @SuppressWarnings("resource")
public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller { public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller {
@ -39,9 +46,11 @@ public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller {
} }
@Override @Override
protected boolean parseResponseAndAddToBuffer(String line, StringBuilder responseBuffer, StringBuilder thinkingBuffer) { protected boolean parseResponseAndAddToBuffer(
String line, StringBuilder responseBuffer, StringBuilder thinkingBuffer) {
try { try {
OllamaGenerateResponseModel ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaGenerateResponseModel.class); OllamaGenerateResponseModel ollamaResponseModel =
Utils.getObjectMapper().readValue(line, OllamaGenerateResponseModel.class);
if (ollamaResponseModel.getResponse() != null) { if (ollamaResponseModel.getResponse() != null) {
responseBuffer.append(ollamaResponseModel.getResponse()); responseBuffer.append(ollamaResponseModel.getResponse());
} }
@ -58,8 +67,13 @@ public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller {
} }
} }
public OllamaResult call(OllamaRequestBody body, OllamaStreamHandler thinkingStreamHandler, OllamaStreamHandler responseStreamHandler) throws OllamaBaseException, IOException, InterruptedException { public OllamaResult call(
responseStreamObserver = new OllamaGenerateStreamObserver(thinkingStreamHandler, responseStreamHandler); OllamaRequestBody body,
OllamaStreamHandler thinkingStreamHandler,
OllamaStreamHandler responseStreamHandler)
throws OllamaBaseException, IOException, InterruptedException {
responseStreamObserver =
new OllamaGenerateStreamObserver(thinkingStreamHandler, responseStreamHandler);
return callSync(body); return callSync(body);
} }
@ -73,40 +87,54 @@ public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller {
* @throws InterruptedException in case the server is not reachable or network issues happen * @throws InterruptedException in case the server is not reachable or network issues happen
*/ */
@SuppressWarnings("DuplicatedCode") @SuppressWarnings("DuplicatedCode")
public OllamaResult callSync(OllamaRequestBody body) throws OllamaBaseException, IOException, InterruptedException { public OllamaResult callSync(OllamaRequestBody body)
throws OllamaBaseException, IOException, InterruptedException {
// Create Request // Create Request
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
HttpClient httpClient = HttpClient.newHttpClient(); HttpClient httpClient = HttpClient.newHttpClient();
URI uri = URI.create(getHost() + getEndpointSuffix()); URI uri = URI.create(getHost() + getEndpointSuffix());
HttpRequest.Builder requestBuilder = getRequestBuilderDefault(uri).POST(body.getBodyPublisher()); HttpRequest.Builder requestBuilder =
getRequestBuilderDefault(uri).POST(body.getBodyPublisher());
HttpRequest request = requestBuilder.build(); HttpRequest request = requestBuilder.build();
LOG.debug("Asking model: {}", body); LOG.debug("Asking model: {}", body);
HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); HttpResponse<InputStream> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
InputStream responseBodyStream = response.body(); InputStream responseBodyStream = response.body();
StringBuilder responseBuffer = new StringBuilder(); StringBuilder responseBuffer = new StringBuilder();
StringBuilder thinkingBuffer = new StringBuilder(); StringBuilder thinkingBuffer = new StringBuilder();
OllamaGenerateResponseModel ollamaGenerateResponseModel = null; OllamaGenerateResponseModel ollamaGenerateResponseModel = null;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) { try (BufferedReader reader =
new BufferedReader(
new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
if (statusCode == 404) { if (statusCode == 404) {
LOG.warn("Status code: 404 (Not Found)"); LOG.warn("Status code: 404 (Not Found)");
OllamaErrorResponse ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaErrorResponse.class); OllamaErrorResponse ollamaResponseModel =
Utils.getObjectMapper().readValue(line, OllamaErrorResponse.class);
responseBuffer.append(ollamaResponseModel.getError()); responseBuffer.append(ollamaResponseModel.getError());
} else if (statusCode == 401) { } else if (statusCode == 401) {
LOG.warn("Status code: 401 (Unauthorized)"); LOG.warn("Status code: 401 (Unauthorized)");
OllamaErrorResponse ollamaResponseModel = Utils.getObjectMapper().readValue("{\"error\":\"Unauthorized\"}", OllamaErrorResponse.class); OllamaErrorResponse ollamaResponseModel =
Utils.getObjectMapper()
.readValue(
"{\"error\":\"Unauthorized\"}",
OllamaErrorResponse.class);
responseBuffer.append(ollamaResponseModel.getError()); responseBuffer.append(ollamaResponseModel.getError());
} else if (statusCode == 400) { } else if (statusCode == 400) {
LOG.warn("Status code: 400 (Bad Request)"); LOG.warn("Status code: 400 (Bad Request)");
OllamaErrorResponse ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaErrorResponse.class); OllamaErrorResponse ollamaResponseModel =
Utils.getObjectMapper().readValue(line, OllamaErrorResponse.class);
responseBuffer.append(ollamaResponseModel.getError()); responseBuffer.append(ollamaResponseModel.getError());
} else { } else {
boolean finished = parseResponseAndAddToBuffer(line, responseBuffer, thinkingBuffer); boolean finished =
parseResponseAndAddToBuffer(line, responseBuffer, thinkingBuffer);
if (finished) { if (finished) {
ollamaGenerateResponseModel = Utils.getObjectMapper().readValue(line, OllamaGenerateResponseModel.class); ollamaGenerateResponseModel =
Utils.getObjectMapper()
.readValue(line, OllamaGenerateResponseModel.class);
break; break;
} }
} }
@ -118,7 +146,12 @@ public class OllamaGenerateEndpointCaller extends OllamaEndpointCaller {
throw new OllamaBaseException(responseBuffer.toString()); throw new OllamaBaseException(responseBuffer.toString());
} else { } else {
long endTime = System.currentTimeMillis(); long endTime = System.currentTimeMillis();
OllamaResult ollamaResult = new OllamaResult(responseBuffer.toString(), thinkingBuffer.toString(), endTime - startTime, statusCode); OllamaResult ollamaResult =
new OllamaResult(
responseBuffer.toString(),
thinkingBuffer.toString(),
endTime - startTime,
statusCode);
ollamaResult.setModel(ollamaGenerateResponseModel.getModel()); ollamaResult.setModel(ollamaGenerateResponseModel.getModel());
ollamaResult.setCreatedAt(ollamaGenerateResponseModel.getCreatedAt()); ollamaResult.setCreatedAt(ollamaGenerateResponseModel.getCreatedAt());

View File

@ -1,9 +1,16 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import lombok.Data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Data;
@Data @Data
public class LibraryModel { public class LibraryModel {

View File

@ -1,8 +1,15 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import lombok.Data;
import java.util.List; import java.util.List;
import lombok.Data;
@Data @Data
public class LibraryModelDetail { public class LibraryModelDetail {

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import lombok.Data; import lombok.Data;

View File

@ -1,8 +1,15 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import lombok.Data;
import java.util.List; import java.util.List;
import lombok.Data;
@Data @Data
public class ListModelsResponse { public class ListModelsResponse {

View File

@ -1,12 +1,19 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import lombok.Data;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import lombok.Data;
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@ -14,16 +21,19 @@ public class Model {
private String name; private String name;
private String model; private String model;
@JsonProperty("modified_at") @JsonProperty("modified_at")
private OffsetDateTime modifiedAt; private OffsetDateTime modifiedAt;
@JsonProperty("expires_at") @JsonProperty("expires_at")
private OffsetDateTime expiresAt; private OffsetDateTime expiresAt;
private String digest; private String digest;
private long size; private long size;
@JsonProperty("details") @JsonProperty("details")
private ModelMeta modelMeta; private ModelMeta modelMeta;
/** /**
* Returns the model name without its version * Returns the model name without its version
* *
@ -45,10 +55,11 @@ public class Model {
@Override @Override
public String toString() { public String toString() {
try { try {
return Utils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this); return Utils.getObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(this);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@ -22,7 +30,9 @@ public class ModelDetail {
@Override @Override
public String toString() { public String toString() {
try { try {
return Utils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this); return Utils.getObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(this);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@ -27,7 +35,9 @@ public class ModelMeta {
@Override @Override
public String toString() { public String toString() {
try { try {
return Utils.getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(this); return Utils.getObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(this);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import io.github.ollama4j.exceptions.OllamaBaseException; import io.github.ollama4j.exceptions.OllamaBaseException;
@ -5,11 +13,6 @@ import io.github.ollama4j.models.generate.OllamaGenerateRequest;
import io.github.ollama4j.models.generate.OllamaGenerateResponseModel; import io.github.ollama4j.models.generate.OllamaGenerateResponseModel;
import io.github.ollama4j.utils.Constants; import io.github.ollama4j.utils.Constants;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -19,6 +22,10 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ -31,32 +38,30 @@ public class OllamaAsyncResultStreamer extends Thread {
private String completeResponse; private String completeResponse;
private String completeThinkingResponse; private String completeThinkingResponse;
/** /**
* -- GETTER -- Returns the status of the request. Indicates if the request was successful or a * -- GETTER -- Returns the status of the request. Indicates if the request was successful or a
* failure. If the request was a failure, the `getResponse()` method will return the error * failure. If the request was a failure, the `getResponse()` method will return the error
* message. * message.
*/ */
@Getter @Getter private boolean succeeded;
private boolean succeeded;
@Setter @Setter private long requestTimeoutSeconds;
private long requestTimeoutSeconds;
/** /**
* -- GETTER -- Returns the HTTP response status code for the request that was made to Ollama * -- GETTER -- Returns the HTTP response status code for the request that was made to Ollama
* server. * server.
*/ */
@Getter @Getter private int httpStatusCode;
private int httpStatusCode;
/** /**
* -- GETTER -- Returns the response time in milliseconds. * -- GETTER -- Returns the response time in milliseconds.
*/ */
@Getter @Getter private long responseTime = 0;
private long responseTime = 0;
public OllamaAsyncResultStreamer(HttpRequest.Builder requestBuilder, OllamaGenerateRequest ollamaRequestModel, long requestTimeoutSeconds) { public OllamaAsyncResultStreamer(
HttpRequest.Builder requestBuilder,
OllamaGenerateRequest ollamaRequestModel,
long requestTimeoutSeconds) {
this.requestBuilder = requestBuilder; this.requestBuilder = requestBuilder;
this.ollamaRequestModel = ollamaRequestModel; this.ollamaRequestModel = ollamaRequestModel;
this.completeResponse = ""; this.completeResponse = "";
@ -70,25 +75,41 @@ public class OllamaAsyncResultStreamer extends Thread {
HttpClient httpClient = HttpClient.newHttpClient(); HttpClient httpClient = HttpClient.newHttpClient();
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
try { try {
HttpRequest request = requestBuilder.POST(HttpRequest.BodyPublishers.ofString(Utils.getObjectMapper().writeValueAsString(ollamaRequestModel))).header(Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE, Constants.HttpConstants.APPLICATION_JSON).timeout(Duration.ofSeconds(requestTimeoutSeconds)).build(); HttpRequest request =
HttpResponse<InputStream> response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); requestBuilder
.POST(
HttpRequest.BodyPublishers.ofString(
Utils.getObjectMapper()
.writeValueAsString(ollamaRequestModel)))
.header(
Constants.HttpConstants.HEADER_KEY_CONTENT_TYPE,
Constants.HttpConstants.APPLICATION_JSON)
.timeout(Duration.ofSeconds(requestTimeoutSeconds))
.build();
HttpResponse<InputStream> response =
httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
int statusCode = response.statusCode(); int statusCode = response.statusCode();
this.httpStatusCode = statusCode; this.httpStatusCode = statusCode;
InputStream responseBodyStream = response.body(); InputStream responseBodyStream = response.body();
BufferedReader reader = null; BufferedReader reader = null;
try { try {
reader = new BufferedReader(new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8)); reader =
new BufferedReader(
new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8));
String line; String line;
StringBuilder thinkingBuffer = new StringBuilder(); StringBuilder thinkingBuffer = new StringBuilder();
StringBuilder responseBuffer = new StringBuilder(); StringBuilder responseBuffer = new StringBuilder();
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
if (statusCode == 404) { if (statusCode == 404) {
OllamaErrorResponse ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaErrorResponse.class); OllamaErrorResponse ollamaResponseModel =
Utils.getObjectMapper().readValue(line, OllamaErrorResponse.class);
responseStream.add(ollamaResponseModel.getError()); responseStream.add(ollamaResponseModel.getError());
responseBuffer.append(ollamaResponseModel.getError()); responseBuffer.append(ollamaResponseModel.getError());
} else { } else {
OllamaGenerateResponseModel ollamaResponseModel = Utils.getObjectMapper().readValue(line, OllamaGenerateResponseModel.class); OllamaGenerateResponseModel ollamaResponseModel =
Utils.getObjectMapper()
.readValue(line, OllamaGenerateResponseModel.class);
String thinkingTokens = ollamaResponseModel.getThinking(); String thinkingTokens = ollamaResponseModel.getThinking();
String responseTokens = ollamaResponseModel.getResponse(); String responseTokens = ollamaResponseModel.getResponse();
if (thinkingTokens == null) { if (thinkingTokens == null) {
@ -134,5 +155,4 @@ public class OllamaAsyncResultStreamer extends Thread {
this.completeResponse = "[FAILED] " + e.getMessage(); this.completeResponse = "[FAILED] " + e.getMessage();
} }
} }
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

View File

@ -1,16 +1,23 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import lombok.Data;
import lombok.Getter;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.Data;
import static io.github.ollama4j.utils.Utils.getObjectMapper; import lombok.Getter;
/** /**
* The type Ollama result. * The type Ollama result.
@ -24,14 +31,17 @@ public class OllamaResult {
* Get the completion/response text * Get the completion/response text
*/ */
private final String response; private final String response;
/** /**
* Get the thinking text (if available) * Get the thinking text (if available)
*/ */
private final String thinking; private final String thinking;
/** /**
* Get the response status code. * Get the response status code.
*/ */
private int httpStatusCode; private int httpStatusCode;
/** /**
* Get the response time in milliseconds. * Get the response time in milliseconds.
*/ */
@ -75,7 +85,9 @@ public class OllamaResult {
responseMap.put("promptEvalDuration", this.promptEvalDuration); responseMap.put("promptEvalDuration", this.promptEvalDuration);
responseMap.put("evalCount", this.evalCount); responseMap.put("evalCount", this.evalCount);
responseMap.put("evalDuration", this.evalDuration); responseMap.put("evalDuration", this.evalDuration);
return getObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(responseMap); return getObjectMapper()
.writerWithDefaultPrettyPrinter()
.writeValueAsString(responseMap);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -95,17 +107,18 @@ public class OllamaResult {
try { try {
// Check if the response is a valid JSON // Check if the response is a valid JSON
if ((!responseStr.trim().startsWith("{") && !responseStr.trim().startsWith("[")) || if ((!responseStr.trim().startsWith("{") && !responseStr.trim().startsWith("["))
(!responseStr.trim().endsWith("}") && !responseStr.trim().endsWith("]"))) { || (!responseStr.trim().endsWith("}") && !responseStr.trim().endsWith("]"))) {
throw new IllegalArgumentException("Response is not a valid JSON object"); throw new IllegalArgumentException("Response is not a valid JSON object");
} }
Map<String, Object> response = getObjectMapper().readValue(responseStr, Map<String, Object> response =
new TypeReference<Map<String, Object>>() { getObjectMapper()
}); .readValue(responseStr, new TypeReference<Map<String, Object>>() {});
return response; return response;
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to parse response as JSON: " + e.getMessage(), e); throw new IllegalArgumentException(
"Failed to parse response as JSON: " + e.getMessage(), e);
} }
} }
@ -126,13 +139,14 @@ public class OllamaResult {
try { try {
// Check if the response is a valid JSON // Check if the response is a valid JSON
if ((!responseStr.trim().startsWith("{") && !responseStr.trim().startsWith("[")) || if ((!responseStr.trim().startsWith("{") && !responseStr.trim().startsWith("["))
(!responseStr.trim().endsWith("}") && !responseStr.trim().endsWith("]"))) { || (!responseStr.trim().endsWith("}") && !responseStr.trim().endsWith("]"))) {
throw new IllegalArgumentException("Response is not a valid JSON object"); throw new IllegalArgumentException("Response is not a valid JSON object");
} }
return getObjectMapper().readValue(responseStr, clazz); return getObjectMapper().readValue(responseStr, clazz);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new IllegalArgumentException("Failed to parse response as JSON: " + e.getMessage(), e); throw new IllegalArgumentException(
"Failed to parse response as JSON: " + e.getMessage(), e);
} }
} }
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import java.util.Iterator; import java.util.Iterator;

View File

@ -1,18 +1,25 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
import java.util.Map;
import lombok.Data; import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
import static io.github.ollama4j.utils.Utils.getObjectMapper;
@Getter @Getter
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Data @Data
@ -58,9 +65,11 @@ public class OllamaStructuredResult {
*/ */
public Map<String, Object> getStructuredResponse() { public Map<String, Object> getStructuredResponse() {
try { try {
Map<String, Object> response = getObjectMapper().readValue(this.getResponse(), Map<String, Object> response =
new TypeReference<Map<String, Object>>() { getObjectMapper()
}); .readValue(
this.getResponse(),
new TypeReference<Map<String, Object>>() {});
return response; return response;
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.models.response; package io.github.ollama4j.models.response;
import lombok.Data; import lombok.Data;

View File

@ -1,12 +1,19 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools; package io.github.ollama4j.tools;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.Map;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.Map;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor

View File

@ -1,13 +1,20 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools; package io.github.ollama4j.tools;
import io.github.ollama4j.models.response.OllamaResult; import io.github.ollama4j.models.response.OllamaResult;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@ -22,7 +29,8 @@ public class OllamaToolsResult {
return results; return results;
} }
for (Map.Entry<ToolFunctionCallSpec, Object> r : this.toolResults.entrySet()) { for (Map.Entry<ToolFunctionCallSpec, Object> r : this.toolResults.entrySet()) {
results.add(new ToolResult(r.getKey().getName(), r.getKey().getArguments(), r.getValue())); results.add(
new ToolResult(r.getKey().getName(), r.getKey().getArguments(), r.getValue()));
} }
return results; return results;
} }

View File

@ -1,13 +1,20 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools; package io.github.ollama4j.tools;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
/** /**
* Specification of a {@link ToolFunction} that provides the implementation via java reflection calling. * Specification of a {@link ToolFunction} that provides the implementation via java reflection calling.
@ -25,7 +32,8 @@ public class ReflectionalToolFunction implements ToolFunction {
public Object apply(Map<String, Object> arguments) { public Object apply(Map<String, Object> arguments) {
LinkedHashMap<String, Object> argumentsCopy = new LinkedHashMap<>(this.propertyDefinition); LinkedHashMap<String, Object> argumentsCopy = new LinkedHashMap<>(this.propertyDefinition);
for (Map.Entry<String, String> param : this.propertyDefinition.entrySet()) { for (Map.Entry<String, String> param : this.propertyDefinition.entrySet()) {
argumentsCopy.replace(param.getKey(), typeCast(arguments.get(param.getKey()), param.getValue())); argumentsCopy.replace(
param.getKey(), typeCast(arguments.get(param.getKey()), param.getValue()));
} }
try { try {
return function.invoke(functionHolder, argumentsCopy.values().toArray()); return function.invoke(functionHolder, argumentsCopy.values().toArray());
@ -50,5 +58,4 @@ public class ReflectionalToolFunction implements ToolFunction {
return inputValueString; return inputValueString;
} }
} }
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools; package io.github.ollama4j.tools;
import java.util.Map; import java.util.Map;

View File

@ -1,11 +1,18 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools; package io.github.ollama4j.tools;
import java.util.Map;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.Map;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools; package io.github.ollama4j.tools;
import java.util.Collection; import java.util.Collection;

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools; package io.github.ollama4j.tools;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
@ -6,15 +14,14 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
public class Tools { public class Tools {
@Data @Data
@ -62,11 +69,12 @@ public class Tools {
public static class Property { public static class Property {
private String type; private String type;
private String description; private String description;
@JsonProperty("enum") @JsonProperty("enum")
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
private List<String> enumValues; private List<String> enumValues;
@JsonIgnore
private boolean required; @JsonIgnore private boolean required;
} }
} }
@ -89,7 +97,11 @@ public class Tools {
private String promptText; private String promptText;
public String build() throws JsonProcessingException { public String build() throws JsonProcessingException {
return "[AVAILABLE_TOOLS] " + Utils.getObjectMapper().writeValueAsString(tools) + "[/AVAILABLE_TOOLS][INST] " + promptText + " [/INST]"; return "[AVAILABLE_TOOLS] "
+ Utils.getObjectMapper().writeValueAsString(tools)
+ "[/AVAILABLE_TOOLS][INST] "
+ promptText
+ " [/INST]";
} }
public PromptBuilder withPrompt(String prompt) throws JsonProcessingException { public PromptBuilder withPrompt(String prompt) throws JsonProcessingException {
@ -101,7 +113,8 @@ public class Tools {
PromptFuncDefinition def = new PromptFuncDefinition(); PromptFuncDefinition def = new PromptFuncDefinition();
def.setType("function"); def.setType("function");
PromptFuncDefinition.PromptFuncSpec functionDetail = new PromptFuncDefinition.PromptFuncSpec(); PromptFuncDefinition.PromptFuncSpec functionDetail =
new PromptFuncDefinition.PromptFuncSpec();
functionDetail.setName(spec.getFunctionName()); functionDetail.setName(spec.getFunctionName());
functionDetail.setDescription(spec.getFunctionDescription()); functionDetail.setDescription(spec.getFunctionDescription());
@ -110,7 +123,8 @@ public class Tools {
parameters.setProperties(spec.getToolPrompt().getFunction().parameters.getProperties()); parameters.setProperties(spec.getToolPrompt().getFunction().parameters.getProperties());
List<String> requiredValues = new ArrayList<>(); List<String> requiredValues = new ArrayList<>();
for (Map.Entry<String, PromptFuncDefinition.Property> p : spec.getToolPrompt().getFunction().getParameters().getProperties().entrySet()) { for (Map.Entry<String, PromptFuncDefinition.Property> p :
spec.getToolPrompt().getFunction().getParameters().getProperties().entrySet()) {
if (p.getValue().isRequired()) { if (p.getValue().isRequired()) {
requiredValues.add(p.getKey()); requiredValues.add(p.getKey());
} }

View File

@ -1,7 +1,14 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools.annotations; package io.github.ollama4j.tools.annotations;
import io.github.ollama4j.OllamaAPI; import io.github.ollama4j.OllamaAPI;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools.annotations; package io.github.ollama4j.tools.annotations;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;

View File

@ -1,7 +1,14 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools.annotations; package io.github.ollama4j.tools.annotations;
import io.github.ollama4j.OllamaAPI; import io.github.ollama4j.OllamaAPI;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;

View File

@ -1,15 +1,21 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.tools.sampletools; package io.github.ollama4j.tools.sampletools;
import io.github.ollama4j.tools.Tools; import io.github.ollama4j.tools.Tools;
import java.util.Map; import java.util.Map;
@SuppressWarnings("resource") @SuppressWarnings("resource")
public class WeatherTool { public class WeatherTool {
private String paramCityName = "cityName"; private String paramCityName = "cityName";
public WeatherTool() { public WeatherTool() {}
}
public String getCurrentWeather(Map<String, Object> arguments) { public String getCurrentWeather(Map<String, Object> arguments) {
String city = (String) arguments.get(paramCityName); String city = (String) arguments.get(paramCityName);
@ -20,14 +26,14 @@ public class WeatherTool {
return Tools.ToolSpecification.builder() return Tools.ToolSpecification.builder()
.functionName("weather-reporter") .functionName("weather-reporter")
.functionDescription( .functionDescription(
"You are a tool who simply finds the city name from the user's message input/query about weather.") "You are a tool who simply finds the city name from the user's message"
+ " input/query about weather.")
.toolFunction(this::getCurrentWeather) .toolFunction(this::getCurrentWeather)
.toolPrompt( .toolPrompt(
Tools.PromptFuncDefinition.builder() Tools.PromptFuncDefinition.builder()
.type("prompt") .type("prompt")
.function( .function(
Tools.PromptFuncDefinition.PromptFuncSpec Tools.PromptFuncDefinition.PromptFuncSpec.builder()
.builder()
.name("get-city-name") .name("get-city-name")
.description("Get the city name") .description("Get the city name")
.parameters( .parameters(
@ -37,15 +43,24 @@ public class WeatherTool {
.properties( .properties(
Map.of( Map.of(
paramCityName, paramCityName,
Tools.PromptFuncDefinition.Property Tools
.PromptFuncDefinition
.Property
.builder() .builder()
.type("string") .type(
"string")
.description( .description(
"The name of the city. e.g. Bengaluru") "The name"
.required(true) + " of the"
+ " city."
+ " e.g."
+ " Bengaluru")
.required(
true)
.build())) .build()))
.required(java.util.List .required(
.of(paramCityName)) java.util.List.of(
paramCityName))
.build()) .build())
.build()) .build())
.build()) .build())

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.types; package io.github.ollama4j.types;
/** /**

View File

@ -1,15 +1,23 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.utils; package io.github.ollama4j.utils;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException; import java.io.IOException;
public class BooleanToJsonFormatFlagSerializer extends JsonSerializer<Boolean> { public class BooleanToJsonFormatFlagSerializer extends JsonSerializer<Boolean> {
@Override @Override
public void serialize(Boolean value, JsonGenerator gen, SerializerProvider serializers) throws IOException { public void serialize(Boolean value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString("json"); gen.writeString("json");
} }

View File

@ -1,9 +1,16 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.utils; package io.github.ollama4j.utils;
public final class Constants { public final class Constants {
public static final class HttpConstants { public static final class HttpConstants {
private HttpConstants() { private HttpConstants() {}
}
public static final String APPLICATION_JSON = "application/json"; public static final String APPLICATION_JSON = "application/json";
public static final String APPLICATION_XML = "application/xml"; public static final String APPLICATION_XML = "application/xml";

View File

@ -1,9 +1,16 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.utils; package io.github.ollama4j.utils;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException; import java.io.IOException;
import java.util.Base64; import java.util.Base64;
import java.util.Collection; import java.util.Collection;
@ -11,7 +18,9 @@ import java.util.Collection;
public class FileToBase64Serializer extends JsonSerializer<Collection<byte[]>> { public class FileToBase64Serializer extends JsonSerializer<Collection<byte[]>> {
@Override @Override
public void serialize(Collection<byte[]> value, JsonGenerator jsonGenerator, SerializerProvider serializers) throws IOException { public void serialize(
Collection<byte[]> value, JsonGenerator jsonGenerator, SerializerProvider serializers)
throws IOException {
jsonGenerator.writeStartArray(); jsonGenerator.writeStartArray();
for (byte[] file : value) { for (byte[] file : value) {
jsonGenerator.writeString(Base64.getEncoder().encodeToString(file)); jsonGenerator.writeString(Base64.getEncoder().encodeToString(file));

View File

@ -1,8 +1,15 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.utils; package io.github.ollama4j.utils;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import java.net.http.HttpRequest.BodyPublisher; import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers; import java.net.http.HttpRequest.BodyPublishers;
@ -19,8 +26,7 @@ public interface OllamaRequestBody {
@JsonIgnore @JsonIgnore
default BodyPublisher getBodyPublisher() { default BodyPublisher getBodyPublisher() {
try { try {
return BodyPublishers.ofString( return BodyPublishers.ofString(Utils.getObjectMapper().writeValueAsString(this));
Utils.getObjectMapper().writeValueAsString(this));
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new IllegalArgumentException("Request not Body convertible.", e); throw new IllegalArgumentException("Request not Body convertible.", e);
} }

View File

@ -1,8 +1,15 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.utils; package io.github.ollama4j.utils;
import lombok.Data;
import java.util.Map; import java.util.Map;
import lombok.Data;
/** /**
* Class for options for Ollama model. * Class for options for Ollama model.

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.utils; package io.github.ollama4j.utils;
import java.util.HashMap; import java.util.HashMap;
@ -230,15 +238,16 @@ public class OptionsBuilder {
* @return The updated OptionsBuilder. * @return The updated OptionsBuilder.
* @throws IllegalArgumentException if parameter has an unsupported type * @throws IllegalArgumentException if parameter has an unsupported type
*/ */
public OptionsBuilder setCustomOption(String name, Object value) throws IllegalArgumentException { public OptionsBuilder setCustomOption(String name, Object value)
throws IllegalArgumentException {
if (!(value instanceof Integer || value instanceof Float || value instanceof String)) { if (!(value instanceof Integer || value instanceof Float || value instanceof String)) {
throw new IllegalArgumentException("Invalid type for parameter. Allowed types are: Integer, Float, or String."); throw new IllegalArgumentException(
"Invalid type for parameter. Allowed types are: Integer, Float, or String.");
} }
options.getOptionsMap().put(name, value); options.getOptionsMap().put(name, value);
return this; return this;
} }
/** /**
* Builds the options map. * Builds the options map.
* *
@ -247,6 +256,4 @@ public class OptionsBuilder {
public Options build() { public Options build() {
return options; return options;
} }
} }

View File

@ -1,3 +1,11 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.utils; package io.github.ollama4j.utils;
/** /**

View File

@ -1,10 +1,15 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.utils; package io.github.ollama4j.utils;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@ -13,6 +18,8 @@ import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.time.Duration; import java.time.Duration;
import java.util.Objects; import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Utils { public class Utils {
private static final Logger LOG = LoggerFactory.getLogger(Utils.class); private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
@ -27,26 +34,40 @@ public class Utils {
return objectMapper; return objectMapper;
} }
public static byte[] loadImageBytesFromUrl(String imageUrl, int connectTimeoutSeconds, int readTimeoutSeconds) public static byte[] loadImageBytesFromUrl(
String imageUrl, int connectTimeoutSeconds, int readTimeoutSeconds)
throws IOException, InterruptedException { throws IOException, InterruptedException {
LOG.debug("Attempting to load image from URL: {} (connectTimeout={}s, readTimeout={}s)", imageUrl, connectTimeoutSeconds, readTimeoutSeconds); LOG.debug(
HttpClient client = HttpClient.newBuilder() "Attempting to load image from URL: {} (connectTimeout={}s, readTimeout={}s)",
imageUrl,
connectTimeoutSeconds,
readTimeoutSeconds);
HttpClient client =
HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(connectTimeoutSeconds)) .connectTimeout(Duration.ofSeconds(connectTimeoutSeconds))
.build(); .build();
HttpRequest request = HttpRequest.newBuilder() HttpRequest request =
HttpRequest.newBuilder()
.uri(URI.create(imageUrl)) .uri(URI.create(imageUrl))
.timeout(Duration.ofSeconds(readTimeoutSeconds)) .timeout(Duration.ofSeconds(readTimeoutSeconds))
.header("User-Agent", "Mozilla/5.0") .header("User-Agent", "Mozilla/5.0")
.GET() .GET()
.build(); .build();
LOG.debug("Sending HTTP GET request to {}", imageUrl); LOG.debug("Sending HTTP GET request to {}", imageUrl);
HttpResponse<byte[]> response = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); HttpResponse<byte[]> response =
client.send(request, HttpResponse.BodyHandlers.ofByteArray());
LOG.debug("Received HTTP response with status code: {}", response.statusCode()); LOG.debug("Received HTTP response with status code: {}", response.statusCode());
if (response.statusCode() >= 200 && response.statusCode() < 300) { if (response.statusCode() >= 200 && response.statusCode() < 300) {
LOG.debug("Successfully loaded image from URL: {} ({} bytes)", imageUrl, response.body().length); LOG.debug(
"Successfully loaded image from URL: {} ({} bytes)",
imageUrl,
response.body().length);
return response.body(); return response.body();
} else { } else {
LOG.error("Failed to load image from URL: {}. HTTP status: {}", imageUrl, response.statusCode()); LOG.error(
"Failed to load image from URL: {}. HTTP status: {}",
imageUrl,
response.statusCode());
throw new IOException("Failed to load image: HTTP " + response.statusCode()); throw new IOException("Failed to load image: HTTP " + response.statusCode());
} }
} }

View File

@ -1,10 +1,28 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.integrationtests; package io.github.ollama4j.integrationtests;
import static org.junit.jupiter.api.Assertions.*;
import io.github.ollama4j.OllamaAPI; import io.github.ollama4j.OllamaAPI;
import io.github.ollama4j.exceptions.OllamaBaseException; import io.github.ollama4j.exceptions.OllamaBaseException;
import io.github.ollama4j.models.response.OllamaResult; import io.github.ollama4j.models.response.OllamaResult;
import io.github.ollama4j.samples.AnnotatedTool; import io.github.ollama4j.samples.AnnotatedTool;
import io.github.ollama4j.tools.annotations.OllamaToolService; import io.github.ollama4j.tools.annotations.OllamaToolService;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Order;
@ -19,20 +37,14 @@ import org.testcontainers.ollama.OllamaContainer;
import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.MountableFile; import org.testcontainers.utility.MountableFile;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
@OllamaToolService(providers = {AnnotatedTool.class}) @OllamaToolService(providers = {AnnotatedTool.class})
@TestMethodOrder(OrderAnnotation.class) @TestMethodOrder(OrderAnnotation.class)
@SuppressWarnings({"HttpUrlsUsage", "SpellCheckingInspection", "resource", "ResultOfMethodCallIgnored"}) @SuppressWarnings({
"HttpUrlsUsage",
"SpellCheckingInspection",
"resource",
"ResultOfMethodCallIgnored"
})
public class WithAuth { public class WithAuth {
private static final Logger LOG = LoggerFactory.getLogger(WithAuth.class); private static final Logger LOG = LoggerFactory.getLogger(WithAuth.class);
@ -44,7 +56,6 @@ public class WithAuth {
private static final String GENERAL_PURPOSE_MODEL = "gemma3:270m"; private static final String GENERAL_PURPOSE_MODEL = "gemma3:270m";
// private static final String THINKING_MODEL = "gpt-oss:20b"; // private static final String THINKING_MODEL = "gpt-oss:20b";
private static OllamaContainer ollama; private static OllamaContainer ollama;
private static GenericContainer<?> nginx; private static GenericContainer<?> nginx;
private static OllamaAPI api; private static OllamaAPI api;
@ -63,43 +74,48 @@ public class WithAuth {
api.setRequestTimeoutSeconds(120); api.setRequestTimeoutSeconds(120);
api.setNumberOfRetriesForModelPull(3); api.setNumberOfRetriesForModelPull(3);
String ollamaUrl = "http://" + ollama.getHost() + ":" + ollama.getMappedPort(OLLAMA_INTERNAL_PORT); String ollamaUrl =
"http://" + ollama.getHost() + ":" + ollama.getMappedPort(OLLAMA_INTERNAL_PORT);
String nginxUrl = "http://" + nginx.getHost() + ":" + nginx.getMappedPort(NGINX_PORT); String nginxUrl = "http://" + nginx.getHost() + ":" + nginx.getMappedPort(NGINX_PORT);
LOG.info( LOG.info(
"The Ollama service is now accessible via the Nginx proxy with bearer-auth authentication mode.\n" + "The Ollama service is now accessible via the Nginx proxy with bearer-auth"
"→ Ollama URL: {}\n" + + " authentication mode.\n"
"→ Proxy URL: {}", + "→ Ollama URL: {}\n"
ollamaUrl, nginxUrl + "→ Proxy URL: {}",
); ollamaUrl,
nginxUrl);
LOG.info("OllamaAPI initialized with bearer auth token: {}", BEARER_AUTH_TOKEN); LOG.info("OllamaAPI initialized with bearer auth token: {}", BEARER_AUTH_TOKEN);
} }
private static OllamaContainer createOllamaContainer() { private static OllamaContainer createOllamaContainer() {
return new OllamaContainer("ollama/ollama:" + OLLAMA_VERSION).withExposedPorts(OLLAMA_INTERNAL_PORT); return new OllamaContainer("ollama/ollama:" + OLLAMA_VERSION)
.withExposedPorts(OLLAMA_INTERNAL_PORT);
} }
private static String generateNginxConfig(int ollamaPort) { private static String generateNginxConfig(int ollamaPort) {
return String.format("events {}\n" + return String.format(
"\n" + "events {}\n"
"http {\n" + + "\n"
" server {\n" + + "http {\n"
" listen 80;\n" + + " server {\n"
"\n" + + " listen 80;\n"
" location / {\n" + + "\n"
" set $auth_header $http_authorization;\n" + + " location / {\n"
"\n" + + " set $auth_header $http_authorization;\n"
" if ($auth_header != \"Bearer secret-token\") {\n" + + "\n"
" return 401;\n" + + " if ($auth_header != \"Bearer secret-token\") {\n"
" }\n" + + " return 401;\n"
"\n" + + " }\n"
" proxy_pass http://host.docker.internal:%s/;\n" + + "\n"
" proxy_set_header Host $host;\n" + + " proxy_pass http://host.docker.internal:%s/;\n"
" proxy_set_header X-Real-IP $remote_addr;\n" + + " proxy_set_header Host $host;\n"
" proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n" + + " proxy_set_header X-Real-IP $remote_addr;\n"
" proxy_set_header X-Forwarded-Proto $scheme;\n" + + " proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n"
" }\n" + + " proxy_set_header X-Forwarded-Proto $scheme;\n"
" }\n" + + " }\n"
"}\n", ollamaPort); + " }\n"
+ "}\n",
ollamaPort);
} }
public static GenericContainer<?> createNginxContainer(int ollamaPort) { public static GenericContainer<?> createNginxContainer(int ollamaPort) {
@ -117,14 +133,12 @@ public class WithAuth {
.withExposedPorts(NGINX_PORT) .withExposedPorts(NGINX_PORT)
.withCopyFileToContainer( .withCopyFileToContainer(
MountableFile.forHostPath(nginxConf.getAbsolutePath()), MountableFile.forHostPath(nginxConf.getAbsolutePath()),
"/etc/nginx/nginx.conf" "/etc/nginx/nginx.conf")
)
.withExtraHost("host.docker.internal", "host-gateway") .withExtraHost("host.docker.internal", "host-gateway")
.waitingFor( .waitingFor(
Wait.forHttp("/") Wait.forHttp("/")
.forStatusCode(401) .forStatusCode(401)
.withStartupTimeout(Duration.ofSeconds(30)) .withStartupTimeout(Duration.ofSeconds(30)));
);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Failed to create nginx.conf", e); throw new RuntimeException("Failed to create nginx.conf", e);
} }
@ -134,14 +148,18 @@ public class WithAuth {
@Order(1) @Order(1)
void testOllamaBehindProxy() { void testOllamaBehindProxy() {
api.setBearerAuth(BEARER_AUTH_TOKEN); api.setBearerAuth(BEARER_AUTH_TOKEN);
assertTrue(api.ping(), "Expected OllamaAPI to successfully ping through NGINX with valid auth token."); assertTrue(
api.ping(),
"Expected OllamaAPI to successfully ping through NGINX with valid auth token.");
} }
@Test @Test
@Order(1) @Order(1)
void testWithWrongToken() { void testWithWrongToken() {
api.setBearerAuth("wrong-token"); api.setBearerAuth("wrong-token");
assertFalse(api.ping(), "Expected OllamaAPI ping to fail through NGINX with an invalid auth token."); assertFalse(
api.ping(),
"Expected OllamaAPI ping to fail through NGINX with an invalid auth token.");
} }
@Test @Test
@ -152,13 +170,19 @@ public class WithAuth {
String model = GENERAL_PURPOSE_MODEL; String model = GENERAL_PURPOSE_MODEL;
api.pullModel(model); api.pullModel(model);
String prompt = "The sun is shining brightly and is directly overhead at the zenith, casting my shadow over my foot, so it must be noon."; String prompt =
"The sun is shining brightly and is directly overhead at the zenith, casting my"
+ " shadow over my foot, so it must be noon.";
Map<String, Object> format = new HashMap<>(); Map<String, Object> format = new HashMap<>();
format.put("type", "object"); format.put("type", "object");
format.put("properties", new HashMap<String, Object>() { format.put(
"properties",
new HashMap<String, Object>() {
{ {
put("isNoon", new HashMap<String, Object>() { put(
"isNoon",
new HashMap<String, Object>() {
{ {
put("type", "boolean"); put("type", "boolean");
} }

View File

@ -1,21 +1,35 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.samples; package io.github.ollama4j.samples;
import io.github.ollama4j.tools.annotations.ToolProperty; import io.github.ollama4j.tools.annotations.ToolProperty;
import io.github.ollama4j.tools.annotations.ToolSpec; import io.github.ollama4j.tools.annotations.ToolSpec;
import java.math.BigDecimal; import java.math.BigDecimal;
public class AnnotatedTool { public class AnnotatedTool {
@ToolSpec(desc = "Computes the most important constant all around the globe!") @ToolSpec(desc = "Computes the most important constant all around the globe!")
public String computeImportantConstant(@ToolProperty(name = "noOfDigits", desc = "Number of digits that shall be returned") Integer noOfDigits) { public String computeImportantConstant(
@ToolProperty(name = "noOfDigits", desc = "Number of digits that shall be returned")
Integer noOfDigits) {
return BigDecimal.valueOf((long) (Math.random() * 1000000L), noOfDigits).toString(); return BigDecimal.valueOf((long) (Math.random() * 1000000L), noOfDigits).toString();
} }
@ToolSpec(desc = "Says hello to a friend!") @ToolSpec(desc = "Says hello to a friend!")
public String sayHello(@ToolProperty(name = "name", desc = "Name of the friend") String name, @ToolProperty(name = "numberOfHearts", desc = "number of heart emojis that should be used", required = false) Integer numberOfHearts) { public String sayHello(
@ToolProperty(name = "name", desc = "Name of the friend") String name,
@ToolProperty(
name = "numberOfHearts",
desc = "number of heart emojis that should be used",
required = false)
Integer numberOfHearts) {
String hearts = numberOfHearts != null ? "".repeat(numberOfHearts) : ""; String hearts = numberOfHearts != null ? "".repeat(numberOfHearts) : "";
return "Hello, " + name + "! " + hearts; return "Hello, " + name + "! " + hearts;
} }
} }

View File

@ -1,24 +1,31 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.*;
import io.github.ollama4j.tools.annotations.OllamaToolService; import io.github.ollama4j.tools.annotations.OllamaToolService;
import io.github.ollama4j.tools.annotations.ToolProperty; import io.github.ollama4j.tools.annotations.ToolProperty;
import io.github.ollama4j.tools.annotations.ToolSpec; import io.github.ollama4j.tools.annotations.ToolSpec;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TestAnnotations { class TestAnnotations {
@OllamaToolService(providers = {SampleProvider.class}) @OllamaToolService(providers = {SampleProvider.class})
static class SampleToolService { static class SampleToolService {}
}
static class SampleProvider { static class SampleProvider {
@ToolSpec(name = "sum", desc = "adds two numbers") @ToolSpec(name = "sum", desc = "adds two numbers")
public int sum(@ToolProperty(name = "a", desc = "first addend") int a, public int sum(
@ToolProperty(name = "a", desc = "first addend") int a,
@ToolProperty(name = "b", desc = "second addend", required = false) int b) { @ToolProperty(name = "b", desc = "second addend", required = false) int b) {
return a + b; return a + b;
} }

View File

@ -1,12 +1,20 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.github.ollama4j.models.request.BasicAuth; import io.github.ollama4j.models.request.BasicAuth;
import io.github.ollama4j.models.request.BearerAuth; import io.github.ollama4j.models.request.BearerAuth;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TestAuth { class TestAuth {
@Test @Test

View File

@ -1,5 +1,15 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@ -8,8 +18,6 @@ import io.github.ollama4j.utils.BooleanToJsonFormatFlagSerializer;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TestBooleanToJsonFormatFlagSerializer { class TestBooleanToJsonFormatFlagSerializer {
static class Holder { static class Holder {

View File

@ -1,15 +1,22 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import io.github.ollama4j.utils.FileToBase64Serializer; import io.github.ollama4j.utils.FileToBase64Serializer;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestFileToBase64Serializer { public class TestFileToBase64Serializer {

View File

@ -1,5 +1,17 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.*;
import io.github.ollama4j.OllamaAPI; import io.github.ollama4j.OllamaAPI;
import io.github.ollama4j.exceptions.OllamaBaseException; import io.github.ollama4j.exceptions.OllamaBaseException;
import io.github.ollama4j.exceptions.RoleNotFoundException; import io.github.ollama4j.exceptions.RoleNotFoundException;
@ -12,18 +24,13 @@ import io.github.ollama4j.models.response.OllamaAsyncResultStreamer;
import io.github.ollama4j.models.response.OllamaResult; import io.github.ollama4j.models.response.OllamaResult;
import io.github.ollama4j.types.OllamaModelType; import io.github.ollama4j.types.OllamaModelType;
import io.github.ollama4j.utils.OptionsBuilder; import io.github.ollama4j.utils.OptionsBuilder;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.Mockito.*;
class TestMockedAPIs { class TestMockedAPIs {
@Test @Test
@ -54,7 +61,12 @@ class TestMockedAPIs {
@Test @Test
void testCreateModel() { void testCreateModel() {
OllamaAPI ollamaAPI = Mockito.mock(OllamaAPI.class); OllamaAPI ollamaAPI = Mockito.mock(OllamaAPI.class);
CustomModelRequest customModelRequest = CustomModelRequest.builder().model("mario").from("llama3.2:latest").system("You are Mario from Super Mario Bros.").build(); CustomModelRequest customModelRequest =
CustomModelRequest.builder()
.model("mario")
.from("llama3.2:latest")
.system("You are Mario from Super Mario Bros.")
.build();
try { try {
doNothing().when(ollamaAPI).createModel(customModelRequest); doNothing().when(ollamaAPI).createModel(customModelRequest);
ollamaAPI.createModel(customModelRequest); ollamaAPI.createModel(customModelRequest);
@ -128,7 +140,8 @@ class TestMockedAPIs {
String model = OllamaModelType.LLAMA2; String model = OllamaModelType.LLAMA2;
List<String> inputs = List.of("some prompt text"); List<String> inputs = List.of("some prompt text");
try { try {
when(ollamaAPI.embed(new OllamaEmbedRequestModel(model, inputs))).thenReturn(new OllamaEmbedResponseModel()); when(ollamaAPI.embed(new OllamaEmbedRequestModel(model, inputs)))
.thenReturn(new OllamaEmbedResponseModel());
ollamaAPI.embed(new OllamaEmbedRequestModel(model, inputs)); ollamaAPI.embed(new OllamaEmbedRequestModel(model, inputs));
verify(ollamaAPI, times(1)).embed(new OllamaEmbedRequestModel(model, inputs)); verify(ollamaAPI, times(1)).embed(new OllamaEmbedRequestModel(model, inputs));
} catch (IOException | OllamaBaseException | InterruptedException e) { } catch (IOException | OllamaBaseException | InterruptedException e) {
@ -146,7 +159,8 @@ class TestMockedAPIs {
when(ollamaAPI.generate(model, prompt, false, false, optionsBuilder.build())) when(ollamaAPI.generate(model, prompt, false, false, optionsBuilder.build()))
.thenReturn(new OllamaResult("", "", 0, 200)); .thenReturn(new OllamaResult("", "", 0, 200));
ollamaAPI.generate(model, prompt, false, false, optionsBuilder.build()); ollamaAPI.generate(model, prompt, false, false, optionsBuilder.build());
verify(ollamaAPI, times(1)).generate(model, prompt, false, false, optionsBuilder.build()); verify(ollamaAPI, times(1))
.generate(model, prompt, false, false, optionsBuilder.build());
} catch (IOException | OllamaBaseException | InterruptedException e) { } catch (IOException | OllamaBaseException | InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -159,13 +173,28 @@ class TestMockedAPIs {
String prompt = "some prompt text"; String prompt = "some prompt text";
try { try {
when(ollamaAPI.generateWithImages( when(ollamaAPI.generateWithImages(
model, prompt, Collections.emptyList(), new OptionsBuilder().build(), null, null)) model,
prompt,
Collections.emptyList(),
new OptionsBuilder().build(),
null,
null))
.thenReturn(new OllamaResult("", "", 0, 200)); .thenReturn(new OllamaResult("", "", 0, 200));
ollamaAPI.generateWithImages( ollamaAPI.generateWithImages(
model, prompt, Collections.emptyList(), new OptionsBuilder().build(), null, null); model,
prompt,
Collections.emptyList(),
new OptionsBuilder().build(),
null,
null);
verify(ollamaAPI, times(1)) verify(ollamaAPI, times(1))
.generateWithImages( .generateWithImages(
model, prompt, Collections.emptyList(), new OptionsBuilder().build(), null, null); model,
prompt,
Collections.emptyList(),
new OptionsBuilder().build(),
null,
null);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -178,13 +207,28 @@ class TestMockedAPIs {
String prompt = "some prompt text"; String prompt = "some prompt text";
try { try {
when(ollamaAPI.generateWithImages( when(ollamaAPI.generateWithImages(
model, prompt, Collections.emptyList(), new OptionsBuilder().build(), null, null)) model,
prompt,
Collections.emptyList(),
new OptionsBuilder().build(),
null,
null))
.thenReturn(new OllamaResult("", "", 0, 200)); .thenReturn(new OllamaResult("", "", 0, 200));
ollamaAPI.generateWithImages( ollamaAPI.generateWithImages(
model, prompt, Collections.emptyList(), new OptionsBuilder().build(), null, null); model,
prompt,
Collections.emptyList(),
new OptionsBuilder().build(),
null,
null);
verify(ollamaAPI, times(1)) verify(ollamaAPI, times(1))
.generateWithImages( .generateWithImages(
model, prompt, Collections.emptyList(), new OptionsBuilder().build(), null, null); model,
prompt,
Collections.emptyList(),
new OptionsBuilder().build(),
null,
null);
} catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) { } catch (IOException | OllamaBaseException | InterruptedException | URISyntaxException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -229,7 +273,8 @@ class TestMockedAPIs {
OllamaAPI ollamaAPI = mock(OllamaAPI.class); OllamaAPI ollamaAPI = mock(OllamaAPI.class);
String roleName = "non-existing-role"; String roleName = "non-existing-role";
try { try {
when(ollamaAPI.getRole(roleName)).thenThrow(new RoleNotFoundException("Role not found")); when(ollamaAPI.getRole(roleName))
.thenThrow(new RoleNotFoundException("Role not found"));
} catch (RoleNotFoundException exception) { } catch (RoleNotFoundException exception) {
throw new RuntimeException("Failed to run test: testGetRoleNotFound"); throw new RuntimeException("Failed to run test: testGetRoleNotFound");
} }

View File

@ -1,23 +1,33 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import io.github.ollama4j.models.chat.OllamaChatMessage; import io.github.ollama4j.models.chat.OllamaChatMessage;
import io.github.ollama4j.models.chat.OllamaChatMessageRole; import io.github.ollama4j.models.chat.OllamaChatMessageRole;
import org.json.JSONObject; import org.json.JSONObject;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TestOllamaChatMessage { class TestOllamaChatMessage {
@Test @Test
void testToStringProducesJson() { void testToStringProducesJson() {
OllamaChatMessage msg = new OllamaChatMessage(OllamaChatMessageRole.USER, "hello", null, null, null); OllamaChatMessage msg =
new OllamaChatMessage(OllamaChatMessageRole.USER, "hello", null, null, null);
String json = msg.toString(); String json = msg.toString();
JSONObject obj = new JSONObject(json); JSONObject obj = new JSONObject(json);
assertEquals("user", obj.getString("role")); assertEquals("user", obj.getString("role"));
assertEquals("hello", obj.getString("content")); assertEquals("hello", obj.getString("content"));
assertTrue(obj.has("tool_calls")); assertTrue(obj.has("tool_calls"));
// thinking and images may or may not be present depending on null handling, just ensure no exception // thinking and images may or may not be present depending on null handling, just ensure no
// exception
} }
} }

View File

@ -1,12 +1,19 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.*;
import io.github.ollama4j.exceptions.RoleNotFoundException; import io.github.ollama4j.exceptions.RoleNotFoundException;
import io.github.ollama4j.models.chat.OllamaChatMessageRole; import io.github.ollama4j.models.chat.OllamaChatMessageRole;
import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TestOllamaChatMessageRole { class TestOllamaChatMessageRole {
@ -33,12 +40,14 @@ class TestOllamaChatMessageRole {
void testCustomRoleCreationAndLookup() throws Exception { void testCustomRoleCreationAndLookup() throws Exception {
OllamaChatMessageRole custom = OllamaChatMessageRole.newCustomRole("myrole"); OllamaChatMessageRole custom = OllamaChatMessageRole.newCustomRole("myrole");
assertEquals("myrole", custom.toString()); assertEquals("myrole", custom.toString());
// custom roles are registered globally (per current implementation), so lookup should succeed // custom roles are registered globally (per current implementation), so lookup should
// succeed
assertSame(custom, OllamaChatMessageRole.getRole("myrole")); assertSame(custom, OllamaChatMessageRole.getRole("myrole"));
} }
@Test @Test
void testGetRoleThrowsOnUnknown() { void testGetRoleThrowsOnUnknown() {
assertThrows(RoleNotFoundException.class, () -> OllamaChatMessageRole.getRole("does-not-exist")); assertThrows(
RoleNotFoundException.class, () -> OllamaChatMessageRole.getRole("does-not-exist"));
} }
} }

View File

@ -1,20 +1,28 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.*;
import io.github.ollama4j.models.chat.OllamaChatMessage; import io.github.ollama4j.models.chat.OllamaChatMessage;
import io.github.ollama4j.models.chat.OllamaChatMessageRole; import io.github.ollama4j.models.chat.OllamaChatMessageRole;
import io.github.ollama4j.models.chat.OllamaChatRequest; import io.github.ollama4j.models.chat.OllamaChatRequest;
import io.github.ollama4j.models.chat.OllamaChatRequestBuilder; import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
import org.junit.jupiter.api.Test;
import java.util.Collections; import java.util.Collections;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TestOllamaChatRequestBuilder { class TestOllamaChatRequestBuilder {
@Test @Test
void testResetClearsMessagesButKeepsModelAndThink() { void testResetClearsMessagesButKeepsModelAndThink() {
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance("my-model") OllamaChatRequestBuilder builder =
OllamaChatRequestBuilder.getInstance("my-model")
.withThinking(true) .withThinking(true)
.withMessage(OllamaChatMessageRole.USER, "first"); .withMessage(OllamaChatMessageRole.USER, "first");
@ -33,17 +41,22 @@ class TestOllamaChatRequestBuilder {
@Test @Test
void testImageUrlFailuresAreIgnoredAndDoNotBreakBuild() { void testImageUrlFailuresAreIgnoredAndDoNotBreakBuild() {
// Provide a syntactically invalid URL, but catch the expected exception to verify builder robustness // Provide a syntactically invalid URL, but catch the expected exception to verify builder
// robustness
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance("m"); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance("m");
try { try {
builder.withMessage(OllamaChatMessageRole.USER, "hi", Collections.emptyList(), builder.withMessage(
OllamaChatMessageRole.USER,
"hi",
Collections.emptyList(),
"ht!tp://invalid url \n not a uri"); "ht!tp://invalid url \n not a uri");
fail("Expected IllegalArgumentException due to malformed URL"); fail("Expected IllegalArgumentException due to malformed URL");
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// Expected: malformed URL should throw IllegalArgumentException // Expected: malformed URL should throw IllegalArgumentException
} }
// The builder should still be usable after the exception // The builder should still be usable after the exception
OllamaChatRequest req = builder.withMessage(OllamaChatMessageRole.USER, "hello", Collections.emptyList()) OllamaChatRequest req =
builder.withMessage(OllamaChatMessageRole.USER, "hello", Collections.emptyList())
.build(); .build();
assertNotNull(req.getMessages()); assertNotNull(req.getMessages());

View File

@ -1,15 +1,22 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import io.github.ollama4j.utils.OllamaRequestBody; import io.github.ollama4j.utils.OllamaRequestBody;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import org.junit.jupiter.api.Test;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.concurrent.Flow; import java.util.concurrent.Flow;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TestOllamaRequestBody { class TestOllamaRequestBody {
@ -30,7 +37,8 @@ class TestOllamaRequestBody {
var publisher = req.getBodyPublisher(); var publisher = req.getBodyPublisher();
StringBuilder data = new StringBuilder(); StringBuilder data = new StringBuilder();
publisher.subscribe(new Flow.Subscriber<>() { publisher.subscribe(
new Flow.Subscriber<>() {
@Override @Override
public void onSubscribe(Flow.Subscription subscription) { public void onSubscribe(Flow.Subscription subscription) {
subscription.request(Long.MAX_VALUE); subscription.request(Long.MAX_VALUE);
@ -42,17 +50,16 @@ class TestOllamaRequestBody {
} }
@Override @Override
public void onError(Throwable throwable) { public void onError(Throwable throwable) {}
}
@Override @Override
public void onComplete() { public void onComplete() {}
}
}); });
// Trigger the publishing by converting it to a string via the same mapper for determinism // Trigger the publishing by converting it to a string via the same mapper for determinism
String expected = Utils.getObjectMapper().writeValueAsString(req); String expected = Utils.getObjectMapper().writeValueAsString(req);
// Due to asynchronous nature, expected content already delivered synchronously by StringPublisher // Due to asynchronous nature, expected content already delivered synchronously by
// StringPublisher
assertEquals(expected, data.toString()); assertEquals(expected, data.toString());
} }
} }

View File

@ -1,15 +1,22 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.*;
import io.github.ollama4j.models.response.OllamaResult; import io.github.ollama4j.models.response.OllamaResult;
import io.github.ollama4j.tools.OllamaToolsResult; import io.github.ollama4j.tools.OllamaToolsResult;
import io.github.ollama4j.tools.ToolFunctionCallSpec; import io.github.ollama4j.tools.ToolFunctionCallSpec;
import org.junit.jupiter.api.Test;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestOllamaToolsResult { public class TestOllamaToolsResult {

View File

@ -1,21 +1,29 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.*;
import io.github.ollama4j.utils.Options; import io.github.ollama4j.utils.Options;
import io.github.ollama4j.utils.OptionsBuilder; import io.github.ollama4j.utils.OptionsBuilder;
import io.github.ollama4j.utils.PromptBuilder; import io.github.ollama4j.utils.PromptBuilder;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import org.junit.jupiter.api.Test;
import java.io.File; import java.io.File;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TestOptionsAndUtils { class TestOptionsAndUtils {
@Test @Test
void testOptionsBuilderSetsValues() { void testOptionsBuilderSetsValues() {
Options options = new OptionsBuilder() Options options =
new OptionsBuilder()
.setMirostat(1) .setMirostat(1)
.setMirostatEta(0.2f) .setMirostatEta(0.2f)
.setMirostatTau(4.5f) .setMirostatTau(4.5f)
@ -60,19 +68,22 @@ class TestOptionsAndUtils {
@Test @Test
void testOptionsBuilderRejectsUnsupportedCustomType() { void testOptionsBuilderRejectsUnsupportedCustomType() {
OptionsBuilder builder = new OptionsBuilder(); OptionsBuilder builder = new OptionsBuilder();
assertThrows(IllegalArgumentException.class, () -> builder.setCustomOption("bad", new Object())); assertThrows(
IllegalArgumentException.class, () -> builder.setCustomOption("bad", new Object()));
} }
@Test @Test
void testPromptBuilderBuildsExpectedString() { void testPromptBuilderBuildsExpectedString() {
String prompt = new PromptBuilder() String prompt =
new PromptBuilder()
.add("Hello") .add("Hello")
.addLine(", world!") .addLine(", world!")
.addSeparator() .addSeparator()
.add("Continue.") .add("Continue.")
.build(); .build();
String expected = "Hello, world!\n\n--------------------------------------------------\nContinue."; String expected =
"Hello, world!\n\n--------------------------------------------------\nContinue.";
assertEquals(expected, prompt); assertEquals(expected, prompt);
} }
@ -80,7 +91,8 @@ class TestOptionsAndUtils {
void testUtilsGetObjectMapperSingletonAndModule() { void testUtilsGetObjectMapperSingletonAndModule() {
assertSame(Utils.getObjectMapper(), Utils.getObjectMapper()); assertSame(Utils.getObjectMapper(), Utils.getObjectMapper());
// Basic serialization sanity check with JavaTimeModule registered // Basic serialization sanity check with JavaTimeModule registered
assertDoesNotThrow(() -> Utils.getObjectMapper().writeValueAsString(java.time.OffsetDateTime.now())); assertDoesNotThrow(
() -> Utils.getObjectMapper().writeValueAsString(java.time.OffsetDateTime.now()));
} }
@Test @Test

View File

@ -1,14 +1,21 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import io.github.ollama4j.tools.ReflectionalToolFunction; import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import io.github.ollama4j.tools.ReflectionalToolFunction;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TestReflectionalToolFunction { class TestReflectionalToolFunction {
@ -25,7 +32,9 @@ class TestReflectionalToolFunction {
@Test @Test
void testApplyInvokesMethodWithTypeCasting() throws Exception { void testApplyInvokesMethodWithTypeCasting() throws Exception {
SampleToolHolder holder = new SampleToolHolder(); SampleToolHolder holder = new SampleToolHolder();
Method method = SampleToolHolder.class.getMethod("combine", Integer.class, Boolean.class, BigDecimal.class, String.class); Method method =
SampleToolHolder.class.getMethod(
"combine", Integer.class, Boolean.class, BigDecimal.class, String.class);
LinkedHashMap<String, String> propDef = new LinkedHashMap<>(); LinkedHashMap<String, String> propDef = new LinkedHashMap<>();
// preserve order to match method parameters // preserve order to match method parameters
@ -36,7 +45,8 @@ class TestReflectionalToolFunction {
ReflectionalToolFunction fn = new ReflectionalToolFunction(holder, method, propDef); ReflectionalToolFunction fn = new ReflectionalToolFunction(holder, method, propDef);
Map<String, Object> args = Map.of( Map<String, Object> args =
Map.of(
"i", "42", "i", "42",
"b", "true", "b", "true",
"d", "3.14", "d", "3.14",
@ -50,7 +60,9 @@ class TestReflectionalToolFunction {
@Test @Test
void testTypeCastNullsWhenClassOrValueIsNull() throws Exception { void testTypeCastNullsWhenClassOrValueIsNull() throws Exception {
SampleToolHolder holder = new SampleToolHolder(); SampleToolHolder holder = new SampleToolHolder();
Method method = SampleToolHolder.class.getMethod("combine", Integer.class, Boolean.class, BigDecimal.class, String.class); Method method =
SampleToolHolder.class.getMethod(
"combine", Integer.class, Boolean.class, BigDecimal.class, String.class);
LinkedHashMap<String, String> propDef = new LinkedHashMap<>(); LinkedHashMap<String, String> propDef = new LinkedHashMap<>();
propDef.put("i", null); // className null -> expect null passed propDef.put("i", null); // className null -> expect null passed

View File

@ -1,13 +1,20 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.*;
import io.github.ollama4j.tools.ToolFunction; import io.github.ollama4j.tools.ToolFunction;
import io.github.ollama4j.tools.ToolRegistry; import io.github.ollama4j.tools.ToolRegistry;
import io.github.ollama4j.tools.Tools; import io.github.ollama4j.tools.Tools;
import org.junit.jupiter.api.Test;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class TestToolRegistry { class TestToolRegistry {
@ -16,7 +23,8 @@ class TestToolRegistry {
ToolRegistry registry = new ToolRegistry(); ToolRegistry registry = new ToolRegistry();
ToolFunction fn = args -> "ok:" + args.get("x"); ToolFunction fn = args -> "ok:" + args.get("x");
Tools.ToolSpecification spec = Tools.ToolSpecification.builder() Tools.ToolSpecification spec =
Tools.ToolSpecification.builder()
.functionName("test") .functionName("test")
.functionDescription("desc") .functionDescription("desc")
.toolFunction(fn) .toolFunction(fn)

View File

@ -1,54 +1,65 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests; package io.github.ollama4j.unittests;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import io.github.ollama4j.tools.Tools; import io.github.ollama4j.tools.Tools;
import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TestToolsPromptBuilder { class TestToolsPromptBuilder {
@Test @Test
void testPromptBuilderIncludesToolsAndPrompt() throws JsonProcessingException { void testPromptBuilderIncludesToolsAndPrompt() throws JsonProcessingException {
Tools.PromptFuncDefinition.Property cityProp = Tools.PromptFuncDefinition.Property.builder() Tools.PromptFuncDefinition.Property cityProp =
Tools.PromptFuncDefinition.Property.builder()
.type("string") .type("string")
.description("city name") .description("city name")
.required(true) .required(true)
.build(); .build();
Tools.PromptFuncDefinition.Property unitsProp = Tools.PromptFuncDefinition.Property.builder() Tools.PromptFuncDefinition.Property unitsProp =
Tools.PromptFuncDefinition.Property.builder()
.type("string") .type("string")
.description("units") .description("units")
.enumValues(List.of("metric", "imperial")) .enumValues(List.of("metric", "imperial"))
.required(false) .required(false)
.build(); .build();
Tools.PromptFuncDefinition.Parameters params = Tools.PromptFuncDefinition.Parameters.builder() Tools.PromptFuncDefinition.Parameters params =
Tools.PromptFuncDefinition.Parameters.builder()
.type("object") .type("object")
.properties(Map.of("city", cityProp, "units", unitsProp)) .properties(Map.of("city", cityProp, "units", unitsProp))
.build(); .build();
Tools.PromptFuncDefinition.PromptFuncSpec spec = Tools.PromptFuncDefinition.PromptFuncSpec.builder() Tools.PromptFuncDefinition.PromptFuncSpec spec =
Tools.PromptFuncDefinition.PromptFuncSpec.builder()
.name("getWeather") .name("getWeather")
.description("Get weather for a city") .description("Get weather for a city")
.parameters(params) .parameters(params)
.build(); .build();
Tools.PromptFuncDefinition def = Tools.PromptFuncDefinition.builder() Tools.PromptFuncDefinition def =
.type("function") Tools.PromptFuncDefinition.builder().type("function").function(spec).build();
.function(spec)
.build();
Tools.ToolSpecification toolSpec = Tools.ToolSpecification.builder() Tools.ToolSpecification toolSpec =
Tools.ToolSpecification.builder()
.functionName("getWeather") .functionName("getWeather")
.functionDescription("Get weather for a city") .functionDescription("Get weather for a city")
.toolPrompt(def) .toolPrompt(def)
.build(); .build();
Tools.PromptBuilder pb = new Tools.PromptBuilder() Tools.PromptBuilder pb =
new Tools.PromptBuilder()
.withToolSpecification(toolSpec) .withToolSpecification(toolSpec)
.withPrompt("Tell me the weather."); .withPrompt("Tell me the weather.");

View File

@ -1,12 +1,20 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests.jackson; package io.github.ollama4j.unittests.jackson;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.ollama4j.utils.Utils; import io.github.ollama4j.utils.Utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
public abstract class AbstractSerializationTest<T> { public abstract class AbstractSerializationTest<T> {
protected ObjectMapper mapper = Utils.getObjectMapper(); protected ObjectMapper mapper = Utils.getObjectMapper();
@ -29,8 +37,7 @@ public abstract class AbstractSerializationTest<T> {
} }
} }
protected void assertEqualsAfterUnmarshalling(T unmarshalledObject, protected void assertEqualsAfterUnmarshalling(T unmarshalledObject, T req) {
T req) {
assertEquals(req, unmarshalledObject); assertEquals(req, unmarshalledObject);
} }
} }

View File

@ -1,19 +1,26 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests.jackson; package io.github.ollama4j.unittests.jackson;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
import io.github.ollama4j.models.chat.OllamaChatMessageRole; import io.github.ollama4j.models.chat.OllamaChatMessageRole;
import io.github.ollama4j.models.chat.OllamaChatRequest; import io.github.ollama4j.models.chat.OllamaChatRequest;
import io.github.ollama4j.models.chat.OllamaChatRequestBuilder; import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
import io.github.ollama4j.utils.OptionsBuilder; import io.github.ollama4j.utils.OptionsBuilder;
import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File; import java.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.json.JSONObject;
import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly; import org.junit.jupiter.api.Test;
public class TestChatRequestSerialization extends AbstractSerializationTest<OllamaChatRequest> { public class TestChatRequestSerialization extends AbstractSerializationTest<OllamaChatRequest> {
@ -26,14 +33,16 @@ public class TestChatRequestSerialization extends AbstractSerializationTest<Olla
@Test @Test
public void testRequestOnlyMandatoryFields() { public void testRequestOnlyMandatoryFields() {
OllamaChatRequest req = builder.withMessage(OllamaChatMessageRole.USER, "Some prompt").build(); OllamaChatRequest req =
builder.withMessage(OllamaChatMessageRole.USER, "Some prompt").build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
assertEqualsAfterUnmarshalling(deserialize(jsonRequest, OllamaChatRequest.class), req); assertEqualsAfterUnmarshalling(deserialize(jsonRequest, OllamaChatRequest.class), req);
} }
@Test @Test
public void testRequestMultipleMessages() { public void testRequestMultipleMessages() {
OllamaChatRequest req = builder.withMessage(OllamaChatMessageRole.SYSTEM, "System prompt") OllamaChatRequest req =
builder.withMessage(OllamaChatMessageRole.SYSTEM, "System prompt")
.withMessage(OllamaChatMessageRole.USER, "Some prompt") .withMessage(OllamaChatMessageRole.USER, "Some prompt")
.build(); .build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
@ -42,8 +51,13 @@ public class TestChatRequestSerialization extends AbstractSerializationTest<Olla
@Test @Test
public void testRequestWithMessageAndImage() { public void testRequestWithMessageAndImage() {
OllamaChatRequest req = builder.withMessage(OllamaChatMessageRole.USER, "Some prompt", Collections.emptyList(), OllamaChatRequest req =
List.of(new File("src/test/resources/dog-on-a-boat.jpg"))).build(); builder.withMessage(
OllamaChatMessageRole.USER,
"Some prompt",
Collections.emptyList(),
List.of(new File("src/test/resources/dog-on-a-boat.jpg")))
.build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
assertEqualsAfterUnmarshalling(deserialize(jsonRequest, OllamaChatRequest.class), req); assertEqualsAfterUnmarshalling(deserialize(jsonRequest, OllamaChatRequest.class), req);
} }
@ -51,7 +65,8 @@ public class TestChatRequestSerialization extends AbstractSerializationTest<Olla
@Test @Test
public void testRequestWithOptions() { public void testRequestWithOptions() {
OptionsBuilder b = new OptionsBuilder(); OptionsBuilder b = new OptionsBuilder();
OllamaChatRequest req = builder.withMessage(OllamaChatMessageRole.USER, "Some prompt") OllamaChatRequest req =
builder.withMessage(OllamaChatMessageRole.USER, "Some prompt")
.withOptions(b.setMirostat(1).build()) .withOptions(b.setMirostat(1).build())
.withOptions(b.setTemperature(1L).build()) .withOptions(b.setTemperature(1L).build())
.withOptions(b.setMirostatEta(1L).build()) .withOptions(b.setMirostatEta(1L).build())
@ -86,17 +101,23 @@ public class TestChatRequestSerialization extends AbstractSerializationTest<Olla
@Test @Test
public void testRequestWithInvalidCustomOption() { public void testRequestWithInvalidCustomOption() {
OptionsBuilder b = new OptionsBuilder(); OptionsBuilder b = new OptionsBuilder();
assertThrowsExactly(IllegalArgumentException.class, () -> { assertThrowsExactly(
OllamaChatRequest req = builder.withMessage(OllamaChatMessageRole.USER, "Some prompt") IllegalArgumentException.class,
.withOptions(b.setCustomOption("cust_obj", new Object()).build()) () -> {
OllamaChatRequest req =
builder.withMessage(OllamaChatMessageRole.USER, "Some prompt")
.withOptions(
b.setCustomOption("cust_obj", new Object()).build())
.build(); .build();
}); });
} }
@Test @Test
public void testWithJsonFormat() { public void testWithJsonFormat() {
OllamaChatRequest req = builder.withMessage(OllamaChatMessageRole.USER, "Some prompt") OllamaChatRequest req =
.withGetJsonResponse().build(); builder.withMessage(OllamaChatMessageRole.USER, "Some prompt")
.withGetJsonResponse()
.build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
// no jackson deserialization as format property is not boolean ==> omit as deserialization // no jackson deserialization as format property is not boolean ==> omit as deserialization
@ -108,8 +129,7 @@ public class TestChatRequestSerialization extends AbstractSerializationTest<Olla
@Test @Test
public void testWithTemplate() { public void testWithTemplate() {
OllamaChatRequest req = builder.withTemplate("System Template") OllamaChatRequest req = builder.withTemplate("System Template").build();
.build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
assertEqualsAfterUnmarshalling(deserialize(jsonRequest, OllamaChatRequest.class), req); assertEqualsAfterUnmarshalling(deserialize(jsonRequest, OllamaChatRequest.class), req);
} }
@ -124,9 +144,10 @@ public class TestChatRequestSerialization extends AbstractSerializationTest<Olla
@Test @Test
public void testWithKeepAlive() { public void testWithKeepAlive() {
String expectedKeepAlive = "5m"; String expectedKeepAlive = "5m";
OllamaChatRequest req = builder.withKeepAlive(expectedKeepAlive) OllamaChatRequest req = builder.withKeepAlive(expectedKeepAlive).build();
.build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
assertEquals(deserialize(jsonRequest, OllamaChatRequest.class).getKeepAlive(), expectedKeepAlive); assertEquals(
deserialize(jsonRequest, OllamaChatRequest.class).getKeepAlive(),
expectedKeepAlive);
} }
} }

View File

@ -1,14 +1,23 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests.jackson; package io.github.ollama4j.unittests.jackson;
import static org.junit.jupiter.api.Assertions.assertEquals;
import io.github.ollama4j.models.embeddings.OllamaEmbedRequestBuilder; import io.github.ollama4j.models.embeddings.OllamaEmbedRequestBuilder;
import io.github.ollama4j.models.embeddings.OllamaEmbedRequestModel; import io.github.ollama4j.models.embeddings.OllamaEmbedRequestModel;
import io.github.ollama4j.utils.OptionsBuilder; import io.github.ollama4j.utils.OptionsBuilder;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; public class TestEmbedRequestSerialization
extends AbstractSerializationTest<OllamaEmbedRequestModel> {
public class TestEmbedRequestSerialization extends AbstractSerializationTest<OllamaEmbedRequestModel> {
private OllamaEmbedRequestBuilder builder; private OllamaEmbedRequestBuilder builder;
@ -21,17 +30,18 @@ public class TestEmbedRequestSerialization extends AbstractSerializationTest<Oll
public void testRequestOnlyMandatoryFields() { public void testRequestOnlyMandatoryFields() {
OllamaEmbedRequestModel req = builder.build(); OllamaEmbedRequestModel req = builder.build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
assertEqualsAfterUnmarshalling(deserialize(jsonRequest, OllamaEmbedRequestModel.class), req); assertEqualsAfterUnmarshalling(
deserialize(jsonRequest, OllamaEmbedRequestModel.class), req);
} }
@Test @Test
public void testRequestWithOptions() { public void testRequestWithOptions() {
OptionsBuilder b = new OptionsBuilder(); OptionsBuilder b = new OptionsBuilder();
OllamaEmbedRequestModel req = builder OllamaEmbedRequestModel req = builder.withOptions(b.setMirostat(1).build()).build();
.withOptions(b.setMirostat(1).build()).build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
OllamaEmbedRequestModel deserializeRequest = deserialize(jsonRequest, OllamaEmbedRequestModel.class); OllamaEmbedRequestModel deserializeRequest =
deserialize(jsonRequest, OllamaEmbedRequestModel.class);
assertEqualsAfterUnmarshalling(deserializeRequest, req); assertEqualsAfterUnmarshalling(deserializeRequest, req);
assertEquals(1, deserializeRequest.getOptions().get("mirostat")); assertEquals(1, deserializeRequest.getOptions().get("mirostat"));
} }

View File

@ -1,5 +1,15 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests.jackson; package io.github.ollama4j.unittests.jackson;
import static org.junit.jupiter.api.Assertions.assertEquals;
import io.github.ollama4j.models.generate.OllamaGenerateRequest; import io.github.ollama4j.models.generate.OllamaGenerateRequest;
import io.github.ollama4j.models.generate.OllamaGenerateRequestBuilder; import io.github.ollama4j.models.generate.OllamaGenerateRequestBuilder;
import io.github.ollama4j.utils.OptionsBuilder; import io.github.ollama4j.utils.OptionsBuilder;
@ -7,9 +17,8 @@ import org.json.JSONObject;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; public class TestGenerateRequestSerialization
extends AbstractSerializationTest<OllamaGenerateRequest> {
public class TestGenerateRequestSerialization extends AbstractSerializationTest<OllamaGenerateRequest> {
private OllamaGenerateRequestBuilder builder; private OllamaGenerateRequestBuilder builder;
@ -33,15 +42,15 @@ public class TestGenerateRequestSerialization extends AbstractSerializationTest<
builder.withPrompt("Some prompt").withOptions(b.setMirostat(1).build()).build(); builder.withPrompt("Some prompt").withOptions(b.setMirostat(1).build()).build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
OllamaGenerateRequest deserializeRequest = deserialize(jsonRequest, OllamaGenerateRequest.class); OllamaGenerateRequest deserializeRequest =
deserialize(jsonRequest, OllamaGenerateRequest.class);
assertEqualsAfterUnmarshalling(deserializeRequest, req); assertEqualsAfterUnmarshalling(deserializeRequest, req);
assertEquals(1, deserializeRequest.getOptions().get("mirostat")); assertEquals(1, deserializeRequest.getOptions().get("mirostat"));
} }
@Test @Test
public void testWithJsonFormat() { public void testWithJsonFormat() {
OllamaGenerateRequest req = OllamaGenerateRequest req = builder.withPrompt("Some prompt").withGetJsonResponse().build();
builder.withPrompt("Some prompt").withGetJsonResponse().build();
String jsonRequest = serialize(req); String jsonRequest = serialize(req);
System.out.printf(jsonRequest); System.out.printf(jsonRequest);

View File

@ -1,17 +1,26 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests.jackson; package io.github.ollama4j.unittests.jackson;
import static org.junit.jupiter.api.Assertions.*;
import io.github.ollama4j.models.response.ModelPullResponse; import io.github.ollama4j.models.response.ModelPullResponse;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* Test serialization and deserialization of ModelPullResponse, * Test serialization and deserialization of ModelPullResponse,
* This test verifies that the ModelPullResponse class can properly parse * This test verifies that the ModelPullResponse class can properly parse
* error responses from Ollama server that return HTTP 200 with error messages * error responses from Ollama server that return HTTP 200 with error messages
* in the JSON body. * in the JSON body.
*/ */
public class TestModelPullResponseSerialization extends AbstractSerializationTest<ModelPullResponse> { public class TestModelPullResponseSerialization
extends AbstractSerializationTest<ModelPullResponse> {
/** /**
* Test the specific error case reported in GitHub issue #138. * Test the specific error case reported in GitHub issue #138.
@ -20,7 +29,16 @@ public class TestModelPullResponseSerialization extends AbstractSerializationTes
@Test @Test
public void testDeserializationWithErrorFromGitHubIssue138() { public void testDeserializationWithErrorFromGitHubIssue138() {
// This is the exact error JSON from GitHub issue #138 // This is the exact error JSON from GitHub issue #138
String errorJson = "{\"error\":\"pull model manifest: 412: \\n\\nThe model you are attempting to pull requires a newer version of Ollama.\\n\\nPlease download the latest version at:\\n\\n\\thttps://ollama.com/download\\n\\n\"}"; String errorJson =
"{\"error\":\"pull model manifest: 412: \\n"
+ "\\n"
+ "The model you are attempting to pull requires a newer version of Ollama.\\n"
+ "\\n"
+ "Please download the latest version at:\\n"
+ "\\n"
+ "\\thttps://ollama.com/download\\n"
+ "\\n"
+ "\"}";
ModelPullResponse response = deserialize(errorJson, ModelPullResponse.class); ModelPullResponse response = deserialize(errorJson, ModelPullResponse.class);
@ -59,7 +77,9 @@ public class TestModelPullResponseSerialization extends AbstractSerializationTes
*/ */
@Test @Test
public void testDeserializationWithProgressFields() { public void testDeserializationWithProgressFields() {
String progressJson = "{\"status\":\"pulling digestname\",\"digest\":\"sha256:abc123\",\"total\":2142590208,\"completed\":241970}"; String progressJson =
"{\"status\":\"pulling"
+ " digestname\",\"digest\":\"sha256:abc123\",\"total\":2142590208,\"completed\":241970}";
ModelPullResponse response = deserialize(progressJson, ModelPullResponse.class); ModelPullResponse response = deserialize(progressJson, ModelPullResponse.class);
@ -95,7 +115,8 @@ public class TestModelPullResponseSerialization extends AbstractSerializationTes
*/ */
@Test @Test
public void testDeserializationWithAllFields() { public void testDeserializationWithAllFields() {
String completeJson = "{\"status\":\"downloading\",\"digest\":\"sha256:def456\",\"total\":1000000,\"completed\":500000,\"error\":null}"; String completeJson =
"{\"status\":\"downloading\",\"digest\":\"sha256:def456\",\"total\":1000000,\"completed\":500000,\"error\":null}";
ModelPullResponse response = deserialize(completeJson, ModelPullResponse.class); ModelPullResponse response = deserialize(completeJson, ModelPullResponse.class);
@ -115,7 +136,9 @@ public class TestModelPullResponseSerialization extends AbstractSerializationTes
@Test @Test
public void testDeserializationWithUnknownFields() { public void testDeserializationWithUnknownFields() {
// Test that unknown fields are ignored due to @JsonIgnoreProperties(ignoreUnknown = true) // Test that unknown fields are ignored due to @JsonIgnoreProperties(ignoreUnknown = true)
String jsonWithUnknownFields = "{\"status\":\"pulling\",\"unknown_field\":\"should_be_ignored\",\"error\":\"test error\",\"another_unknown\":123,\"nested_unknown\":{\"key\":\"value\"}}"; String jsonWithUnknownFields =
"{\"status\":\"pulling\",\"unknown_field\":\"should_be_ignored\",\"error\":\"test"
+ " error\",\"another_unknown\":123,\"nested_unknown\":{\"key\":\"value\"}}";
ModelPullResponse response = deserialize(jsonWithUnknownFields, ModelPullResponse.class); ModelPullResponse response = deserialize(jsonWithUnknownFields, ModelPullResponse.class);
@ -227,21 +250,25 @@ public class TestModelPullResponseSerialization extends AbstractSerializationTes
String errorJson = "{\"error\":\"test error\"}"; String errorJson = "{\"error\":\"test error\"}";
ModelPullResponse errorResponse = deserialize(errorJson, ModelPullResponse.class); ModelPullResponse errorResponse = deserialize(errorJson, ModelPullResponse.class);
assertTrue(errorResponse.getError() != null && !errorResponse.getError().trim().isEmpty(), assertTrue(
errorResponse.getError() != null && !errorResponse.getError().trim().isEmpty(),
"Error response should trigger error handling logic"); "Error response should trigger error handling logic");
// Normal case - should not trigger error handling // Normal case - should not trigger error handling
String normalJson = "{\"status\":\"pulling\"}"; String normalJson = "{\"status\":\"pulling\"}";
ModelPullResponse normalResponse = deserialize(normalJson, ModelPullResponse.class); ModelPullResponse normalResponse = deserialize(normalJson, ModelPullResponse.class);
assertFalse(normalResponse.getError() != null && !normalResponse.getError().trim().isEmpty(), assertFalse(
normalResponse.getError() != null && !normalResponse.getError().trim().isEmpty(),
"Normal response should not trigger error handling logic"); "Normal response should not trigger error handling logic");
// Empty error case - should not trigger error handling // Empty error case - should not trigger error handling
String emptyErrorJson = "{\"error\":\"\",\"status\":\"pulling\"}"; String emptyErrorJson = "{\"error\":\"\",\"status\":\"pulling\"}";
ModelPullResponse emptyErrorResponse = deserialize(emptyErrorJson, ModelPullResponse.class); ModelPullResponse emptyErrorResponse = deserialize(emptyErrorJson, ModelPullResponse.class);
assertFalse(emptyErrorResponse.getError() != null && !emptyErrorResponse.getError().trim().isEmpty(), assertFalse(
emptyErrorResponse.getError() != null
&& !emptyErrorResponse.getError().trim().isEmpty(),
"Empty error response should not trigger error handling logic"); "Empty error response should not trigger error handling logic");
} }
} }

View File

@ -1,33 +1,45 @@
/*
* Ollama4j - Java library for interacting with Ollama server.
* Copyright (c) 2025 Amith Koujalgi and contributors.
*
* Licensed under the MIT License (the "License");
* you may not use this file except in compliance with the License.
*
*/
package io.github.ollama4j.unittests.jackson; package io.github.ollama4j.unittests.jackson;
import static org.junit.jupiter.api.Assertions.*;
import io.github.ollama4j.models.response.Model; import io.github.ollama4j.models.response.Model;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TestModelRequestSerialization extends AbstractSerializationTest<Model> { public class TestModelRequestSerialization extends AbstractSerializationTest<Model> {
@Test @Test
public void testDeserializationOfModelResponseWithOffsetTime() { public void testDeserializationOfModelResponseWithOffsetTime() {
String serializedTestStringWithOffsetTime = "{\n" + String serializedTestStringWithOffsetTime =
" \"name\": \"codellama:13b\",\n" + "{\n"
" \"modified_at\": \"2023-11-04T14:56:49.277302595-07:00\",\n" + + " \"name\": \"codellama:13b\",\n"
" \"size\": 7365960935,\n" + + " \"modified_at\": \"2023-11-04T14:56:49.277302595-07:00\",\n"
" \"digest\": \"9f438cb9cd581fc025612d27f7c1a6669ff83a8bb0ed86c94fcf4c5440555697\",\n" + + " \"size\": 7365960935,\n"
" \"details\": {\n" + + " \"digest\":"
" \"format\": \"gguf\",\n" + + " \"9f438cb9cd581fc025612d27f7c1a6669ff83a8bb0ed86c94fcf4c5440555697\",\n"
" \"family\": \"llama\",\n" + + " \"details\": {\n"
" \"families\": null,\n" + + " \"format\": \"gguf\",\n"
" \"parameter_size\": \"13B\",\n" + + " \"family\": \"llama\",\n"
" \"quantization_level\": \"Q4_0\"\n" + + " \"families\": null,\n"
" }\n" + + " \"parameter_size\": \"13B\",\n"
"}"; + " \"quantization_level\": \"Q4_0\"\n"
+ " }\n"
+ "}";
Model model = deserialize(serializedTestStringWithOffsetTime, Model.class); Model model = deserialize(serializedTestStringWithOffsetTime, Model.class);
assertNotNull(model); assertNotNull(model);
assertEquals("codellama:13b", model.getName()); assertEquals("codellama:13b", model.getName());
assertEquals("2023-11-04T21:56:49.277302595Z", model.getModifiedAt().toString()); assertEquals("2023-11-04T21:56:49.277302595Z", model.getModifiedAt().toString());
assertEquals(7365960935L, model.getSize()); assertEquals(7365960935L, model.getSize());
assertEquals("9f438cb9cd581fc025612d27f7c1a6669ff83a8bb0ed86c94fcf4c5440555697", model.getDigest()); assertEquals(
"9f438cb9cd581fc025612d27f7c1a6669ff83a8bb0ed86c94fcf4c5440555697",
model.getDigest());
assertNotNull(model.getModelMeta()); assertNotNull(model.getModelMeta());
assertEquals("gguf", model.getModelMeta().getFormat()); assertEquals("gguf", model.getModelMeta().getFormat());
assertEquals("llama", model.getModelMeta().getFamily()); assertEquals("llama", model.getModelMeta().getFamily());
@ -38,25 +50,29 @@ public class TestModelRequestSerialization extends AbstractSerializationTest<Mod
@Test @Test
public void testDeserializationOfModelResponseWithZuluTime() { public void testDeserializationOfModelResponseWithZuluTime() {
String serializedTestStringWithZuluTimezone = "{\n" + String serializedTestStringWithZuluTimezone =
" \"name\": \"codellama:13b\",\n" + "{\n"
" \"modified_at\": \"2023-11-04T14:56:49.277302595Z\",\n" + + " \"name\": \"codellama:13b\",\n"
" \"size\": 7365960935,\n" + + " \"modified_at\": \"2023-11-04T14:56:49.277302595Z\",\n"
" \"digest\": \"9f438cb9cd581fc025612d27f7c1a6669ff83a8bb0ed86c94fcf4c5440555697\",\n" + + " \"size\": 7365960935,\n"
" \"details\": {\n" + + " \"digest\":"
" \"format\": \"gguf\",\n" + + " \"9f438cb9cd581fc025612d27f7c1a6669ff83a8bb0ed86c94fcf4c5440555697\",\n"
" \"family\": \"llama\",\n" + + " \"details\": {\n"
" \"families\": null,\n" + + " \"format\": \"gguf\",\n"
" \"parameter_size\": \"13B\",\n" + + " \"family\": \"llama\",\n"
" \"quantization_level\": \"Q4_0\"\n" + + " \"families\": null,\n"
" }\n" + + " \"parameter_size\": \"13B\",\n"
"}"; + " \"quantization_level\": \"Q4_0\"\n"
+ " }\n"
+ "}";
Model model = deserialize(serializedTestStringWithZuluTimezone, Model.class); Model model = deserialize(serializedTestStringWithZuluTimezone, Model.class);
assertNotNull(model); assertNotNull(model);
assertEquals("codellama:13b", model.getName()); assertEquals("codellama:13b", model.getName());
assertEquals("2023-11-04T14:56:49.277302595Z", model.getModifiedAt().toString()); assertEquals("2023-11-04T14:56:49.277302595Z", model.getModifiedAt().toString());
assertEquals(7365960935L, model.getSize()); assertEquals(7365960935L, model.getSize());
assertEquals("9f438cb9cd581fc025612d27f7c1a6669ff83a8bb0ed86c94fcf4c5440555697", model.getDigest()); assertEquals(
"9f438cb9cd581fc025612d27f7c1a6669ff83a8bb0ed86c94fcf4c5440555697",
model.getDigest());
assertNotNull(model.getModelMeta()); assertNotNull(model.getModelMeta());
assertEquals("gguf", model.getModelMeta().getFormat()); assertEquals("gguf", model.getModelMeta().getFormat());
assertEquals("llama", model.getModelMeta().getFamily()); assertEquals("llama", model.getModelMeta().getFamily());
@ -64,5 +80,4 @@ public class TestModelRequestSerialization extends AbstractSerializationTest<Mod
assertEquals("13B", model.getModelMeta().getParameterSize()); assertEquals("13B", model.getModelMeta().getParameterSize());
assertEquals("Q4_0", model.getModelMeta().getQuantizationLevel()); assertEquals("Q4_0", model.getModelMeta().getQuantizationLevel());
} }
} }