Skip to content

CAMEL-23952: Support executeToolsConcurrently for langchain4j-agent - #25228

Open
atiaomar1978-hub wants to merge 6 commits into
apache:mainfrom
atiaomar1978-hub:CAMEL-23952-execute-tools-concurrently
Open

CAMEL-23952: Support executeToolsConcurrently for langchain4j-agent#25228
atiaomar1978-hub wants to merge 6 commits into
apache:mainfrom
atiaomar1978-hub:CAMEL-23952-execute-tools-concurrently

Conversation

@atiaomar1978-hub

@atiaomar1978-hub atiaomar1978-hub commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

AI-generated on behalf of atiaomar1978-hub

  • Exposes AgentConfiguration.withExecuteToolsConcurrently() and withExecuteToolsConcurrently(Executor), wired into AbstractAgent.configureBuilder() for LangChain4j AiServices.
  • When concurrent execution is enabled without an explicit executor, LangChain4jAgentProducer registers a Camel-managed thread pool via ExecutorServiceManager (on a duplicated configuration so registry beans are not mutated) and shuts it down on component stop.
  • Builds on exchange-isolated Camel route tool execution (CAMEL-23944) so parallel tool calls are safe.
  • Documents concurrent tool mode in the langchain4j-agent component guide.

Review follow-up (Bugbot / Grok)

  • Fixed: Managed executor is no longer written into shared #agentConfiguration registry beans; producer uses AgentConfiguration.duplicate() for the build-time copy.
  • Added: Context stop/restart test for managed executor path; duplicate() unit test.

Test plan

  • AgentConfigurationTest (executeToolsConcurrently + duplicate)
  • LangChain4jAgentExecuteToolsConcurrentlyTest (parallel overlap, registry not mutated, restart)
  • mvn test -pl components/camel-ai/camel-langchain4j-agent -am -Dtest=LangChain4jAgentExecuteToolsConcurrentlyTest,AgentConfigurationTest

@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

AI-generated comment on behalf of atiaomar1978-hub

Follow-up commit 16b9389254f addresses Bugbot/Grok review:

  • Registry safety: LangChain4jAgentProducer now builds agents from AgentConfiguration.duplicate() so the managed concurrent-tool executor is attached only to the producer-owned copy, not shared #agentConfiguration beans.
  • Tests: Added duplicate() coverage and a context stop/restart regression for the managed-executor path.

Re-ran LangChain4jAgentExecuteToolsConcurrentlyTest + AgentConfigurationTest — green.

@github-actions

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

AI-generated comment on behalf of atiaomar1978-hub

Pushed a0102839 to fix the failing sourcecheck job (run 30468300277).

The build regenerated catalog docs from langchain4j-agent-component.adoc, but the committed copy under catalog/camel-catalog/src/generated/resources/.../langchain4j-agent-component.adoc was missing the new Concurrent tool execution section. That file is now synced with the component docs so git status stays clean after the build.

Please re-run or wait for CI on the latest commit.

@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • catalog/camel-catalog
  • components/camel-ai/camel-langchain4j-agent-api
  • components/camel-ai/camel-langchain4j-agent

🔬 Scalpel shadow comparison — Scalpel: 11 tested, 28 compile-only — current: 10 all tested

Maveniverse Scalpel detected 39 affected modules (current approach: 10).

⚠️ Modules only in Scalpel (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

Skip-tests mode would test 11 modules (3 direct + 8 downstream), skip tests for 28 (generated code, meta-modules)

Modules Scalpel would test (11)
  • camel-catalog
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-langchain4j-agent
  • camel-langchain4j-agent-api
  • camel-launcher-container
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
Modules with tests skipped (28)
  • apache-camel
  • camel-allcomponents
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

⚠️ Some tests are disabled on GitHub Actions (@DisabledIfSystemProperty(named = "ci.env.name")) and require manual verification:

  • components/camel-ai/camel-langchain4j-agent: 16 test(s) disabled on GitHub Actions
All tested modules (39 modules)
  • Camel :: AI :: LangChain4j :: Agent
  • Camel :: AI :: LangChain4j :: Agent :: API
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated)
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: Kamelet Main
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well-structured PR. The core design is sound: duplicate() protects shared registry beans from mutation, Camel's ExecutorServiceManager manages the thread pool lifecycle correctly (doInit create, doStop shutdown), and the concurrency test genuinely validates parallel tool overlap using a CountDownLatch barrier + AtomicInteger high-water-mark. CI is green. No correctness issues — only convention cleanups below.

Findings

1. [Convention] Test methods use public and JUnit assertions instead of AssertJAgentConfigurationTest.java

The two new test methods (testDuplicateCopiesExecuteToolsConcurrentlySettings, testExecuteToolsConcurrently) use public visibility and JUnit assertions (assertTrue, assertSame, assertEquals, assertNull). Per project conventions: new test methods should be package-private and use AssertJ where it improves readability. Example migration:

@Test
void testExecuteToolsConcurrently() {
    AgentConfiguration config = new AgentConfiguration();
    assertThat(config.getExecuteToolsConcurrently()).isNull();
    assertThat(config.getExecuteToolsExecutor()).isNull();

    AgentConfiguration enabled = config.withExecuteToolsConcurrently();
    assertThat(enabled).isSameAs(config);
    assertThat(config.getExecuteToolsConcurrently()).isTrue();
    // ...
}

2. [Convention] FQCNs in test codeLangChain4jAgentExecuteToolsConcurrentlyTest.java

Two method signatures use fully-qualified class names instead of imports:

  • bindToRegistry(org.apache.camel.spi.Registry registry) → import Registry
  • recordConcurrentEntry(String label, org.apache.camel.Exchange exchange) → import Exchange

3. [Nit] assertSame on autoboxed int is fragileAgentConfigurationTest.java

assertSame(original.getMaxToolCallingRoundTrips(), copy.getMaxToolCallingRoundTrips());

getMaxToolCallingRoundTrips() returns int, so both sides are autoboxed. For value 3 this works due to Integer caching (-128 to 127), but for values > 127 it would fail. The preceding assertEquals already validates the value — this line can be removed or changed to assertEquals/assertThat(...).isEqualTo(...).

4. [Suggestion] Tests leak Executors.newSingleThreadExecutor() instancesAgentConfigurationTest.java

Both new test methods create single-thread executors without shutdown, leaking non-daemon threads. Minor — won't cause test failures — but wrapping in try/finally with executor.shutdownNow() would be cleaner.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of Guillaume Nodet

Omar Atie and others added 4 commits July 30, 2026 09:22
Wire LangChain4j parallel tool execution through AgentConfiguration,
resolve a Camel-managed executor when none is supplied, and add tests
plus component documentation.

Co-authored-by: Cursor <cursoragent@cursor.com>
Avoid mutating registry-held configuration when attaching the
Camel-managed concurrent tool pool; add duplicate() and a context
restart regression test.

Co-authored-by: Cursor <cursoragent@cursor.com>
Regenerate catalog copy of component adoc so sourcecheck passes after
executeToolsConcurrently documentation was added.

Co-authored-by: Cursor <cursoragent@cursor.com>
Migrate new AgentConfigurationTest methods to AssertJ, shut down test
executors, remove fragile assertSame on autoboxed int, and replace FQCNs
with imports in LangChain4jAgentExecuteToolsConcurrentlyTest.

Co-authored-by: Cursor <cursoragent@cursor.com>
@atiaomar1978-hub
atiaomar1978-hub force-pushed the CAMEL-23952-execute-tools-concurrently branch from a010283 to 3a5d72c Compare July 30, 2026 16:27
@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

AI-generated comment on behalf of atiaomar1978-hub

Addressed @gnodet review in 3a5d72cc81c (rebased onto current main):

  1. AgentConfigurationTest — new concurrent-tool tests now use AssertJ, package-private visibility, and shut down test executors in finally; removed fragile assertSame on autoboxed int.
  2. LangChain4jAgentExecuteToolsConcurrentlyTest — replaced FQCNs with Registry and Exchange imports.
  3. Added assertj-core test dependency to camel-langchain4j-agent-api for the migrated assertions.

Re-ran AgentConfigurationTest (28 tests) + LangChain4jAgentExecuteToolsConcurrentlyTest (3 tests) — all green.

Ready for re-review.

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Well-structured PR that adds concurrent tool execution support for langchain4j-agent. The core design is sound: duplicate() protects shared registry beans from mutation, Camel's ExecutorServiceManager manages the thread pool lifecycle correctly, and the concurrency test genuinely validates parallel tool overlap.

All findings from the previous review have been addressed: test methods now use package-private visibility, AssertJ assertions, isEqualTo instead of assertSame on primitives, and proper try/finally with shutdownNow() for executor cleanup.

The concurrency test (LangChain4jAgentExecuteToolsConcurrentlyTest) is particularly well-designed with CountDownLatch(2) barrier to prove both tools are executing simultaneously. The documentation addition correctly notes that tool-route header side-effects are not merged back onto the main exchange when tools run concurrently.

Minor suggestions (non-blocking)

  • duplicate() fragility: Consider adding a reflection-based test that verifies all declared instance fields are copied by duplicate() — prevents copy staleness when new fields are added to AgentConfiguration.
  • Null guard on withExecuteToolsConcurrently(Executor): Passing null silently enables the managed executor fallback. A defensive Objects.requireNonNull() would make the API self-documenting since withExecuteToolsConcurrently() (no-arg) already exists for that intent.
  • Test class visibility: AgentConfigurationTest class declaration is still public — per project convention it should be package-private.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of @gnodet

… API

Reject null executors in withExecuteToolsConcurrently(Executor), add a
reflection test ensuring duplicate() copies every instance field, and
make AgentConfigurationTest package-private.

Co-authored-by: Cursor <cursoragent@cursor.com>
@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

AI-generated comment on behalf of atiaomar1978-hub

Pushed a9f0680dae5 addressing @gnodet's follow-up suggestions from the re-review:

  1. duplicate() coverage — added duplicateCopiesAllDeclaredInstanceFields() reflection test to ensure every non-static instance field is copied (guards against copy staleness when new fields are added).
  2. Null guardwithExecuteToolsConcurrently(Executor) now uses Objects.requireNonNull; added withExecuteToolsConcurrentlyRejectsNullExecutor test.
  3. Test class visibilityAgentConfigurationTest is now package-private.

Tests (all green locally):

  • AgentConfigurationTest
  • LangChain4jAgentExecuteToolsConcurrentlyTest

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: All suggestions addressed ✅

Commit a9f0680 cleanly addresses all three suggestions from the previous review:

  1. duplicate() fragility — Added duplicateCopiesAllDeclaredInstanceFields() using reflection to verify every non-static instance field is copied. Uses field.setAccessible(true) and AssertJ .as() messages for clear failure diagnostics.

  2. Null guard on withExecuteToolsConcurrently(Executor) — Now rejects null with Objects.requireNonNull, backed by withExecuteToolsConcurrentlyRejectsNullExecutor test using assertThatThrownBy. Javadoc enhanced with @throws NullPointerException and cross-reference to the no-arg variant.

  3. Test class visibilityAgentConfigurationTest class declaration is now package-private.

New test methods consistently follow project conventions: package-private visibility, AssertJ assertions, proper executor cleanup via try/finally.

Minor non-blocking note

The reflection test doesn't populate all fields on the original (e.g., chatModel, outputGuardrailClasses), so some equality checks trivially pass with null==null. Populating a few more fields would strengthen coverage, but this is a very minor point.


This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of @gnodet

@davsclaus

Copy link
Copy Markdown
Contributor

I think @Croway also did something like this or what was it that he did ?

@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

AI-generated comment on behalf of atiaomar1978-hub

@davsclaus Good question — I looked through Croway's langchain4j-related work and there is no prior PR that exposed LangChain4j's executeToolsConcurrently() on AgentConfiguration. That wiring is new in this PR (CAMEL-23952).

What Croway did do in the same area (and what this PR builds on):

Ticket / PR Author What it did
CAMEL-16861 @Croway MCP client support for camel-langchain4j-agent (CompositeToolProvider, endpoint MCP servers, ITs). Concurrent tool mode helps when the LLM calls multiple MCP + Camel route tools in one turn.
CAMEL-23621 / #23535 @Croway Filter tool-argument headers to declared parameters only (prevents header leakage between tools).
CAMEL-21937 / #23807 @davsclaus Fix stop() EIP leaking ROUTE_STOP across parallel tool invocations in camel-langchain4j-tools (LangChain4jToolStopEipTest).
CAMEL-23704 / #24062 @k-krawczyk Isolated ExchangeHelper.createCopy() per tool in camel-langchain4j-tools.
CAMEL-23944 / #24992 @gnodet Same exchange-copy isolation in camel-langchain4j-agent route tools — prerequisite for safe concurrent execution.

This PR adds the LangChain4j-side parallel dispatch (withExecuteToolsConcurrently() / optional Executor), a Camel-managed executor when none is supplied, and AgentConfiguration.duplicate() so shared registry beans are not mutated. The exchange-copy safety net was already in place from CAMEL-23944.

So if you had MCP integration in mind — yes, that's Croway's piece. The concurrent execution toggle itself is new here.

@davsclaus davsclaus left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, well-tested implementation. The duplicate() pattern correctly prevents mutation of registry-held configuration beans, and the reflection-based test is a nice maintenance safeguard against future field additions. Managed executor lifecycle follows standard Camel ExecutorServiceManager conventions.

One observation (non-blocking): the managed executor is created in doInit() and shut down in doStop(). A route-level stop/start (without context restart) won't recreate it — but this matches the existing pattern where the agent field is also only created in doInit().

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Populate chatModel, output guardrails, and customTools in the
reflection-based duplicate() test so field equality checks exercise
non-null values per review feedback.

Co-authored-by: Cursor <cursoragent@cursor.com>
@atiaomar1978-hub

Copy link
Copy Markdown
Contributor Author

AI-generated comment on behalf of atiaomar1978-hub

Follow-up (8dca208ff8a)

Checked out CAMEL-23952-execute-tools-concurrently and verified all prior review threads are resolved:

  • @gnodet — AssertJ/package-private tests, Objects.requireNonNull, reflection duplicate() test, and test class visibility were already in a9f0680dae5.
  • @davsclaus — Croway overlap clarified in prior comment (MCP integration vs. new executeToolsConcurrently wiring).

This push addresses @gnodet's minor re-review note: duplicateCopiesAllDeclaredInstanceFields() now populates chatModel, outputGuardrailClasses, and customTools so the reflection equality checks exercise non-null field values (not just null==null).

Tests (all green locally):

  • AgentConfigurationTest (30 tests)
  • LangChain4jAgentExecuteToolsConcurrentlyTest (3 tests)

CI was already green on a9f0680; please re-run on the latest commit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants