Ollama4j
A Java library (wrapper/binding) for Ollama server.
Loading...
Searching...
No Matches
OllamaAPI.java
Go to the documentation of this file.
1package io.github.ollama4j;
2
3import io.github.ollama4j.exceptions.OllamaBaseException;
4import io.github.ollama4j.exceptions.ToolInvocationException;
5import io.github.ollama4j.exceptions.ToolNotFoundException;
6import io.github.ollama4j.models.chat.OllamaChatMessage;
7import io.github.ollama4j.models.chat.OllamaChatRequest;
8import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
9import io.github.ollama4j.models.chat.OllamaChatResult;
10import io.github.ollama4j.models.embeddings.OllamaEmbeddingResponseModel;
11import io.github.ollama4j.models.embeddings.OllamaEmbeddingsRequestModel;
12import io.github.ollama4j.models.generate.OllamaGenerateRequest;
13import io.github.ollama4j.models.generate.OllamaStreamHandler;
14import io.github.ollama4j.models.ps.ModelsProcessResponse;
15import io.github.ollama4j.models.request.*;
16import io.github.ollama4j.models.response.*;
17import io.github.ollama4j.tools.*;
18import io.github.ollama4j.utils.Options;
19import io.github.ollama4j.utils.Utils;
20import lombok.Setter;
21import org.slf4j.Logger;
22import org.slf4j.LoggerFactory;
23
24import java.io.*;
25import java.net.URI;
26import java.net.URISyntaxException;
27import java.net.http.HttpClient;
28import java.net.http.HttpConnectTimeoutException;
29import java.net.http.HttpRequest;
30import java.net.http.HttpResponse;
31import java.nio.charset.StandardCharsets;
32import java.nio.file.Files;
33import java.time.Duration;
34import java.util.*;
35
39@SuppressWarnings("DuplicatedCode")
40public class OllamaAPI {
41
42 private static final Logger logger = LoggerFactory.getLogger(OllamaAPI.class);
43 private final String host;
48 @Setter
49 private long requestTimeoutSeconds = 10;
54 @Setter
55 private boolean verbose = true;
56 private BasicAuth basicAuth;
57
58 private final ToolRegistry toolRegistry = new ToolRegistry();
59
63 public OllamaAPI() {
64 this.host = "http://localhost:11434";
65 }
66
72 public OllamaAPI(String host) {
73 if (host.endsWith("/")) {
74 this.host = host.substring(0, host.length() - 1);
75 } else {
76 this.host = host;
77 }
78 }
79
86 public void setBasicAuth(String username, String password) {
87 this.basicAuth = new BasicAuth(username, password);
88 }
89
95 public boolean ping() {
96 String url = this.host + "/api/tags";
97 HttpClient httpClient = HttpClient.newHttpClient();
98 HttpRequest httpRequest = null;
99 try {
100 httpRequest =
101 getRequestBuilderDefault(new URI(url))
102 .header("Accept", "application/json")
103 .header("Content-type", "application/json")
104 .GET()
105 .build();
106 } catch (URISyntaxException e) {
107 throw new RuntimeException(e);
108 }
109 HttpResponse<String> response = null;
110 try {
111 response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
112 } catch (HttpConnectTimeoutException e) {
113 return false;
114 } catch (IOException | InterruptedException e) {
115 throw new RuntimeException(e);
116 }
117 int statusCode = response.statusCode();
118 return statusCode == 200;
119 }
120
126 public ModelsProcessResponse ps() throws IOException, InterruptedException, OllamaBaseException {
127 String url = this.host + "/api/ps";
128 HttpClient httpClient = HttpClient.newHttpClient();
129 HttpRequest httpRequest = null;
130 try {
131 httpRequest =
132 getRequestBuilderDefault(new URI(url))
133 .header("Accept", "application/json")
134 .header("Content-type", "application/json")
135 .GET()
136 .build();
137 } catch (URISyntaxException e) {
138 throw new RuntimeException(e);
139 }
140 HttpResponse<String> response = null;
141 response = httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
142 int statusCode = response.statusCode();
143 String responseString = response.body();
144 if (statusCode == 200) {
145 return Utils.getObjectMapper()
146 .readValue(responseString, ModelsProcessResponse.class);
147 } else {
148 throw new OllamaBaseException(statusCode + " - " + responseString);
149 }
150 }
151
157 public List<Model> listModels()
158 throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
159 String url = this.host + "/api/tags";
160 HttpClient httpClient = HttpClient.newHttpClient();
161 HttpRequest httpRequest =
162 getRequestBuilderDefault(new URI(url))
163 .header("Accept", "application/json")
164 .header("Content-type", "application/json")
165 .GET()
166 .build();
167 HttpResponse<String> response =
168 httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
169 int statusCode = response.statusCode();
170 String responseString = response.body();
171 if (statusCode == 200) {
172 return Utils.getObjectMapper()
173 .readValue(responseString, ListModelsResponse.class)
174 .getModels();
175 } else {
176 throw new OllamaBaseException(statusCode + " - " + responseString);
177 }
178 }
179
186 public void pullModel(String modelName)
187 throws OllamaBaseException, IOException, URISyntaxException, InterruptedException {
188 String url = this.host + "/api/pull";
189 String jsonData = new ModelRequest(modelName).toString();
190 HttpRequest request =
191 getRequestBuilderDefault(new URI(url))
192 .POST(HttpRequest.BodyPublishers.ofString(jsonData))
193 .header("Accept", "application/json")
194 .header("Content-type", "application/json")
195 .build();
196 HttpClient client = HttpClient.newHttpClient();
197 HttpResponse<InputStream> response =
198 client.send(request, HttpResponse.BodyHandlers.ofInputStream());
199 int statusCode = response.statusCode();
200 InputStream responseBodyStream = response.body();
201 String responseString = "";
202 try (BufferedReader reader =
203 new BufferedReader(new InputStreamReader(responseBodyStream, StandardCharsets.UTF_8))) {
204 String line;
205 while ((line = reader.readLine()) != null) {
206 ModelPullResponse modelPullResponse =
207 Utils.getObjectMapper().readValue(line, ModelPullResponse.class);
208 if (verbose) {
209 logger.info(modelPullResponse.getStatus());
210 }
211 }
212 }
213 if (statusCode != 200) {
214 throw new OllamaBaseException(statusCode + " - " + responseString);
215 }
216 }
217
224 public ModelDetail getModelDetails(String modelName)
225 throws IOException, OllamaBaseException, InterruptedException, URISyntaxException {
226 String url = this.host + "/api/show";
227 String jsonData = new ModelRequest(modelName).toString();
228 HttpRequest request =
229 getRequestBuilderDefault(new URI(url))
230 .header("Accept", "application/json")
231 .header("Content-type", "application/json")
232 .POST(HttpRequest.BodyPublishers.ofString(jsonData))
233 .build();
234 HttpClient client = HttpClient.newHttpClient();
235 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
236 int statusCode = response.statusCode();
237 String responseBody = response.body();
238 if (statusCode == 200) {
239 return Utils.getObjectMapper().readValue(responseBody, ModelDetail.class);
240 } else {
241 throw new OllamaBaseException(statusCode + " - " + responseBody);
242 }
243 }
244
252 public void createModelWithFilePath(String modelName, String modelFilePath)
253 throws IOException, InterruptedException, OllamaBaseException, URISyntaxException {
254 String url = this.host + "/api/create";
255 String jsonData = new CustomModelFilePathRequest(modelName, modelFilePath).toString();
256 HttpRequest request =
257 getRequestBuilderDefault(new URI(url))
258 .header("Accept", "application/json")
259 .header("Content-Type", "application/json")
260 .POST(HttpRequest.BodyPublishers.ofString(jsonData, StandardCharsets.UTF_8))
261 .build();
262 HttpClient client = HttpClient.newHttpClient();
263 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
264 int statusCode = response.statusCode();
265 String responseString = response.body();
266 if (statusCode != 200) {
267 throw new OllamaBaseException(statusCode + " - " + responseString);
268 }
269 // FIXME: Ollama API returns HTTP status code 200 for model creation failure cases. Correct this
270 // if the issue is fixed in the Ollama API server.
271 if (responseString.contains("error")) {
272 throw new OllamaBaseException(responseString);
273 }
274 if (verbose) {
275 logger.info(responseString);
276 }
277 }
278
286 public void createModelWithModelFileContents(String modelName, String modelFileContents)
287 throws IOException, InterruptedException, OllamaBaseException, URISyntaxException {
288 String url = this.host + "/api/create";
289 String jsonData = new CustomModelFileContentsRequest(modelName, modelFileContents).toString();
290 HttpRequest request =
291 getRequestBuilderDefault(new URI(url))
292 .header("Accept", "application/json")
293 .header("Content-Type", "application/json")
294 .POST(HttpRequest.BodyPublishers.ofString(jsonData, StandardCharsets.UTF_8))
295 .build();
296 HttpClient client = HttpClient.newHttpClient();
297 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
298 int statusCode = response.statusCode();
299 String responseString = response.body();
300 if (statusCode != 200) {
301 throw new OllamaBaseException(statusCode + " - " + responseString);
302 }
303 if (responseString.contains("error")) {
304 throw new OllamaBaseException(responseString);
305 }
306 if (verbose) {
307 logger.info(responseString);
308 }
309 }
310
317 public void deleteModel(String modelName, boolean ignoreIfNotPresent)
318 throws IOException, InterruptedException, OllamaBaseException, URISyntaxException {
319 String url = this.host + "/api/delete";
320 String jsonData = new ModelRequest(modelName).toString();
321 HttpRequest request =
322 getRequestBuilderDefault(new URI(url))
323 .method("DELETE", HttpRequest.BodyPublishers.ofString(jsonData, StandardCharsets.UTF_8))
324 .header("Accept", "application/json")
325 .header("Content-type", "application/json")
326 .build();
327 HttpClient client = HttpClient.newHttpClient();
328 HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
329 int statusCode = response.statusCode();
330 String responseBody = response.body();
331 if (statusCode == 404 && responseBody.contains("model") && responseBody.contains("not found")) {
332 return;
333 }
334 if (statusCode != 200) {
335 throw new OllamaBaseException(statusCode + " - " + responseBody);
336 }
337 }
338
346 public List<Double> generateEmbeddings(String model, String prompt)
347 throws IOException, InterruptedException, OllamaBaseException {
348 return generateEmbeddings(new OllamaEmbeddingsRequestModel(model, prompt));
349 }
350
357 public List<Double> generateEmbeddings(OllamaEmbeddingsRequestModel modelRequest) throws IOException, InterruptedException, OllamaBaseException {
358 URI uri = URI.create(this.host + "/api/embeddings");
359 String jsonData = modelRequest.toString();
360 HttpClient httpClient = HttpClient.newHttpClient();
361 HttpRequest.Builder requestBuilder =
362 getRequestBuilderDefault(uri)
363 .header("Accept", "application/json")
364 .POST(HttpRequest.BodyPublishers.ofString(jsonData));
365 HttpRequest request = requestBuilder.build();
366 HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
367 int statusCode = response.statusCode();
368 String responseBody = response.body();
369 if (statusCode == 200) {
370 OllamaEmbeddingResponseModel embeddingResponse =
371 Utils.getObjectMapper().readValue(responseBody, OllamaEmbeddingResponseModel.class);
372 return embeddingResponse.getEmbedding();
373 } else {
374 throw new OllamaBaseException(statusCode + " - " + responseBody);
375 }
376 }
377
378
391 public OllamaResult generate(String model, String prompt, boolean raw, Options options, OllamaStreamHandler streamHandler)
392 throws OllamaBaseException, IOException, InterruptedException {
393 OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt);
394 ollamaRequestModel.setRaw(raw);
395 ollamaRequestModel.setOptions(options.getOptionsMap());
396 return generateSyncForOllamaRequestModel(ollamaRequestModel, streamHandler);
397 }
398
410 public OllamaResult generate(String model, String prompt, boolean raw, Options options)
411 throws OllamaBaseException, IOException, InterruptedException {
412 return generate(model, prompt, raw, options, null);
413 }
414
415
429 public OllamaToolsResult generateWithTools(String model, String prompt, Options options)
430 throws OllamaBaseException, IOException, InterruptedException, ToolInvocationException {
431 boolean raw = true;
432 OllamaToolsResult toolResult = new OllamaToolsResult();
433 Map<ToolFunctionCallSpec, Object> toolResults = new HashMap<>();
434
435 OllamaResult result = generate(model, prompt, raw, options, null);
436 toolResult.setModelResult(result);
437
438 String toolsResponse = result.getResponse();
439 if (toolsResponse.contains("[TOOL_CALLS]")) {
440 toolsResponse = toolsResponse.replace("[TOOL_CALLS]", "");
441 }
442
443 List<ToolFunctionCallSpec> toolFunctionCallSpecs = Utils.getObjectMapper().readValue(toolsResponse, Utils.getObjectMapper().getTypeFactory().constructCollectionType(List.class, ToolFunctionCallSpec.class));
444 for (ToolFunctionCallSpec toolFunctionCallSpec : toolFunctionCallSpecs) {
445 toolResults.put(toolFunctionCallSpec, invokeTool(toolFunctionCallSpec));
446 }
447 toolResult.setToolResults(toolResults);
448 return toolResult;
449 }
450
451
461 public OllamaAsyncResultStreamer generateAsync(String model, String prompt, boolean raw) {
462 OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt);
463 ollamaRequestModel.setRaw(raw);
464 URI uri = URI.create(this.host + "/api/generate");
465 OllamaAsyncResultStreamer ollamaAsyncResultStreamer =
467 getRequestBuilderDefault(uri), ollamaRequestModel, requestTimeoutSeconds);
468 ollamaAsyncResultStreamer.start();
469 return ollamaAsyncResultStreamer;
470 }
471
486 String model, String prompt, List<File> imageFiles, Options options, OllamaStreamHandler streamHandler)
487 throws OllamaBaseException, IOException, InterruptedException {
488 List<String> images = new ArrayList<>();
489 for (File imageFile : imageFiles) {
490 images.add(encodeFileToBase64(imageFile));
491 }
492 OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt, images);
493 ollamaRequestModel.setOptions(options.getOptionsMap());
494 return generateSyncForOllamaRequestModel(ollamaRequestModel, streamHandler);
495 }
496
503 String model, String prompt, List<File> imageFiles, Options options)
504 throws OllamaBaseException, IOException, InterruptedException {
505 return generateWithImageFiles(model, prompt, imageFiles, options, null);
506 }
507
522 String model, String prompt, List<String> imageURLs, Options options, OllamaStreamHandler streamHandler)
523 throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
524 List<String> images = new ArrayList<>();
525 for (String imageURL : imageURLs) {
526 images.add(encodeByteArrayToBase64(Utils.loadImageBytesFromUrl(imageURL)));
527 }
528 OllamaGenerateRequest ollamaRequestModel = new OllamaGenerateRequest(model, prompt, images);
529 ollamaRequestModel.setOptions(options.getOptionsMap());
530 return generateSyncForOllamaRequestModel(ollamaRequestModel, streamHandler);
531 }
532
538 public OllamaResult generateWithImageURLs(String model, String prompt, List<String> imageURLs,
539 Options options)
540 throws OllamaBaseException, IOException, InterruptedException, URISyntaxException {
541 return generateWithImageURLs(model, prompt, imageURLs, options, null);
542 }
543
544
556 public OllamaChatResult chat(String model, List<OllamaChatMessage> messages) throws OllamaBaseException, IOException, InterruptedException {
558 return chat(builder.withMessages(messages).build());
559 }
560
572 public OllamaChatResult chat(OllamaChatRequest request) throws OllamaBaseException, IOException, InterruptedException {
573 return chat(request, null);
574 }
575
588 public OllamaChatResult chat(OllamaChatRequest request, OllamaStreamHandler streamHandler) throws OllamaBaseException, IOException, InterruptedException {
589 OllamaChatEndpointCaller requestCaller = new OllamaChatEndpointCaller(host, basicAuth, requestTimeoutSeconds, verbose);
590 OllamaResult result;
591 if (streamHandler != null) {
592 request.setStream(true);
593 result = requestCaller.call(request, streamHandler);
594 } else {
595 result = requestCaller.callSync(request);
596 }
597 return new OllamaChatResult(result.getResponse(), result.getResponseTime(), result.getHttpStatusCode(), request.getMessages());
598 }
599
600 public void registerTool(Tools.ToolSpecification toolSpecification) {
601 toolRegistry.addFunction(toolSpecification.getFunctionName(), toolSpecification.getToolDefinition());
602 }
603
604 // technical private methods //
605
606 private static String encodeFileToBase64(File file) throws IOException {
607 return Base64.getEncoder().encodeToString(Files.readAllBytes(file.toPath()));
608 }
609
610 private static String encodeByteArrayToBase64(byte[] bytes) {
611 return Base64.getEncoder().encodeToString(bytes);
612 }
613
614 private OllamaResult generateSyncForOllamaRequestModel(
615 OllamaGenerateRequest ollamaRequestModel, OllamaStreamHandler streamHandler)
616 throws OllamaBaseException, IOException, InterruptedException {
617 OllamaGenerateEndpointCaller requestCaller =
618 new OllamaGenerateEndpointCaller(host, basicAuth, requestTimeoutSeconds, verbose);
619 OllamaResult result;
620 if (streamHandler != null) {
621 ollamaRequestModel.setStream(true);
622 result = requestCaller.call(ollamaRequestModel, streamHandler);
623 } else {
624 result = requestCaller.callSync(ollamaRequestModel);
625 }
626 return result;
627 }
628
635 private HttpRequest.Builder getRequestBuilderDefault(URI uri) {
636 HttpRequest.Builder requestBuilder =
637 HttpRequest.newBuilder(uri)
638 .header("Content-Type", "application/json")
639 .timeout(Duration.ofSeconds(requestTimeoutSeconds));
640 if (isBasicAuthCredentialsSet()) {
641 requestBuilder.header("Authorization", getBasicAuthHeaderValue());
642 }
643 return requestBuilder;
644 }
645
651 private String getBasicAuthHeaderValue() {
652 String credentialsToEncode = basicAuth.getUsername() + ":" + basicAuth.getPassword();
653 return "Basic " + Base64.getEncoder().encodeToString(credentialsToEncode.getBytes());
654 }
655
661 private boolean isBasicAuthCredentialsSet() {
662 return basicAuth != null;
663 }
664
665
666 private Object invokeTool(ToolFunctionCallSpec toolFunctionCallSpec) throws ToolInvocationException {
667 try {
668 String methodName = toolFunctionCallSpec.getName();
669 Map<String, Object> arguments = toolFunctionCallSpec.getArguments();
670 ToolFunction function = toolRegistry.getFunction(methodName);
671 if (verbose) {
672 logger.debug("Invoking function {} with arguments {}", methodName, arguments);
673 }
674 if (function == null) {
675 throw new ToolNotFoundException("No such tool: " + methodName);
676 }
677 return function.apply(arguments);
678 } catch (Exception e) {
679 throw new ToolInvocationException("Failed to invoke tool: " + toolFunctionCallSpec.getName(), e);
680 }
681 }
682}
OllamaResult generateWithImageFiles(String model, String prompt, List< File > imageFiles, Options options, OllamaStreamHandler streamHandler)
OllamaChatResult chat(OllamaChatRequest request)
ModelDetail getModelDetails(String modelName)
List< Double > generateEmbeddings(OllamaEmbeddingsRequestModel modelRequest)
OllamaAsyncResultStreamer generateAsync(String model, String prompt, boolean raw)
OllamaChatResult chat(String model, List< OllamaChatMessage > messages)
List< Double > generateEmbeddings(String model, String prompt)
void pullModel(String modelName)
void setBasicAuth(String username, String password)
OllamaResult generate(String model, String prompt, boolean raw, Options options, OllamaStreamHandler streamHandler)
OllamaChatResult chat(OllamaChatRequest request, OllamaStreamHandler streamHandler)
void deleteModel(String modelName, boolean ignoreIfNotPresent)
void createModelWithFilePath(String modelName, String modelFilePath)
OllamaResult generate(String model, String prompt, boolean raw, Options options)
ModelsProcessResponse ps()
OllamaResult generateWithImageURLs(String model, String prompt, List< String > imageURLs, Options options, OllamaStreamHandler streamHandler)
void registerTool(Tools.ToolSpecification toolSpecification)
OllamaToolsResult generateWithTools(String model, String prompt, Options options)
OllamaResult generateWithImageURLs(String model, String prompt, List< String > imageURLs, Options options)
void createModelWithModelFileContents(String modelName, String modelFileContents)
OllamaResult generateWithImageFiles(String model, String prompt, List< File > imageFiles, Options options)
OllamaChatRequestBuilder withMessages(List< OllamaChatMessage > messages)
static OllamaChatRequestBuilder getInstance(String model)
OllamaResult call(OllamaRequestBody body, OllamaStreamHandler streamHandler)
OllamaResult call(OllamaRequestBody body, OllamaStreamHandler streamHandler)
ToolFunction getFunction(String name)
void addFunction(String name, ToolFunction function)
static byte[] loadImageBytesFromUrl(String imageUrl)
Definition Utils.java:25
static ObjectMapper getObjectMapper()
Definition Utils.java:17
Object apply(Map< String, Object > arguments)