davsclaus commented on code in PR #27264:
URL: https://github.com/apache/camel/pull/27264#discussion_r4163912436


##########
components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java:
##########
@@ -175,4 +175,19 @@ 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, as an isolated copy of 
the calling (agent) exchange. Context set
+     * before the agent ran - most importantly the authenticated caller's 
identity kept as an exchange property -
+     * reaches the tool route, while header, body and exception changes on the 
tool exchange 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 to pass to {@link 
#execute(AiToolSpec, Map, Exchange)}
+     */
+    public static Exchange createToolExchange(Exchange callingExchange) {
+        return ExchangeHelper.createCopy(callingExchange, true);

Review Comment:
   `ExchangeHelper` is not imported, so `camel-ai-tool` fails to compile 
(`cannot find symbol: variable ExchangeHelper`). The import block needs:
   ```java
   import org.apache.camel.support.ExchangeHelper;
   ```
   Also, `createCopy` copies the caller's whole message (body + headers) along 
with properties and variables. See point 2 in the review summary for why 
clearing the copied message here may be preferable.



##########
components/camel-ai/camel-ai-tool/src/main/docs/ai-tool-component.adoc:
##########
@@ -353,6 +353,23 @@ YAML::
 ----
 ====
 
+== The calling exchange
+
+Each tool invocation runs on an isolated copy of the *calling exchange* — the 
exchange that drives the agent
+(the `langchain4j-agent`, `openai` or `spring-ai-chat` producer). The context 
the caller set before the agent ran
+is carried onto the tool route:
+
+* exchange *properties* — most importantly an authenticated caller's identity 
kept as a property, so a tool route
+  can be guarded on it (for example `exchangeProperty.subject`) and the model 
cannot forge it;
+* exchange *variables*.
+
+The tool's own *headers*, *body* and any *exception* are isolated to the copy 
and do not leak back into the calling
+exchange. The tool arguments supplied by the model are placed on this copy 
before the route runs.

Review Comment:
   This reads as if the tool route starts without the caller's headers and 
body, but with `exchange.copy()` it gets both: the caller's body (often the 
user prompt) and every caller header, including inbound HTTP headers. Isolation 
only holds in the other direction. Please either clear them in 
`createToolExchange`, or say here exactly what the tool route receives.



##########
components/camel-ai/camel-openai/src/main/java/org/apache/camel/component/openai/McpToolCallExecutor.java:
##########
@@ -279,30 +282,29 @@ private ToolResult executeRouteTool(
 
         try {
             Map<String, Object> argsMap = OBJECT_MAPPER.readValue(argsJson, 
Map.class);
-            Exchange toolExchange = spec.getConsumer().createExchange(false);
-            try {
-                AiToolResult result = AiToolExecutor.execute(spec, argsMap, 
toolExchange);
-                if (result instanceof AiToolResult.Success success) {
-                    LOG.debug("Route tool '{}' result: {}", toolName, 
success.value());
-                    return new ToolResult(
-                            toolCall.asFunction().id(), toolName, 
success.value(),
-                            toolState.returnDirectTools().contains(toolName), 
0, true);
-                } else if (result instanceof AiToolResult.ArgumentError error) 
{
-                    LOG.warn("Route tool '{}' argument error: {}", toolName, 
error.message());
-                    return errorResult(toolCall, "Error: invalid tool 
arguments: " + error.message());
-                } else {
-                    AiToolResult.ExecutionError error = 
(AiToolResult.ExecutionError) result;
-                    if (config.getToolExecutionErrorStrategy() == 
ToolExecutionErrorStrategy.FAIL_EXCHANGE) {
-                        if (error.cause() != null) {
-                            throw error.cause();
-                        }
-                        throw new IllegalStateException(error.message());
+            // isolated copy of the calling exchange so the caller's context 
(e.g. an authenticated subject kept as an
+            // exchange property) reaches the tool route; this is not a pooled 
consumer exchange, so it is not released
+            // here (CAMEL-24832)
+            Exchange toolExchange = 
AiToolExecutor.createToolExchange(callingExchange);

Review Comment:
   Before this change the tool exchange started empty. Now a route tool that 
never sets a body returns the caller's body (the user prompt) to the model 
instead of `No result`, and sees all caller headers. A test here that asserts 
the tool route reads `${exchangeProperty.subject}` would also guard against 
regressing back to a fresh exchange. Nothing tests this wiring today.



-- 
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]

Reply via email to