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

gnodet pushed a commit to branch 
fix-camel-23943-camel-langchain4j-tools-has-three
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 4a423267c4fa951902cda667f9f57fb8e07f28a9
Author: Guillaume Nodet <[email protected]>
AuthorDate: Tue Jul 21 17:22:49 2026 +0000

    CAMEL-23943: Regenerate missing files and improve test reproducer
    
    - Regenerated catalog JSON and endpoint DSL factory for the new
      maxToolCallingRoundTrips option (fixes CI "uncommitted changes" check)
    - Improved defect 1 test to use a custom mock that always returns tool
      calls, creating a true infinite loop scenario. Added @Timeout safety
      net so the test fails cleanly (timeout) if the fix is reverted, instead
      of hanging forever.
    - Verified all three tests fail before the fix and pass after:
      - Test 1: times out (infinite loop) → RuntimeCamelException (fix works)
      - Test 2: NoSuchElementException → graceful recovery
      - Test 3: exception on exchange → error sent to LLM
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../catalog/components/langchain4j-tools.json      |  5 +--
 .../tools/LangChain4jToolDefectsTest.java          | 41 ++++++++++++++--------
 .../LangChain4jToolsEndpointBuilderFactory.java    | 38 ++++++++++++++++++++
 3 files changed, 67 insertions(+), 17 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
index 0477acdec1cc..1197e5ae486a 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/langchain4j-tools.json
@@ -47,7 +47,8 @@
     "camelToolParameter": { "index": 7, "kind": "parameter", "displayName": 
"Camel Tool Parameter", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.component.langchain4j.tools.spec.CamelSimpleToolParameter", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "description": "Tool's Camel Parameters, programmatically define Tool 
description and parameters" },
     "exceptionHandler": { "index": 8, "kind": "parameter", "displayName": 
"Exception Handler", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "object", "javaType": 
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", 
"deprecated": false, "autowired": false, "secret": false, "description": "To 
let the consumer use a custom ExceptionHandler. Notice if the option 
bridgeErrorHandler is enabled then this option is not in use. By def [...]
     "exchangePattern": { "index": 9, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
-    "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
-    "chatModel": { "index": 11, "kind": "parameter", "displayName": "Chat 
Model", "group": "advanced", "label": "advanced", "required": false, "type": 
"object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated": 
false, "deprecationNote": "", "autowired": true, "secret": false, 
"configurationClass": 
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration", 
"configurationField": "configuration", "description": "Chat Model of type 
dev.langchain4j.model.cha [...]
+    "maxToolCallingRoundTrips": { "index": 10, "kind": "parameter", 
"displayName": "Max Tool Calling Round Trips", "group": "producer", "label": 
"producer", "required": false, "type": "integer", "javaType": "int", 
"deprecated": false, "deprecationNote": "", "autowired": false, "secret": 
false, "defaultValue": 10, "description": "Maximum number of tool-calling round 
trips (iterations) allowed before stopping. This prevents infinite loops when 
the LLM keeps requesting tool calls indefinite [...]
+    "lazyStartProducer": { "index": 11, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
+    "chatModel": { "index": 12, "kind": "parameter", "displayName": "Chat 
Model", "group": "advanced", "label": "advanced", "required": false, "type": 
"object", "javaType": "dev.langchain4j.model.chat.ChatModel", "deprecated": 
false, "deprecationNote": "", "autowired": true, "secret": false, 
"configurationClass": 
"org.apache.camel.component.langchain4j.tools.LangChain4jToolsConfiguration", 
"configurationField": "configuration", "description": "Chat Model of type 
dev.langchain4j.model.cha [...]
   }
 }
diff --git 
a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolDefectsTest.java
 
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolDefectsTest.java
index 505bf9df7579..3efec31a7c8f 100644
--- 
a/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolDefectsTest.java
+++ 
b/components/camel-ai/camel-langchain4j-tools/src/test/java/org/apache/camel/component/langchain4j/tools/LangChain4jToolDefectsTest.java
@@ -18,6 +18,7 @@ package org.apache.camel.component.langchain4j.tools;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.UUID;
 
 import dev.langchain4j.data.message.ChatMessage;
 import dev.langchain4j.data.message.SystemMessage;
@@ -30,6 +31,7 @@ import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.test.infra.openai.mock.OpenAIMock;
 import org.apache.camel.test.junit6.CamelTestSupport;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -48,18 +50,11 @@ class LangChain4jToolDefectsTest extends CamelTestSupport {
 
     @RegisterExtension
     static OpenAIMock openAIMock = new OpenAIMock().builder()
-            // Scenario 1: LLM keeps requesting tools — 5 sequential round 
trips
-            .when("Do something that requires many round trips\n")
-            .invokeTool("RealTool")
-            .withParam("input", "round1")
-            .andThenInvokeTool("RealTool")
-            .withParam("input", "round2")
-            .andThenInvokeTool("RealTool")
-            .withParam("input", "round3")
-            .andThenInvokeTool("RealTool")
-            .withParam("input", "round4")
-            .andThenInvokeTool("RealTool")
-            .withParam("input", "round5")
+            // Scenario 1: LLM always requests tools — never stops on its own.
+            // Uses a custom response function that returns a tool call on 
every request,
+            // simulating an LLM stuck in an infinite tool-calling loop.
+            .when("Infinite tool loop\n")
+            .thenRespondWith((exchange, input) -> 
createAlwaysToolCallResponse())
             .end()
             // Scenario 2: LLM hallucinates a tool name that doesn't exist
             .when("Call a tool that does not exist\n")
@@ -72,6 +67,20 @@ class LangChain4jToolDefectsTest extends CamelTestSupport {
             .withParam("input", "test")
             .build();
 
+    /**
+     * Creates a mock OpenAI response that always requests a tool call. Each 
invocation returns unique IDs to simulate a
+     * fresh tool call request from the LLM.
+     */
+    private static String createAlwaysToolCallResponse() {
+        return "{\"id\":\"" + UUID.randomUUID() + "\","
+               + "\"choices\":[{\"finish_reason\":\"tool_calls\",\"index\":0,"
+               + 
"\"message\":{\"role\":\"assistant\",\"refusal\":null,\"content\":null,"
+               + "\"tool_calls\":[{\"id\":\"" + UUID.randomUUID() + 
"\",\"type\":\"function\","
+               + "\"function\":{\"name\":\"RealTool\","
+               + "\"arguments\":\"{\\\"input\\\":\\\"loop\\\"}\"}}]}}],"
+               + 
"\"created\":1234567890,\"model\":\"openai-mock\",\"object\":\"chat.completion\"}";
+    }
+
     @Override
     protected void setupResources() throws Exception {
         super.setupResources();
@@ -119,14 +128,16 @@ class LangChain4jToolDefectsTest extends CamelTestSupport 
{
      * CAMEL-23943 defect 1: The do/while(true) loop in toolsChat has no 
iteration cap. If the LLM keeps requesting
      * tools indefinitely, this never terminates.
      *
-     * With the fix, the loop is bounded by maxToolCallingRoundTrips (default 
10, set to 2 for this test). Exceeding the
-     * cap throws a RuntimeCamelException.
+     * This test uses a mock that always returns tool calls (never a final 
text response), simulating an LLM stuck in an
+     * infinite loop. Without the fix, this test would hang indefinitely 
(caught by the @Timeout annotation). With the
+     * fix, the loop is bounded by maxToolCallingRoundTrips and throws a 
RuntimeCamelException.
      */
     @Test
+    @Timeout(10) // Safety net: without the fix, the infinite loop would hang 
forever
     void testMaxToolCallingRoundTripsExceeded() {
         List<ChatMessage> messages = new ArrayList<>();
         messages.add(new SystemMessage("You are a helpful assistant."));
-        messages.add(new UserMessage("Do something that requires many round 
trips\n"));
+        messages.add(new UserMessage("Infinite tool loop\n"));
 
         Exchange exchange = fluentTemplate.to("direct:testMaxRoundTrips")
                 .withBody(messages).request(Exchange.class);
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
index f53dd7e54b32..84c3900047f5 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/LangChain4jToolsEndpointBuilderFactory.java
@@ -386,6 +386,44 @@ public interface LangChain4jToolsEndpointBuilderFactory {
             doSetProperty("tags", tags);
             return this;
         }
+        /**
+         * Maximum number of tool-calling round trips (iterations) allowed
+         * before stopping. This prevents infinite loops when the LLM keeps
+         * requesting tool calls indefinitely. Each round trip consists of one
+         * LLM call and the execution of all tools requested in that call. Set
+         * to 0 for unlimited (not recommended).
+         * 
+         * The option is a: <code>int</code> type.
+         * 
+         * Default: 10
+         * Group: producer
+         * 
+         * @param maxToolCallingRoundTrips the value to set
+         * @return the dsl builder
+         */
+        default LangChain4jToolsEndpointProducerBuilder 
maxToolCallingRoundTrips(int maxToolCallingRoundTrips) {
+            doSetProperty("maxToolCallingRoundTrips", 
maxToolCallingRoundTrips);
+            return this;
+        }
+        /**
+         * Maximum number of tool-calling round trips (iterations) allowed
+         * before stopping. This prevents infinite loops when the LLM keeps
+         * requesting tool calls indefinitely. Each round trip consists of one
+         * LLM call and the execution of all tools requested in that call. Set
+         * to 0 for unlimited (not recommended).
+         * 
+         * The option will be converted to a <code>int</code> type.
+         * 
+         * Default: 10
+         * Group: producer
+         * 
+         * @param maxToolCallingRoundTrips the value to set
+         * @return the dsl builder
+         */
+        default LangChain4jToolsEndpointProducerBuilder 
maxToolCallingRoundTrips(String maxToolCallingRoundTrips) {
+            doSetProperty("maxToolCallingRoundTrips", 
maxToolCallingRoundTrips);
+            return this;
+        }
     }
 
     /**

Reply via email to