You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by gn...@apache.org on 2026/07/21 17:23:04 UTC

(camel) branch fix-camel-23943-camel-langchain4j-tools-has-three created (now 4a423267c4fa)

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

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


      at 4a423267c4fa CAMEL-23943: Regenerate missing files and improve test reproducer

This branch includes the following new commits:

     new 4a423267c4fa CAMEL-23943: Regenerate missing files and improve test reproducer

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(camel) 01/01: CAMEL-23943: Regenerate missing files and improve test reproducer

Posted by gn...@apache.org.
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 <gn...@gmail.com>
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 <no...@anthropic.com>
---
 .../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;
+        }
     }
 
     /**