diff --git a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java index 38b3c9026fb..4631ab807cd 100644 --- a/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java +++ b/models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java @@ -15,6 +15,7 @@ */ package org.springframework.ai.mistralai; +import io.micrometer.common.KeyValue; import io.micrometer.observation.tck.TestObservationRegistry; import io.micrometer.observation.tck.TestObservationRegistryAssert; import org.junit.jupiter.api.BeforeEach; @@ -127,18 +128,9 @@ private void validate(ChatResponseMetadata responseMetadata) { .hasLowCardinalityKeyValue(LowCardinalityKeyNames.AI_PROVIDER.asString(), AiProvider.MISTRAL_AI.value()) .hasLowCardinalityKeyValue(LowCardinalityKeyNames.REQUEST_MODEL.asString(), MistralAiApi.ChatModel.OPEN_MISTRAL_7B.getValue()) - .matches(contextView -> { - var keyValue = contextView.getLowCardinalityKeyValues() - .stream() - .filter(tag -> tag.getKey().equals(LowCardinalityKeyNames.RESPONSE_MODEL.asString())) - .findFirst(); - if (StringUtils.hasText(responseMetadata.getModel())) { - return keyValue.isPresent() && keyValue.get().getValue().equals(responseMetadata.getModel()); - } - else { - return keyValue.isEmpty(); - } - }) + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), + StringUtils.hasText(responseMetadata.getModel()) ? responseMetadata.getModel() + : KeyValue.NONE_VALUE) .doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString()) .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_MAX_TOKENS.asString(), "2048") .doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY.asString()) diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConvention.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConvention.java index ad8b80d5e44..fe1d9741d03 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConvention.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConvention.java @@ -34,6 +34,9 @@ public class DefaultChatModelObservationConvention implements ChatModelObservati private static final KeyValue REQUEST_MODEL_NONE = KeyValue .of(ChatModelObservationDocumentation.LowCardinalityKeyNames.REQUEST_MODEL, KeyValue.NONE_VALUE); + private static final KeyValue RESPONSE_MODEL_NONE = KeyValue + .of(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL, KeyValue.NONE_VALUE); + public static final String DEFAULT_NAME = "gen_ai.client.operation"; @Override @@ -52,9 +55,8 @@ public String getContextualName(ChatModelObservationContext context) { @Override public KeyValues getLowCardinalityKeyValues(ChatModelObservationContext context) { - var keyValues = KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context)); - keyValues = responseModel(keyValues, context); - return keyValues; + return KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context), + responseModel(context)); } protected KeyValue aiOperationType(ChatModelObservationContext context) { @@ -75,13 +77,13 @@ protected KeyValue requestModel(ChatModelObservationContext context) { return REQUEST_MODEL_NONE; } - protected KeyValues responseModel(KeyValues keyValues, ChatModelObservationContext context) { + protected KeyValue responseModel(ChatModelObservationContext context) { if (context.getResponse() != null && context.getResponse().getMetadata() != null && StringUtils.hasText(context.getResponse().getMetadata().getModel())) { - return keyValues.and(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL.asString(), + return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL, context.getResponse().getMetadata().getModel()); } - return keyValues; + return RESPONSE_MODEL_NONE; } @Override diff --git a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConvention.java b/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConvention.java index 9b4c3163a82..6e4269fe9b4 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConvention.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConvention.java @@ -30,6 +30,9 @@ public class DefaultEmbeddingModelObservationConvention implements EmbeddingMode private static final KeyValue REQUEST_MODEL_NONE = KeyValue .of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.REQUEST_MODEL, KeyValue.NONE_VALUE); + private static final KeyValue RESPONSE_MODEL_NONE = KeyValue + .of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL, KeyValue.NONE_VALUE); + public static final String DEFAULT_NAME = "gen_ai.client.operation"; @Override @@ -48,9 +51,8 @@ public String getContextualName(EmbeddingModelObservationContext context) { @Override public KeyValues getLowCardinalityKeyValues(EmbeddingModelObservationContext context) { - var keyValues = KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context)); - keyValues = responseModel(keyValues, context); - return keyValues; + return KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context), + responseModel(context)); } protected KeyValue aiOperationType(EmbeddingModelObservationContext context) { @@ -71,14 +73,13 @@ protected KeyValue requestModel(EmbeddingModelObservationContext context) { return REQUEST_MODEL_NONE; } - protected KeyValues responseModel(KeyValues keyValues, EmbeddingModelObservationContext context) { + protected KeyValue responseModel(EmbeddingModelObservationContext context) { if (context.getResponse() != null && context.getResponse().getMetadata() != null && StringUtils.hasText(context.getResponse().getMetadata().getModel())) { - return keyValues.and( - EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL.asString(), + return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL, context.getResponse().getMetadata().getModel()); } - return keyValues; + return RESPONSE_MODEL_NONE; } @Override diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConventionTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConventionTests.java index 2885a35a0d7..42164fa9463 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConventionTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConventionTests.java @@ -140,9 +140,8 @@ void shouldNotHaveKeyValuesWhenMissing() { .requestOptions(ChatOptionsBuilder.builder().build()) .build(); assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)) - .contains(KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE)); - assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)) - .noneMatch(keyValue -> keyValue.getKey().equals(LowCardinalityKeyNames.RESPONSE_MODEL.asString())); + .contains(KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE)) + .contains(KeyValue.of(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), KeyValue.NONE_VALUE)); assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext) .stream() .map(KeyValue::getKey) diff --git a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConventionTests.java b/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConventionTests.java index ff101b31edf..f973d95eb49 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConventionTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConventionTests.java @@ -115,9 +115,8 @@ void shouldNotHaveKeyValuesWhenMissing() { .requestOptions(EmbeddingOptionsBuilder.builder().build()) .build(); assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)) - .contains(KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE)); - assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)) - .noneMatch(keyValue -> keyValue.getKey().equals(LowCardinalityKeyNames.RESPONSE_MODEL.asString())); + .contains(KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE)) + .contains(KeyValue.of(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), KeyValue.NONE_VALUE)); assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext) .stream() .map(KeyValue::getKey)