This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 6e54de444432 CAMEL-24241: Apply the tool argument allowlist to tools
declaring no parameters
6e54de444432 is described below
commit 6e54de4444320eb7171c7ca3094f8501087a79b7
Author: Andrea Cosentino <[email protected]>
AuthorDate: Sat Jul 25 11:38:37 2026 +0200
CAMEL-24241: Apply the tool argument allowlist to tools declaring no
parameters
Drop the !spec.getParameterDefs().isEmpty() guard in AiToolExecutor so
that a tool declaring no parameters rejects all LLM-supplied arguments
instead of letting them pass through as exchange headers. This aligns
the shared executor with the strict form already applied in
LangChain4jToolsProducer and SpringAiToolsEndpoint since CAMEL-23621.
Add regression test verifying arguments sent to a zero-parameter tool
do not reach exchange headers, and adjust the Camel-prefix test to use
a tool that declares its argument names.
Closes #25094
Co-authored-by: Claude Opus 4.8 <[email protected]>
---
.../camel/component/ai/tool/AiToolExecutor.java | 4 ++-
.../component/ai/tool/AiToolExecutorTest.java | 41 ++++++++++++++++++++--
2 files changed, 42 insertions(+), 3 deletions(-)
diff --git
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java
index 5b61d3cab8da..bacd29dbc37b 100644
---
a/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java
+++
b/components/camel-ai/camel-ai-tool/src/main/java/org/apache/camel/component/ai/tool/AiToolExecutor.java
@@ -97,7 +97,9 @@ public final class AiToolExecutor {
// Filter out undeclared arguments -- LLMs frequently hallucinate
extra parameters
// and the generated schema advertises additionalProperties: false.
- if (!argsCopy.isEmpty() && !spec.getParameterDefs().isEmpty()) {
+ // A tool that declares no parameters accepts none, so an empty
declaration must filter
+ // everything rather than let every argument through.
+ if (!argsCopy.isEmpty()) {
Set<String> declaredParams = spec.getParameterDefs().keySet();
argsCopy.keySet().removeIf(name -> {
diff --git
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolExecutorTest.java
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolExecutorTest.java
index 230f53defdc7..5b04de63623b 100644
---
a/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolExecutorTest.java
+++
b/components/camel-ai/camel-ai-tool/src/test/java/org/apache/camel/component/ai/tool/AiToolExecutorTest.java
@@ -49,6 +49,16 @@ public class AiToolExecutorTest extends CamelTestSupport {
+ "&description=A tool with no parameters")
.setBody(constant("no-param result"));
+ // declares the Camel-prefixed names so they survive the
undeclared-argument filter
+ // and actually reach the Camel-prefix rejection this tool is
used to test
+ from("ai-tool:prefixTool"
+ + "?tags=test"
+ + "&description=A tool declaring Camel-prefixed
parameters"
+ + "¶meter.CamelOverrideEndpoint=string"
+ + "¶meter.camelFoo=string"
+ + "¶meter.safeParam=string")
+ .setBody(constant("prefix result"));
+
from("ai-tool:nullBodyTool"
+ "?tags=test"
+ "&description=A tool that returns null body")
@@ -117,6 +127,29 @@ public class AiToolExecutorTest extends CamelTestSupport {
.isEqualTo("no-param result");
}
+ @Test
+ public void
testExecuteIgnoresUndeclaredArgumentsForToolWithoutParameters() {
+ AiToolSpec spec = findSpec("noParams");
+ Exchange exchange = new DefaultExchange(context);
+
+ // a tool declaring no parameters accepts none, so every supplied
argument is undeclared
+ Map<String, Object> arguments = new HashMap<>();
+ arguments.put("injected", "should-be-ignored");
+ arguments.put("anotherOne", 42);
+
+ AiToolResult result = AiToolExecutor.execute(spec, arguments,
exchange);
+
+ assertThat(result).isInstanceOf(AiToolResult.Success.class);
+ assertThat(((AiToolResult.Success) result).value())
+ .isEqualTo("no-param result");
+ assertThat(exchange.getMessage().getHeader("injected"))
+ .as("Undeclared argument must not be set as header on a tool
with no parameters")
+ .isNull();
+ assertThat(exchange.getMessage().getHeader("anotherOne"))
+ .as("Undeclared argument must not be set as header on a tool
with no parameters")
+ .isNull();
+ }
+
@Test
public void testExecuteWithEmptyArguments() {
AiToolSpec spec = findSpec("noParams");
@@ -236,7 +269,9 @@ public class AiToolExecutorTest extends CamelTestSupport {
@Test
public void testCamelPrefixedArgumentsRejectedFromHeaders() {
- AiToolSpec spec = findSpec("noParams");
+ // prefixTool declares these names, so they reach the Camel-prefix
check rather than being
+ // dropped earlier as undeclared -- otherwise this test would pass
even without that check
+ AiToolSpec spec = findSpec("prefixTool");
Exchange exchange = new DefaultExchange(context);
Map<String, Object> arguments = new HashMap<>();
@@ -253,11 +288,13 @@ public class AiToolExecutorTest extends CamelTestSupport {
assertThat(exchange.getMessage().getHeader("camelFoo"))
.as("camel-prefixed argument must not be set as header")
.isNull();
+ // a dotted name cannot be declared through the endpoint URI (the key
is split on the first
+ // dot), so this one is dropped as an undeclared argument rather than
by the prefix check
assertThat(exchange.getMessage().getHeader("org.apache.camel.hack"))
.as("org.apache.camel. prefixed argument must not be set as
header")
.isNull();
assertThat(exchange.getMessage().getHeader("safeParam", String.class))
- .as("Non-Camel argument should be set as header")
+ .as("Declared non-Camel argument should be set as header")
.isEqualTo("ok");
}