From 50e38dacf49f503df6cac78f4b15cd1b7cd9c739 Mon Sep 17 00:00:00 2001 From: Thomas Vitale Date: Sun, 18 May 2025 14:58:29 +0200 Subject: [PATCH] Relaxed toolCallArguments validation There seems to be some models not handling correctly the non-nullability of toolCallArguments when defining a tool call. For example, the Ollama implementation is always null-safe (if no tool argument, then '{}' is used). Other implementations might produce a null value. This PR relaxes the toolCallArguments validation to support null toolCallArguments within ToolCallingObservationContext. Fixes gh-3234 Signed-off-by: Thomas Vitale --- .../ToolCallingObservationContext.java | 5 +-- .../ToolCallingObservationContextTests.java | 37 ++++++++++--------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/spring-ai-model/src/main/java/org/springframework/ai/tool/observation/ToolCallingObservationContext.java b/spring-ai-model/src/main/java/org/springframework/ai/tool/observation/ToolCallingObservationContext.java index ea73f6fd75e..edb22de8cfa 100644 --- a/spring-ai-model/src/main/java/org/springframework/ai/tool/observation/ToolCallingObservationContext.java +++ b/spring-ai-model/src/main/java/org/springframework/ai/tool/observation/ToolCallingObservationContext.java @@ -47,14 +47,13 @@ public final class ToolCallingObservationContext extends Observation.Context { private String toolCallResult; private ToolCallingObservationContext(ToolDefinition toolDefinition, ToolMetadata toolMetadata, - String toolCallArguments, @Nullable String toolCallResult) { + @Nullable String toolCallArguments, @Nullable String toolCallResult) { Assert.notNull(toolDefinition, "toolDefinition cannot be null"); Assert.notNull(toolMetadata, "toolMetadata cannot be null"); - Assert.hasText(toolCallArguments, "toolCallArguments cannot be null or empty"); this.toolDefinition = toolDefinition; this.toolMetadata = toolMetadata; - this.toolCallArguments = toolCallArguments; + this.toolCallArguments = toolCallArguments != null ? toolCallArguments : "{}"; this.toolCallResult = toolCallResult; } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/tool/observation/ToolCallingObservationContextTests.java b/spring-ai-model/src/test/java/org/springframework/ai/tool/observation/ToolCallingObservationContextTests.java index da58cb30d62..44f3aabaf6d 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/tool/observation/ToolCallingObservationContextTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/tool/observation/ToolCallingObservationContextTests.java @@ -32,11 +32,30 @@ class ToolCallingObservationContextTests { @Test void whenMandatoryRequestOptionsThenReturn() { + var observationContext = ToolCallingObservationContext.builder() + .toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build()) + .build(); + assertThat(observationContext).isNotNull(); + } + + @Test + void whenToolArgumentsIsNullThenReturn() { + var observationContext = ToolCallingObservationContext.builder() + .toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build()) + .toolCallArguments(null) + .build(); + assertThat(observationContext).isNotNull(); + assertThat(observationContext.getToolCallArguments()).isEqualTo("{}"); + } + + @Test + void whenToolArgumentsIsNotNullThenReturn() { var observationContext = ToolCallingObservationContext.builder() .toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build()) .toolCallArguments("lizard") .build(); assertThat(observationContext).isNotNull(); + assertThat(observationContext.getToolCallArguments()).isEqualTo("lizard"); } @Test @@ -55,22 +74,4 @@ void whenToolMetadataIsNullThenThrow() { .build()).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("toolMetadata cannot be null"); } - @Test - void whenToolCallInputIsNullThenThrow() { - assertThatThrownBy(() -> ToolCallingObservationContext.builder() - .toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build()) - .toolCallArguments(null) - .build()).isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("toolCallArguments cannot be null or empty"); - } - - @Test - void whenToolCallInputIsEmptyThenThrow() { - assertThatThrownBy(() -> ToolCallingObservationContext.builder() - .toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build()) - .toolCallArguments("") - .build()).isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("toolCallArguments cannot be null or empty"); - } - }