diff --git a/Makefile b/Makefile index b5af010..94c020f 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,8 @@ unit-tests: integration-tests: export USE_EXTERNAL_OLLAMA_HOST=false && mvn clean verify -Pintegration-tests -integration-tests-local: - export USE_EXTERNAL_OLLAMA_HOST=true && export OLLAMA_HOST=http://localhost:11434 && mvn clean verify -Pintegration-tests -Dgpg.skip=true +integration-tests-remote: + export USE_EXTERNAL_OLLAMA_HOST=true && export OLLAMA_HOST=http://192.168.29.223:11434 && mvn clean verify -Pintegration-tests -Dgpg.skip=true doxygen: doxygen Doxyfile diff --git a/docs/docs/apis-generate/generate-with-tools.md b/docs/docs/apis-generate/generate-with-tools.md index a40969f..f0722e3 100644 --- a/docs/docs/apis-generate/generate-with-tools.md +++ b/docs/docs/apis-generate/generate-with-tools.md @@ -61,6 +61,9 @@ details. class DBQueryFunction implements ToolFunction { @Override public Object apply(Map arguments) { + if (arguments == null || arguments.isEmpty() || arguments.get("employee-name") == null || arguments.get("employee-address") == null || arguments.get("employee-phone") == null) { + throw new RuntimeException("Tool was called but the model failed to provide all the required arguments."); + } // perform DB operations here return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name").toString(), arguments.get("employee-address").toString(), arguments.get("employee-phone").toString()); } @@ -78,14 +81,39 @@ Lets define a sample tool specification called **Fuel Price Tool** for getting t Tools.ToolSpecification fuelPriceToolSpecification = Tools.ToolSpecification.builder() .functionName("current-fuel-price") .functionDescription("Get current fuel price") - .properties( - new Tools.PropsBuilder() - .withProperty("location", Tools.PromptFuncDefinition.Property.builder().type("string").description("The city, e.g. New Delhi, India").required(true).build()) - .withProperty("fuelType", Tools.PromptFuncDefinition.Property.builder().type("string").description("The fuel type.").enumValues(Arrays.asList("petrol", "diesel")).required(true).build()) + .toolFunction(SampleTools::getCurrentFuelPrice) + .toolPrompt( + Tools.PromptFuncDefinition.builder() + .type("prompt") + .function( + Tools.PromptFuncDefinition.PromptFuncSpec.builder() + .name("get-location-fuel-info") + .description("Get location and fuel type details") + .parameters( + Tools.PromptFuncDefinition.Parameters.builder() + .type("object") + .properties( + Map.of( + "location", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The city, e.g. New Delhi, India") + .required(true) + .build(), + "fuelType", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The fuel type.") + .enumValues(Arrays.asList("petrol", "diesel")) + .required(true) + .build() + ) + ) + .required(java.util.List.of("location", "fuelType")) + .build() + ) + .build() + ) .build() - ) - .toolDefinition(SampleTools::getCurrentFuelPrice) - .build(); + ).build(); ``` Lets also define a sample tool specification called **Weather Tool** for getting the current weather. @@ -97,13 +125,33 @@ Lets also define a sample tool specification called **Weather Tool** for getting Tools.ToolSpecification weatherToolSpecification = Tools.ToolSpecification.builder() .functionName("current-weather") .functionDescription("Get current weather") - .properties( - new Tools.PropsBuilder() - .withProperty("city", Tools.PromptFuncDefinition.Property.builder().type("string").description("The city, e.g. New Delhi, India").required(true).build()) + .toolFunction(SampleTools::getCurrentWeather) + .toolPrompt( + Tools.PromptFuncDefinition.builder() + .type("prompt") + .function( + Tools.PromptFuncDefinition.PromptFuncSpec.builder() + .name("get-location-weather-info") + .description("Get location details") + .parameters( + Tools.PromptFuncDefinition.Parameters.builder() + .type("object") + .properties( + Map.of( + "city", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The city, e.g. New Delhi, India") + .required(true) + .build() + ) + ) + .required(java.util.List.of("city")) + .build() + ) + .build() + ) .build() - ) - .toolDefinition(SampleTools::getCurrentWeather) - .build(); + ).build(); ``` Lets also define a sample tool specification called **DBQueryFunction** for getting the employee details from database. @@ -115,14 +163,43 @@ Lets also define a sample tool specification called **DBQueryFunction** for gett Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder() .functionName("get-employee-details") .functionDescription("Get employee details from the database") - .properties( - new Tools.PropsBuilder() - .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build()) - .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build()) - .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build()) + .toolFunction(new DBQueryFunction()) + .toolPrompt( + Tools.PromptFuncDefinition.builder() + .type("prompt") + .function( + Tools.PromptFuncDefinition.PromptFuncSpec.builder() + .name("get-employee-details") + .description("Get employee details from the database") + .parameters( + Tools.PromptFuncDefinition.Parameters.builder() + .type("object") + .properties( + Map.of( + "employee-name", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The name of the employee, e.g. John Doe") + .required(true) + .build(), + "employee-address", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India") + .required(true) + .build(), + "employee-phone", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The phone number of the employee. Always return a random value. e.g. 9911002233") + .required(true) + .build() + ) + ) + .required(java.util.List.of("employee-name", "employee-address", "employee-phone")) + .build() + ) + .build() + ) .build() ) - .toolDefinition(new DBQueryFunction()) .build(); ``` @@ -239,37 +316,111 @@ public class FunctionCallingWithMistralExample { Tools.ToolSpecification fuelPriceToolSpecification = Tools.ToolSpecification.builder() .functionName("current-fuel-price") .functionDescription("Get current fuel price") - .properties( - new Tools.PropsBuilder() - .withProperty("location", Tools.PromptFuncDefinition.Property.builder().type("string").description("The city, e.g. New Delhi, India").required(true).build()) - .withProperty("fuelType", Tools.PromptFuncDefinition.Property.builder().type("string").description("The fuel type.").enumValues(Arrays.asList("petrol", "diesel")).required(true).build()) + .toolFunction(SampleTools::getCurrentFuelPrice) + .toolPrompt( + Tools.PromptFuncDefinition.builder() + .type("prompt") + .function( + Tools.PromptFuncDefinition.PromptFuncSpec.builder() + .name("get-location-fuel-info") + .description("Get location and fuel type details") + .parameters( + Tools.PromptFuncDefinition.Parameters.builder() + .type("object") + .properties( + Map.of( + "location", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The city, e.g. New Delhi, India") + .required(true) + .build(), + "fuelType", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The fuel type.") + .enumValues(Arrays.asList("petrol", "diesel")) + .required(true) + .build() + ) + ) + .required(java.util.List.of("location", "fuelType")) + .build() + ) + .build() + ) .build() - ) - .toolDefinition(SampleTools::getCurrentFuelPrice) - .build(); + ).build(); Tools.ToolSpecification weatherToolSpecification = Tools.ToolSpecification.builder() .functionName("current-weather") .functionDescription("Get current weather") - .properties( - new Tools.PropsBuilder() - .withProperty("city", Tools.PromptFuncDefinition.Property.builder().type("string").description("The city, e.g. New Delhi, India").required(true).build()) + .toolFunction(SampleTools::getCurrentWeather) + .toolPrompt( + Tools.PromptFuncDefinition.builder() + .type("prompt") + .function( + Tools.PromptFuncDefinition.PromptFuncSpec.builder() + .name("get-location-weather-info") + .description("Get location details") + .parameters( + Tools.PromptFuncDefinition.Parameters.builder() + .type("object") + .properties( + Map.of( + "city", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The city, e.g. New Delhi, India") + .required(true) + .build() + ) + ) + .required(java.util.List.of("city")) + .build() + ) + .build() + ) .build() - ) - .toolDefinition(SampleTools::getCurrentWeather) - .build(); + ).build(); Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder() .functionName("get-employee-details") .functionDescription("Get employee details from the database") - .properties( - new Tools.PropsBuilder() - .withProperty("employee-name", Tools.PromptFuncDefinition.Property.builder().type("string").description("The name of the employee, e.g. John Doe").required(true).build()) - .withProperty("employee-address", Tools.PromptFuncDefinition.Property.builder().type("string").description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India").required(true).build()) - .withProperty("employee-phone", Tools.PromptFuncDefinition.Property.builder().type("string").description("The phone number of the employee. Always return a random value. e.g. 9911002233").required(true).build()) + .toolFunction(new DBQueryFunction()) + .toolPrompt( + Tools.PromptFuncDefinition.builder() + .type("prompt") + .function( + Tools.PromptFuncDefinition.PromptFuncSpec.builder() + .name("get-employee-details") + .description("Get employee details from the database") + .parameters( + Tools.PromptFuncDefinition.Parameters.builder() + .type("object") + .properties( + Map.of( + "employee-name", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The name of the employee, e.g. John Doe") + .required(true) + .build(), + "employee-address", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The address of the employee, Always return a random value. e.g. Roy St, Bengaluru, India") + .required(true) + .build(), + "employee-phone", Tools.PromptFuncDefinition.Property.builder() + .type("string") + .description("The phone number of the employee. Always return a random value. e.g. 9911002233") + .required(true) + .build() + ) + ) + .required(java.util.List.of("employee-name", "employee-address", "employee-phone")) + .build() + ) + .build() + ) .build() ) - .toolDefinition(new DBQueryFunction()) .build(); ollamaAPI.registerTool(fuelPriceToolSpecification); @@ -326,6 +477,9 @@ class SampleTools { class DBQueryFunction implements ToolFunction { @Override public Object apply(Map arguments) { + if (arguments == null || arguments.isEmpty() || arguments.get("employee-name") == null || arguments.get("employee-address") == null || arguments.get("employee-phone") == null) { + throw new RuntimeException("Tool was called but the model failed to provide all the required arguments."); + } // perform DB operations here return String.format("Employee Details {ID: %s, Name: %s, Address: %s, Phone: %s}", UUID.randomUUID(), arguments.get("employee-name").toString(), arguments.get("employee-address").toString(), arguments.get("employee-phone").toString()); } @@ -347,17 +501,17 @@ Rahul Kumar, Address: King St, Hyderabad, India, Phone: 9876543210}` ### Using tools in Chat-API -Instead of using the specific `ollamaAPI.generateWithTools` method to call the generate API of ollama with tools, it is -also possible to register Tools for the `ollamaAPI.chat` methods. In this case, the tool calling/callback is done +Instead of using the specific `ollamaAPI.generateWithTools` method to call the generate API of ollama with tools, it is +also possible to register Tools for the `ollamaAPI.chat` methods. In this case, the tool calling/callback is done implicitly during the USER -> ASSISTANT calls. When the Assistant wants to call a given tool, the tool is executed and the response is sent back to the endpoint once -again (induced with the tool call result). +again (induced with the tool call result). #### Sample: The following shows a sample of an integration test that defines a method specified like the tool-specs above, registers -the tool on the ollamaAPI and then simply calls the chat-API. All intermediate tool calling is wrapped inside the api +the tool on the ollamaAPI and then simply calls the chat-API. All intermediate tool calling is wrapped inside the api call. ```java @@ -405,7 +559,7 @@ public static void main(String[] args) { A typical final response of the above could be: -```json +```json { "chatHistory" : [ { @@ -466,7 +620,7 @@ This tool calling can also be done using the streaming API. ### Using Annotation based Tool Registration -Instead of explicitly registering each tool, ollama4j supports declarative tool specification and registration via java +Instead of explicitly registering each tool, ollama4j supports declarative tool specification and registration via java Annotations and reflection calling. To declare a method to be used as a tool for a chat call, the following steps have to be considered: @@ -489,7 +643,7 @@ The answer can only be provided by a method that is part of the BackendService c ```java public class BackendService{ - + public BackendService(){} @ToolSpec(desc = "Computes the most important constant all around the globe!") @@ -505,7 +659,7 @@ import io.github.ollama4j.tools.annotations.OllamaToolService; @OllamaToolService(providers = BackendService.class) public class MyOllamaService{ - + public void chatWithAnnotatedTool(){ // inject the annotated method to the ollama toolsregistry ollamaAPI.registerAnnotatedTools(); @@ -517,14 +671,14 @@ public class MyOllamaService{ OllamaChatResult chatResult = ollamaAPI.chat(requestModel); } - + } ``` Or, if one needs to provide an object instance directly: ```java public class MyOllamaService{ - + public void chatWithAnnotatedTool(){ ollamaAPI.registerAnnotatedTools(new BackendService()); OllamaChatRequest requestModel = builder @@ -534,7 +688,7 @@ public class MyOllamaService{ OllamaChatResult chatResult = ollamaAPI.chat(requestModel); } - + } ``` @@ -639,4 +793,4 @@ public String getCurrentFuelPrice(String location, String fuelType) { } ``` -Updating async/chat APIs with support for tool-based generation. +Updating async/chat APIs with support for tool-based generation. diff --git a/src/main/java/io/github/ollama4j/OllamaAPI.java b/src/main/java/io/github/ollama4j/OllamaAPI.java index 7de76ec..1a628c5 100644 --- a/src/main/java/io/github/ollama4j/OllamaAPI.java +++ b/src/main/java/io/github/ollama4j/OllamaAPI.java @@ -1058,7 +1058,7 @@ public class OllamaAPI { * @param model the ollama model to ask the question to * @param messages chat history / message stack to send to the model * @return {@link OllamaChatResult} containing the api response and the message - * history including the newly aqcuired assistant response. + * history including the newly acquired assistant response. * @throws OllamaBaseException any response code than 200 has been returned * @throws IOException in case the responseStream can not be read * @throws InterruptedException in case the server is not reachable or network @@ -1066,9 +1066,10 @@ public class OllamaAPI { * @throws OllamaBaseException if the response indicates an error status * @throws IOException if an I/O error occurs during the HTTP request * @throws InterruptedException if the operation is interrupted + * @throws ToolInvocationException if the tool invocation fails */ public OllamaChatResult chat(String model, List messages) - throws OllamaBaseException, IOException, InterruptedException { + throws OllamaBaseException, IOException, InterruptedException, ToolInvocationException { OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(model); return chat(builder.withMessages(messages).build()); } @@ -1088,9 +1089,10 @@ public class OllamaAPI { * @throws OllamaBaseException if the response indicates an error status * @throws IOException if an I/O error occurs during the HTTP request * @throws InterruptedException if the operation is interrupted + * @throws ToolInvocationException if the tool invocation fails */ public OllamaChatResult chat(OllamaChatRequest request) - throws OllamaBaseException, IOException, InterruptedException { + throws OllamaBaseException, IOException, InterruptedException, ToolInvocationException { return chat(request, null); } @@ -1102,7 +1104,7 @@ public class OllamaAPI { * * @param request request object to be sent to the server * @param streamHandler callback handler to handle the last message from stream - * (caution: all previous messages from stream will be + * (caution: all previous tokens from stream will be * concatenated) * @return {@link OllamaChatResult} * @throws OllamaBaseException any response code than 200 has been returned @@ -1112,9 +1114,10 @@ public class OllamaAPI { * @throws OllamaBaseException if the response indicates an error status * @throws IOException if an I/O error occurs during the HTTP request * @throws InterruptedException if the operation is interrupted + * @throws ToolInvocationException if the tool invocation fails */ public OllamaChatResult chat(OllamaChatRequest request, OllamaStreamHandler streamHandler) - throws OllamaBaseException, IOException, InterruptedException { + throws OllamaBaseException, IOException, InterruptedException, ToolInvocationException { return chatStreaming(request, new OllamaChatStreamObserver(streamHandler)); } @@ -1126,7 +1129,7 @@ public class OllamaAPI { * * @param request request object to be sent to the server * @param tokenHandler callback handler to handle the last token from stream - * (caution: all previous messages from stream will be + * (caution: the previous tokens from stream will not be * concatenated) * @return {@link OllamaChatResult} * @throws OllamaBaseException any response code than 200 has been returned @@ -1138,7 +1141,7 @@ public class OllamaAPI { * @throws InterruptedException if the operation is interrupted */ public OllamaChatResult chatStreaming(OllamaChatRequest request, OllamaTokenHandler tokenHandler) - throws OllamaBaseException, IOException, InterruptedException { + throws OllamaBaseException, IOException, InterruptedException, ToolInvocationException { OllamaChatEndpointCaller requestCaller = new OllamaChatEndpointCaller(host, auth, requestTimeoutSeconds, verbose); OllamaChatResult result; @@ -1161,6 +1164,9 @@ public class OllamaAPI { for (OllamaChatToolCalls toolCall : toolCalls) { String toolName = toolCall.getFunction().getName(); ToolFunction toolFunction = toolRegistry.getToolFunction(toolName); + if (toolFunction == null) { + throw new ToolInvocationException("Tool function not found: " + toolName); + } Map arguments = toolCall.getFunction().getArguments(); Object res = toolFunction.apply(arguments); request.getMessages().add(new OllamaChatMessage(OllamaChatMessageRole.TOOL, diff --git a/src/main/java/io/github/ollama4j/exceptions/ToolInvocationException.java b/src/main/java/io/github/ollama4j/exceptions/ToolInvocationException.java index ea81bb9..4707e55 100644 --- a/src/main/java/io/github/ollama4j/exceptions/ToolInvocationException.java +++ b/src/main/java/io/github/ollama4j/exceptions/ToolInvocationException.java @@ -2,6 +2,10 @@ package io.github.ollama4j.exceptions; public class ToolInvocationException extends Exception { + public ToolInvocationException(String s) { + super(s); + } + public ToolInvocationException(String s, Exception e) { super(s, e); } diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java index c73383b..abe388c 100644 --- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java +++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java @@ -3,6 +3,7 @@ package io.github.ollama4j.integrationtests; import com.fasterxml.jackson.annotation.JsonProperty; import io.github.ollama4j.OllamaAPI; import io.github.ollama4j.exceptions.OllamaBaseException; +import io.github.ollama4j.exceptions.ToolInvocationException; import io.github.ollama4j.models.chat.*; import io.github.ollama4j.models.embeddings.OllamaEmbedResponseModel; import io.github.ollama4j.models.response.LibraryModel; @@ -233,7 +234,7 @@ public class OllamaAPIIntegrationTest { @Test @Order(8) void testAskModelWithOptions() - throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { + throws OllamaBaseException, IOException, URISyntaxException, InterruptedException, ToolInvocationException { api.pullModel(CHAT_MODEL_INSTRUCT); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(CHAT_MODEL_INSTRUCT); @@ -253,7 +254,7 @@ public class OllamaAPIIntegrationTest { @Test @Order(9) void testChatWithSystemPrompt() - throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { + throws OllamaBaseException, IOException, URISyntaxException, InterruptedException, ToolInvocationException { api.pullModel(CHAT_MODEL_SYSTEM_PROMPT); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(CHAT_MODEL_SYSTEM_PROMPT); OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.SYSTEM, @@ -318,7 +319,7 @@ public class OllamaAPIIntegrationTest { @Test @Order(10) void testChatWithImageFromURL() - throws OllamaBaseException, IOException, InterruptedException, URISyntaxException { + throws OllamaBaseException, IOException, InterruptedException, URISyntaxException, ToolInvocationException { api.pullModel(IMAGE_MODEL_LLAVA); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(IMAGE_MODEL_LLAVA); @@ -336,7 +337,7 @@ public class OllamaAPIIntegrationTest { @Test @Order(10) void testChatWithImageFromFileWithHistoryRecognition() - throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { + throws OllamaBaseException, IOException, URISyntaxException, InterruptedException, ToolInvocationException { api.pullModel(IMAGE_MODEL_LLAVA); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(IMAGE_MODEL_LLAVA); OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER, @@ -360,7 +361,7 @@ public class OllamaAPIIntegrationTest { @Test @Order(11) void testChatWithExplicitToolDefinition() - throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { + throws OllamaBaseException, IOException, URISyntaxException, InterruptedException, ToolInvocationException { api.pullModel(CHAT_MODEL_SYSTEM_PROMPT); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(CHAT_MODEL_SYSTEM_PROMPT); @@ -440,7 +441,7 @@ public class OllamaAPIIntegrationTest { @Test @Order(12) void testChatWithAnnotatedToolsAndSingleParam() - throws OllamaBaseException, IOException, InterruptedException, URISyntaxException { + throws OllamaBaseException, IOException, InterruptedException, URISyntaxException, ToolInvocationException { api.pullModel(CHAT_MODEL_SYSTEM_PROMPT); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(CHAT_MODEL_SYSTEM_PROMPT); @@ -471,7 +472,7 @@ public class OllamaAPIIntegrationTest { @Test @Order(13) void testChatWithAnnotatedToolsAndMultipleParams() - throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { + throws OllamaBaseException, IOException, URISyntaxException, InterruptedException, ToolInvocationException { api.pullModel(CHAT_MODEL_SYSTEM_PROMPT); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(CHAT_MODEL_SYSTEM_PROMPT); @@ -508,7 +509,7 @@ public class OllamaAPIIntegrationTest { @Test @Order(14) void testChatWithToolsAndStream() - throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { + throws OllamaBaseException, IOException, URISyntaxException, InterruptedException, ToolInvocationException { api.pullModel(CHAT_MODEL_SYSTEM_PROMPT); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(CHAT_MODEL_SYSTEM_PROMPT); final Tools.ToolSpecification databaseQueryToolSpecification = Tools.ToolSpecification.builder() @@ -585,7 +586,7 @@ public class OllamaAPIIntegrationTest { @Test @Order(15) - void testChatWithStream() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException { + void testChatWithStream() throws OllamaBaseException, IOException, URISyntaxException, InterruptedException, ToolInvocationException { api.pullModel(CHAT_MODEL_SYSTEM_PROMPT); OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(CHAT_MODEL_SYSTEM_PROMPT); OllamaChatRequest requestModel = builder.withMessage(OllamaChatMessageRole.USER,