oscerd commented on code in PR #27264:
URL: https://github.com/apache/camel/pull/27264#discussion_r4176961858
##########
components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java:
##########
@@ -175,4 +176,28 @@ private static AiToolResult buildSuccessResult(AiToolSpec
spec, Exchange exchang
String.format("Error executing tool '%s': %s",
spec.getName(), e.getMessage()), e);
}
}
+
+ /**
+ * Builds the exchange used to invoke a route tool from the calling
(agent) exchange. The caller's <em>context</em>
+ * is carried over - exchange properties (most importantly the
authenticated caller's identity, so a tool route can
+ * be guarded on {@code exchangeProperty.subject} and the model cannot
forge it) and variables - but the tool route
+ * is given a <em>clean message</em>: it receives only its own tool
arguments (set as headers by
+ * {@link #execute(AiToolSpec, Map, Exchange)}), not the caller's body or
inbound headers, and a tool that sets no
+ * body returns {@code No result} rather than echoing the caller's body
back to the model. Changes the tool makes
+ * are isolated to this copy and do not leak back into the calling
exchange. Every route-tool runtime
+ * (langchain4j-agent, openai, spring-ai-chat) builds the tool exchange
this way, so an authorization check on an
+ * exchange property behaves identically across them (CAMEL-24832,
CAMEL-23944).
+ *
+ * @param callingExchange the exchange driving the agent
+ * @return an isolated copy, carrying the caller's
properties and variables but a clean message, to
+ * pass to {@link #execute(AiToolSpec, Map,
Exchange)}
+ */
+ public static Exchange createToolExchange(Exchange callingExchange) {
+ // copy carries properties and variables; then wipe the message so the
tool route starts from its arguments
+ // only, not the caller's body/headers
+ Exchange toolExchange = ExchangeHelper.createCopy(callingExchange,
true);
+ toolExchange.getMessage().setBody(null);
Review Comment:
Thanks — applied your fix. `createToolExchange` now does
`callingExchange.copy()` + `getExchangeExtension().setUnitOfWork(null)` and
then clears the message, so the tool exchange runs in its **own** unit of work
and with its **own** exchange id (the `ExchangeHelper` import is gone).
That settles all three rows you found: the tool route's own `onCompletion`
fires, parallel tool calls get distinct ids, and — the important one — an error
handler's `useOriginalMessage()` now restores the tool's own clean message
rather than the caller's, so the clean-message guarantee holds through a
dead-letter path.
Added a regression test
`AiToolExecutorTest.createToolExchangeGivesTheToolRouteItsOwnUnitOfWork`: a
tool route with
`errorHandler(deadLetterChannel("mock:dlq").useOriginalMessage())` that throws
— the DLQ message body is `null` (the tool's own clean original, not the
caller's `caller-prompt`/headers) and the tool result is `No result`. The
existing helper test now also asserts a distinct exchange id. Full
camel-ai-tool suite (120) and camel-openai `McpToolCallExecutorTest` (12) stay
green. The "The calling exchange" doc section and the 4.23 upgrade guide now
state the tool route runs in its own unit of work and id.
_Claude Code on behalf of oscerd_
##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/McpToolCallExecutor.java:
##########
@@ -109,7 +109,7 @@ protected void doStop() throws Exception {
* @return one result per tool call, in the same order
* @throws Exception when a tool call fails and the configured strategy is
to fail the exchange
*/
- List<ToolResult> execute(List<ChatCompletionMessageToolCall> toolCalls)
throws Exception {
+ List<ToolResult> execute(List<ChatCompletionMessageToolCall> toolCalls,
Exchange callingExchange) throws Exception {
Review Comment:
Added — `McpToolCallExecutor.execute` now documents `@param callingExchange`
(the exchange driving the agent loop, copied into each route-tool invocation so
the caller's context — properties, variables — reaches the tool route). Thanks
for the persistence.
_Claude Code on behalf of oscerd_
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]