Skip to content

Commit e6555c1

Browse files
committed
Keep model metric attributes fixed for Prometheus
Unlike other metrics implementations supported by Micrometer, when using the Prometheus integration, all metric attributes are supposed to have a value, or else the related metrics are dropped. Signed-off-by: Thomas Vitale <[email protected]>
1 parent 3b1c68a commit e6555c1

File tree

5 files changed

+24
-31
lines changed

5 files changed

+24
-31
lines changed

models/spring-ai-mistral-ai/src/test/java/org/springframework/ai/mistralai/MistralAiChatModelObservationIT.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.ai.mistralai;
1717

18+
import io.micrometer.common.KeyValue;
1819
import io.micrometer.observation.tck.TestObservationRegistry;
1920
import io.micrometer.observation.tck.TestObservationRegistryAssert;
2021
import org.junit.jupiter.api.BeforeEach;
@@ -127,18 +128,9 @@ private void validate(ChatResponseMetadata responseMetadata) {
127128
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.AI_PROVIDER.asString(), AiProvider.MISTRAL_AI.value())
128129
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.REQUEST_MODEL.asString(),
129130
MistralAiApi.ChatModel.OPEN_MISTRAL_7B.getValue())
130-
.matches(contextView -> {
131-
var keyValue = contextView.getLowCardinalityKeyValues()
132-
.stream()
133-
.filter(tag -> tag.getKey().equals(LowCardinalityKeyNames.RESPONSE_MODEL.asString()))
134-
.findFirst();
135-
if (StringUtils.hasText(responseMetadata.getModel())) {
136-
return keyValue.isPresent() && keyValue.get().getValue().equals(responseMetadata.getModel());
137-
}
138-
else {
139-
return keyValue.isEmpty();
140-
}
141-
})
131+
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.RESPONSE_MODEL.asString(),
132+
StringUtils.hasText(responseMetadata.getModel()) ? responseMetadata.getModel()
133+
: KeyValue.NONE_VALUE)
142134
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString())
143135
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_MAX_TOKENS.asString(), "2048")
144136
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY.asString())

spring-ai-core/src/main/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConvention.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public class DefaultChatModelObservationConvention implements ChatModelObservati
3434
private static final KeyValue REQUEST_MODEL_NONE = KeyValue
3535
.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.REQUEST_MODEL, KeyValue.NONE_VALUE);
3636

37+
private static final KeyValue RESPONSE_MODEL_NONE = KeyValue
38+
.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL, KeyValue.NONE_VALUE);
39+
3740
public static final String DEFAULT_NAME = "gen_ai.client.operation";
3841

3942
@Override
@@ -52,9 +55,8 @@ public String getContextualName(ChatModelObservationContext context) {
5255

5356
@Override
5457
public KeyValues getLowCardinalityKeyValues(ChatModelObservationContext context) {
55-
var keyValues = KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context));
56-
keyValues = responseModel(keyValues, context);
57-
return keyValues;
58+
return KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context),
59+
responseModel(context));
5860
}
5961

6062
protected KeyValue aiOperationType(ChatModelObservationContext context) {
@@ -75,13 +77,13 @@ protected KeyValue requestModel(ChatModelObservationContext context) {
7577
return REQUEST_MODEL_NONE;
7678
}
7779

78-
protected KeyValues responseModel(KeyValues keyValues, ChatModelObservationContext context) {
80+
protected KeyValue responseModel(ChatModelObservationContext context) {
7981
if (context.getResponse() != null && context.getResponse().getMetadata() != null
8082
&& StringUtils.hasText(context.getResponse().getMetadata().getModel())) {
81-
return keyValues.and(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL.asString(),
83+
return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL,
8284
context.getResponse().getMetadata().getModel());
8385
}
84-
return keyValues;
86+
return RESPONSE_MODEL_NONE;
8587
}
8688

8789
@Override

spring-ai-core/src/main/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConvention.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ public class DefaultEmbeddingModelObservationConvention implements EmbeddingMode
3030
private static final KeyValue REQUEST_MODEL_NONE = KeyValue
3131
.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.REQUEST_MODEL, KeyValue.NONE_VALUE);
3232

33+
private static final KeyValue RESPONSE_MODEL_NONE = KeyValue
34+
.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL, KeyValue.NONE_VALUE);
35+
3336
public static final String DEFAULT_NAME = "gen_ai.client.operation";
3437

3538
@Override
@@ -48,9 +51,8 @@ public String getContextualName(EmbeddingModelObservationContext context) {
4851

4952
@Override
5053
public KeyValues getLowCardinalityKeyValues(EmbeddingModelObservationContext context) {
51-
var keyValues = KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context));
52-
keyValues = responseModel(keyValues, context);
53-
return keyValues;
54+
return KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context),
55+
responseModel(context));
5456
}
5557

5658
protected KeyValue aiOperationType(EmbeddingModelObservationContext context) {
@@ -71,14 +73,13 @@ protected KeyValue requestModel(EmbeddingModelObservationContext context) {
7173
return REQUEST_MODEL_NONE;
7274
}
7375

74-
protected KeyValues responseModel(KeyValues keyValues, EmbeddingModelObservationContext context) {
76+
protected KeyValue responseModel(EmbeddingModelObservationContext context) {
7577
if (context.getResponse() != null && context.getResponse().getMetadata() != null
7678
&& StringUtils.hasText(context.getResponse().getMetadata().getModel())) {
77-
return keyValues.and(
78-
EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL.asString(),
79+
return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL,
7980
context.getResponse().getMetadata().getModel());
8081
}
81-
return keyValues;
82+
return RESPONSE_MODEL_NONE;
8283
}
8384

8485
@Override

spring-ai-core/src/test/java/org/springframework/ai/chat/observation/DefaultChatModelObservationConventionTests.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,8 @@ void shouldNotHaveKeyValuesWhenMissing() {
140140
.requestOptions(ChatOptionsBuilder.builder().build())
141141
.build();
142142
assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext))
143-
.contains(KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE));
144-
assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext))
145-
.noneMatch(keyValue -> keyValue.getKey().equals(LowCardinalityKeyNames.RESPONSE_MODEL.asString()));
143+
.contains(KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE))
144+
.contains(KeyValue.of(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), KeyValue.NONE_VALUE));
146145
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)
147146
.stream()
148147
.map(KeyValue::getKey)

spring-ai-core/src/test/java/org/springframework/ai/embedding/observation/DefaultEmbeddingModelObservationConventionTests.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,8 @@ void shouldNotHaveKeyValuesWhenMissing() {
115115
.requestOptions(EmbeddingOptionsBuilder.builder().build())
116116
.build();
117117
assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext))
118-
.contains(KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE));
119-
assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext))
120-
.noneMatch(keyValue -> keyValue.getKey().equals(LowCardinalityKeyNames.RESPONSE_MODEL.asString()));
118+
.contains(KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE))
119+
.contains(KeyValue.of(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), KeyValue.NONE_VALUE));
121120
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)
122121
.stream()
123122
.map(KeyValue::getKey)

0 commit comments

Comments
 (0)