diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 5bfd646..7c1bf5c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -21,7 +21,7 @@ repos:
# for commit message formatting
- repo: https://github.com/commitizen-tools/commitizen
- rev: v4.8.3
+ rev: v4.9.0
hooks:
- id: commitizen
stages: [commit-msg]
diff --git a/README.md b/README.md
index 9701618..37e35fc 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@

-
+
### Ollama4j
diff --git a/pom.xml b/pom.xml
index 3338089..82d69a0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -426,4 +426,4 @@
-
\ No newline at end of file
+
diff --git a/src/main/java/io/github/ollama4j/OllamaAPI.java b/src/main/java/io/github/ollama4j/OllamaAPI.java
index 99f1f7a..a399adf 100644
--- a/src/main/java/io/github/ollama4j/OllamaAPI.java
+++ b/src/main/java/io/github/ollama4j/OllamaAPI.java
@@ -93,6 +93,15 @@ public class OllamaAPI {
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
private int numberOfRetriesForModelPull = 0;
+ /**
+ * When set to true, tools will not be automatically executed by the library.
+ * Instead, tool calls will be returned to the client for manual handling.
+ *
+ * Default is false for backward compatibility.
+ */
+ @Setter
+ private boolean clientHandlesTools = false;
+
/**
* Instantiates the Ollama API with default Ollama host:
* http://localhost:11434
@@ -1349,6 +1358,10 @@ public class OllamaAPI {
result = requestCaller.callSync(request);
}
+ if (clientHandlesTools) {
+ return result;
+ }
+
// check if toolCallIsWanted
List toolCalls = result.getResponseModel().getMessage().getToolCalls();
int toolCallTries = 0;
diff --git a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
index 3b3b74a..51d8edf 100644
--- a/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
+++ b/src/test/java/io/github/ollama4j/integrationtests/OllamaAPIIntegrationTest.java
@@ -303,6 +303,8 @@ class OllamaAPIIntegrationTest {
@Order(11)
void testChatWithExplicitToolDefinition() throws OllamaBaseException, IOException, URISyntaxException,
InterruptedException, ToolInvocationException {
+ // Ensure default behavior (library handles tools) for baseline assertions
+ api.setClientHandlesTools(false);
String theToolModel = TOOLS_MODEL;
api.pullModel(theToolModel);
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(theToolModel);
@@ -336,10 +338,61 @@ class OllamaAPIIntegrationTest {
assertNull(finalToolCalls, "Final tool calls in the response message should be null");
}
+ @Test
+ @Order(13)
+ void testChatWithExplicitToolDefinitionWithClientHandlesTools() throws OllamaBaseException, IOException, URISyntaxException,
+ InterruptedException, ToolInvocationException {
+ String theToolModel = TOOLS_MODEL;
+ api.pullModel(theToolModel);
+ OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(theToolModel);
+
+ api.registerTool(employeeFinderTool());
+
+ try {
+ // enable client-handled tools so the library does not auto-execute tool calls
+ api.setClientHandlesTools(true);
+
+ OllamaChatRequest requestModel = builder
+ .withMessage(OllamaChatMessageRole.USER, "Give me the ID and address of the employee Rahul Kumar.")
+ .build();
+ requestModel.setOptions(new OptionsBuilder().setTemperature(0.9f).build().getOptionsMap());
+
+ OllamaChatResult chatResult = api.chat(requestModel);
+
+ assertNotNull(chatResult, "chatResult should not be null");
+ assertNotNull(chatResult.getResponseModel(), "Response model should not be null");
+ assertNotNull(chatResult.getResponseModel().getMessage(), "Response message should not be null");
+ assertEquals(
+ OllamaChatMessageRole.ASSISTANT.getRoleName(),
+ chatResult.getResponseModel().getMessage().getRole().getRoleName(),
+ "Role of the response message should be ASSISTANT"
+ );
+
+ // When clientHandlesTools is true, the assistant message should contain tool calls
+ List toolCalls = chatResult.getResponseModel().getMessage().getToolCalls();
+ assertNotNull(toolCalls, "Assistant message should contain tool calls when clientHandlesTools is true");
+ assertFalse(toolCalls.isEmpty(), "Tool calls should not be empty");
+ OllamaToolCallsFunction function = toolCalls.get(0).getFunction();
+ assertEquals("get-employee-details", function.getName(), "Tool function name should be 'get-employee-details'");
+ Object employeeName = function.getArguments().get("employee-name");
+ assertNotNull(employeeName, "Employee name argument should not be null");
+ assertEquals("Rahul Kumar", employeeName, "Employee name argument should be 'Rahul Kumar'");
+
+ // Since tools were not auto-executed, chat history should contain only the user and assistant messages
+ assertEquals(2, chatResult.getChatHistory().size(),
+ "Chat history should contain only user and assistant (tool call) messages when clientHandlesTools is true");
+ } finally {
+ // reset to default to avoid affecting other tests
+ api.setClientHandlesTools(false);
+ }
+ }
+
@Test
@Order(14)
void testChatWithToolsAndStream() throws OllamaBaseException, IOException, URISyntaxException,
InterruptedException, ToolInvocationException {
+ // Ensure default behavior (library handles tools) for streamed test
+ api.setClientHandlesTools(false);
String theToolModel = TOOLS_MODEL;
api.pullModel(theToolModel);