feat: add client-side tool handling option

This commit is contained in:
twosom 2025-09-13 21:21:00 +09:00
parent 356bd0b81f
commit 7bd3bed5e4
5 changed files with 69 additions and 3 deletions

View File

@ -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]

View File

@ -1,6 +1,6 @@
<div align="center">
<img src='https://raw.githubusercontent.com/ollama4j/ollama4j/65a9d526150da8fcd98e2af6a164f055572bf722/ollama4j.jpeg' width='100' alt="ollama4j-icon">
### Ollama4j
</div>

View File

@ -426,4 +426,4 @@
</profile>
</profiles>
</project>
</project>

View File

@ -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.
* <p>
* Default is false for backward compatibility.
*/
@Setter
private boolean clientHandlesTools = false;
/**
* Instantiates the Ollama API with default Ollama host:
* <a href="http://localhost:11434">http://localhost:11434</a>
@ -1349,6 +1358,10 @@ public class OllamaAPI {
result = requestCaller.callSync(request);
}
if (clientHandlesTools) {
return result;
}
// check if toolCallIsWanted
List<OllamaChatToolCalls> toolCalls = result.getResponseModel().getMessage().getToolCalls();
int toolCallTries = 0;

View File

@ -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<OllamaChatToolCalls> 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);