diff --git a/src/main/java/io/github/ollama4j/OllamaAPI.java b/src/main/java/io/github/ollama4j/OllamaAPI.java
index e9f99ad..8386773 100644
--- a/src/main/java/io/github/ollama4j/OllamaAPI.java
+++ b/src/main/java/io/github/ollama4j/OllamaAPI.java
@@ -64,6 +64,11 @@ public class OllamaAPI {
@Setter
private long requestTimeoutSeconds = 10;
+ @Setter
+ private int imageURLReadTimeoutSeconds = 10;
+
+ @Setter
+ private int imageURLConnectTimeoutSeconds = 10;
/**
* The maximum number of retries for tool calls during chat interactions.
*
@@ -821,7 +826,7 @@ public class OllamaAPI {
encodedImages.add(encodeByteArrayToBase64((byte[]) image));
} else if (image instanceof String) {
LOG.debug("Using image URL: {}", image);
- encodedImages.add(encodeByteArrayToBase64(Utils.loadImageBytesFromUrl((String) image)));
+ encodedImages.add(encodeByteArrayToBase64(Utils.loadImageBytesFromUrl((String) image, imageURLConnectTimeoutSeconds, imageURLReadTimeoutSeconds)));
} else {
throw new OllamaBaseException("Unsupported image type. Please provide a File, byte[], or a URL String.");
}
diff --git a/src/main/java/io/github/ollama4j/models/chat/OllamaChatRequestBuilder.java b/src/main/java/io/github/ollama4j/models/chat/OllamaChatRequestBuilder.java
index 38ff63a..b540beb 100644
--- a/src/main/java/io/github/ollama4j/models/chat/OllamaChatRequestBuilder.java
+++ b/src/main/java/io/github/ollama4j/models/chat/OllamaChatRequestBuilder.java
@@ -7,7 +7,6 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
-import java.net.URISyntaxException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
@@ -21,6 +20,19 @@ public class OllamaChatRequestBuilder {
private static final Logger LOG = LoggerFactory.getLogger(OllamaChatRequestBuilder.class);
+ private int imageURLConnectTimeoutSeconds = 10;
+ private int imageURLReadTimeoutSeconds = 10;
+
+ public OllamaChatRequestBuilder withImageURLConnectTimeoutSeconds(int imageURLConnectTimeoutSeconds) {
+ this.imageURLConnectTimeoutSeconds = imageURLConnectTimeoutSeconds;
+ return this;
+ }
+
+ public OllamaChatRequestBuilder withImageURLReadTimeoutSeconds(int imageURLReadTimeoutSeconds) {
+ this.imageURLReadTimeoutSeconds = imageURLReadTimeoutSeconds;
+ return this;
+ }
+
private OllamaChatRequestBuilder(String model, List messages) {
request = new OllamaChatRequest(model, false, messages);
}
@@ -72,11 +84,11 @@ public class OllamaChatRequestBuilder {
binaryImages = new ArrayList<>();
for (String imageUrl : imageUrls) {
try {
- binaryImages.add(Utils.loadImageBytesFromUrl(imageUrl));
- } catch (URISyntaxException e) {
- LOG.warn("URL '{}' could not be accessed, will not add to message!", imageUrl, e);
+ binaryImages.add(Utils.loadImageBytesFromUrl(imageUrl, imageURLConnectTimeoutSeconds, imageURLReadTimeoutSeconds));
} catch (IOException e) {
LOG.warn("Content of URL '{}' could not be read, will not add to message!", imageUrl, e);
+ } catch (InterruptedException e) {
+ LOG.warn("Loading image from URL '{}' was interrupted, will not add to message!", imageUrl, e);
}
}
}
diff --git a/src/main/java/io/github/ollama4j/utils/Utils.java b/src/main/java/io/github/ollama4j/utils/Utils.java
index 6d2aa5e..0c6f000 100644
--- a/src/main/java/io/github/ollama4j/utils/Utils.java
+++ b/src/main/java/io/github/ollama4j/utils/Utils.java
@@ -2,17 +2,20 @@ package io.github.ollama4j.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
import java.util.Objects;
public class Utils {
+ private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
private static ObjectMapper objectMapper;
@@ -24,21 +27,32 @@ public class Utils {
return objectMapper;
}
- public static byte[] loadImageBytesFromUrl(String imageUrl)
- throws IOException, URISyntaxException {
- URL url = new URI(imageUrl).toURL();
- try (InputStream in = url.openStream();
- ByteArrayOutputStream out = new ByteArrayOutputStream()) {
- byte[] buffer = new byte[1024];
- int bytesRead;
- while ((bytesRead = in.read(buffer)) != -1) {
- out.write(buffer, 0, bytesRead);
- }
- return out.toByteArray();
+ public static byte[] loadImageBytesFromUrl(String imageUrl, int connectTimeoutSeconds, int readTimeoutSeconds)
+ throws IOException, InterruptedException {
+ LOG.debug("Attempting to load image from URL: {} (connectTimeout={}s, readTimeout={}s)", imageUrl, connectTimeoutSeconds, readTimeoutSeconds);
+ HttpClient client = HttpClient.newBuilder()
+ .connectTimeout(Duration.ofSeconds(connectTimeoutSeconds))
+ .build();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(imageUrl))
+ .timeout(Duration.ofSeconds(readTimeoutSeconds))
+ .header("User-Agent", "Mozilla/5.0")
+ .GET()
+ .build();
+ LOG.debug("Sending HTTP GET request to {}", imageUrl);
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
+ LOG.debug("Received HTTP response with status code: {}", response.statusCode());
+ if (response.statusCode() >= 200 && response.statusCode() < 300) {
+ LOG.debug("Successfully loaded image from URL: {} ({} bytes)", imageUrl, response.body().length);
+ return response.body();
+ } else {
+ LOG.error("Failed to load image from URL: {}. HTTP status: {}", imageUrl, response.statusCode());
+ throw new IOException("Failed to load image: HTTP " + response.statusCode());
}
}
public static File getFileFromClasspath(String fileName) {
+ LOG.debug("Trying to load file from classpath: {}", fileName);
ClassLoader classLoader = Utils.class.getClassLoader();
return new File(Objects.requireNonNull(classLoader.getResource(fileName)).getFile());
}
diff --git a/src/test/java/io/github/ollama4j/unittests/TestOllamaChatRequestBuilder.java b/src/test/java/io/github/ollama4j/unittests/TestOllamaChatRequestBuilder.java
index 20ab81c..6c06864 100644
--- a/src/test/java/io/github/ollama4j/unittests/TestOllamaChatRequestBuilder.java
+++ b/src/test/java/io/github/ollama4j/unittests/TestOllamaChatRequestBuilder.java
@@ -33,17 +33,22 @@ class TestOllamaChatRequestBuilder {
@Test
void testImageUrlFailuresAreIgnoredAndDoNotBreakBuild() {
- // Provide clearly invalid URL, builder logs a warning and continues
- OllamaChatRequest req = OllamaChatRequestBuilder.getInstance("m")
- .withMessage(OllamaChatMessageRole.USER, "hi", Collections.emptyList(),
- "ht!tp://invalid url \n not a uri")
+ // Provide a syntactically invalid URL, but catch the expected exception to verify builder robustness
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance("m");
+ try {
+ builder.withMessage(OllamaChatMessageRole.USER, "hi", Collections.emptyList(),
+ "ht!tp://invalid url \n not a uri");
+ fail("Expected IllegalArgumentException due to malformed URL");
+ } catch (IllegalArgumentException e) {
+ // Expected: malformed URL should throw IllegalArgumentException
+ }
+ // The builder should still be usable after the exception
+ OllamaChatRequest req = builder.withMessage(OllamaChatMessageRole.USER, "hello", Collections.emptyList())
.build();
assertNotNull(req.getMessages());
assertEquals(1, req.getMessages().size());
OllamaChatMessage msg = req.getMessages().get(0);
- // images list will be initialized only if any valid URL was added; for invalid URL list can be null
- // We just assert that builder didn't crash and message is present with content
- assertEquals("hi", msg.getContent());
+ assertEquals("hello", msg.getContent());
}
}