This is an automated email from the ASF dual-hosted git repository.

wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git


The following commit(s) were added to refs/heads/main by this push:
     new 97718d7a [integration][ollama] Forward assistant tool calls in the 
Java chat history (#1166)
97718d7a is described below

commit 97718d7a66ef75c058f00f5e23526c7607f60984
Author: Edson <[email protected]>
AuthorDate: Tue Sep 29 00:00:26 2026 -0400

    [integration][ollama] Forward assistant tool calls in the Java chat history 
(#1166)
    
    Generated-by: Claude Code 2.1.259 (Claude Opus 5.5)
---
 .../ollama/OllamaChatModelConnection.java          | 51 +++++++++++++++++++++-
 .../ollama/OllamaChatModelConnectionTest.java      | 36 +++++++++++++++
 2 files changed, 86 insertions(+), 1 deletion(-)

diff --git 
a/integrations/chat-models/ollama/src/main/java/org/apache/flink/agents/integrations/chatmodels/ollama/OllamaChatModelConnection.java
 
b/integrations/chat-models/ollama/src/main/java/org/apache/flink/agents/integrations/chatmodels/ollama/OllamaChatModelConnection.java
index 96187868..36057c95 100644
--- 
a/integrations/chat-models/ollama/src/main/java/org/apache/flink/agents/integrations/chatmodels/ollama/OllamaChatModelConnection.java
+++ 
b/integrations/chat-models/ollama/src/main/java/org/apache/flink/agents/integrations/chatmodels/ollama/OllamaChatModelConnection.java
@@ -26,6 +26,7 @@ import io.github.ollama4j.exceptions.RoleNotFoundException;
 import io.github.ollama4j.models.chat.*;
 import io.github.ollama4j.models.request.OllamaChatEndpointCaller;
 import io.github.ollama4j.models.request.ThinkMode;
+import io.github.ollama4j.tools.OllamaToolCallsFunction;
 import io.github.ollama4j.tools.Tools;
 import org.apache.flink.agents.api.chat.messages.ChatMessage;
 import org.apache.flink.agents.api.chat.messages.MessageRole;
@@ -174,12 +175,60 @@ public class OllamaChatModelConnection extends 
BaseChatModelConnection {
         try {
             final OllamaChatMessageRole ollamaRole =
                     OllamaChatMessageRole.getRole(role.name().toLowerCase());
-            return new OllamaChatMessage(ollamaRole, message.getText());
+            final OllamaChatMessage ollamaMessage =
+                    new OllamaChatMessage(ollamaRole, message.getText());
+            final List<Map<String, Object>> toolCalls = message.getToolCalls();
+            if (toolCalls != null && !toolCalls.isEmpty()) {
+                // Without the calls, the history shows tool results the model 
never requested.
+                ollamaMessage.setToolCalls(toOllamaToolCalls(toolCalls));
+            }
+            return ollamaMessage;
         } catch (RoleNotFoundException e) {
             throw new RuntimeException(e);
         }
     }
 
+    /**
+     * Converts framework tool calls back to Ollama's shape, the function name 
and its arguments as
+     * an object, as the Python connection does. The framework-assigned id is 
not sent.
+     */
+    @SuppressWarnings("unchecked")
+    private static List<OllamaChatToolCalls> toOllamaToolCalls(
+            List<Map<String, Object>> toolCalls) {
+        final List<OllamaChatToolCalls> ollamaToolCalls = new 
ArrayList<>(toolCalls.size());
+        for (Map<String, Object> toolCall : toolCalls) {
+            final Map<String, Object> function = (Map<String, Object>) 
toolCall.get("function");
+            if (function == null || function.get("name") == null) {
+                throw new IllegalArgumentException("A tool call must have a 
function name.");
+            }
+            ollamaToolCalls.add(
+                    new OllamaChatToolCalls(
+                            null,
+                            new OllamaToolCallsFunction(
+                                    String.valueOf(function.get("name")),
+                                    
toArgumentsMap(function.get("arguments")))));
+        }
+        return ollamaToolCalls;
+    }
+
+    /** Ollama expects the arguments as an object; a JSON string is parsed 
into one. */
+    @SuppressWarnings("unchecked")
+    private static Map<String, Object> toArgumentsMap(Object arguments) {
+        if (arguments == null) {
+            return Collections.emptyMap();
+        }
+        if (arguments instanceof Map) {
+            return (Map<String, Object>) arguments;
+        }
+        try {
+            return new ObjectMapper()
+                    .readValue(
+                            String.valueOf(arguments), new 
TypeReference<Map<String, Object>>() {});
+        } catch (Exception e) {
+            throw new IllegalArgumentException("Tool call arguments must be a 
JSON object.", e);
+        }
+    }
+
     /**
      * Whether Ollama can constrain generation to a schema for {@code 
effectiveModel}.
      *
diff --git 
a/integrations/chat-models/ollama/src/test/java/org/apache/flink/agents/integrations/chatmodels/ollama/OllamaChatModelConnectionTest.java
 
b/integrations/chat-models/ollama/src/test/java/org/apache/flink/agents/integrations/chatmodels/ollama/OllamaChatModelConnectionTest.java
index 3d8a0721..894fd990 100644
--- 
a/integrations/chat-models/ollama/src/test/java/org/apache/flink/agents/integrations/chatmodels/ollama/OllamaChatModelConnectionTest.java
+++ 
b/integrations/chat-models/ollama/src/test/java/org/apache/flink/agents/integrations/chatmodels/ollama/OllamaChatModelConnectionTest.java
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.github.ollama4j.models.chat.OllamaChatRequest;
 import io.github.ollama4j.tools.Tools;
+import io.github.ollama4j.utils.Utils;
 import org.apache.flink.agents.api.chat.messages.ChatMessage;
 import org.apache.flink.agents.api.chat.messages.MessageRole;
 import org.apache.flink.agents.api.resource.ResourceContext;
@@ -169,6 +170,41 @@ class OllamaChatModelConnectionTest {
                 .isFalse();
     }
 
+    @Test
+    @DisplayName("Assistant tool calls in the history are sent back to Ollama")
+    void buildRequestForwardsAssistantToolCalls() {
+        Map<String, Object> call =
+                Map.of(
+                        "id",
+                        "fa-call-1",
+                        "type",
+                        "function",
+                        "function",
+                        Map.of("name", "get_weather", "arguments", 
Map.of("city", "Paris")));
+        Map<String, Object> jsonArgumentsCall =
+                Map.of("function", Map.of("name", "get_time", "arguments", 
"{\"zone\":\"CET\"}"));
+        List<ChatMessage> history =
+                List.of(
+                        new ChatMessage(MessageRole.USER, "Weather and time in 
Paris?"),
+                        ChatMessage.assistant("", List.of(call, 
jsonArgumentsCall)),
+                        new ChatMessage(MessageRole.TOOL, "sunny"),
+                        new ChatMessage(MessageRole.TOOL, "10:00"));
+
+        OllamaChatRequest request =
+                connection().buildRequest(history, List.of(), 
params("qwen3:4b"), null);
+        JsonNode wire = Utils.getObjectMapper().valueToTree(request);
+
+        JsonNode toolCalls = wire.at("/messages/1/tool_calls");
+        assertThat(toolCalls).hasSize(2);
+        
assertThat(toolCalls.at("/0/function/name").asText()).isEqualTo("get_weather");
+        
assertThat(toolCalls.at("/0/function/arguments/city").asText()).isEqualTo("Paris");
+        
assertThat(toolCalls.at("/1/function/arguments/zone").asText()).isEqualTo("CET");
+        assertThat(
+                        wire.at("/messages/0/tool_calls").isMissingNode()
+                                || wire.at("/messages/0/tool_calls").isNull())
+                .isTrue();
+    }
+
     @Test
     @DisplayName("A POJO output schema is sent as the native format")
     void buildRequestSetsFormatForPojoSchema() {

Reply via email to