Improve optional values handling in chat model observations
Chat model observations support several key-value pairs, coming from the Spring AI abstractions. Currently, whenever a value is not available (either because not configured by the user or not supported by the model provider), a span/metrics is generated anyway with value none. That causes several issues, including an unneeded increase in time series, challenges in alerting/monitoring (especially for integer/double attributes that suddenly are populated with a string), and incompliance with the OpenTelemetry Semantic Conventions (according to which, attributes should be excluded altogether if there's no value). This pull request changes the conventions for chat model observation to exclude the generation of span/metrics attributes for optional values which don't have any value. Signed-off-by: Thomas Vitale <ThomasVitale@users.noreply.github.com>
This commit is contained in:
committed by
Christian Tzolov
parent
dc0476478c
commit
3b1c68ac10
@@ -39,7 +39,6 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -133,11 +132,9 @@ public class AnthropicChatModelObservationIT {
|
||||
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.REQUEST_MODEL.asString(),
|
||||
AnthropicApi.ChatModel.CLAUDE_3_HAIKU.getValue())
|
||||
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), responseMetadata.getModel())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString(),
|
||||
KeyValue.NONE_VALUE)
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_MAX_TOKENS.asString(), "2048")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY.asString(),
|
||||
KeyValue.NONE_VALUE)
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY.asString())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES.asString(),
|
||||
"[\"this-is-the-end\"]")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TEMPERATURE.asString(), "0.7")
|
||||
|
||||
@@ -39,7 +39,6 @@ import com.azure.ai.openai.OpenAIClientBuilder;
|
||||
import com.azure.ai.openai.OpenAIServiceVersion;
|
||||
import com.azure.core.credential.AzureKeyCredential;
|
||||
import com.azure.core.http.policy.HttpLogOptions;
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
|
||||
@@ -106,9 +105,8 @@ class AzureOpenAiChatModelObservationIT {
|
||||
"[\"this-is-the-end\"]")
|
||||
.hasHighCardinalityKeyValue(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TEMPERATURE.asString(), "0.7")
|
||||
.hasHighCardinalityKeyValue(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TOP_K.asString(),
|
||||
KeyValue.NONE_VALUE)
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TOP_K.asString())
|
||||
.hasHighCardinalityKeyValue(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TOP_P.asString(), "1.0")
|
||||
.hasHighCardinalityKeyValue(
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
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;
|
||||
@@ -128,19 +127,38 @@ public class MistralAiChatModelObservationIT {
|
||||
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.AI_PROVIDER.asString(), AiProvider.MISTRAL_AI.value())
|
||||
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.REQUEST_MODEL.asString(),
|
||||
MistralAiApi.ChatModel.OPEN_MISTRAL_7B.getValue())
|
||||
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), responseMetadata.getModel())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString(),
|
||||
KeyValue.NONE_VALUE)
|
||||
.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();
|
||||
}
|
||||
})
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_MAX_TOKENS.asString(), "2048")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY.asString(),
|
||||
KeyValue.NONE_VALUE)
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY.asString())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES.asString(),
|
||||
"[\"this-is-the-end\"]")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TEMPERATURE.asString(), "0.7")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TOP_K.asString(), KeyValue.NONE_VALUE)
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_TOP_K.asString())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TOP_P.asString(), "1.0")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.RESPONSE_ID.asString(),
|
||||
StringUtils.hasText(responseMetadata.getId()) ? responseMetadata.getId() : KeyValue.NONE_VALUE)
|
||||
.matches(contextView -> {
|
||||
var keyValue = contextView.getHighCardinalityKeyValues()
|
||||
.stream()
|
||||
.filter(tag -> tag.getKey().equals(HighCardinalityKeyNames.RESPONSE_ID.asString()))
|
||||
.findFirst();
|
||||
if (StringUtils.hasText(responseMetadata.getId())) {
|
||||
return keyValue.isPresent() && keyValue.get().getValue().equals(responseMetadata.getId());
|
||||
}
|
||||
else {
|
||||
return keyValue.isEmpty();
|
||||
}
|
||||
})
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.RESPONSE_FINISH_REASONS.asString(), "[\"STOP\"]")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(),
|
||||
String.valueOf(responseMetadata.getUsage().getPromptTokens()))
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
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.Test;
|
||||
@@ -81,8 +80,7 @@ public class MistralAiEmbeddingModelObservationIT {
|
||||
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.REQUEST_MODEL.asString(),
|
||||
MistralAiApi.EmbeddingModel.EMBED.getValue())
|
||||
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), responseMetadata.getModel())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString(),
|
||||
KeyValue.NONE_VALUE)
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(),
|
||||
String.valueOf(responseMetadata.getUsage().getPromptTokens()))
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(),
|
||||
|
||||
@@ -141,8 +141,7 @@ public class OllamaChatModelObservationIT extends BaseOllamaIT {
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TEMPERATURE.asString(), "0.7")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TOP_K.asString(), "1")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TOP_P.asString(), "1.0")
|
||||
// .hasHighCardinalityKeyValue(HighCardinalityKeyNames.RESPONSE_ID.asString(),
|
||||
// responseMetadata.getId())
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.RESPONSE_ID.asString())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.RESPONSE_FINISH_REASONS.asString(), "[\"stop\"]")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(),
|
||||
String.valueOf(responseMetadata.getUsage().getPromptTokens()))
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.ai.ollama;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
|
||||
@@ -45,8 +44,6 @@ import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation.HighCardinalityKeyNames;
|
||||
import static org.springframework.ai.embedding.observation.EmbeddingModelObservationDocumentation.LowCardinalityKeyNames;
|
||||
|
||||
/**
|
||||
* Integration tests for observation instrumentation in {@link OllamaEmbeddingModel}.
|
||||
@@ -101,8 +98,7 @@ public class OllamaEmbeddingModelObservationIT extends BaseOllamaIT {
|
||||
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.REQUEST_MODEL.asString(),
|
||||
OllamaModel.NOMIC_EMBED_TEXT.getName())
|
||||
.hasLowCardinalityKeyValue(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), responseMetadata.getModel())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString(),
|
||||
KeyValue.NONE_VALUE)
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(),
|
||||
String.valueOf(responseMetadata.getUsage().getPromptTokens()))
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(),
|
||||
|
||||
@@ -22,6 +22,6 @@ import org.testcontainers.utility.DockerImageName;
|
||||
*/
|
||||
public class OllamaImage {
|
||||
|
||||
public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("ollama/ollama:0.3.6");
|
||||
public static final DockerImageName DEFAULT_IMAGE = DockerImageName.parse("ollama/ollama:0.3.9");
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package org.springframework.ai.openai.chat;
|
||||
|
||||
import io.micrometer.common.KeyValue;
|
||||
import io.micrometer.observation.tck.TestObservationRegistry;
|
||||
import io.micrometer.observation.tck.TestObservationRegistryAssert;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -143,7 +142,7 @@ public class OpenAiChatModelObservationIT {
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES.asString(),
|
||||
"[\"this-is-the-end\"]")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TEMPERATURE.asString(), "0.7")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TOP_K.asString(), KeyValue.NONE_VALUE)
|
||||
.doesNotHaveHighCardinalityKeyValueWithKey(HighCardinalityKeyNames.REQUEST_TOP_K.asString())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TOP_P.asString(), "1.0")
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.RESPONSE_ID.asString(), responseMetadata.getId())
|
||||
.hasHighCardinalityKeyValue(HighCardinalityKeyNames.RESPONSE_FINISH_REASONS.asString(), "[\"STOP\"]")
|
||||
|
||||
@@ -77,7 +77,7 @@ class OllamaWithOpenAiChatModelIT {
|
||||
private static final String DEFAULT_OLLAMA_MODEL = "mistral";
|
||||
|
||||
@Container
|
||||
static OllamaContainer ollamaContainer = new OllamaContainer("ollama/ollama:0.3.6");
|
||||
static OllamaContainer ollamaContainer = new OllamaContainer("ollama/ollama:0.3.9");
|
||||
|
||||
static String baseUrl = "http://localhost:11434";
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import io.micrometer.common.KeyValues;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
/**
|
||||
@@ -33,45 +34,6 @@ 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);
|
||||
|
||||
private static final KeyValue REQUEST_FREQUENCY_PENALTY_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue REQUEST_MAX_TOKENS_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_MAX_TOKENS, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue REQUEST_PRESENCE_PENALTY_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue REQUEST_STOP_SEQUENCES_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue REQUEST_TEMPERATURE_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TEMPERATURE, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue REQUEST_TOP_K_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TOP_K, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue REQUEST_TOP_P_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TOP_P, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue RESPONSE_FINISH_REASONS_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.RESPONSE_FINISH_REASONS, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue RESPONSE_ID_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.RESPONSE_ID, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue USAGE_INPUT_TOKENS_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGE_INPUT_TOKENS, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue USAGE_OUTPUT_TOKENS_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGE_OUTPUT_TOKENS, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue USAGE_TOTAL_TOKENS_NONE = KeyValue
|
||||
.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGE_TOTAL_TOKENS, KeyValue.NONE_VALUE);
|
||||
|
||||
public static final String DEFAULT_NAME = "gen_ai.client.operation";
|
||||
|
||||
@Override
|
||||
@@ -90,8 +52,9 @@ public class DefaultChatModelObservationConvention implements ChatModelObservati
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(ChatModelObservationContext context) {
|
||||
return KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context),
|
||||
responseModel(context));
|
||||
var keyValues = KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context));
|
||||
keyValues = responseModel(keyValues, context);
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue aiOperationType(ChatModelObservationContext context) {
|
||||
@@ -112,88 +75,107 @@ public class DefaultChatModelObservationConvention implements ChatModelObservati
|
||||
return REQUEST_MODEL_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue responseModel(ChatModelObservationContext context) {
|
||||
protected KeyValues responseModel(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getResponse() != null && context.getResponse().getMetadata() != null
|
||||
&& context.getResponse().getMetadata().getModel() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL,
|
||||
&& StringUtils.hasText(context.getResponse().getMetadata().getModel())) {
|
||||
return keyValues.and(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL.asString(),
|
||||
context.getResponse().getMetadata().getModel());
|
||||
}
|
||||
return RESPONSE_MODEL_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ChatModelObservationContext context) {
|
||||
return KeyValues.of(requestFrequencyPenalty(context), requestMaxTokens(context),
|
||||
requestPresencePenalty(context), requestStopSequences(context), requestTemperature(context),
|
||||
requestTopK(context), requestTopP(context), responseFinishReasons(context), responseId(context),
|
||||
usageInputTokens(context), usageOutputTokens(context), usageTotalTokens(context));
|
||||
var keyValues = KeyValues.empty();
|
||||
// Request
|
||||
keyValues = requestFrequencyPenalty(keyValues, context);
|
||||
keyValues = requestMaxTokens(keyValues, context);
|
||||
keyValues = requestPresencePenalty(keyValues, context);
|
||||
keyValues = requestStopSequences(keyValues, context);
|
||||
keyValues = requestTemperature(keyValues, context);
|
||||
keyValues = requestTopK(keyValues, context);
|
||||
keyValues = requestTopP(keyValues, context);
|
||||
// Response
|
||||
keyValues = responseFinishReasons(keyValues, context);
|
||||
keyValues = responseId(keyValues, context);
|
||||
keyValues = usageInputTokens(keyValues, context);
|
||||
keyValues = usageOutputTokens(keyValues, context);
|
||||
keyValues = usageTotalTokens(keyValues, context);
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
// Request
|
||||
|
||||
protected KeyValue requestFrequencyPenalty(ChatModelObservationContext context) {
|
||||
protected KeyValues requestFrequencyPenalty(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getRequestOptions().getFrequencyPenalty() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY,
|
||||
return keyValues.and(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString(),
|
||||
String.valueOf(context.getRequestOptions().getFrequencyPenalty()));
|
||||
}
|
||||
return REQUEST_FREQUENCY_PENALTY_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue requestMaxTokens(ChatModelObservationContext context) {
|
||||
protected KeyValues requestMaxTokens(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getRequestOptions().getMaxTokens() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_MAX_TOKENS,
|
||||
return keyValues.and(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_MAX_TOKENS.asString(),
|
||||
String.valueOf(context.getRequestOptions().getMaxTokens()));
|
||||
}
|
||||
return REQUEST_MAX_TOKENS_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue requestPresencePenalty(ChatModelObservationContext context) {
|
||||
protected KeyValues requestPresencePenalty(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getRequestOptions().getPresencePenalty() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY,
|
||||
return keyValues.and(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY.asString(),
|
||||
String.valueOf(context.getRequestOptions().getPresencePenalty()));
|
||||
}
|
||||
return REQUEST_PRESENCE_PENALTY_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue requestStopSequences(ChatModelObservationContext context) {
|
||||
protected KeyValues requestStopSequences(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (!CollectionUtils.isEmpty(context.getRequestOptions().getStopSequences())) {
|
||||
StringJoiner stopSequencesJoiner = new StringJoiner(", ", "[", "]");
|
||||
context.getRequestOptions()
|
||||
.getStopSequences()
|
||||
.forEach(value -> stopSequencesJoiner.add("\"" + value + "\""));
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES,
|
||||
KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES,
|
||||
context.getRequestOptions().getStopSequences(), Objects::nonNull);
|
||||
return keyValues.and(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES.asString(),
|
||||
stopSequencesJoiner.toString());
|
||||
}
|
||||
return REQUEST_STOP_SEQUENCES_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue requestTemperature(ChatModelObservationContext context) {
|
||||
protected KeyValues requestTemperature(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getRequestOptions().getTemperature() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TEMPERATURE,
|
||||
return keyValues.and(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TEMPERATURE.asString(),
|
||||
String.valueOf(context.getRequestOptions().getTemperature()));
|
||||
}
|
||||
return REQUEST_TEMPERATURE_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue requestTopK(ChatModelObservationContext context) {
|
||||
protected KeyValues requestTopK(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getRequestOptions().getTopK() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TOP_K,
|
||||
return keyValues.and(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TOP_K.asString(),
|
||||
String.valueOf(context.getRequestOptions().getTopK()));
|
||||
}
|
||||
return REQUEST_TOP_K_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue requestTopP(ChatModelObservationContext context) {
|
||||
protected KeyValues requestTopP(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getRequestOptions().getTopP() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TOP_P,
|
||||
return keyValues.and(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_TOP_P.asString(),
|
||||
String.valueOf(context.getRequestOptions().getTopP()));
|
||||
}
|
||||
return REQUEST_TOP_P_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
// Response
|
||||
|
||||
protected KeyValue responseFinishReasons(ChatModelObservationContext context) {
|
||||
protected KeyValues responseFinishReasons(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getResponse() != null && !CollectionUtils.isEmpty(context.getResponse().getResults())) {
|
||||
var finishReasons = context.getResponse()
|
||||
.getResults()
|
||||
@@ -202,53 +184,57 @@ public class DefaultChatModelObservationConvention implements ChatModelObservati
|
||||
.map(generation -> generation.getMetadata().getFinishReason())
|
||||
.toList();
|
||||
if (CollectionUtils.isEmpty(finishReasons)) {
|
||||
return RESPONSE_FINISH_REASONS_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
StringJoiner finishReasonsJoiner = new StringJoiner(", ", "[", "]");
|
||||
finishReasons.forEach(finishReason -> finishReasonsJoiner.add("\"" + finishReason + "\""));
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.RESPONSE_FINISH_REASONS,
|
||||
return keyValues.and(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.RESPONSE_FINISH_REASONS.asString(),
|
||||
finishReasonsJoiner.toString());
|
||||
}
|
||||
return RESPONSE_FINISH_REASONS_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue responseId(ChatModelObservationContext context) {
|
||||
protected KeyValues responseId(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getResponse() != null && context.getResponse().getMetadata() != null
|
||||
&& StringUtils.hasText(context.getResponse().getMetadata().getId())) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.RESPONSE_ID,
|
||||
return keyValues.and(ChatModelObservationDocumentation.HighCardinalityKeyNames.RESPONSE_ID.asString(),
|
||||
context.getResponse().getMetadata().getId());
|
||||
}
|
||||
return RESPONSE_ID_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue usageInputTokens(ChatModelObservationContext context) {
|
||||
protected KeyValues usageInputTokens(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getResponse() != null && context.getResponse().getMetadata() != null
|
||||
&& context.getResponse().getMetadata().getUsage() != null
|
||||
&& context.getResponse().getMetadata().getUsage().getPromptTokens() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGE_INPUT_TOKENS,
|
||||
return keyValues.and(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(),
|
||||
String.valueOf(context.getResponse().getMetadata().getUsage().getPromptTokens()));
|
||||
}
|
||||
return USAGE_INPUT_TOKENS_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue usageOutputTokens(ChatModelObservationContext context) {
|
||||
protected KeyValues usageOutputTokens(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getResponse() != null && context.getResponse().getMetadata() != null
|
||||
&& context.getResponse().getMetadata().getUsage() != null
|
||||
&& context.getResponse().getMetadata().getUsage().getGenerationTokens() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGE_OUTPUT_TOKENS,
|
||||
return keyValues.and(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGE_OUTPUT_TOKENS.asString(),
|
||||
String.valueOf(context.getResponse().getMetadata().getUsage().getGenerationTokens()));
|
||||
}
|
||||
return USAGE_OUTPUT_TOKENS_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue usageTotalTokens(ChatModelObservationContext context) {
|
||||
protected KeyValues usageTotalTokens(KeyValues keyValues, ChatModelObservationContext context) {
|
||||
if (context.getResponse() != null && context.getResponse().getMetadata() != null
|
||||
&& context.getResponse().getMetadata().getUsage() != null
|
||||
&& context.getResponse().getMetadata().getUsage().getTotalTokens() != null) {
|
||||
return KeyValue.of(ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGE_TOTAL_TOKENS,
|
||||
return keyValues.and(
|
||||
ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(),
|
||||
String.valueOf(context.getResponse().getMetadata().getUsage().getTotalTokens()));
|
||||
}
|
||||
return USAGE_TOTAL_TOKENS_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,19 +30,6 @@ 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);
|
||||
|
||||
private static final KeyValue REQUEST_EMBEDDING_DIMENSION_NONE = KeyValue.of(
|
||||
EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS,
|
||||
KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue USAGE_INPUT_TOKENS_NONE = KeyValue
|
||||
.of(EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.USAGE_INPUT_TOKENS, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue USAGE_TOTAL_TOKENS_NONE = KeyValue
|
||||
.of(EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.USAGE_TOTAL_TOKENS, KeyValue.NONE_VALUE);
|
||||
|
||||
public static final String DEFAULT_NAME = "gen_ai.client.operation";
|
||||
|
||||
@Override
|
||||
@@ -61,8 +48,9 @@ public class DefaultEmbeddingModelObservationConvention implements EmbeddingMode
|
||||
|
||||
@Override
|
||||
public KeyValues getLowCardinalityKeyValues(EmbeddingModelObservationContext context) {
|
||||
return KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context),
|
||||
responseModel(context));
|
||||
var keyValues = KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context));
|
||||
keyValues = responseModel(keyValues, context);
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue aiOperationType(EmbeddingModelObservationContext context) {
|
||||
@@ -83,51 +71,60 @@ public class DefaultEmbeddingModelObservationConvention implements EmbeddingMode
|
||||
return REQUEST_MODEL_NONE;
|
||||
}
|
||||
|
||||
protected KeyValue responseModel(EmbeddingModelObservationContext context) {
|
||||
protected KeyValues responseModel(KeyValues keyValues, EmbeddingModelObservationContext context) {
|
||||
if (context.getResponse() != null && context.getResponse().getMetadata() != null
|
||||
&& StringUtils.hasText(context.getResponse().getMetadata().getModel())) {
|
||||
return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL,
|
||||
return keyValues.and(
|
||||
EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSE_MODEL.asString(),
|
||||
context.getResponse().getMetadata().getModel());
|
||||
}
|
||||
return RESPONSE_MODEL_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(EmbeddingModelObservationContext context) {
|
||||
return KeyValues.of(requestEmbeddingDimension(context), usageInputTokens(context), usageTotalTokens(context));
|
||||
var keyValues = KeyValues.empty();
|
||||
// Request
|
||||
keyValues = requestEmbeddingDimension(keyValues, context);
|
||||
// Response
|
||||
keyValues = usageInputTokens(keyValues, context);
|
||||
keyValues = usageTotalTokens(keyValues, context);
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
// Request
|
||||
|
||||
protected KeyValue requestEmbeddingDimension(EmbeddingModelObservationContext context) {
|
||||
protected KeyValues requestEmbeddingDimension(KeyValues keyValues, EmbeddingModelObservationContext context) {
|
||||
if (context.getRequestOptions().getDimensions() != null) {
|
||||
return KeyValue.of(
|
||||
EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS,
|
||||
String.valueOf(context.getRequestOptions().getDimensions()));
|
||||
return keyValues
|
||||
.and(EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS
|
||||
.asString(), String.valueOf(context.getRequestOptions().getDimensions()));
|
||||
}
|
||||
return REQUEST_EMBEDDING_DIMENSION_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
// Response
|
||||
|
||||
protected KeyValue usageInputTokens(EmbeddingModelObservationContext context) {
|
||||
protected KeyValues usageInputTokens(KeyValues keyValues, EmbeddingModelObservationContext context) {
|
||||
if (context.getResponse() != null && context.getResponse().getMetadata() != null
|
||||
&& context.getResponse().getMetadata().getUsage() != null
|
||||
&& context.getResponse().getMetadata().getUsage().getPromptTokens() != null) {
|
||||
return KeyValue.of(EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.USAGE_INPUT_TOKENS,
|
||||
return keyValues.and(
|
||||
EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(),
|
||||
String.valueOf(context.getResponse().getMetadata().getUsage().getPromptTokens()));
|
||||
}
|
||||
return USAGE_INPUT_TOKENS_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue usageTotalTokens(EmbeddingModelObservationContext context) {
|
||||
protected KeyValues usageTotalTokens(KeyValues keyValues, EmbeddingModelObservationContext context) {
|
||||
if (context.getResponse() != null && context.getResponse().getMetadata() != null
|
||||
&& context.getResponse().getMetadata().getUsage() != null
|
||||
&& context.getResponse().getMetadata().getUsage().getTotalTokens() != null) {
|
||||
return KeyValue.of(EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.USAGE_TOTAL_TOKENS,
|
||||
return keyValues.and(
|
||||
EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(),
|
||||
String.valueOf(context.getResponse().getMetadata().getUsage().getTotalTokens()));
|
||||
}
|
||||
return USAGE_TOTAL_TOKENS_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,16 +30,6 @@ public class DefaultImageModelObservationConvention implements ImageModelObserva
|
||||
private static final KeyValue REQUEST_MODEL_NONE = KeyValue
|
||||
.of(ImageModelObservationDocumentation.LowCardinalityKeyNames.REQUEST_MODEL, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue REQUEST_IMAGE_RESPONSE_FORMAT_NONE = KeyValue.of(
|
||||
ImageModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_IMAGE_RESPONSE_FORMAT,
|
||||
KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue REQUEST_IMAGE_SIZE_NONE = KeyValue
|
||||
.of(ImageModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_IMAGE_SIZE, KeyValue.NONE_VALUE);
|
||||
|
||||
private static final KeyValue REQUEST_IMAGE_STYLE_NONE = KeyValue
|
||||
.of(ImageModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_IMAGE_STYLE, KeyValue.NONE_VALUE);
|
||||
|
||||
public static final String DEFAULT_NAME = "gen_ai.client.operation";
|
||||
|
||||
@Override
|
||||
@@ -81,33 +71,41 @@ public class DefaultImageModelObservationConvention implements ImageModelObserva
|
||||
|
||||
@Override
|
||||
public KeyValues getHighCardinalityKeyValues(ImageModelObservationContext context) {
|
||||
return KeyValues.of(requestImageFormat(context), requestImageSize(context), requestImageStyle(context));
|
||||
var keyValues = KeyValues.empty();
|
||||
// Request
|
||||
keyValues = requestImageFormat(keyValues, context);
|
||||
keyValues = requestImageSize(keyValues, context);
|
||||
keyValues = requestImageStyle(keyValues, context);
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
// Request
|
||||
|
||||
protected KeyValue requestImageFormat(ImageModelObservationContext context) {
|
||||
protected KeyValues requestImageFormat(KeyValues keyValues, ImageModelObservationContext context) {
|
||||
if (StringUtils.hasText(context.getRequestOptions().getResponseFormat())) {
|
||||
return KeyValue.of(ImageModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_IMAGE_RESPONSE_FORMAT,
|
||||
return keyValues.and(
|
||||
ImageModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_IMAGE_RESPONSE_FORMAT.asString(),
|
||||
context.getRequestOptions().getResponseFormat());
|
||||
}
|
||||
return REQUEST_IMAGE_RESPONSE_FORMAT_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue requestImageSize(ImageModelObservationContext context) {
|
||||
protected KeyValues requestImageSize(KeyValues keyValues, ImageModelObservationContext context) {
|
||||
if (context.getRequestOptions().getWidth() != null && context.getRequestOptions().getHeight() != null) {
|
||||
return KeyValue.of(ImageModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_IMAGE_SIZE,
|
||||
return keyValues.and(
|
||||
ImageModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_IMAGE_SIZE.asString(),
|
||||
"%sx%s".formatted(context.getRequestOptions().getWidth(), context.getRequestOptions().getHeight()));
|
||||
}
|
||||
return REQUEST_IMAGE_SIZE_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
protected KeyValue requestImageStyle(ImageModelObservationContext context) {
|
||||
protected KeyValues requestImageStyle(KeyValues keyValues, ImageModelObservationContext context) {
|
||||
if (StringUtils.hasText(context.getRequestOptions().getStyle())) {
|
||||
return KeyValue.of(ImageModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_IMAGE_STYLE,
|
||||
return keyValues.and(
|
||||
ImageModelObservationDocumentation.HighCardinalityKeyNames.REQUEST_IMAGE_STYLE.asString(),
|
||||
context.getRequestOptions().getStyle());
|
||||
}
|
||||
return REQUEST_IMAGE_STYLE_NONE;
|
||||
return keyValues;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ public enum AiOperationType {
|
||||
|
||||
// @formatter:off
|
||||
|
||||
// Please, keep the alphabetical sorting.
|
||||
CHAT("chat"),
|
||||
EMBEDDING("embedding"),
|
||||
FRAMEWORK("framework"),
|
||||
|
||||
@@ -29,14 +29,15 @@ public enum AiProvider {
|
||||
|
||||
// @formatter:off
|
||||
|
||||
// Please, keep the alphabetical sorting.
|
||||
ANTHROPIC("anthropic"),
|
||||
AZURE_OPENAI("azure-openai"),
|
||||
MISTRAL_AI("mistral_ai"),
|
||||
OCI_GENAI("oci_genai"),
|
||||
OLLAMA("ollama"),
|
||||
OPENAI("openai"),
|
||||
SPRING_AI("spring_ai"),
|
||||
VERTEX_AI("vertex_ai"),
|
||||
OCI_GENAI("oci_genai"),
|
||||
AZURE_OPENAI("azure-openai");
|
||||
VERTEX_AI("vertex_ai");
|
||||
|
||||
private final String value;
|
||||
|
||||
|
||||
@@ -133,32 +133,34 @@ class DefaultChatModelObservationConventionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHaveNoneKeyValuesWhenMissing() {
|
||||
void shouldNotHaveKeyValuesWhenMissing() {
|
||||
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
|
||||
.prompt(generatePrompt())
|
||||
.provider("superprovider")
|
||||
.requestOptions(ChatOptionsBuilder.builder().build())
|
||||
.build();
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)).contains(
|
||||
KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), KeyValue.NONE_VALUE));
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)).contains(
|
||||
KeyValue.of(HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.REQUEST_MAX_TOKENS.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.REQUEST_TEMPERATURE.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.REQUEST_TOP_K.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.REQUEST_TOP_P.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.RESPONSE_FINISH_REASONS.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.RESPONSE_ID.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.USAGE_OUTPUT_TOKENS.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(), KeyValue.NONE_VALUE));
|
||||
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()));
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)
|
||||
.stream()
|
||||
.map(KeyValue::getKey)
|
||||
.toList()).doesNotContain(HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString(),
|
||||
HighCardinalityKeyNames.REQUEST_MAX_TOKENS.asString(),
|
||||
HighCardinalityKeyNames.REQUEST_PRESENCE_PENALTY.asString(),
|
||||
HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES.asString(),
|
||||
HighCardinalityKeyNames.REQUEST_TEMPERATURE.asString(),
|
||||
HighCardinalityKeyNames.REQUEST_TOP_K.asString(), HighCardinalityKeyNames.REQUEST_TOP_P.asString(),
|
||||
HighCardinalityKeyNames.RESPONSE_FINISH_REASONS.asString(),
|
||||
HighCardinalityKeyNames.RESPONSE_ID.asString(),
|
||||
HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(),
|
||||
HighCardinalityKeyNames.USAGE_OUTPUT_TOKENS.asString(),
|
||||
HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHaveNoneKeyValuesWhenEmptyValues() {
|
||||
void shouldNotHaveKeyValuesWhenEmptyValues() {
|
||||
ChatModelObservationContext observationContext = ChatModelObservationContext.builder()
|
||||
.prompt(generatePrompt())
|
||||
.provider("superprovider")
|
||||
@@ -167,10 +169,12 @@ class DefaultChatModelObservationConventionTests {
|
||||
observationContext.setResponse(new ChatResponse(
|
||||
List.of(new Generation(new AssistantMessage("response"), ChatGenerationMetadata.from("", null))),
|
||||
ChatResponseMetadata.builder().withId("").build()));
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)).contains(
|
||||
KeyValue.of(HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.RESPONSE_FINISH_REASONS.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.RESPONSE_ID.asString(), KeyValue.NONE_VALUE));
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)
|
||||
.stream()
|
||||
.map(KeyValue::getKey)
|
||||
.toList()).doesNotContain(HighCardinalityKeyNames.REQUEST_STOP_SEQUENCES.asString(),
|
||||
HighCardinalityKeyNames.RESPONSE_FINISH_REASONS.asString(),
|
||||
HighCardinalityKeyNames.RESPONSE_ID.asString());
|
||||
}
|
||||
|
||||
private Prompt generatePrompt() {
|
||||
|
||||
@@ -108,19 +108,22 @@ class DefaultEmbeddingModelObservationConventionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHaveNoneKeyValuesWhenMissing() {
|
||||
void shouldNotHaveKeyValuesWhenMissing() {
|
||||
EmbeddingModelObservationContext observationContext = EmbeddingModelObservationContext.builder()
|
||||
.embeddingRequest(generateEmbeddingRequest())
|
||||
.provider("superprovider")
|
||||
.requestOptions(EmbeddingOptionsBuilder.builder().build())
|
||||
.build();
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)).contains(
|
||||
KeyValue.of(LowCardinalityKeyNames.REQUEST_MODEL.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), KeyValue.NONE_VALUE));
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)).contains(
|
||||
KeyValue.of(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString(), KeyValue.NONE_VALUE));
|
||||
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()));
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)
|
||||
.stream()
|
||||
.map(KeyValue::getKey)
|
||||
.toList()).doesNotContain(HighCardinalityKeyNames.REQUEST_EMBEDDING_DIMENSIONS.asString(),
|
||||
HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(),
|
||||
HighCardinalityKeyNames.USAGE_TOTAL_TOKENS.asString());
|
||||
}
|
||||
|
||||
private EmbeddingRequest generateEmbeddingRequest() {
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.springframework.ai.image.ImagePrompt;
|
||||
import org.springframework.ai.observation.conventions.AiObservationAttributes;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.ai.image.observation.ImageModelObservationDocumentation.HighCardinalityKeyNames;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultImageModelObservationConvention}.
|
||||
@@ -104,7 +105,7 @@ class DefaultImageModelObservationConventionTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldHaveNoneKeyValuesWhenMissing() {
|
||||
void shouldNotHaveKeyValuesWhenEmptyValues() {
|
||||
ImageModelObservationContext observationContext = ImageModelObservationContext.builder()
|
||||
.imagePrompt(generateImagePrompt())
|
||||
.provider("superprovider")
|
||||
@@ -113,10 +114,12 @@ class DefaultImageModelObservationConventionTests {
|
||||
|
||||
assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext))
|
||||
.contains(KeyValue.of(AiObservationAttributes.REQUEST_MODEL.value(), KeyValue.NONE_VALUE));
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)).contains(
|
||||
KeyValue.of(AiObservationAttributes.REQUEST_IMAGE_RESPONSE_FORMAT.value(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(AiObservationAttributes.REQUEST_IMAGE_SIZE.value(), KeyValue.NONE_VALUE),
|
||||
KeyValue.of(AiObservationAttributes.REQUEST_IMAGE_STYLE.value(), KeyValue.NONE_VALUE));
|
||||
assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)
|
||||
.stream()
|
||||
.map(KeyValue::getKey)
|
||||
.toList()).doesNotContain(HighCardinalityKeyNames.REQUEST_IMAGE_RESPONSE_FORMAT.asString(),
|
||||
HighCardinalityKeyNames.REQUEST_IMAGE_SIZE.asString(),
|
||||
HighCardinalityKeyNames.REQUEST_IMAGE_STYLE.asString());
|
||||
}
|
||||
|
||||
private ImagePrompt generateImagePrompt() {
|
||||
|
||||
@@ -17,6 +17,6 @@ package org.springframework.ai.autoconfigure.ollama;
|
||||
|
||||
public class OllamaImage {
|
||||
|
||||
public static final String IMAGE = "ollama/ollama:0.3.6";
|
||||
public static final String IMAGE = "ollama/ollama:0.3.9";
|
||||
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class OllamaContainerConnectionDetailsFactoryTest {
|
||||
|
||||
@Container
|
||||
@ServiceConnection
|
||||
static OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.3.6");
|
||||
static OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.3.9");
|
||||
|
||||
@Autowired
|
||||
private OllamaEmbeddingModel embeddingModel;
|
||||
|
||||
Reference in New Issue
Block a user