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-icon - + ### 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);