Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,15 @@ private Mono<ToolResultBlock> 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<String, Object> mergedInput = new HashMap<>();
if (registered != null) {
mergedInput.putAll(registered.getPresetParameters());
}
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());
}
mergedInput.putAll(registered.getPresetParameters());
Comment thread
LearningGp marked this conversation as resolved.
Outdated

// Build final execution param
ToolCallParam executionParam =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,82 @@ public Mono<ToolResultBlock> 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<String, Object> 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 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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<String, Object> overrideInput = Map.of("param1", "agent_value1");
ToolUseBlock toolCall =
ToolUseBlock.builder()
Expand All @@ -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");
}

Expand Down
Loading