mirror of
https://github.com/amithkoujalgi/ollama4j.git
synced 2025-09-16 03:39:05 +02:00
Merge pull request #174 from twosom/client-handles-tools-support
feat: add client-side tool handling option
This commit is contained in:
commit
f4ce7e2ec6
@ -21,7 +21,7 @@ repos:
|
|||||||
|
|
||||||
# for commit message formatting
|
# for commit message formatting
|
||||||
- repo: https://github.com/commitizen-tools/commitizen
|
- repo: https://github.com/commitizen-tools/commitizen
|
||||||
rev: v4.8.3
|
rev: v4.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: commitizen
|
- id: commitizen
|
||||||
stages: [commit-msg]
|
stages: [commit-msg]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src='https://raw.githubusercontent.com/ollama4j/ollama4j/65a9d526150da8fcd98e2af6a164f055572bf722/ollama4j.jpeg' width='100' alt="ollama4j-icon">
|
<img src='https://raw.githubusercontent.com/ollama4j/ollama4j/65a9d526150da8fcd98e2af6a164f055572bf722/ollama4j.jpeg' width='100' alt="ollama4j-icon">
|
||||||
|
|
||||||
### Ollama4j
|
### Ollama4j
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -29,6 +29,18 @@ session. The tool invocation and response handling are all managed internally by
|
|||||||
|
|
||||||
This tool calling can also be done using the streaming API.
|
This tool calling can also be done using the streaming API.
|
||||||
|
|
||||||
|
### Client-managed tool calls (clientHandlesTools)
|
||||||
|
|
||||||
|
By default, ollama4j automatically executes tool calls returned by the model during chat, runs the corresponding registered Java methods, and appends the tool results back into the conversation. For some applications, you may want to intercept tool calls and decide yourself when and how to execute them (for example, to queue them, to show a confirmation UI to the user, to run them in a sandbox, or to perform multi‑step orchestration).
|
||||||
|
|
||||||
|
To enable this behavior, set the clientHandlesTools flag to true on your OllamaAPI instance. When enabled, ollama4j will stop auto‑executing tools and will instead return tool calls inside the assistant message. You can then inspect the tool calls and execute them manually.
|
||||||
|
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Default value: clientHandlesTools is false for backward compatibility.
|
||||||
|
- When clientHandlesTools is false, ollama4j auto‑executes tools and loops internally until tools are resolved or max retries is reached.
|
||||||
|
- When clientHandlesTools is true, ollama4j will not execute tools; you are responsible for invoking tools and passing results back as TOOL messages, then re‑calling chat() to continue.
|
||||||
|
|
||||||
### Annotation-Based Tool Registration
|
### Annotation-Based Tool Registration
|
||||||
|
|
||||||
Ollama4j provides a declarative and convenient way to define and register tools using Java annotations and reflection.
|
Ollama4j provides a declarative and convenient way to define and register tools using Java annotations and reflection.
|
||||||
|
2
pom.xml
2
pom.xml
@ -426,4 +426,4 @@
|
|||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -93,6 +93,15 @@ public class OllamaAPI {
|
|||||||
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
@SuppressWarnings({"FieldMayBeFinal", "FieldCanBeLocal"})
|
||||||
private int numberOfRetriesForModelPull = 0;
|
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:
|
* Instantiates the Ollama API with default Ollama host:
|
||||||
* <a href="http://localhost:11434">http://localhost:11434</a>
|
* <a href="http://localhost:11434">http://localhost:11434</a>
|
||||||
@ -1349,6 +1358,10 @@ public class OllamaAPI {
|
|||||||
result = requestCaller.callSync(request);
|
result = requestCaller.callSync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clientHandlesTools) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// check if toolCallIsWanted
|
// check if toolCallIsWanted
|
||||||
List<OllamaChatToolCalls> toolCalls = result.getResponseModel().getMessage().getToolCalls();
|
List<OllamaChatToolCalls> toolCalls = result.getResponseModel().getMessage().getToolCalls();
|
||||||
int toolCallTries = 0;
|
int toolCallTries = 0;
|
||||||
|
@ -303,6 +303,8 @@ class OllamaAPIIntegrationTest {
|
|||||||
@Order(11)
|
@Order(11)
|
||||||
void testChatWithExplicitToolDefinition() throws OllamaBaseException, IOException, URISyntaxException,
|
void testChatWithExplicitToolDefinition() throws OllamaBaseException, IOException, URISyntaxException,
|
||||||
InterruptedException, ToolInvocationException {
|
InterruptedException, ToolInvocationException {
|
||||||
|
// Ensure default behavior (library handles tools) for baseline assertions
|
||||||
|
api.setClientHandlesTools(false);
|
||||||
String theToolModel = TOOLS_MODEL;
|
String theToolModel = TOOLS_MODEL;
|
||||||
api.pullModel(theToolModel);
|
api.pullModel(theToolModel);
|
||||||
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(theToolModel);
|
OllamaChatRequestBuilder builder = OllamaChatRequestBuilder.getInstance(theToolModel);
|
||||||
@ -336,10 +338,61 @@ class OllamaAPIIntegrationTest {
|
|||||||
assertNull(finalToolCalls, "Final tool calls in the response message should be null");
|
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
|
@Test
|
||||||
@Order(14)
|
@Order(14)
|
||||||
void testChatWithToolsAndStream() throws OllamaBaseException, IOException, URISyntaxException,
|
void testChatWithToolsAndStream() throws OllamaBaseException, IOException, URISyntaxException,
|
||||||
InterruptedException, ToolInvocationException {
|
InterruptedException, ToolInvocationException {
|
||||||
|
// Ensure default behavior (library handles tools) for streamed test
|
||||||
|
api.setClientHandlesTools(false);
|
||||||
String theToolModel = TOOLS_MODEL;
|
String theToolModel = TOOLS_MODEL;
|
||||||
api.pullModel(theToolModel);
|
api.pullModel(theToolModel);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user