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

orpiske pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 0d6ec8493d34f75dca74d5d0f174f4a451acb06d
Author: Otavio Rodolfo Piske <angusyo...@gmail.com>
AuthorDate: Thu Mar 13 10:03:05 2025 +0100

    CAMEL-21816: Add missing tool information when returning function call 
response
---
 .../tools/LangChain4jToolsProducer.java            |  25 ++--
 .../tools/LangChain4jToolMultipleCallsIT.java      | 153 +++++++++++++++++++++
 2 files changed, 166 insertions(+), 12 deletions(-)

diff --git 
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
 
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
index 4493bf5bcc0..13aa4a1b42e 100644
--- 
a/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
+++ 
b/components/camel-ai/camel-langchain4j-tools/src/main/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolsProducer.java
@@ -99,20 +99,22 @@ public class LangChain4jToolsProducer extends 
DefaultProducer {
         }
 
         // First talk to the model to get the tools to be called
-        final Response<AiMessage> response = chatWithLLMForTools(chatMessages, 
toolPair, exchange);
-        if (response == null) {
-            return null;
-        }
+        do {
+            final Response<AiMessage> response = 
chatWithLLMForTools(chatMessages, toolPair, exchange);
+            if (!response.content().hasToolExecutionRequests()) {
+                return extractAiResponse(response);
+            }
 
-        if (!response.content().hasToolExecutionRequests()) {
-            return response.content().text();
-        }
+            // Then, talk again to call the tools and compute the final 
response
+            final Response<AiMessage> toolsCallResponse = 
chatWithLLMForToolCalling(chatMessages, exchange, response, toolPair);
+            if (!toolsCallResponse.content().hasToolExecutionRequests()) {
+                return extractAiResponse(toolsCallResponse);
+            }
 
-        // Then, talk again to call the tools and compute the final response
-        return chatWithLLMForToolCalling(chatMessages, exchange, response, 
toolPair);
+        } while (true);
     }
 
-    private String chatWithLLMForToolCalling(
+    private Response<AiMessage> chatWithLLMForToolCalling(
             List<ChatMessage> chatMessages, Exchange exchange, 
Response<AiMessage> response, ToolPair toolPair) {
         for (ToolExecutionRequest toolExecutionRequest : 
response.content().toolExecutionRequests()) {
             String toolName = toolExecutionRequest.name();
@@ -140,8 +142,7 @@ public class LangChain4jToolsProducer extends 
DefaultProducer {
                     exchange.getIn().getBody(String.class)));
         }
 
-        final Response<AiMessage> generate = 
this.chatLanguageModel.generate(chatMessages);
-        return extractAiResponse(generate);
+        return this.chatLanguageModel.generate(chatMessages);
     }
 
     /**
diff --git 
a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleCallsIT.java
 
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleCallsIT.java
new file mode 100644
index 00000000000..3873976e79a
--- /dev/null
+++ 
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolMultipleCallsIT.java
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.langchain4j.tools;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import dev.langchain4j.data.message.ChatMessage;
+import dev.langchain4j.data.message.SystemMessage;
+import dev.langchain4j.data.message.UserMessage;
+import dev.langchain4j.model.chat.ChatLanguageModel;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.infra.ollama.services.OllamaService;
+import org.apache.camel.test.infra.ollama.services.OllamaServiceFactory;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static java.time.Duration.ofSeconds;
+
+@DisabledIfSystemProperty(named = "ci.env.name", matches = ".*", 
disabledReason = "Requires too much network resources")
+public class LangChain4jToolMultipleCallsIT extends CamelTestSupport {
+
+    public static final String MODEL_NAME = "llama3.1:latest";
+    private ChatLanguageModel chatLanguageModel;
+
+    @RegisterExtension
+    static OllamaService OLLAMA = 
OllamaServiceFactory.createServiceWithConfiguration(() -> MODEL_NAME);
+
+    private volatile boolean intermediateCalled = false;
+    private volatile boolean intermediateHasValidBody = false;
+
+    @Override
+    protected void setupResources() throws Exception {
+        super.setupResources();
+
+        chatLanguageModel = createModel();
+    }
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+
+        LangChain4jToolsComponent component
+                = context.getComponent(LangChain4jTools.SCHEME, 
LangChain4jToolsComponent.class);
+
+        component.getConfiguration().setChatModel(chatLanguageModel);
+
+        return context;
+    }
+
+    protected ChatLanguageModel createModel() {
+        chatLanguageModel = OpenAiChatModel.builder()
+                .apiKey("NO_API_KEY")
+                .modelName(MODEL_NAME)
+                .baseUrl(OLLAMA.getEndpoint())
+                .temperature(0.0)
+                .timeout(ofSeconds(60))
+                .logRequests(true)
+                .logResponses(true)
+                .build();
+
+        return chatLanguageModel;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+
+                from("direct:test")
+                        .to("langchain4j-tools:test1?tags=geo")
+                        .log("response is: ${body}");
+
+                from("langchain4j-tools:test1?tags=geo&description=Forecasts 
the weather for the given latitude and 
longitude&parameter.latitude=integer&parameters.longitude=integer")
+                        .log("intermediate body is: ${body}")
+                        .process(exchange -> {
+                            intermediateCalled = true;
+                            String body = 
exchange.getIn().getBody(String.class);
+                            if (body != null) {
+                                if (body.contains("51.50758961965397") && 
body.contains("-0.13388057363742217")) {
+                                    intermediateHasValidBody = true;
+                                }
+                            }
+                        })
+                        .setBody(simple("""
+                                {
+                                  "location": "London, UK",
+                                  "date": "2025-03-13",
+                                  "time": "07:44",
+                                  "current_conditions": {
+                                    "temperature_celsius": 3,
+                                    "temperature_fahrenheit": 37,
+                                    "humidity": 93,
+                                    "condition": {
+                                      "text": "Light rain",
+                                      "icon": "drizzle",
+                                      "code": 1183
+                                    },
+                                """));
+
+                from("langchain4j-tools:test1?tags=geo&description=Finds the 
latitude and longitude of a given city&parameter.name=string")
+                        .setBody(simple("{\"latitude\": \"51.50758961965397\", 
\"longitude\": \"-0.13388057363742217\"}"));
+
+            }
+        };
+    }
+
+    @RepeatedTest(10)
+    public void testSimpleInvocation() {
+        List<ChatMessage> messages = new ArrayList<>();
+        messages.add(new SystemMessage(
+                """
+                        You are a meteorologist, and you need to answer 
questions asked by the user about weather using at most 3 lines.
+                         The weather information is a JSON object and has the 
following fields:
+                             maxTemperature is the maximum temperature of the 
day in Celsius degrees
+                             minTemperature is the minimum temperature of the 
day in Celsius degrees   \s
+                             precipitation is the amount of water in mm   \s
+                             windSpeed is the speed of wind in kilometers per 
hour   \s
+                             weather is the overall weather.
+                        """));
+        messages.add(new UserMessage("""
+                What is the weather in london ??
+                """));
+
+        Exchange message = 
fluentTemplate.to("direct:test").withBody(messages).request(Exchange.class);
+
+        Assertions.assertThat(message).isNotNull();
+        final String responseContent = 
message.getMessage().getBody().toString();
+        Assertions.assertThat(responseContent).containsIgnoringCase("The 
weather in London");
+        Assertions.assertThat(intermediateCalled).isTrue();
+        Assertions.assertThat(intermediateHasValidBody).isTrue();
+    }
+}

Reply via email to