Skip to content

Commit 58643ee

Browse files
alxkmWillam2004
authored andcommitted
test: Add comprehensive test coverage for tool calling observation components (spring-projects#4259)
Signed-off-by: 家娃 <[email protected]>
1 parent b91313b commit 58643ee

File tree

3 files changed

+147
-0
lines changed

3 files changed

+147
-0
lines changed

spring-ai-model/src/test/java/org/springframework/ai/tool/observation/DefaultToolCallingObservationConventionTests.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,47 @@ void shouldHaveHighCardinalityKeyValues() {
9898
"{}"));
9999
}
100100

101+
@Test
102+
void shouldHaveAllStandardLowCardinalityKeys() {
103+
ToolCallingObservationContext observationContext = ToolCallingObservationContext.builder()
104+
.toolDefinition(ToolDefinition.builder().name("tool").description("Tool").inputSchema("{}").build())
105+
.toolCallArguments("args")
106+
.build();
107+
108+
var lowCardinalityKeys = this.observationConvention.getLowCardinalityKeyValues(observationContext);
109+
110+
// Verify all expected low cardinality keys are present
111+
assertThat(lowCardinalityKeys).extracting(KeyValue::getKey)
112+
.contains(ToolCallingObservationDocumentation.LowCardinalityKeyNames.TOOL_DEFINITION_NAME.asString(),
113+
ToolCallingObservationDocumentation.LowCardinalityKeyNames.AI_OPERATION_TYPE.asString(),
114+
ToolCallingObservationDocumentation.LowCardinalityKeyNames.AI_PROVIDER.asString(),
115+
ToolCallingObservationDocumentation.LowCardinalityKeyNames.SPRING_AI_KIND.asString());
116+
}
117+
118+
@Test
119+
void shouldHandleNullContext() {
120+
assertThat(this.observationConvention.supportsContext(null)).isFalse();
121+
}
122+
123+
@Test
124+
void shouldBeConsistentAcrossMultipleCalls() {
125+
ToolCallingObservationContext observationContext = ToolCallingObservationContext.builder()
126+
.toolDefinition(ToolDefinition.builder()
127+
.name("consistentTool")
128+
.description("Consistent description")
129+
.inputSchema("{}")
130+
.build())
131+
.toolCallArguments("args")
132+
.build();
133+
134+
// Call multiple times and verify consistency
135+
String name1 = this.observationConvention.getContextualName(observationContext);
136+
String name2 = this.observationConvention.getContextualName(observationContext);
137+
var lowCard1 = this.observationConvention.getLowCardinalityKeyValues(observationContext);
138+
var lowCard2 = this.observationConvention.getLowCardinalityKeyValues(observationContext);
139+
140+
assertThat(name1).isEqualTo(name2);
141+
assertThat(lowCard1).isEqualTo(lowCard2);
142+
}
143+
101144
}

spring-ai-model/src/test/java/org/springframework/ai/tool/observation/ToolCallingContentObservationFilterTests.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,62 @@ void augmentContextWhenNullResult() {
7474
.isEmpty();
7575
}
7676

77+
@Test
78+
void whenToolCallArgumentsIsEmptyStringThenHighCardinalityKeyValueIsEmpty() {
79+
var originalContext = ToolCallingObservationContext.builder()
80+
.toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build())
81+
.toolCallArguments("")
82+
.toolCallResult("result")
83+
.build();
84+
var augmentedContext = this.observationFilter.map(originalContext);
85+
86+
assertThat(augmentedContext.getHighCardinalityKeyValues()).contains(KeyValue
87+
.of(ToolCallingObservationDocumentation.HighCardinalityKeyNames.TOOL_CALL_ARGUMENTS.asString(), ""));
88+
assertThat(augmentedContext.getHighCardinalityKeyValues()).contains(KeyValue
89+
.of(ToolCallingObservationDocumentation.HighCardinalityKeyNames.TOOL_CALL_RESULT.asString(), "result"));
90+
}
91+
92+
@Test
93+
void whenToolCallResultIsEmptyStringThenHighCardinalityKeyValueIsEmpty() {
94+
var originalContext = ToolCallingObservationContext.builder()
95+
.toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build())
96+
.toolCallArguments("input")
97+
.toolCallResult("")
98+
.build();
99+
var augmentedContext = this.observationFilter.map(originalContext);
100+
101+
assertThat(augmentedContext.getHighCardinalityKeyValues()).contains(KeyValue
102+
.of(ToolCallingObservationDocumentation.HighCardinalityKeyNames.TOOL_CALL_ARGUMENTS.asString(), "input"));
103+
assertThat(augmentedContext.getHighCardinalityKeyValues()).contains(KeyValue
104+
.of(ToolCallingObservationDocumentation.HighCardinalityKeyNames.TOOL_CALL_RESULT.asString(), ""));
105+
}
106+
107+
@Test
108+
void whenFilterAppliedMultipleTimesThenIdempotent() {
109+
var originalContext = ToolCallingObservationContext.builder()
110+
.toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build())
111+
.toolCallArguments("input")
112+
.toolCallResult("result")
113+
.build();
114+
115+
var augmentedOnce = this.observationFilter.map(originalContext);
116+
var augmentedTwice = this.observationFilter.map(augmentedOnce);
117+
118+
// Count occurrences of each key
119+
long argumentsCount = augmentedTwice.getHighCardinalityKeyValues()
120+
.stream()
121+
.filter(kv -> kv.getKey()
122+
.equals(ToolCallingObservationDocumentation.HighCardinalityKeyNames.TOOL_CALL_ARGUMENTS.asString()))
123+
.count();
124+
long resultCount = augmentedTwice.getHighCardinalityKeyValues()
125+
.stream()
126+
.filter(kv -> kv.getKey()
127+
.equals(ToolCallingObservationDocumentation.HighCardinalityKeyNames.TOOL_CALL_RESULT.asString()))
128+
.count();
129+
130+
// Should not duplicate keys
131+
assertThat(argumentsCount).isEqualTo(1);
132+
assertThat(resultCount).isEqualTo(1);
133+
}
134+
77135
}

spring-ai-model/src/test/java/org/springframework/ai/tool/observation/ToolCallingObservationContextTests.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,50 @@ void whenToolMetadataIsNullThenThrow() {
7474
.build()).isInstanceOf(IllegalArgumentException.class).hasMessageContaining("toolMetadata cannot be null");
7575
}
7676

77+
@Test
78+
void whenToolArgumentsIsEmptyStringThenReturnEmptyString() {
79+
var observationContext = ToolCallingObservationContext.builder()
80+
.toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build())
81+
.toolCallArguments("")
82+
.build();
83+
assertThat(observationContext).isNotNull();
84+
assertThat(observationContext.getToolCallArguments()).isEqualTo("");
85+
}
86+
87+
@Test
88+
void whenToolCallResultIsNullThenReturnNull() {
89+
var observationContext = ToolCallingObservationContext.builder()
90+
.toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build())
91+
.toolCallResult(null)
92+
.build();
93+
assertThat(observationContext).isNotNull();
94+
assertThat(observationContext.getToolCallResult()).isNull();
95+
}
96+
97+
@Test
98+
void whenToolCallResultIsEmptyStringThenReturnEmptyString() {
99+
var observationContext = ToolCallingObservationContext.builder()
100+
.toolDefinition(ToolDefinition.builder().name("toolA").description("description").inputSchema("{}").build())
101+
.toolCallResult("")
102+
.build();
103+
assertThat(observationContext).isNotNull();
104+
assertThat(observationContext.getToolCallResult()).isEqualTo("");
105+
}
106+
107+
@Test
108+
void whenToolDefinitionIsSetThenGetReturnsIt() {
109+
var toolDef = ToolDefinition.builder()
110+
.name("testTool")
111+
.description("Test description")
112+
.inputSchema("{\"type\": \"object\"}")
113+
.build();
114+
115+
var observationContext = ToolCallingObservationContext.builder().toolDefinition(toolDef).build();
116+
117+
assertThat(observationContext.getToolDefinition()).isEqualTo(toolDef);
118+
assertThat(observationContext.getToolDefinition().name()).isEqualTo("testTool");
119+
assertThat(observationContext.getToolDefinition().description()).isEqualTo("Test description");
120+
assertThat(observationContext.getToolDefinition().inputSchema()).isEqualTo("{\"type\": \"object\"}");
121+
}
122+
77123
}

0 commit comments

Comments
 (0)