Skip to content

CAMEL-23704: Use isolated exchange copy per tool in langchain4j-tools#24062

Open
k-krawczyk wants to merge 1 commit into
apache:mainfrom
k-krawczyk:CAMEL-23704-correlated-exchange-copy
Open

CAMEL-23704: Use isolated exchange copy per tool in langchain4j-tools#24062
k-krawczyk wants to merge 1 commit into
apache:mainfrom
k-krawczyk:CAMEL-23704-correlated-exchange-copy

Conversation

@k-krawczyk

@k-krawczyk k-krawczyk commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

CAMEL-23704

LangChain4jToolsProducer.invokeTools() reused a single Exchange across all tool invocations within a request. As a result, one tool's message body, accumulated argument headers, and exceptions leaked into subsequent tool invocations.

Changes

  • Snapshot the original incoming exchange once (toolsChat) and reuse it as a clean baseline across all LLM iterations.
  • Invoke each tool on its own independent copy of that baseline (ExchangeHelper.createCopy), mirroring the multicast/splitter isolation pattern.
  • Copy each tool's outcome back onto the parent exchange with ExchangeHelper.copyResults, so the producer output still reflects the (last) tool's result body and declared argument headers — no accumulation across tools ("last tool wins").
  • Exceptions are kept on the tool's own exchange and propagated to the parent without affecting sibling tools.

Behavior change

A tool route no longer receives the previous tool's output as its body, nor the previous tool's argument headers. Documented in the 4.21 upgrade guide.

Note on the test

LangChain4jToolMultipleCallsTest previously asserted that the second tool's body contained the first tool's output — i.e. it codified the exact leak this ticket reports (the loop and the test both landed in 050af78ca). It has been updated to assert isolation: each tool sees its own argument headers, does not inherit the previous tool's name header, and receives the original incoming body.

Relation to CAMEL-21937

This change was rebased on top of CAMEL-21937 (Reset route stop flag between tool invocations). The per-tool exchange isolation introduced here subsumes the per-iteration ROUTE_STOP reset from that fix — since every tool now runs on its own copy of the baseline exchange, a stop() in one tool route can no longer reach sibling tools, so that per-iteration reset was removed as dead code. The post-loop exchange.setRouteStop(false) is kept, because ExchangeHelper.copyResults propagates the last tool's routeStop onto the parent exchange and it must not leak into the calling route. LangChain4jToolStopEipTest continues to pass.

Testing

mvn test -pl components/camel-ai/camel-langchain4j-tools — all green (39 tests, including LangChain4jToolStopEipTest).

Reported by Claude Code on behalf of Karol Krawczyk

@k-krawczyk

Copy link
Copy Markdown
Contributor Author

cc @Croway @davsclaus for review — you're the most active committers on camel-langchain4j-tools (and @davsclaus is the reporter). Would appreciate your feedback on the per-tool exchange isolation approach.

Reported by Claude Code on behalf of Karol Krawczyk

@k-krawczyk k-krawczyk force-pushed the CAMEL-23704-correlated-exchange-copy branch from 1d19432 to f4f5433 Compare June 16, 2026 20:54
When the LLM requests multiple tool invocations within a single request,
each tool is now invoked on its own independent copy of the original
incoming exchange (a baseline snapshot reused across all iterations),
instead of sharing a single exchange across all tools.

This prevents the message body, argument headers and exceptions produced
by one tool from leaking into sibling tool invocations. Each tool's
outcome is copied back onto the parent exchange so the producer output
still reflects the (last) tool result together with its declared argument
headers.
@k-krawczyk k-krawczyk force-pushed the CAMEL-23704-correlated-exchange-copy branch from f4f5433 to 0b2955e Compare June 16, 2026 21:11
@k-krawczyk

Copy link
Copy Markdown
Contributor Author

Rebased onto current main to resolve conflicts. Notably integrated with CAMEL-21937: per-tool isolation makes the per-iteration ROUTE_STOP reset redundant (removed), while the post-loop reset is retained because copyResults propagates routeStop to the parent. All module tests pass, including LangChain4jToolStopEipTest.

Reported by Claude Code on behalf of Karol Krawczyk

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant