From a8802581fd2a586cfd4fea449316b20865e8b0ad Mon Sep 17 00:00:00 2001 From: JGoP-L <741047428@qq.com> Date: Thu, 9 Apr 2026 09:30:32 +0800 Subject: [PATCH 1/3] Fix preset parameter precedence in tool execution --- .../main/java/io/agentscope/core/tool/ToolExecutor.java | 9 +++++---- .../test/java/io/agentscope/core/tool/ToolkitTest.java | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/agentscope-core/src/main/java/io/agentscope/core/tool/ToolExecutor.java b/agentscope-core/src/main/java/io/agentscope/core/tool/ToolExecutor.java index cb1731937..33acd23e4 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/tool/ToolExecutor.java +++ b/agentscope-core/src/main/java/io/agentscope/core/tool/ToolExecutor.java @@ -219,16 +219,17 @@ private Mono executeCore(ToolCallParam param) { // Create emitter for streaming ToolEmitter toolEmitter = new DefaultToolEmitter(toolCall, getEffectiveChunkCallback()); - // Merge preset parameters with input + // Merge input with preset parameters. Preset values win so framework-controlled + // parameters remain immutable from the caller/LLM perspective. Map mergedInput = new HashMap<>(); - if (registered != null) { - mergedInput.putAll(registered.getPresetParameters()); - } if (param.getInput() != null && !param.getInput().isEmpty()) { mergedInput.putAll(param.getInput()); } else if (toolCall.getInput() != null) { mergedInput.putAll(toolCall.getInput()); } + if (registered != null) { + mergedInput.putAll(registered.getPresetParameters()); + } // Build final execution param ToolCallParam executionParam = diff --git a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolkitTest.java b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolkitTest.java index a65f02506..4f8f898cf 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolkitTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolkitTest.java @@ -673,7 +673,7 @@ public ToolResultBlock testWithContext( } @Test - @DisplayName("Should allow agent parameters to override preset parameters") + @DisplayName("Should keep preset parameters authoritative over agent parameters") void testPresetParametersOverride() { class OverrideTool { @Tool(description = "Test tool for parameter override") @@ -692,7 +692,7 @@ public ToolResultBlock testOverride( Map.of("param1", "preset_value1", "param2", "preset_value2")); toolkit.registration().tool(new OverrideTool()).presetParameters(presetParams).apply(); - // Call with agent providing param1 (should override preset) + // Call with agent providing param1 (preset should still win) Map overrideInput = Map.of("param1", "agent_value1"); ToolUseBlock toolCall = ToolUseBlock.builder() @@ -705,9 +705,10 @@ public ToolResultBlock testOverride( toolkit.callTool(ToolCallParam.builder().toolUseBlock(toolCall).build()).block(); String resultText = getResultText(result); - // param1 should be overridden by agent, param2 should use preset + // Preset values should not be overridden by agent input assertTrue( - resultText.contains("param1: agent_value1"), "Agent value should override preset"); + resultText.contains("param1: preset_value1"), + "Preset value should override agent input"); assertTrue(resultText.contains("param2: preset_value2"), "Preset value should be used"); } From a57ff71d922ee7eea2518ce6918b0cba3d73d725 Mon Sep 17 00:00:00 2001 From: JGoP-L <741047428@qq.com> Date: Thu, 9 Apr 2026 09:55:37 +0800 Subject: [PATCH 2/3] Add coverage for preset parameter precedence path --- .../core/tool/ToolExecutorTest.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java index a084ba136..05fcdad77 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java @@ -271,6 +271,52 @@ public Mono callAsync(ToolCallParam param) { assertEquals(2, successCount, "Exactly two calls should succeed"); } + @Test + @DisplayName("Should apply preset parameters after explicit ToolCallParam input") + void shouldApplyPresetParametersAfterExplicitInput() { + class OverrideTool { + @Tool(description = "Test preset precedence with explicit ToolCallParam input") + public ToolResultBlock testOverride( + @ToolParam(name = "param1") String param1, + @ToolParam(name = "param2") String param2) { + return ToolResultBlock.text( + String.format("param1: %s, param2: %s", param1, param2)); + } + } + + toolkit.registration() + .tool(new OverrideTool()) + .presetParameters( + Map.of( + "testOverride", + Map.of("param1", "preset_value1", "param2", "preset_value2"))) + .apply(); + + Map explicitInput = Map.of("param1", "agent_value1"); + ToolUseBlock toolCall = + ToolUseBlock.builder() + .id("call-override") + .name("testOverride") + .input(Map.of()) + .content("{}") + .build(); + + ToolResultBlock result = + toolkit.callTool( + ToolCallParam.builder() + .toolUseBlock(toolCall) + .input(explicitInput) + .build()) + .block(TIMEOUT); + + assertNotNull(result, "Result should not be null"); + String resultText = extractFirstText(result); + assertTrue( + resultText.contains("param1: preset_value1"), + "Preset value should override explicit ToolCallParam input"); + assertTrue(resultText.contains("param2: preset_value2"), "Preset value should be used"); + } + @Test @DisplayName("Should format all error messages consistently") void testErrorMessageFormat() { From e72b79e340b2c433ec62605a84d161c11593622a Mon Sep 17 00:00:00 2001 From: JGoP-L <741047428@qq.com> Date: Thu, 9 Apr 2026 10:17:10 +0800 Subject: [PATCH 3/3] Improve coverage for preset parameter merge logic --- .../io/agentscope/core/tool/ToolExecutor.java | 8 ++--- .../core/tool/ToolExecutorTest.java | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/agentscope-core/src/main/java/io/agentscope/core/tool/ToolExecutor.java b/agentscope-core/src/main/java/io/agentscope/core/tool/ToolExecutor.java index 33acd23e4..39d7e4abe 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/tool/ToolExecutor.java +++ b/agentscope-core/src/main/java/io/agentscope/core/tool/ToolExecutor.java @@ -222,14 +222,12 @@ private Mono executeCore(ToolCallParam param) { // Merge input with preset parameters. Preset values win so framework-controlled // parameters remain immutable from the caller/LLM perspective. Map mergedInput = new HashMap<>(); - if (param.getInput() != null && !param.getInput().isEmpty()) { + if (!param.getInput().isEmpty()) { mergedInput.putAll(param.getInput()); - } else if (toolCall.getInput() != null) { + } else if (!toolCall.getInput().isEmpty()) { mergedInput.putAll(toolCall.getInput()); } - if (registered != null) { - mergedInput.putAll(registered.getPresetParameters()); - } + mergedInput.putAll(registered.getPresetParameters()); // Build final execution param ToolCallParam executionParam = diff --git a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java index 05fcdad77..aa435072f 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java @@ -317,6 +317,36 @@ public ToolResultBlock testOverride( assertTrue(resultText.contains("param2: preset_value2"), "Preset value should be used"); } + @Test + @DisplayName("Should use only preset parameters when both input sources are absent") + void shouldUseOnlyPresetParametersWhenInputsAbsent() { + class PresetOnlyTool { + @Tool(description = "Test preset usage when no explicit inputs are present") + public ToolResultBlock presetOnly(@ToolParam(name = "param1") String param1) { + return ToolResultBlock.text("param1: " + param1); + } + } + + toolkit.registration() + .tool(new PresetOnlyTool()) + .presetParameters(Map.of("presetOnly", Map.of("param1", "preset_value1"))) + .apply(); + + ToolUseBlock toolCall = + ToolUseBlock.builder() + .id("call-preset-only") + .name("presetOnly") + .content("{}") + .build(); + + ToolResultBlock result = + toolkit.callTool(ToolCallParam.builder().toolUseBlock(toolCall).build()) + .block(TIMEOUT); + + assertNotNull(result, "Result should not be null"); + assertEquals("param1: preset_value1", extractFirstText(result)); + } + @Test @DisplayName("Should format all error messages consistently") void testErrorMessageFormat() {