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 b6a8ea41e..e679be240 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 @@ -139,7 +139,8 @@ public class MistralAiChatModelObservationIT { .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TEMPERATURE.asString(), "0.7") .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TOP_K.asString(), KeyValue.NONE_VALUE) .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_TOP_P.asString(), "1.0") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.RESPONSE_ID.asString(), responseMetadata.getId()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.RESPONSE_ID.asString(), + StringUtils.hasText(responseMetadata.getId()) ? responseMetadata.getId() : KeyValue.NONE_VALUE) .hasHighCardinalityKeyValue(HighCardinalityKeyNames.RESPONSE_FINISH_REASONS.asString(), "[\"STOP\"]") .hasHighCardinalityKeyValue(HighCardinalityKeyNames.USAGE_INPUT_TOKENS.asString(), String.valueOf(responseMetadata.getUsage().getPromptTokens())) diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationFilter.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationFilter.java index aadc292de..72cb7d825 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationFilter.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationFilter.java @@ -17,6 +17,7 @@ package org.springframework.ai.chat.observation; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationFilter; +import org.springframework.ai.observation.tracing.TracingHelper; /** * An {@link ObservationFilter} to include the chat completion content in the observation. @@ -36,7 +37,7 @@ public class ChatModelCompletionObservationFilter implements ObservationFilter { chatModelObservationContext .addHighCardinalityKeyValue(ChatModelObservationDocumentation.HighCardinalityKeyNames.COMPLETION - .withValue(ChatModelObservationContentProcessor.concatenateStrings(completions))); + .withValue(TracingHelper.concatenateStrings(completions))); return chatModelObservationContext; } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationHandler.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationHandler.java index f6c2efe80..9404c2556 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationHandler.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationHandler.java @@ -23,6 +23,7 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import org.springframework.ai.observation.conventions.AiObservationAttributes; import org.springframework.ai.observation.conventions.AiObservationEventNames; +import org.springframework.ai.observation.tracing.TracingHelper; /** * Handler for including the chat completion content in the observation as a span event. @@ -36,7 +37,7 @@ public class ChatModelCompletionObservationHandler implements ObservationHandler public void onStop(ChatModelObservationContext context) { TracingObservationHandler.TracingContext tracingContext = context .get(TracingObservationHandler.TracingContext.class); - Span otelSpan = ChatModelObservationContentProcessor.extractOtelSpan(tracingContext); + Span otelSpan = TracingHelper.extractOtelSpan(tracingContext); if (otelSpan != null) { otelSpan.addEvent(AiObservationEventNames.CONTENT_COMPLETION.value(), diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelObservationContentProcessor.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelObservationContentProcessor.java index e299c6741..2e13b571a 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelObservationContentProcessor.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelObservationContentProcessor.java @@ -15,19 +15,11 @@ */ package org.springframework.ai.chat.observation; -import io.micrometer.tracing.handler.TracingObservationHandler; -import io.opentelemetry.api.trace.Span; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.ai.model.Content; -import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.List; -import java.util.StringJoiner; /** * Utilities to process the prompt and completion content in observations for chat models. @@ -36,8 +28,6 @@ import java.util.StringJoiner; */ public final class ChatModelObservationContentProcessor { - private static final Logger logger = LoggerFactory.getLogger(ChatModelObservationContentProcessor.class); - public static List prompt(ChatModelObservationContext context) { if (CollectionUtils.isEmpty(context.getRequest().getInstructions())) { return List.of(); @@ -65,35 +55,4 @@ public final class ChatModelObservationContentProcessor { .toList(); } - public static String concatenateStrings(List strings) { - var promptMessagesJoiner = new StringJoiner(", ", "[", "]"); - strings.forEach(string -> promptMessagesJoiner.add("\"" + string + "\"")); - return promptMessagesJoiner.toString(); - } - - @Nullable - public static Span extractOtelSpan(@Nullable TracingObservationHandler.TracingContext tracingContext) { - if (tracingContext == null) { - return null; - } - - io.micrometer.tracing.Span micrometerSpan = tracingContext.getSpan(); - try { - Method toOtelMethod = tracingContext.getSpan() - .getClass() - .getDeclaredMethod("toOtel", io.micrometer.tracing.Span.class); - toOtelMethod.setAccessible(true); - Object otelSpanObject = toOtelMethod.invoke(null, micrometerSpan); - if (otelSpanObject instanceof Span otelSpan) { - return otelSpan; - } - } - catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) { - logger.warn("It wasn't possible to extract the OpenTelemetry Span object from Micrometer", ex); - return null; - } - - return null; - } - } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationFilter.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationFilter.java index 785fddf92..3516dc94d 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationFilter.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationFilter.java @@ -17,6 +17,7 @@ package org.springframework.ai.chat.observation; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationFilter; +import org.springframework.ai.observation.tracing.TracingHelper; /** * An {@link ObservationFilter} to include the chat prompt content in the observation. @@ -36,7 +37,7 @@ public class ChatModelPromptContentObservationFilter implements ObservationFilte chatModelObservationContext .addHighCardinalityKeyValue(ChatModelObservationDocumentation.HighCardinalityKeyNames.PROMPT - .withValue(ChatModelObservationContentProcessor.concatenateStrings(prompts))); + .withValue(TracingHelper.concatenateStrings(prompts))); return chatModelObservationContext; } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationHandler.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationHandler.java index 61866a473..8112caa83 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationHandler.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationHandler.java @@ -23,6 +23,7 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import org.springframework.ai.observation.conventions.AiObservationAttributes; import org.springframework.ai.observation.conventions.AiObservationEventNames; +import org.springframework.ai.observation.tracing.TracingHelper; /** * Handler for including the chat prompt content in the observation as a span event. @@ -36,7 +37,7 @@ public class ChatModelPromptContentObservationHandler implements ObservationHand public void onStop(ChatModelObservationContext context) { TracingObservationHandler.TracingContext tracingContext = context .get(TracingObservationHandler.TracingContext.class); - Span otelSpan = ChatModelObservationContentProcessor.extractOtelSpan(tracingContext); + Span otelSpan = TracingHelper.extractOtelSpan(tracingContext); if (otelSpan != null) { otelSpan.addEvent(AiObservationEventNames.CONTENT_PROMPT.value(), diff --git a/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/AiObservationAttributes.java b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/AiObservationAttributes.java index 9e7aec175..295664493 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/AiObservationAttributes.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/AiObservationAttributes.java @@ -132,11 +132,7 @@ public enum AiObservationAttributes { /** * The full response received from the model. */ - COMPLETION("gen_ai.completion"), - /** - * The name of the operation or command being executed. - */ - DB_OPERATION_NAME("db.operation.name"),; + COMPLETION("gen_ai.completion"); private final String value; diff --git a/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/SpringAiKind.java b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/SpringAiKind.java new file mode 100644 index 000000000..74a56df2b --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/SpringAiKind.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.observation.conventions; + +/** + * Types of Spring AI constructs which can be observed. + * + * @author Thomas Vitale + * @since 1.0.0 + */ +public enum SpringAiKind { + + // @formatter:off + + CHAT_CLIENT("chat_client"), + CHAT_CLIENT_ADVISOR("chat_client_advisor"), + VECTOR_STORE("vector_store"); + + private final String value; + + SpringAiKind(String value) { + this.value = value; + } + + public String value() { + return this.value; + } + + // @formatter:on + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreObservationAttributes.java b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreObservationAttributes.java new file mode 100644 index 000000000..9bfada67c --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreObservationAttributes.java @@ -0,0 +1,120 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.observation.conventions; + +/** + * Collection of attribute keys used in vector store observations (spans, metrics, + * events). Based on the OpenTelemetry Semantic Conventions for Vector Databases. + * + * @author Thomas Vitale + * @since 1.0.0 + * @see DB + * Semantic Conventions. + */ +public enum VectorStoreObservationAttributes { + +// @formatter:off + + // DB General + + /** + * The name of a collection (table, container) within the database. + */ + DB_COLLECTION_NAME("db.collection.name"), + + /** + * The name of the database, fully qualified within the server address and port. + */ + DB_NAMESPACE("db.namespace"), + + /** + * The name of the operation or command being executed. + */ + DB_OPERATION_NAME("db.operation.name"), + + /** + * The record identifier if present. + */ + DB_RECORD_ID("db.record.id"), + + /** + * The database management system (DBMS) product as identified by the client instrumentation. + */ + DB_SYSTEM("db.system"), + + // DB Vector + + /** + * The dimension of the vector. + */ + DB_VECTOR_DIMENSION_COUNT("db.vector.dimension_count"), + + /** + * The name field as of the vector (e.g. a field name). + */ + DB_VECTOR_FIELD_NAME("db.vector.field_name"), + + /** + * The model used for the embedding. + */ + DB_VECTOR_MODEL("db.vector.model"), + + /** + * The content of the search query being executed. + */ + DB_VECTOR_QUERY_CONTENT("db.vector.query.content"), + + /** + * The metadata filters used in the search query. + */ + DB_VECTOR_QUERY_FILTER("db.vector.query.filter"), + + /** + * Returned documents from a similarity search query. + */ + DB_VECTOR_QUERY_RESPONSE_DOCUMENTS("db.vector.query.response.documents"), + + /** + * Similarity threshold that accepts all search scores. A threshold value of 0.0 + * means any similarity is accepted or disable the similarity threshold filtering. + * A threshold value of 1.0 means an exact match is required. + */ + DB_VECTOR_QUERY_SIMILARITY_THRESHOLD("db.vector.query.similarity_threshold"), + + /** + * The top-k most similar vectors returned by a query. + */ + DB_VECTOR_QUERY_TOP_K("db.vector.query.top_k"), + + /** + * The metric used in similarity search. + */ + DB_VECTOR_SIMILARITY_METRIC("db.vector.similarity_metric"); + + private final String value; + + VectorStoreObservationAttributes(String value) { + this.value = value; + } + + public String value() { + return value; + } + +// @formatter:on + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreObservationEventNames.java b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreObservationEventNames.java new file mode 100644 index 000000000..9dc843e13 --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreObservationEventNames.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.observation.conventions; + +/** + * Collection of event names used in vector store observations. + * + * @author Thomas Vitale + * @since 1.0.0 + */ +public enum VectorStoreObservationEventNames { + +// @formatter:off + + CONTENT_QUERY_RESPONSE("db.vector.content.query.response"); + + private final String value; + + VectorStoreObservationEventNames(String value) { + this.value = value; + } + + public String value() { + return value; + } + +// @formatter:on + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreSimilarityMetric.java b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreSimilarityMetric.java index 6b95fa941..ec60701a4 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreSimilarityMetric.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreSimilarityMetric.java @@ -16,31 +16,40 @@ package org.springframework.ai.observation.conventions; /** + * Types of similarity metrics used in vector store operations. Based on the OpenTelemetry + * Semantic Conventions for Vector Databases. + * * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 + * @see DB + * Semantic Conventions. */ public enum VectorStoreSimilarityMetric { // @formatter:off - /** - * The cosine metric. - */ - COSINE("cosine"), - /** - * The euclidean distance metric. - */ - EUCLIDEAN("euclidean"), - /** - * The manhattan distance metric. - */ - MANHATTAN("manhattan"), - /** - * The dot product metric. - */ - DOT("dot"); + /** + * The cosine metric. + */ + COSINE("cosine"), + + /** + * The dot product metric. + */ + DOT("dot"), + + /** + * The euclidean distance metric. + */ + EUCLIDEAN("euclidean"), + + /** + * The manhattan distance metric. + */ + MANHATTAN("manhattan"); - // @formatter:on private final String value; VectorStoreSimilarityMetric(String value) { @@ -51,4 +60,6 @@ public enum VectorStoreSimilarityMetric { return this.value; } + // @formatter:on + } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/observation/tracing/TracingHelper.java b/spring-ai-core/src/main/java/org/springframework/ai/observation/tracing/TracingHelper.java new file mode 100644 index 000000000..791547cb6 --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/observation/tracing/TracingHelper.java @@ -0,0 +1,73 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.observation.tracing; + +import io.micrometer.tracing.handler.TracingObservationHandler; +import io.opentelemetry.api.trace.Span; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.observation.ChatModelObservationContext; +import org.springframework.ai.model.Content; +import org.springframework.lang.Nullable; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.StringJoiner; + +/** + * Utilities to prepare and process traces for observability. + * + * @author Thomas Vitale + */ +public final class TracingHelper { + + private static final Logger logger = LoggerFactory.getLogger(TracingHelper.class); + + @Nullable + public static Span extractOtelSpan(@Nullable TracingObservationHandler.TracingContext tracingContext) { + if (tracingContext == null) { + return null; + } + + io.micrometer.tracing.Span micrometerSpan = tracingContext.getSpan(); + try { + Method toOtelMethod = tracingContext.getSpan() + .getClass() + .getDeclaredMethod("toOtel", io.micrometer.tracing.Span.class); + toOtelMethod.setAccessible(true); + Object otelSpanObject = toOtelMethod.invoke(null, micrometerSpan); + if (otelSpanObject instanceof Span otelSpan) { + return otelSpan; + } + } + catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) { + logger.warn("It wasn't possible to extract the OpenTelemetry Span object from Micrometer", ex); + return null; + } + + return null; + } + + public static String concatenateStrings(List strings) { + var promptMessagesJoiner = new StringJoiner(", ", "[", "]"); + strings.forEach(string -> promptMessagesJoiner.add("\"" + string + "\"")); + return promptMessagesJoiner.toString(); + } + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/DefaultVectorStoreObservationConvention.java b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/DefaultVectorStoreObservationConvention.java index 75ade01bc..246f1c2f4 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/DefaultVectorStoreObservationConvention.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/DefaultVectorStoreObservationConvention.java @@ -15,6 +15,7 @@ */ package org.springframework.ai.vectorstore.observation; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.HighCardinalityKeyNames; import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.LowCardinalityKeyNames; import org.springframework.lang.Nullable; @@ -24,41 +25,41 @@ import io.micrometer.common.KeyValue; import io.micrometer.common.KeyValues; /** + * Default conventions to populate observations for vector store operations. + * * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ - public class DefaultVectorStoreObservationConvention implements VectorStoreObservationConvention { - public static final String DEFAULT_NAME = "spring.ai.vector.store"; + public static final String DEFAULT_NAME = "db.vector.client.operation"; - private static final String VECTOR_STORE_SPRING_AI_KIND = "vector_store"; - - private static final KeyValue DIMENSIONS_NONE = KeyValue.of(HighCardinalityKeyNames.DIMENSIONS, + private static final KeyValue COLLECTION_NAME_NONE = KeyValue.of(HighCardinalityKeyNames.DB_COLLECTION_NAME, KeyValue.NONE_VALUE); - private static final KeyValue QUERY_NONE = KeyValue.of(HighCardinalityKeyNames.QUERY, KeyValue.NONE_VALUE); - - private static final KeyValue METADATA_FILTER_NONE = KeyValue.of(HighCardinalityKeyNames.QUERY_METADATA_FILTER, + private static final KeyValue DIMENSIONS_NONE = KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT, KeyValue.NONE_VALUE); - private static final KeyValue TOP_K_NONE = KeyValue.of(HighCardinalityKeyNames.TOP_K, KeyValue.NONE_VALUE); - - private static final KeyValue SIMILARITY_THRESHOLD_NONE = KeyValue.of(HighCardinalityKeyNames.SIMILARITY_THRESHOLD, + private static final KeyValue METADATA_FILTER_NONE = KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_FILTER, KeyValue.NONE_VALUE); - private static final KeyValue SIMILARITY_METRIC_NONE = KeyValue.of(HighCardinalityKeyNames.SIMILARITY_METRIC, + private static final KeyValue FIELD_NAME_NONE = KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME, KeyValue.NONE_VALUE); - private static final KeyValue COLLECTION_NAME_NONE = KeyValue.of(HighCardinalityKeyNames.COLLECTION_NAME, + private static final KeyValue NAMESPACE_NONE = KeyValue.of(HighCardinalityKeyNames.DB_NAMESPACE, KeyValue.NONE_VALUE); - private static final KeyValue NAMESPACE_NONE = KeyValue.of(HighCardinalityKeyNames.NAMESPACE, KeyValue.NONE_VALUE); - - private static final KeyValue FIELD_NAME_NONE = KeyValue.of(HighCardinalityKeyNames.FIELD_NAME, + private static final KeyValue QUERY_CONTENT_NONE = KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT, KeyValue.NONE_VALUE); - private static final KeyValue INDEX_NAME_NONE = KeyValue.of(HighCardinalityKeyNames.INDEX_NAME, + private static final KeyValue SIMILARITY_METRIC_NONE = KeyValue + .of(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC, KeyValue.NONE_VALUE); + + private static final KeyValue SIMILARITY_THRESHOLD_NONE = KeyValue + .of(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD, KeyValue.NONE_VALUE); + + private static final KeyValue TOP_K_NONE = KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K, KeyValue.NONE_VALUE); private final String name; @@ -79,8 +80,7 @@ public class DefaultVectorStoreObservationConvention implements VectorStoreObser @Override @Nullable public String getContextualName(VectorStoreObservationContext context) { - return "%s %s %s".formatted(VECTOR_STORE_SPRING_AI_KIND, context.getDatabaseSystem(), - context.getOperationName()); + return "%s %s".formatted(context.getDatabaseSystem(), context.getOperationName()); } @Override @@ -88,15 +88,8 @@ public class DefaultVectorStoreObservationConvention implements VectorStoreObser return KeyValues.of(springAiKind(), dbSystem(context), dbOperationName(context)); } - @Override - public KeyValues getHighCardinalityKeyValues(VectorStoreObservationContext context) { - return KeyValues.of(query(context), metadataFilter(context), topK(context), dimensions(context), - similarityMetric(context), collectionName(context), namespace(context), fieldName(context), - indexName(context), similarityThreshold(context)); - } - protected KeyValue springAiKind() { - return KeyValue.of(LowCardinalityKeyNames.SPRING_AI_KIND, VECTOR_STORE_SPRING_AI_KIND); + return KeyValue.of(LowCardinalityKeyNames.SPRING_AI_KIND, SpringAiKind.VECTOR_STORE.value()); } protected KeyValue dbSystem(VectorStoreObservationContext context) { @@ -107,76 +100,76 @@ public class DefaultVectorStoreObservationConvention implements VectorStoreObser return KeyValue.of(LowCardinalityKeyNames.DB_OPERATION_NAME, context.getOperationName()); } - protected KeyValue dimensions(VectorStoreObservationContext context) { - if (context.getDimensions() > 0) { - return KeyValue.of(HighCardinalityKeyNames.DIMENSIONS, "" + context.getDimensions()); - } - return DIMENSIONS_NONE; - } - - protected KeyValue query(VectorStoreObservationContext context) { - if (context.getQueryRequest() != null && StringUtils.hasText(context.getQueryRequest().getQuery())) { - return KeyValue.of(HighCardinalityKeyNames.QUERY, "" + context.getQueryRequest().getQuery()); - } - return QUERY_NONE; - } - - protected KeyValue metadataFilter(VectorStoreObservationContext context) { - if (context.getQueryRequest() != null && context.getQueryRequest().getFilterExpression() != null) { - return KeyValue.of(HighCardinalityKeyNames.QUERY_METADATA_FILTER, - "" + context.getQueryRequest().getFilterExpression().toString()); - } - return METADATA_FILTER_NONE; - } - - protected KeyValue topK(VectorStoreObservationContext context) { - if (context.getQueryRequest() != null && context.getQueryRequest().getTopK() > 0) { - return KeyValue.of(HighCardinalityKeyNames.TOP_K, "" + context.getQueryRequest().getTopK()); - } - return TOP_K_NONE; - } - - protected KeyValue similarityThreshold(VectorStoreObservationContext context) { - if (context.getQueryRequest() != null && context.getQueryRequest().getSimilarityThreshold() >= 0) { - return KeyValue.of(HighCardinalityKeyNames.SIMILARITY_THRESHOLD, - "" + context.getQueryRequest().getSimilarityThreshold()); - } - return SIMILARITY_THRESHOLD_NONE; - } - - protected KeyValue similarityMetric(VectorStoreObservationContext context) { - if (StringUtils.hasText(context.getSimilarityMetric())) { - return KeyValue.of(HighCardinalityKeyNames.SIMILARITY_METRIC, context.getSimilarityMetric()); - } - return SIMILARITY_METRIC_NONE; + @Override + public KeyValues getHighCardinalityKeyValues(VectorStoreObservationContext context) { + return KeyValues.of(collectionName(context), dimensions(context), fieldName(context), metadataFilter(context), + namespace(context), queryContent(context), similarityMetric(context), similarityThreshold(context), + topK(context)); } protected KeyValue collectionName(VectorStoreObservationContext context) { if (StringUtils.hasText(context.getCollectionName())) { - return KeyValue.of(HighCardinalityKeyNames.COLLECTION_NAME, context.getCollectionName()); + return KeyValue.of(HighCardinalityKeyNames.DB_COLLECTION_NAME, context.getCollectionName()); } return COLLECTION_NAME_NONE; } - protected KeyValue namespace(VectorStoreObservationContext context) { - if (StringUtils.hasText(context.getNamespace())) { - return KeyValue.of(HighCardinalityKeyNames.NAMESPACE, context.getNamespace()); + protected KeyValue dimensions(VectorStoreObservationContext context) { + if (context.getDimensions() != null && context.getDimensions() > 0) { + return KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT, "" + context.getDimensions()); } - return NAMESPACE_NONE; + return DIMENSIONS_NONE; } protected KeyValue fieldName(VectorStoreObservationContext context) { if (StringUtils.hasText(context.getFieldName())) { - return KeyValue.of(HighCardinalityKeyNames.FIELD_NAME, context.getFieldName()); + return KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME, context.getFieldName()); } return FIELD_NAME_NONE; } - protected KeyValue indexName(VectorStoreObservationContext context) { - if (StringUtils.hasText(context.getIndexName())) { - return KeyValue.of(HighCardinalityKeyNames.INDEX_NAME, context.getIndexName()); + protected KeyValue metadataFilter(VectorStoreObservationContext context) { + if (context.getQueryRequest() != null && context.getQueryRequest().getFilterExpression() != null) { + return KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_FILTER, + context.getQueryRequest().getFilterExpression().toString()); } - return INDEX_NAME_NONE; + return METADATA_FILTER_NONE; } -} \ No newline at end of file + protected KeyValue namespace(VectorStoreObservationContext context) { + if (StringUtils.hasText(context.getNamespace())) { + return KeyValue.of(HighCardinalityKeyNames.DB_NAMESPACE, context.getNamespace()); + } + return NAMESPACE_NONE; + } + + protected KeyValue queryContent(VectorStoreObservationContext context) { + if (context.getQueryRequest() != null && StringUtils.hasText(context.getQueryRequest().getQuery())) { + return KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT, context.getQueryRequest().getQuery()); + } + return QUERY_CONTENT_NONE; + } + + protected KeyValue similarityMetric(VectorStoreObservationContext context) { + if (StringUtils.hasText(context.getSimilarityMetric())) { + return KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC, context.getSimilarityMetric()); + } + return SIMILARITY_METRIC_NONE; + } + + protected KeyValue similarityThreshold(VectorStoreObservationContext context) { + if (context.getQueryRequest() != null && context.getQueryRequest().getSimilarityThreshold() >= 0) { + return KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD, + String.valueOf(context.getQueryRequest().getSimilarityThreshold())); + } + return SIMILARITY_THRESHOLD_NONE; + } + + protected KeyValue topK(VectorStoreObservationContext context) { + if (context.getQueryRequest() != null && context.getQueryRequest().getTopK() > 0) { + return KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K, "" + context.getQueryRequest().getTopK()); + } + return TOP_K_NONE; + } + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationContentProcessor.java b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationContentProcessor.java new file mode 100644 index 000000000..cb834f586 --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationContentProcessor.java @@ -0,0 +1,38 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.vectorstore.observation; + +import org.springframework.ai.document.Document; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +/** + * Utilities to process the query content in observations for vector store operations. + * + * @author Thomas Vitale + */ +public final class VectorStoreObservationContentProcessor { + + public static List documents(VectorStoreObservationContext context) { + if (CollectionUtils.isEmpty(context.getQueryResponse())) { + return List.of(); + } + + return context.getQueryResponse().stream().map(Document::getContent).toList(); + } + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationContext.java b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationContext.java index 0a80a31e6..67f04f58f 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationContext.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationContext.java @@ -19,12 +19,16 @@ import java.util.List; import org.springframework.ai.document.Document; import org.springframework.ai.vectorstore.SearchRequest; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import io.micrometer.observation.Observation; /** - * @author Christian Tzolov + * Context used to store metadata for vector store operations. + * + * @author Christian Tzolo + * @author Thomas Vitale * @since 1.0.0 */ public class VectorStoreObservationContext extends Observation.Context { @@ -56,28 +60,35 @@ public class VectorStoreObservationContext extends Observation.Context { } - // SEARCH - private SearchRequest queryRequest; - - private List queryResponse; - // COMMON + private final String databaseSystem; - private int dimensions = -1; - - private String similarityMetric = ""; - - private String collectionName = ""; - - private String namespace = ""; - - private String fieldName = ""; - - private String indexName = ""; - private final String operationName; + @Nullable + private String collectionName; + + @Nullable + private Integer dimensions; + + @Nullable + private String fieldName; + + @Nullable + private String namespace; + + @Nullable + private String similarityMetric; + + // SEARCH + + @Nullable + private SearchRequest queryRequest; + + @Nullable + private List queryResponse; + public VectorStoreObservationContext(String databaseSystem, String operationName) { Assert.hasText(databaseSystem, "databaseSystem cannot be null or empty"); Assert.hasText(operationName, "operationName cannot be null or empty"); @@ -85,76 +96,75 @@ public class VectorStoreObservationContext extends Observation.Context { this.operationName = operationName; } - public SearchRequest getQueryRequest() { - return this.queryRequest; - } - - public void setQueryRequest(SearchRequest request) { - this.queryRequest = request; - } - - public List getQueryResponse() { - return this.queryResponse; - } - - public void setQueryResponse(List documents) { - this.queryResponse = documents; - } - public String getDatabaseSystem() { return this.databaseSystem; } - public int getDimensions() { - return this.dimensions; - } - - public void setDimensions(int dimensions) { - this.dimensions = dimensions; - } - - public String getSimilarityMetric() { - return this.similarityMetric; - } - - public void setSimilarityMetric(String similarityMetric) { - this.similarityMetric = similarityMetric; + public String getOperationName() { + return this.operationName; } + @Nullable public String getCollectionName() { - return this.collectionName; + return collectionName; } - public void setCollectionName(String collectionName) { + public void setCollectionName(@Nullable String collectionName) { this.collectionName = collectionName; } - public String getNamespace() { - return this.namespace; + @Nullable + public Integer getDimensions() { + return dimensions; } - public void setNamespace(String namespace) { - this.namespace = namespace; + public void setDimensions(@Nullable Integer dimensions) { + this.dimensions = dimensions; } + @Nullable public String getFieldName() { - return this.fieldName; + return fieldName; } - public void setFieldName(String fieldName) { + public void setFieldName(@Nullable String fieldName) { this.fieldName = fieldName; } - public String getIndexName() { - return this.indexName; + @Nullable + public String getNamespace() { + return namespace; } - public void setIndexName(String indexName) { - this.indexName = indexName; + public void setNamespace(@Nullable String namespace) { + this.namespace = namespace; } - public String getOperationName() { - return this.operationName; + @Nullable + public String getSimilarityMetric() { + return similarityMetric; + } + + public void setSimilarityMetric(@Nullable String similarityMetric) { + this.similarityMetric = similarityMetric; + } + + @Nullable + public SearchRequest getQueryRequest() { + return queryRequest; + } + + public void setQueryRequest(@Nullable SearchRequest queryRequest) { + this.queryRequest = queryRequest; + } + + @Nullable + public List getQueryResponse() { + return queryResponse; + } + + public void setQueryResponse(@Nullable List queryResponse) { + this.queryResponse = queryResponse; } public static Builder builder(String databaseSystem, String operationName) { @@ -167,12 +177,32 @@ public class VectorStoreObservationContext extends Observation.Context { public static class Builder { - private VectorStoreObservationContext context; + private final VectorStoreObservationContext context; public Builder(String databaseSystem, String operationName) { this.context = new VectorStoreObservationContext(databaseSystem, operationName); } + public Builder withCollectionName(String collectionName) { + this.context.setCollectionName(collectionName); + return this; + } + + public Builder withDimensions(Integer dimensions) { + this.context.setDimensions(dimensions); + return this; + } + + public Builder withFieldName(String fieldName) { + this.context.setFieldName(fieldName); + return this; + } + + public Builder withNamespace(String namespace) { + this.context.setNamespace(namespace); + return this; + } + public Builder withQueryRequest(SearchRequest request) { this.context.setQueryRequest(request); return this; @@ -183,40 +213,15 @@ public class VectorStoreObservationContext extends Observation.Context { return this; } - public Builder withDimensions(int dimensions) { - this.context.setDimensions(dimensions); - return this; - } - public Builder withSimilarityMetric(String similarityMetric) { this.context.setSimilarityMetric(similarityMetric); return this; } - public Builder withCollectionName(String collectionName) { - this.context.setCollectionName(collectionName); - return this; - } - - public Builder withNamespace(String namespace) { - this.context.setNamespace(namespace); - return this; - } - - public Builder withFieldName(String fieldName) { - this.context.setFieldName(fieldName); - return this; - } - - public Builder withIndexName(String indexName) { - this.context.setIndexName(indexName); - return this; - } - public VectorStoreObservationContext build() { return this.context; } } -} \ No newline at end of file +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationDocumentation.java b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationDocumentation.java index 4f9af8c51..20996ec2b 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationDocumentation.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationDocumentation.java @@ -15,7 +15,7 @@ */ package org.springframework.ai.vectorstore.observation; -import org.springframework.ai.observation.conventions.AiObservationAttributes; +import org.springframework.ai.observation.conventions.VectorStoreObservationAttributes; import io.micrometer.common.docs.KeyName; import io.micrometer.observation.Observation; @@ -23,7 +23,10 @@ import io.micrometer.observation.ObservationConvention; import io.micrometer.observation.docs.ObservationDocumentation; /** + * Documented conventions for vector store observations. + * * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ public enum VectorStoreObservationDocumentation implements ObservationDocumentation { @@ -48,6 +51,9 @@ public enum VectorStoreObservationDocumentation implements ObservationDocumentat } }; + /** + * Low-cardinality observation key names for vector store operations. + */ public enum LowCardinalityKeyNames implements KeyName { /** @@ -59,15 +65,17 @@ public enum VectorStoreObservationDocumentation implements ObservationDocumentat return "spring.ai.kind"; } }, + /** * The name of the operation or command being executed. */ DB_OPERATION_NAME { @Override public String asString() { - return AiObservationAttributes.DB_OPERATION_NAME.value(); + return VectorStoreObservationAttributes.DB_OPERATION_NAME.value(); } }, + /** * The database management system (DBMS) product as identified by the client * instrumentation. @@ -75,116 +83,123 @@ public enum VectorStoreObservationDocumentation implements ObservationDocumentat DB_SYSTEM { @Override public String asString() { - return "db.system"; + return VectorStoreObservationAttributes.DB_SYSTEM.value(); } }; } + /** + * High-cardinality observation key names for vector store operations. + */ public enum HighCardinalityKeyNames implements KeyName { + // DB General + /** - * Similarity search response content. + * The name of a collection (table, container) within the database. */ - QUERY_RESPONSE { + DB_COLLECTION_NAME { @Override public String asString() { - return "db.vector.query.response.documents"; + return VectorStoreObservationAttributes.DB_COLLECTION_NAME.value(); } }, + /** - * The database query being executed. + * The namespace of the database. */ - QUERY { + DB_NAMESPACE { @Override public String asString() { - return "db.vector.query.content"; + return VectorStoreObservationAttributes.DB_NAMESPACE.value(); } }, + + // DB Vector + /** - * The metadata filters used in the query. + * The dimension of the vector. */ - QUERY_METADATA_FILTER { + DB_VECTOR_DIMENSION_COUNT { + @Override + public String asString() { + return VectorStoreObservationAttributes.DB_VECTOR_DIMENSION_COUNT.value(); + } + }, + + /** + * The name field as of the vector (e.g. a field name). + */ + DB_VECTOR_FIELD_NAME { + @Override + public String asString() { + return VectorStoreObservationAttributes.DB_VECTOR_FIELD_NAME.value(); + } + }, + + /** + * The content of the search query being executed. + */ + DB_VECTOR_QUERY_CONTENT { + @Override + public String asString() { + return VectorStoreObservationAttributes.DB_VECTOR_QUERY_CONTENT.value(); + } + }, + + /** + * The metadata filters used in the search query. + */ + DB_VECTOR_QUERY_FILTER { @Override public String asString() { return "db.vector.query.filter"; } }, + /** - * The metric used in similarity search. + * Returned documents from a similarity search query. */ - SIMILARITY_METRIC { + DB_VECTOR_QUERY_RESPONSE_DOCUMENTS { @Override public String asString() { - return "db.vector.similarity_metric"; - } - }, - /** - * The top-k most similar vectors returned by a query. - */ - TOP_K { - @Override - public String asString() { - return "db.vector.query.top_k"; + return "db.vector.query.response.documents"; } }, + /** * Similarity threshold that accepts all search scores. A threshold value of 0.0 * means any similarity is accepted or disable the similarity threshold filtering. * A threshold value of 1.0 means an exact match is required. */ - SIMILARITY_THRESHOLD { + DB_VECTOR_QUERY_SIMILARITY_THRESHOLD { @Override public String asString() { - return "db.vector.query.similarity_threshold"; + return VectorStoreObservationAttributes.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.value(); } }, + /** - * The dimension of the vector. + * The top-k most similar vectors returned by a query. */ - DIMENSIONS { + DB_VECTOR_QUERY_TOP_K { @Override public String asString() { - return "db.vector.dimension_count"; + return VectorStoreObservationAttributes.DB_VECTOR_QUERY_TOP_K.value(); } }, + /** - * The name field as of the vector (e.g. a field name). + * The metric used in similarity search. */ - FIELD_NAME { + DB_VECTOR_SIMILARITY_METRIC { @Override public String asString() { - return "db.vector.name"; + return VectorStoreObservationAttributes.DB_VECTOR_SIMILARITY_METRIC.value(); } - }, - /** - * The name of a collection (table, container) within the database. - */ - COLLECTION_NAME { - @Override - public String asString() { - return "db.collection.name"; - } - }, - /** - * The namespace of the database. - */ - NAMESPACE { - @Override - public String asString() { - return "db.namespace"; - } - }, - /** - * The index name used in the query. - */ - INDEX_NAME { - @Override - public String asString() { - return "db.index.name"; - } - } + }; } -} \ No newline at end of file +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationFilter.java b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationFilter.java index 0855d278a..4beab2b4f 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationFilter.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationFilter.java @@ -15,18 +15,18 @@ */ package org.springframework.ai.vectorstore.observation; -import java.util.StringJoiner; - -import org.springframework.util.CollectionUtils; +import org.springframework.ai.observation.tracing.TracingHelper; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationFilter; +import org.springframework.util.CollectionUtils; /** * An {@link ObservationFilter} to include the Vector Store search response content in the * observation. * * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ public class VectorStoreQueryResponseObservationFilter implements ObservationFilter { @@ -38,17 +38,14 @@ public class VectorStoreQueryResponseObservationFilter implements ObservationFil return context; } - if (CollectionUtils.isEmpty(observationContext.getQueryResponse())) { - return observationContext; + var documents = VectorStoreObservationContentProcessor.documents(observationContext); + + if (!CollectionUtils.isEmpty(documents)) { + observationContext.addHighCardinalityKeyValue( + VectorStoreObservationDocumentation.HighCardinalityKeyNames.DB_VECTOR_QUERY_RESPONSE_DOCUMENTS + .withValue(TracingHelper.concatenateStrings(documents))); } - StringJoiner joiner = new StringJoiner(", ", "[", "]"); - observationContext.getQueryResponse().forEach(document -> joiner.add("\"" + document.getContent() + "\"")); - - observationContext - .addHighCardinalityKeyValue(VectorStoreObservationDocumentation.HighCardinalityKeyNames.QUERY_RESPONSE - .withValue(joiner.toString())); - return observationContext; } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationHandler.java b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationHandler.java new file mode 100644 index 000000000..1e46710d7 --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.vectorstore.observation; + +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationHandler; +import io.micrometer.tracing.handler.TracingObservationHandler; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import org.springframework.ai.observation.conventions.VectorStoreObservationAttributes; +import org.springframework.ai.observation.conventions.VectorStoreObservationEventNames; +import org.springframework.ai.observation.tracing.TracingHelper; +import org.springframework.util.CollectionUtils; + +/** + * Handler for including the query response content in the observation as a span event. + * + * @author Thomas Vitale + * @since 1.0.0 + */ +public class VectorStoreQueryResponseObservationHandler implements ObservationHandler { + + @Override + public void onStop(VectorStoreObservationContext context) { + TracingObservationHandler.TracingContext tracingContext = context + .get(TracingObservationHandler.TracingContext.class); + Span otelSpan = TracingHelper.extractOtelSpan(tracingContext); + + var documents = VectorStoreObservationContentProcessor.documents(context); + + if (!CollectionUtils.isEmpty(documents) && otelSpan != null) { + otelSpan.addEvent(VectorStoreObservationEventNames.CONTENT_QUERY_RESPONSE.value(), Attributes.of( + AttributeKey.stringArrayKey(VectorStoreObservationAttributes.DB_VECTOR_QUERY_CONTENT.value()), + documents)); + } + } + + @Override + public boolean supportsContext(Observation.Context context) { + return context instanceof VectorStoreObservationContext; + } + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/package-info.java b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/package-info.java new file mode 100644 index 000000000..a7e006093 --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NonNullApi +@NonNullFields +package org.springframework.ai.vectorstore.observation; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; \ No newline at end of file diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationHandlerTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationHandlerTests.java index 261493c13..f5b12e553 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationHandlerTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelCompletionObservationHandlerTests.java @@ -29,6 +29,7 @@ import org.springframework.ai.chat.prompt.ChatOptionsBuilder; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.observation.conventions.AiObservationAttributes; import org.springframework.ai.observation.conventions.AiObservationEventNames; +import org.springframework.ai.observation.tracing.TracingHelper; import java.util.List; @@ -59,7 +60,7 @@ class ChatModelCompletionObservationHandlerTests { new ChatModelCompletionObservationHandler().onStop(observationContext); - var otelSpan = ChatModelObservationContentProcessor.extractOtelSpan(tracingContext); + var otelSpan = TracingHelper.extractOtelSpan(tracingContext); assertThat(otelSpan).isNotNull(); var spanData = ((ReadableSpan) otelSpan).toSpanData(); assertThat(spanData.getEvents().size()).isEqualTo(1); diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationHandlerTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationHandlerTests.java index 681327e8e..375598244 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationHandlerTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelPromptContentObservationHandlerTests.java @@ -26,6 +26,7 @@ import org.springframework.ai.chat.prompt.ChatOptionsBuilder; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.observation.conventions.AiObservationAttributes; import org.springframework.ai.observation.conventions.AiObservationEventNames; +import org.springframework.ai.observation.tracing.TracingHelper; import static org.assertj.core.api.Assertions.assertThat; @@ -52,7 +53,7 @@ class ChatModelPromptContentObservationHandlerTests { new ChatModelPromptContentObservationHandler().onStop(observationContext); - var otelSpan = ChatModelObservationContentProcessor.extractOtelSpan(tracingContext); + var otelSpan = TracingHelper.extractOtelSpan(tracingContext); assertThat(otelSpan).isNotNull(); var spanData = ((ReadableSpan) otelSpan).toSpanData(); assertThat(spanData.getEvents().size()).isEqualTo(1); diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelObservationContentProcessorTests.java b/spring-ai-core/src/test/java/org/springframework/ai/observation/tracing/TracingHelperTests.java similarity index 67% rename from spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelObservationContentProcessorTests.java rename to spring-ai-core/src/test/java/org/springframework/ai/observation/tracing/TracingHelperTests.java index 229a4ae3a..6fc0eeb12 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/observation/ChatModelObservationContentProcessorTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/observation/tracing/TracingHelperTests.java @@ -1,19 +1,4 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.ai.chat.observation; +package org.springframework.ai.observation.tracing; import io.micrometer.tracing.Span; import io.micrometer.tracing.TraceContext; @@ -22,21 +7,23 @@ import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; import io.micrometer.tracing.otel.bridge.OtelTracer; import io.opentelemetry.api.OpenTelemetry; import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.observation.ChatModelObservationContentProcessor; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; /** - * Unit tests for {@link ChatModelObservationContentProcessor}. + * Unit tests for {@link TracingHelper}. * * @author Thomas Vitale */ -class ChatModelObservationContentProcessorTests { +class TracingHelperTests { @Test void extractOtelSpanWhenTracingContextIsNull() { - var actualOtelSpan = ChatModelObservationContentProcessor.extractOtelSpan(null); + var actualOtelSpan = TracingHelper.extractOtelSpan(null); assertThat(actualOtelSpan).isNull(); } @@ -44,7 +31,7 @@ class ChatModelObservationContentProcessorTests { void extractOtelSpanWhenMethodDoesNotExist() { var tracingContext = new TracingObservationHandler.TracingContext(); tracingContext.setSpan(Span.NOOP); - var actualOtelSpan = ChatModelObservationContentProcessor.extractOtelSpan(tracingContext); + var actualOtelSpan = TracingHelper.extractOtelSpan(tracingContext); assertThat(actualOtelSpan).isNull(); } @@ -52,7 +39,7 @@ class ChatModelObservationContentProcessorTests { void extractOtelSpanWhenSpanIsNotOpenTelemetry() { var tracingContext = new TracingObservationHandler.TracingContext(); tracingContext.setSpan(new DemoOtherSpan()); - var actualOtelSpan = ChatModelObservationContentProcessor.extractOtelSpan(tracingContext); + var actualOtelSpan = TracingHelper.extractOtelSpan(tracingContext); assertThat(actualOtelSpan).isNull(); } @@ -61,7 +48,7 @@ class ChatModelObservationContentProcessorTests { var tracingContext = new TracingObservationHandler.TracingContext(); var otelTracer = new OtelTracer(OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null); tracingContext.setSpan(otelTracer.nextSpan()); - var actualOtelSpan = ChatModelObservationContentProcessor.extractOtelSpan(tracingContext); + var actualOtelSpan = TracingHelper.extractOtelSpan(tracingContext); assertThat(actualOtelSpan).isNotNull(); assertThat(actualOtelSpan).isInstanceOf(io.opentelemetry.api.trace.Span.class); } @@ -139,4 +126,4 @@ class ChatModelObservationContentProcessorTests { } -} +} \ No newline at end of file diff --git a/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/DefaultVectorStoreObservationConventionTests.java b/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/DefaultVectorStoreObservationConventionTests.java index d92366849..00e33e517 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/DefaultVectorStoreObservationConventionTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/DefaultVectorStoreObservationConventionTests.java @@ -21,6 +21,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.HighCardinalityKeyNames; import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.LowCardinalityKeyNames; @@ -32,6 +33,7 @@ import io.micrometer.observation.Observation; * Unit tests for {@link DefaultVectorStoreObservationConvention}. * * @author Christian Tzolov + * @author Thomas Vitale */ class DefaultVectorStoreObservationConventionTests { @@ -48,8 +50,7 @@ class DefaultVectorStoreObservationConventionTests { VectorStoreObservationContext observationContext = VectorStoreObservationContext .builder("my-database", VectorStoreObservationContext.Operation.QUERY) .build(); - assertThat(this.observationConvention.getContextualName(observationContext)) - .isEqualTo("vector_store my-database query"); + assertThat(this.observationConvention.getContextualName(observationContext)).isEqualTo("my-database query"); } @Test @@ -79,7 +80,6 @@ class DefaultVectorStoreObservationConventionTests { .withCollectionName("COLLECTION_NAME") .withDimensions(696) .withFieldName("FIELD_NAME") - .withIndexName("INDEX_NAME") .withNamespace("NAMESPACE") .withSimilarityMetric("SIMILARITY_METRIC") .withQueryRequest(SearchRequest.query("VDB QUERY").withFilterExpression("country == 'UK' && year >= 2020")) @@ -95,17 +95,16 @@ class DefaultVectorStoreObservationConventionTests { // Optional, filter only added content assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)) - .doesNotContain(KeyValue.of(HighCardinalityKeyNames.QUERY_RESPONSE, "[doc1,doc2]")); + .doesNotContain(KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_RESPONSE_DOCUMENTS, "[doc1,doc2]")); assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)).contains( - KeyValue.of(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "COLLECTION_NAME"), - KeyValue.of(HighCardinalityKeyNames.DIMENSIONS.asString(), "696"), - KeyValue.of(HighCardinalityKeyNames.FIELD_NAME.asString(), "FIELD_NAME"), - KeyValue.of(HighCardinalityKeyNames.INDEX_NAME.asString(), "INDEX_NAME"), - KeyValue.of(HighCardinalityKeyNames.NAMESPACE.asString(), "NAMESPACE"), - KeyValue.of(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "SIMILARITY_METRIC"), - KeyValue.of(HighCardinalityKeyNames.QUERY.asString(), "VDB QUERY"), - KeyValue.of(HighCardinalityKeyNames.QUERY_METADATA_FILTER.asString(), + KeyValue.of(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), "COLLECTION_NAME"), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "696"), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "FIELD_NAME"), + KeyValue.of(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "NAMESPACE"), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "SIMILARITY_METRIC"), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "VDB QUERY"), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_FILTER.asString(), "Expression[type=AND, left=Expression[type=EQ, left=Key[key=country], right=Value[value=UK]], right=Expression[type=GTE, left=Key[key=year], right=Value[value=2020]]]")); } @@ -116,14 +115,13 @@ class DefaultVectorStoreObservationConventionTests { .build(); assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)).contains( - KeyValue.of(HighCardinalityKeyNames.COLLECTION_NAME.asString(), KeyValue.NONE_VALUE), - KeyValue.of(HighCardinalityKeyNames.DIMENSIONS.asString(), KeyValue.NONE_VALUE), - KeyValue.of(HighCardinalityKeyNames.FIELD_NAME.asString(), KeyValue.NONE_VALUE), - KeyValue.of(HighCardinalityKeyNames.INDEX_NAME.asString(), KeyValue.NONE_VALUE), - KeyValue.of(HighCardinalityKeyNames.NAMESPACE.asString(), KeyValue.NONE_VALUE), - KeyValue.of(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), KeyValue.NONE_VALUE), - KeyValue.of(HighCardinalityKeyNames.QUERY.asString(), KeyValue.NONE_VALUE), - KeyValue.of(HighCardinalityKeyNames.QUERY_METADATA_FILTER.asString(), KeyValue.NONE_VALUE)); + KeyValue.of(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), KeyValue.NONE_VALUE), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), KeyValue.NONE_VALUE), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), KeyValue.NONE_VALUE), + KeyValue.of(HighCardinalityKeyNames.DB_NAMESPACE.asString(), KeyValue.NONE_VALUE), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), KeyValue.NONE_VALUE), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), KeyValue.NONE_VALUE), + KeyValue.of(HighCardinalityKeyNames.DB_VECTOR_QUERY_FILTER.asString(), KeyValue.NONE_VALUE)); } } diff --git a/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationFilterTests.java b/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationFilterTests.java index 08ab42835..652c28862 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationFilterTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationFilterTests.java @@ -30,6 +30,7 @@ import io.micrometer.observation.Observation; * Unit tests for {@link VectorStoreQueryResponseObservationFilter}. * * @author Christian Tzolov + * @author Thomas Vitale */ class VectorStoreQueryResponseObservationFilterTests { @@ -64,8 +65,8 @@ class VectorStoreQueryResponseObservationFilterTests { var augmentedContext = observationFilter.map(expectedContext); - assertThat(augmentedContext.getHighCardinalityKeyValues()) - .contains(KeyValue.of(HighCardinalityKeyNames.QUERY_RESPONSE.asString(), "[\"doc1\", \"doc2\"]")); + assertThat(augmentedContext.getHighCardinalityKeyValues()).contains(KeyValue + .of(HighCardinalityKeyNames.DB_VECTOR_QUERY_RESPONSE_DOCUMENTS.asString(), "[\"doc1\", \"doc2\"]")); } } diff --git a/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationHandlerTests.java b/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationHandlerTests.java new file mode 100644 index 000000000..657f5555f --- /dev/null +++ b/spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationHandlerTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.ai.vectorstore.observation; + +import io.micrometer.tracing.handler.TracingObservationHandler; +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; +import io.micrometer.tracing.otel.bridge.OtelTracer; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.trace.ReadableSpan; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import org.junit.jupiter.api.Test; +import org.springframework.ai.document.Document; +import org.springframework.ai.observation.conventions.VectorStoreObservationAttributes; +import org.springframework.ai.observation.conventions.VectorStoreObservationEventNames; +import org.springframework.ai.observation.tracing.TracingHelper; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link VectorStoreQueryResponseObservationHandler}. + * + * @author Thomas Vitale + */ +class VectorStoreQueryResponseObservationHandlerTests { + + @Test + void whenCompletionWithTextThenSpanEvent() { + var observationContext = VectorStoreObservationContext + .builder("db", VectorStoreObservationContext.Operation.ADD) + .withQueryResponse(List.of(new Document("hello"), new Document("other-side"))) + .build(); + var sdkTracer = SdkTracerProvider.builder().build().get("test"); + var otelTracer = new OtelTracer(sdkTracer, new OtelCurrentTraceContext(), null); + var span = otelTracer.nextSpan(); + var tracingContext = new TracingObservationHandler.TracingContext(); + tracingContext.setSpan(span); + observationContext.put(TracingObservationHandler.TracingContext.class, tracingContext); + + new VectorStoreQueryResponseObservationHandler().onStop(observationContext); + + var otelSpan = TracingHelper.extractOtelSpan(tracingContext); + assertThat(otelSpan).isNotNull(); + var spanData = ((ReadableSpan) otelSpan).toSpanData(); + assertThat(spanData.getEvents().size()).isEqualTo(1); + assertThat(spanData.getEvents().get(0).getName()) + .isEqualTo(VectorStoreObservationEventNames.CONTENT_QUERY_RESPONSE.value()); + assertThat(spanData.getEvents() + .get(0) + .getAttributes() + .get(AttributeKey.stringArrayKey(VectorStoreObservationAttributes.DB_VECTOR_QUERY_CONTENT.value()))) + .containsOnly("hello", "other-side"); + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/observation/VectorStoreObservationAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/observation/VectorStoreObservationAutoConfiguration.java index 46feafc94..c37c22389 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/observation/VectorStoreObservationAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/vectorstore/observation/VectorStoreObservationAutoConfiguration.java @@ -15,21 +15,27 @@ */ package org.springframework.ai.autoconfigure.vectorstore.observation; +import io.micrometer.tracing.otel.bridge.OtelTracer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.observation.VectorStoreQueryResponseObservationFilter; +import org.springframework.ai.vectorstore.observation.VectorStoreQueryResponseObservationHandler; import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; /** * Auto-configuration for Spring AI vector store observations. * * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ @AutoConfiguration( @@ -40,14 +46,47 @@ public class VectorStoreObservationAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(VectorStoreObservationAutoConfiguration.class); - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = VectorStoreObservationProperties.CONFIG_PREFIX, name = "include-query-response", - havingValue = "true") - VectorStoreQueryResponseObservationFilter vectorStoreQueryResponseContentObservationFilter() { + /** + * The query response content is typically too big to be included in an observation as + * span attributes. That's why the preferred way to store it is as span events, which + * are supported by OpenTelemetry but not yet surfaced through the Micrometer APIs. + * This primary/fallback configuration is a temporary solution until + * https://github.com/micrometer-metrics/micrometer/issues/5238 is delivered. + */ + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(OtelTracer.class) + @ConditionalOnBean(OtelTracer.class) + static class PrimaryVectorStoreQueryResponseContentObservationConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = VectorStoreObservationProperties.CONFIG_PREFIX, name = "include-query-response", + havingValue = "true") + VectorStoreQueryResponseObservationHandler vectorStoreQueryResponseObservationHandler() { + logQueryResponseContentWarning(); + return new VectorStoreQueryResponseObservationHandler(); + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnMissingClass("io.micrometer.tracing.otel.bridge.OtelTracer") + static class FallbackVectorStoreQueryResponseContentObservationConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(prefix = VectorStoreObservationProperties.CONFIG_PREFIX, name = "include-query-response", + havingValue = "true") + VectorStoreQueryResponseObservationFilter vectorStoreQueryResponseContentObservationFilter() { + logQueryResponseContentWarning(); + return new VectorStoreQueryResponseObservationFilter(); + } + + } + + private static void logQueryResponseContentWarning() { logger.warn( "You have enabled the inclusion of the query response content in the observations, with the risk of exposing sensitive or private information. Please, be careful!"); - return new VectorStoreQueryResponseObservationFilter(); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreAutoConfigurationIT.java index 343b306f3..46ad43c9c 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/azure/AzureVectorStoreAutoConfigurationIT.java @@ -49,6 +49,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; /** * @author Christian Tzolov * @author Soby Chacko + * @author Thomas Vitale */ @EnabledIfEnvironmentVariable(named = "AZURE_AI_SEARCH_API_KEY", matches = ".+") @EnabledIfEnvironmentVariable(named = "AZURE_AI_SEARCH_ENDPOINT", matches = ".+") @@ -111,7 +112,7 @@ public class AzureVectorStoreAutoConfigurationIT { return vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); }, hasSize(1)); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.AZURE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.AZURE, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -125,7 +126,7 @@ public class AzureVectorStoreAutoConfigurationIT { assertThat(resultDoc.getMetadata()).hasSize(2); assertThat(resultDoc.getMetadata()).containsKeys("spring", "distance"); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.AZURE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.AZURE, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); @@ -136,7 +137,7 @@ public class AzureVectorStoreAutoConfigurationIT { return vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); }, hasSize(0)); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.AZURE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.AZURE, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreAutoConfigurationIT.java index 57b5989b0..36bf85110 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/cassandra/CassandraVectorStoreAutoConfigurationIT.java @@ -45,6 +45,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; /** * @author Mick Semb Wever * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ @Testcontainers @@ -80,7 +81,7 @@ class CassandraVectorStoreAutoConfigurationIT { TestObservationRegistry observationRegistry = context.getBean(TestObservationRegistry.class); vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.CASSANDRA, + assertObservationRegistry(observationRegistry, VectorStoreProvider.CASSANDRA, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -92,7 +93,7 @@ class CassandraVectorStoreAutoConfigurationIT { assertThat(resultDoc.getContent()).contains( "Spring AI provides abstractions that serve as the foundation for developing AI applications."); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.CASSANDRA, + assertObservationRegistry(observationRegistry, VectorStoreProvider.CASSANDRA, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); @@ -102,7 +103,7 @@ class CassandraVectorStoreAutoConfigurationIT { results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); assertThat(results).isEmpty(); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.CASSANDRA, + assertObservationRegistry(observationRegistry, VectorStoreProvider.CASSANDRA, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); }); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreAutoConfigurationIT.java index bfbd1e0f8..fef80b6f1 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/chroma/ChromaVectorStoreAutoConfigurationIT.java @@ -47,6 +47,7 @@ import static org.springframework.ai.autoconfigure.vectorstore.observation.Obser * @author Christian Tzolov * @author Eddú Meléndez * @author Soby Chacko + * @author Thomas Vitale */ @Testcontainers public class ChromaVectorStoreAutoConfigurationIT { @@ -77,7 +78,7 @@ public class ChromaVectorStoreAutoConfigurationIT { vectorStore.add(List.of(bgDocument, nlDocument)); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.CHROMA, + assertObservationRegistry(observationRegistry, VectorStoreProvider.CHROMA, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -102,8 +103,8 @@ public class ChromaVectorStoreAutoConfigurationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store chroma query") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY_METADATA_FILTER.asString(), + .hasContextualNameEqualTo("chroma query") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_FILTER.asString(), "Expression[type=EQ, left=Key[key=country], right=Value[value=Netherlands]]") .hasBeenStarted() .hasBeenStopped(); @@ -116,8 +117,8 @@ public class ChromaVectorStoreAutoConfigurationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store chroma delete") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY_METADATA_FILTER.asString(), "none") + .hasContextualNameEqualTo("chroma delete") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_FILTER.asString(), "none") .hasBeenStarted() .hasBeenStopped(); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/elasticsearch/ElasticsearchVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/elasticsearch/ElasticsearchVectorStoreAutoConfigurationIT.java index 2b8a064fe..60eca7d06 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/elasticsearch/ElasticsearchVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/elasticsearch/ElasticsearchVectorStoreAutoConfigurationIT.java @@ -82,7 +82,7 @@ class ElasticsearchVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.ELASTICSEARCH, + assertObservationRegistry(observationRegistry, VectorStoreProvider.ELASTICSEARCH, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -104,14 +104,14 @@ class ElasticsearchVectorStoreAutoConfigurationIT { assertThat(resultDoc.getMetadata()).containsKey("meta2"); assertThat(resultDoc.getMetadata()).containsKey("distance"); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.ELASTICSEARCH, + assertObservationRegistry(observationRegistry, VectorStoreProvider.ELASTICSEARCH, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); // Remove all documents from the store vectorStore.delete(documents.stream().map(Document::getId).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.ELASTICSEARCH, + assertObservationRegistry(observationRegistry, VectorStoreProvider.ELASTICSEARCH, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfigurationIT.java index a054067ef..b280052ed 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/gemfire/GemFireVectorStoreAutoConfigurationIT.java @@ -55,6 +55,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; /** * @author Geet Rawat * @author Christian Tzolov + * @author Thomas Vitale */ class GemFireVectorStoreAutoConfigurationIT { @@ -149,7 +150,7 @@ class GemFireVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.GEMFIRE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.GEMFIRE, VectorStoreObservationContext.Operation.ADD); Awaitility.await().until(() -> { @@ -159,7 +160,7 @@ class GemFireVectorStoreAutoConfigurationIT { List results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.GEMFIRE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.GEMFIRE, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); @@ -174,7 +175,7 @@ class GemFireVectorStoreAutoConfigurationIT { // Remove all documents from the store vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.GEMFIRE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.GEMFIRE, VectorStoreObservationContext.Operation.DELETE); Awaitility.await().until(() -> { diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfigurationIT.java index d924b6772..6697dffbc 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/milvus/MilvusVectorStoreAutoConfigurationIT.java @@ -44,6 +44,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; * @author Christian Tzolov * @author Eddú Meléndez * @author Soby Chacko + * @author Thomas Vitale */ @Testcontainers public class MilvusVectorStoreAutoConfigurationIT { @@ -76,7 +77,7 @@ public class MilvusVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.MILVUS, + assertObservationRegistry(observationRegistry, VectorStoreProvider.MILVUS, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -90,7 +91,7 @@ public class MilvusVectorStoreAutoConfigurationIT { assertThat(resultDoc.getMetadata()).hasSize(2); assertThat(resultDoc.getMetadata()).containsKeys("spring", "distance"); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.MILVUS, + assertObservationRegistry(observationRegistry, VectorStoreProvider.MILVUS, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); @@ -100,7 +101,7 @@ public class MilvusVectorStoreAutoConfigurationIT { results = vectorStore.similaritySearch(SearchRequest.query("Spring").withTopK(1)); assertThat(results).hasSize(0); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.MILVUS, + assertObservationRegistry(observationRegistry, VectorStoreProvider.MILVUS, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/mongo/MongoDBAtlasVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/mongo/MongoDBAtlasVectorStoreAutoConfigurationIT.java index 47d0a70d8..f2e143590 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/mongo/MongoDBAtlasVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/mongo/MongoDBAtlasVectorStoreAutoConfigurationIT.java @@ -50,6 +50,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; /** * @author Eddú Meléndez * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") @@ -94,7 +95,7 @@ class MongoDBAtlasVectorStoreAutoConfigurationIT { TestObservationRegistry observationRegistry = context.getBean(TestObservationRegistry.class); vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.MONGODB, + assertObservationRegistry(observationRegistry, VectorStoreProvider.MONGODB, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -109,14 +110,14 @@ class MongoDBAtlasVectorStoreAutoConfigurationIT { "Great Depression Great Depression Great Depression Great Depression Great Depression Great Depression"); assertThat(resultDoc.getMetadata()).containsEntry("meta2", "meta2"); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.MONGODB, + assertObservationRegistry(observationRegistry, VectorStoreProvider.MONGODB, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); // Remove all documents from the store vectorStore.delete(documents.stream().map(Document::getId).collect(Collectors.toList())); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.MONGODB, + assertObservationRegistry(observationRegistry, VectorStoreProvider.MONGODB, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfigurationIT.java index 7dba368db..3f56ca284 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/neo4j/Neo4jVectorStoreAutoConfigurationIT.java @@ -46,6 +46,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; * @author Jingzhou Ou * @author Soby Chacko * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers public class Neo4jVectorStoreAutoConfigurationIT { @@ -85,7 +86,7 @@ public class Neo4jVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.NEO4J, + assertObservationRegistry(observationRegistry, VectorStoreProvider.NEO4J, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -97,14 +98,14 @@ public class Neo4jVectorStoreAutoConfigurationIT { assertThat(resultDoc.getContent()).contains( "Spring AI provides abstractions that serve as the foundation for developing AI applications."); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.NEO4J, + assertObservationRegistry(observationRegistry, VectorStoreProvider.NEO4J, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); // Remove all documents from the store vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.NEO4J, + assertObservationRegistry(observationRegistry, VectorStoreProvider.NEO4J, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/observation/ObservationTestUtil.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/observation/ObservationTestUtil.java index 784a812af..01dee1026 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/observation/ObservationTestUtil.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/observation/ObservationTestUtil.java @@ -29,13 +29,13 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; public class ObservationTestUtil { - public static void assertObservationRegistry(TestObservationRegistry observationRegistry, String kind, + public static void assertObservationRegistry(TestObservationRegistry observationRegistry, VectorStoreProvider vectorStoreProvider, VectorStoreObservationContext.Operation operation) { TestObservationRegistryAssert.assertThat(observationRegistry) .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo(kind + " " + vectorStoreProvider.value() + " " + operation.value()) + .hasContextualNameEqualTo(vectorStoreProvider.value() + " " + operation.value()) .hasBeenStarted() .hasBeenStopped(); } diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/observation/VectorStoreObservationAutoConfigurationTests.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/observation/VectorStoreObservationAutoConfigurationTests.java index 24386fbc0..0fdd547ab 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/observation/VectorStoreObservationAutoConfigurationTests.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/observation/VectorStoreObservationAutoConfigurationTests.java @@ -17,8 +17,12 @@ package org.springframework.ai.autoconfigure.vectorstore.observation; import static org.assertj.core.api.Assertions.assertThat; +import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext; +import io.micrometer.tracing.otel.bridge.OtelTracer; +import io.opentelemetry.api.OpenTelemetry; import org.junit.jupiter.api.Test; import org.springframework.ai.vectorstore.observation.VectorStoreQueryResponseObservationFilter; +import org.springframework.ai.vectorstore.observation.VectorStoreQueryResponseObservationHandler; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -40,10 +44,19 @@ class VectorStoreObservationAutoConfigurationTests { } @Test - void queryResponseFilterFilterEnabled() { - contextRunner.withPropertyValues("spring.ai.vectorstore.observations.include-query-response=true") + void queryResponseHandlerDefault() { + contextRunner.run(context -> { + assertThat(context).doesNotHaveBean(VectorStoreQueryResponseObservationHandler.class); + }); + } + + @Test + void queryResponseHandlerEnabled() { + contextRunner + .withBean(OtelTracer.class, OpenTelemetry.noop().getTracer("test"), new OtelCurrentTraceContext(), null) + .withPropertyValues("spring.ai.vectorstore.observations.include-query-response=true") .run(context -> { - assertThat(context).hasSingleBean(VectorStoreQueryResponseObservationFilter.class); + assertThat(context).hasSingleBean(VectorStoreQueryResponseObservationHandler.class); }); } diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfigurationIT.java index 307cf9c60..e7c584621 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/opensearch/OpenSearchVectorStoreAutoConfigurationIT.java @@ -91,7 +91,7 @@ class OpenSearchVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.OPENSEARCH, + assertObservationRegistry(observationRegistry, VectorStoreProvider.OPENSEARCH, VectorStoreObservationContext.Operation.ADD); Awaitility.await() @@ -104,7 +104,7 @@ class OpenSearchVectorStoreAutoConfigurationIT { List results = vectorStore .similaritySearch(SearchRequest.query("Great Depression").withTopK(1).withSimilarityThreshold(0)); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.OPENSEARCH, + assertObservationRegistry(observationRegistry, VectorStoreProvider.OPENSEARCH, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); @@ -120,7 +120,7 @@ class OpenSearchVectorStoreAutoConfigurationIT { // Remove all documents from the store vectorStore.delete(documents.stream().map(Document::getId).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.OPENSEARCH, + assertObservationRegistry(observationRegistry, VectorStoreProvider.OPENSEARCH, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfigurationIT.java index 5bdebdfb2..6f9952ccc 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/oracle/OracleVectorStoreAutoConfigurationIT.java @@ -48,6 +48,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; /** * @author Christian Tzolov * @author Eddú Meléndez + * @author Thomas Vitale */ @Testcontainers public class OracleVectorStoreAutoConfigurationIT { @@ -84,7 +85,7 @@ public class OracleVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.ORACLE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.ORACLE, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -96,14 +97,14 @@ public class OracleVectorStoreAutoConfigurationIT { assertThat(resultDoc.getId()).isEqualTo(documents.get(2).getId()); assertThat(resultDoc.getMetadata()).containsKeys("depression", "distance"); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.ORACLE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.ORACLE, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); // Remove all documents from the store vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.ORACLE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.ORACLE, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/pgvector/PgVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/pgvector/PgVectorStoreAutoConfigurationIT.java index 1e600ccda..6777b04e1 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/pgvector/PgVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/pgvector/PgVectorStoreAutoConfigurationIT.java @@ -52,6 +52,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; * @author Christian Tzolov * @author Muthukumaran Navaneethakrishnan * @author Soby Chacko + * @author Thomas Vitale */ @Testcontainers public class PgVectorStoreAutoConfigurationIT { @@ -101,7 +102,7 @@ public class PgVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.PG_VECTOR, + assertObservationRegistry(observationRegistry, VectorStoreProvider.PG_VECTOR, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -113,14 +114,14 @@ public class PgVectorStoreAutoConfigurationIT { assertThat(resultDoc.getId()).isEqualTo(documents.get(2).getId()); assertThat(resultDoc.getMetadata()).containsKeys("depression", "distance"); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.PG_VECTOR, + assertObservationRegistry(observationRegistry, VectorStoreProvider.PG_VECTOR, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); // Remove all documents from the store vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.PG_VECTOR, + assertObservationRegistry(observationRegistry, VectorStoreProvider.PG_VECTOR, VectorStoreObservationContext.Operation.DELETE); results = vectorStore.similaritySearch(SearchRequest.query("Great Depression").withTopK(1)); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/pinecone/PineconeVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/pinecone/PineconeVectorStoreAutoConfigurationIT.java index 6e7d505e3..461e02abb 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/pinecone/PineconeVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/pinecone/PineconeVectorStoreAutoConfigurationIT.java @@ -48,6 +48,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; /** * @author Christian Tzolov * @author Soby Chacko + * @author Thomas Vitale */ @EnabledIfEnvironmentVariable(named = "PINECONE_API_KEY", matches = ".+") public class PineconeVectorStoreAutoConfigurationIT { @@ -94,7 +95,7 @@ public class PineconeVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.PINECONE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.PINECONE, VectorStoreObservationContext.Operation.ADD); Awaitility.await().until(() -> { @@ -112,14 +113,14 @@ public class PineconeVectorStoreAutoConfigurationIT { assertThat(resultDoc.getMetadata()).hasSize(2); assertThat(resultDoc.getMetadata()).containsKeys("spring", "customDistanceField"); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.PINECONE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.PINECONE, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); // Remove all documents from the store vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.PINECONE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.PINECONE, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfigurationIT.java index 734066157..99525be0e 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/qdrant/QdrantVectorStoreAutoConfigurationIT.java @@ -46,6 +46,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; * @author Christian Tzolov * @author Eddú Meléndez * @author Soby Chacko + * @author Thomas Vitale * @since 0.8.1 */ @Testcontainers @@ -75,7 +76,7 @@ public class QdrantVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.QDRANT, + assertObservationRegistry(observationRegistry, VectorStoreProvider.QDRANT, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -87,7 +88,7 @@ public class QdrantVectorStoreAutoConfigurationIT { assertThat(resultDoc.getId()).isEqualTo(documents.get(2).getId()); assertThat(resultDoc.getMetadata()).containsKeys("depression", "distance"); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.QDRANT, + assertObservationRegistry(observationRegistry, VectorStoreProvider.QDRANT, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); @@ -96,7 +97,7 @@ public class QdrantVectorStoreAutoConfigurationIT { results = vectorStore.similaritySearch(SearchRequest.query("Great Depression").withTopK(1)); assertThat(results).hasSize(0); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.QDRANT, + assertObservationRegistry(observationRegistry, VectorStoreProvider.QDRANT, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); }); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfigurationIT.java index 66e6e61f1..634447464 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/redis/RedisVectorStoreAutoConfigurationIT.java @@ -47,6 +47,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; * @author Eddú Meléndez * @author Soby Chacko * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers class RedisVectorStoreAutoConfigurationIT { @@ -76,7 +77,7 @@ class RedisVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.REDIS, + assertObservationRegistry(observationRegistry, VectorStoreProvider.REDIS, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -88,14 +89,14 @@ class RedisVectorStoreAutoConfigurationIT { assertThat(resultDoc.getContent()).contains( "Spring AI provides abstractions that serve as the foundation for developing AI applications."); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.REDIS, + assertObservationRegistry(observationRegistry, VectorStoreProvider.REDIS, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); // Remove all documents from the store vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.REDIS, + assertObservationRegistry(observationRegistry, VectorStoreProvider.REDIS, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/typesense/TypesenseVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/typesense/TypesenseVectorStoreAutoConfigurationIT.java index 78a244d75..aea63b614 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/typesense/TypesenseVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/typesense/TypesenseVectorStoreAutoConfigurationIT.java @@ -46,6 +46,7 @@ import io.micrometer.observation.tck.TestObservationRegistry; * @author Eddú Meléndez * @author Soby Chacko * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers public class TypesenseVectorStoreAutoConfigurationIT { @@ -81,7 +82,7 @@ public class TypesenseVectorStoreAutoConfigurationIT { vectorStore.add(documents); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.TYPESENSE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.TYPESENSE, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -95,13 +96,13 @@ public class TypesenseVectorStoreAutoConfigurationIT { assertThat(resultDoc.getMetadata()).hasSize(2); assertThat(resultDoc.getMetadata()).containsKeys("spring", "distance"); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.TYPESENSE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.TYPESENSE, VectorStoreObservationContext.Operation.QUERY); observationRegistry.clear(); vectorStore.delete(documents.stream().map(doc -> doc.getId()).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.TYPESENSE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.TYPESENSE, VectorStoreObservationContext.Operation.DELETE); observationRegistry.clear(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfigurationIT.java index 69d2d2116..ba81c7479 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/vectorstore/weaviate/WeaviateVectorStoreAutoConfigurationIT.java @@ -46,6 +46,7 @@ import static org.springframework.ai.autoconfigure.vectorstore.observation.Obser * @author Christian Tzolov * @author Eddú Meléndez * @author Soby Chacko + * @author Thomas Vitale */ @Testcontainers public class WeaviateVectorStoreAutoConfigurationIT { @@ -89,7 +90,7 @@ public class WeaviateVectorStoreAutoConfigurationIT { vectorStore.add(List.of(bgDocument, nlDocument)); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.WEAVIATE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.WEAVIATE, VectorStoreObservationContext.Operation.ADD); observationRegistry.clear(); @@ -103,7 +104,7 @@ public class WeaviateVectorStoreAutoConfigurationIT { assertThat(results).hasSize(1); assertThat(results.get(0).getId()).isEqualTo(bgDocument.getId()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.WEAVIATE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.WEAVIATE, VectorStoreObservationContext.Operation.QUERY); results = vectorStore.similaritySearch( @@ -130,7 +131,7 @@ public class WeaviateVectorStoreAutoConfigurationIT { // Remove all documents from the store vectorStore.delete(List.of(bgDocument, nlDocument).stream().map(doc -> doc.getId()).toList()); - assertObservationRegistry(observationRegistry, "vector_store", VectorStoreProvider.WEAVIATE, + assertObservationRegistry(observationRegistry, VectorStoreProvider.WEAVIATE, VectorStoreObservationContext.Operation.DELETE); }); } diff --git a/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java b/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java index 2e39ed9f3..24a7540e6 100644 --- a/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java +++ b/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureVectorStore.java @@ -72,6 +72,7 @@ import io.micrometer.observation.ObservationRegistry; * @author Xiangyang Yu * @author Christian Tzolov * @author Josh Long + * @author Thomas Vitale */ public class AzureVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -410,9 +411,9 @@ public class AzureVectorStore extends AbstractObservationVectorStore implements public Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.AZURE.value(), operationName) + .withCollectionName(this.indexName) .withDimensions(this.embeddingModel.dimensions()) - .withSimilarityMetric(this.initializeSchema ? VectorStoreSimilarityMetric.COSINE.value() : null) - .withIndexName(this.indexName); + .withSimilarityMetric(this.initializeSchema ? VectorStoreSimilarityMetric.COSINE.value() : null); } -} \ No newline at end of file +} diff --git a/vector-stores/spring-ai-azure-store/src/test/java/org/springframework/ai/vectorstore/azure/AzureVectorStoreObservationIT.java b/vector-stores/spring-ai-azure-store/src/test/java/org/springframework/ai/vectorstore/azure/AzureVectorStoreObservationIT.java index 6b6e7b28a..cced6b5f8 100644 --- a/vector-stores/spring-ai-azure-store/src/test/java/org/springframework/ai/vectorstore/azure/AzureVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-azure-store/src/test/java/org/springframework/ai/vectorstore/azure/AzureVectorStoreObservationIT.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; @@ -56,6 +57,7 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; * {@link AzureVectorStore}. * * @author Christian Tzolov + * @author Thomas Vitale */ @EnabledIfEnvironmentVariable(named = "AZURE_AI_SEARCH_API_KEY", matches = ".+") @EnabledIfEnvironmentVariable(named = "AZURE_AI_SEARCH_ENDPOINT", matches = ".+") @@ -101,18 +103,21 @@ public class AzureVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store azure add") + .hasContextualNameEqualTo("azure add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), "azure") - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + AzureVectorStore.DEFAULT_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -128,19 +133,23 @@ public class AzureVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store azure query") + .hasContextualNameEqualTo("azure query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), "azure") - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + AzureVectorStore.DEFAULT_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/vectorstore/CassandraVectorStore.java b/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/vectorstore/CassandraVectorStore.java index 055fec120..6c8cf7053 100644 --- a/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/vectorstore/CassandraVectorStore.java +++ b/vector-stores/spring-ai-cassandra-store/src/main/java/org/springframework/ai/vectorstore/CassandraVectorStore.java @@ -95,6 +95,7 @@ import java.util.concurrent.ConcurrentMap; * * @author Mick Semb Wever * @author Christian Tzolov + * @author Thomas Vitale * @see VectorStore * @see org.springframework.ai.vectorstore.CassandraVectorStoreConfig * @see EmbeddingModel @@ -381,11 +382,10 @@ public class CassandraVectorStore extends AbstractObservationVectorStore impleme @Override public Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.CASSANDRA.value(), operationName) - .withDimensions(this.embeddingModel.dimensions()) .withCollectionName(this.conf.schema.table()) + .withDimensions(this.embeddingModel.dimensions()) .withNamespace(this.conf.schema.keyspace()) - .withSimilarityMetric(getSimilarityMetric()) - .withIndexName(this.conf.schema.index()); + .withSimilarityMetric(getSimilarityMetric()); } private static Map SIMILARITY_TYPE_MAPPING = Map.of(Similarity.COSINE, diff --git a/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/CassandraVectorStoreObservationIT.java b/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/CassandraVectorStoreObservationIT.java index 03b2ee9f7..3216b3a67 100644 --- a/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/CassandraVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-cassandra-store/src/test/java/org/springframework/ai/vectorstore/CassandraVectorStoreObservationIT.java @@ -25,6 +25,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.CassandraVectorStoreConfig.SchemaColumn; import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention; @@ -50,6 +51,7 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers public class CassandraVectorStoreObservationIT { @@ -92,18 +94,21 @@ public class CassandraVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store cassandra add") + .hasContextualNameEqualTo("cassandra add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), "cassandra") - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "ai_vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "test_springframework") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + CassandraVectorStoreConfig.DEFAULT_TABLE_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "test_springframework") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -119,19 +124,23 @@ public class CassandraVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store cassandra query") + .hasContextualNameEqualTo("cassandra query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), "cassandra") - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "ai_vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "test_springframework") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + CassandraVectorStoreConfig.DEFAULT_TABLE_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "test_springframework") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/vectorstore/ChromaVectorStoreObservationIT.java b/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/vectorstore/ChromaVectorStoreObservationIT.java index 8d6de614b..d0bae9019 100644 --- a/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/vectorstore/ChromaVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-chroma-store/src/test/java/org/springframework/ai/vectorstore/ChromaVectorStoreObservationIT.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.chroma.ChromaApi; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; @@ -49,6 +50,7 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers public class ChromaVectorStoreObservationIT { @@ -89,20 +91,22 @@ public class ChromaVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store chroma add") + .hasContextualNameEqualTo("chroma add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.CHROMA.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), "TestCollection:" + vectorStore.getCollectionId()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "distance") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "distance") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -118,21 +122,24 @@ public class ChromaVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store chroma query") + .hasContextualNameEqualTo("chroma query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.CHROMA.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), "TestCollection:" + vectorStore.getCollectionId()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "distance") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "distance") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/ElasticsearchVectorStore.java b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/ElasticsearchVectorStore.java index 8f53107a8..56db121a8 100644 --- a/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/ElasticsearchVectorStore.java +++ b/vector-stores/spring-ai-elasticsearch-store/src/main/java/org/springframework/ai/vectorstore/ElasticsearchVectorStore.java @@ -68,6 +68,7 @@ import io.micrometer.observation.ObservationRegistry; * @author Laura Trotta * @author Soby Chacko * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ public class ElasticsearchVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -252,10 +253,9 @@ public class ElasticsearchVectorStore extends AbstractObservationVectorStore imp @Override public Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.ELASTICSEARCH.value(), operationName) + .withCollectionName(this.options.getIndexName()) .withDimensions(this.embeddingModel.dimensions()) - .withIndexName(this.options.getIndexName()) .withSimilarityMetric(getSimilarityMetric()); - } private static Map SIMILARITY_TYPE_MAPPING = Map.of( diff --git a/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/ElasticsearchVectorStoreObservationIT.java b/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/ElasticsearchVectorStoreObservationIT.java index a45688d5b..254e903fe 100644 --- a/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/ElasticsearchVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-elasticsearch-store/src/test/java/org/springframework/ai/vectorstore/ElasticsearchVectorStoreObservationIT.java @@ -33,6 +33,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; @@ -63,6 +64,7 @@ import static org.hamcrest.Matchers.greaterThan;; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") @@ -126,19 +128,22 @@ public class ElasticsearchVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store elasticsearch add") + .hasContextualNameEqualTo("elasticsearch add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.ELASTICSEARCH.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + "spring-ai-document-index") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -159,20 +164,24 @@ public class ElasticsearchVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store elasticsearch query") + .hasContextualNameEqualTo("elasticsearch query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.ELASTICSEARCH.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + "spring-ai-document-index") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/GemFireVectorStore.java b/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/GemFireVectorStore.java index 5492e6d85..a2a40c851 100644 --- a/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/GemFireVectorStore.java +++ b/vector-stores/spring-ai-gemfire-store/src/main/java/org/springframework/ai/vectorstore/GemFireVectorStore.java @@ -57,6 +57,7 @@ import reactor.util.annotation.NonNull; * * @author Geet Rawat * @author Christian Tzolov + * @author Thomas Vitale */ public class GemFireVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -538,8 +539,8 @@ public class GemFireVectorStore extends AbstractObservationVectorStore implement @Override public Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.GEMFIRE.value(), operationName) + .withCollectionName(this.indexName) .withDimensions(this.embeddingModel.dimensions()) - .withIndexName(this.indexName) .withFieldName(EMBEDDINGS); } diff --git a/vector-stores/spring-ai-gemfire-store/src/test/java/org/springframework/ai/vectorstore/GemFireVectorStoreObservationIT.java b/vector-stores/spring-ai-gemfire-store/src/test/java/org/springframework/ai/vectorstore/GemFireVectorStoreObservationIT.java index fb1ff582d..c4808a1b6 100644 --- a/vector-stores/spring-ai-gemfire-store/src/test/java/org/springframework/ai/vectorstore/GemFireVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-gemfire-store/src/test/java/org/springframework/ai/vectorstore/GemFireVectorStoreObservationIT.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.observation.DefaultVectorStoreObservationConvention; @@ -53,10 +54,11 @@ import static org.hamcrest.Matchers.hasSize; /** * @author Christian Tzolov + * @author Thomas Vitale */ public class GemFireVectorStoreObservationIT { - public static final String INDEX_NAME = "spring-ai-index1"; + public static final String TEST_INDEX_NAME = "spring-ai-index1"; private static GemFireCluster gemFireCluster; @@ -121,20 +123,21 @@ public class GemFireVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store gemfire add") + .hasContextualNameEqualTo("gemfire add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.GEMFIRE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "/embeddings") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), INDEX_NAME) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), TEST_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "/embeddings") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -156,21 +159,23 @@ public class GemFireVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store gemfire query") + .hasContextualNameEqualTo("gemfire query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.GEMFIRE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "/embeddings") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), INDEX_NAME) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), TEST_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "/embeddings") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); @@ -191,7 +196,7 @@ public class GemFireVectorStoreObservationIT { public GemFireVectorStoreConfig gemfireVectorStoreConfig() { return new GemFireVectorStoreConfig().setHost("localhost") .setPort(HTTP_SERVICE_PORT) - .setIndexName(INDEX_NAME); + .setIndexName(TEST_INDEX_NAME); } @Bean diff --git a/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/HanaVectorStoreObservationIT.java b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/HanaVectorStoreObservationIT.java index 2d1ac7cc2..6f654b591 100644 --- a/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/HanaVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-hanadb-store/src/test/java/org/springframework/ai/vectorstore/HanaVectorStoreObservationIT.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; @@ -50,6 +51,7 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; /** * @author Christian Tzolov + * @author Thomas Vitale */ @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") @EnabledIfEnvironmentVariable(named = "HANA_DATASOURCE_URL", matches = ".+") @@ -57,6 +59,8 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; @EnabledIfEnvironmentVariable(named = "HANA_DATASOURCE_PASSWORD", matches = ".+") public class HanaVectorStoreObservationIT { + private static final String TEST_TABLE_NAME = "CRICKET_WORLD_CUP"; + List documents = List.of( new Document(getText("classpath:/test/data/spring.ai.txt"), Map.of("meta1", "meta1")), new Document(getText("classpath:/test/data/time.shelter.txt")), @@ -90,19 +94,21 @@ public class HanaVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store hana add") + .hasContextualNameEqualTo("hana add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.HANA.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "CRICKET_WORLD_CUP") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), TEST_TABLE_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -118,20 +124,23 @@ public class HanaVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store hana query") + .hasContextualNameEqualTo("hana query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.HANA.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "CRICKET_WORLD_CUP") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), TEST_TABLE_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); @@ -152,7 +161,7 @@ public class HanaVectorStoreObservationIT { public VectorStore hanaCloudVectorStore(CricketWorldCupRepository cricketWorldCupRepository, EmbeddingModel embeddingModel, ObservationRegistry observationRegistry) { return new HanaCloudVectorStore(cricketWorldCupRepository, embeddingModel, - HanaCloudVectorStoreConfig.builder().tableName("CRICKET_WORLD_CUP").topK(1).build(), + HanaCloudVectorStoreConfig.builder().tableName(TEST_TABLE_NAME).topK(1).build(), observationRegistry, null); } diff --git a/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/MilvusVectorStore.java b/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/MilvusVectorStore.java index db182d6d8..b1141949a 100644 --- a/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/MilvusVectorStore.java +++ b/vector-stores/spring-ai-milvus-store/src/main/java/org/springframework/ai/vectorstore/MilvusVectorStore.java @@ -69,6 +69,7 @@ import java.util.stream.Collectors; /** * @author Christian Tzolov * @author Soby Chacko + * @author Thomas Vitale */ public class MilvusVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -551,9 +552,8 @@ public class MilvusVectorStore extends AbstractObservationVectorStore implements String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.MILVUS.value(), operationName) - .withDimensions(this.embeddingModel.dimensions()) .withCollectionName(this.config.collectionName) - .withIndexName(this.config.indexType.name()) + .withDimensions(this.embeddingModel.dimensions()) .withSimilarityMetric(getSimilarityMetric()) .withNamespace(this.config.databaseName); } diff --git a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreObservationIT.java b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreObservationIT.java index e10a1174c..a0c351451 100644 --- a/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-milvus-store/src/test/java/org/springframework/ai/vectorstore/MilvusVectorStoreObservationIT.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.TokenCountBatchingStrategy; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; @@ -52,10 +53,13 @@ import io.milvus.param.MetricType; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers public class MilvusVectorStoreObservationIT { + private static final String TEST_COLLECTION_NAME = "test_vector_store"; + @Container private static MilvusContainer milvusContainer = new MilvusContainer("milvusdb/milvus:v2.3.8"); @@ -92,19 +96,21 @@ public class MilvusVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store milvus add") + .hasContextualNameEqualTo("milvus add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.MILVUS.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "test_vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "default") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), TEST_COLLECTION_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "default") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -120,20 +126,23 @@ public class MilvusVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store milvus query") + .hasContextualNameEqualTo("milvus query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.MILVUS.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "test_vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "default") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), TEST_COLLECTION_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "default") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); @@ -154,7 +163,7 @@ public class MilvusVectorStoreObservationIT { public VectorStore vectorStore(MilvusServiceClient milvusClient, EmbeddingModel embeddingModel, ObservationRegistry observationRegistry) { MilvusVectorStoreConfig config = MilvusVectorStoreConfig.builder() - .withCollectionName("test_vector_store") + .withCollectionName(TEST_COLLECTION_NAME) .withDatabaseName("default") .withIndexType(IndexType.IVF_FLAT) .withMetricType(MetricType.COSINE) diff --git a/vector-stores/spring-ai-mongodb-atlas-store/src/main/java/org/springframework/ai/vectorstore/MongoDBAtlasVectorStore.java b/vector-stores/spring-ai-mongodb-atlas-store/src/main/java/org/springframework/ai/vectorstore/MongoDBAtlasVectorStore.java index 1166f2947..a3c745ae2 100644 --- a/vector-stores/spring-ai-mongodb-atlas-store/src/main/java/org/springframework/ai/vectorstore/MongoDBAtlasVectorStore.java +++ b/vector-stores/spring-ai-mongodb-atlas-store/src/main/java/org/springframework/ai/vectorstore/MongoDBAtlasVectorStore.java @@ -46,6 +46,7 @@ import io.micrometer.observation.ObservationRegistry; * @author Chris Smith * @author Soby Chacko * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ public class MongoDBAtlasVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -58,7 +59,7 @@ public class MongoDBAtlasVectorStore extends AbstractObservationVectorStore impl public static final String SCORE_FIELD_NAME = "score"; - private static final String DEFAULT_VECTOR_COLLECTION_NAME = "vector_store"; + public static final String DEFAULT_VECTOR_COLLECTION_NAME = "vector_store"; private static final String DEFAULT_VECTOR_INDEX_NAME = "vector_index"; @@ -320,10 +321,9 @@ public class MongoDBAtlasVectorStore extends AbstractObservationVectorStore impl public VectorStoreObservationContext.Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.MONGODB.value(), operationName) - .withDimensions(this.embeddingModel.dimensions()) .withCollectionName(this.config.collectionName) - .withFieldName(this.config.pathName) - .withIndexName(this.config.vectorIndexName); + .withDimensions(this.embeddingModel.dimensions()) + .withFieldName(this.config.pathName); } } \ No newline at end of file diff --git a/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/MongoDbVectorStoreObservationIT.java b/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/MongoDbVectorStoreObservationIT.java index 37423ba0a..0d0ae6994 100644 --- a/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/MongoDbVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-mongodb-atlas-store/src/test/java/org/springframework/ai/vectorstore/MongoDbVectorStoreObservationIT.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; @@ -58,6 +59,7 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; /** * @author Christian Tzolov * @author Soby Chacko + * @author Thomas Vitale */ @Testcontainers @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") @@ -111,20 +113,22 @@ public class MongoDbVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store mongodb add") + .hasContextualNameEqualTo("mongodb add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.MONGODB.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "embedding") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), "vector_index") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + MongoDBAtlasVectorStore.DEFAULT_VECTOR_COLLECTION_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "embedding") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -140,21 +144,24 @@ public class MongoDbVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store mongodb query") + .hasContextualNameEqualTo("mongodb query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.MONGODB.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "embedding") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), "vector_index") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + MongoDBAtlasVectorStore.DEFAULT_VECTOR_COLLECTION_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "embedding") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/Neo4jVectorStore.java b/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/Neo4jVectorStore.java index fefbcc345..49be7c23a 100644 --- a/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/Neo4jVectorStore.java +++ b/vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/Neo4jVectorStore.java @@ -42,6 +42,7 @@ import io.micrometer.observation.ObservationRegistry; * @author Gerrit Meier * @author Michael Simons * @author Christian Tzolov + * @author Thomas Vitale */ public class Neo4jVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -433,8 +434,8 @@ public class Neo4jVectorStore extends AbstractObservationVectorStore implements public VectorStoreObservationContext.Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.NEO4J.value(), operationName) + .withCollectionName(this.config.indexName) .withDimensions(this.embeddingModel.dimensions()) - .withIndexName(this.config.indexName) .withSimilarityMetric(getSimilarityMetric()); } diff --git a/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/Neo4jVectorStoreObservationIT.java b/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/Neo4jVectorStoreObservationIT.java index f543c156a..540fc116b 100644 --- a/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/Neo4jVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-neo4j-store/src/test/java/org/springframework/ai/vectorstore/Neo4jVectorStoreObservationIT.java @@ -25,11 +25,13 @@ import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.neo4j.cypherdsl.support.schema_name.SchemaNames; import org.neo4j.driver.AuthTokens; import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; @@ -53,6 +55,7 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") @@ -101,19 +104,22 @@ public class Neo4jVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store neo4j add") + .hasContextualNameEqualTo("neo4j add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.NEO4J.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + SchemaNames.sanitize(Neo4jVectorStore.DEFAULT_INDEX_NAME).orElseThrow()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -129,20 +135,24 @@ public class Neo4jVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store neo4j query") + .hasContextualNameEqualTo("neo4j query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.NEO4J.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + SchemaNames.sanitize(Neo4jVectorStore.DEFAULT_INDEX_NAME).orElseThrow()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-opensearch-store/src/main/java/org/springframework/ai/vectorstore/OpenSearchVectorStore.java b/vector-stores/spring-ai-opensearch-store/src/main/java/org/springframework/ai/vectorstore/OpenSearchVectorStore.java index 3be7e9ced..5bb552407 100644 --- a/vector-stores/spring-ai-opensearch-store/src/main/java/org/springframework/ai/vectorstore/OpenSearchVectorStore.java +++ b/vector-stores/spring-ai-opensearch-store/src/main/java/org/springframework/ai/vectorstore/OpenSearchVectorStore.java @@ -55,6 +55,7 @@ import java.util.stream.Collectors; * @author Jemin Huh * @author Soby Chacko * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ public class OpenSearchVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -261,9 +262,9 @@ public class OpenSearchVectorStore extends AbstractObservationVectorStore implem @Override public Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.OPENSEARCH.value(), operationName) + .withCollectionName(this.index) .withDimensions(this.embeddingModel.dimensions()) - .withSimilarityMetric(getSimilarityFunction()) - .withIndexName(this.index); + .withSimilarityMetric(getSimilarityFunction()); } private String getSimilarityFunction() { diff --git a/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/OpenSearchVectorStoreObservationIT.java b/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/OpenSearchVectorStoreObservationIT.java index 9899680a8..8db248088 100644 --- a/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/OpenSearchVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-opensearch-store/src/test/java/org/springframework/ai/vectorstore/OpenSearchVectorStoreObservationIT.java @@ -36,6 +36,7 @@ import org.opensearch.client.transport.httpclient5.ApacheHttpClient5TransportBui import org.opensearch.testcontainers.OpensearchContainer; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; @@ -60,6 +61,7 @@ import static org.hamcrest.Matchers.hasSize; /** * @author Christian Tzolov + * @author Thomas Vitale */ @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") @Testcontainers @@ -118,19 +120,22 @@ public class OpenSearchVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store opensearch add") + .hasContextualNameEqualTo("opensearch add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.OPENSEARCH.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + OpenSearchVectorStore.DEFAULT_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -150,20 +155,24 @@ public class OpenSearchVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store opensearch query") + .hasContextualNameEqualTo("opensearch query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.OPENSEARCH.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + OpenSearchVectorStore.DEFAULT_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/OracleVectorStoreObservationIT.java b/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/OracleVectorStoreObservationIT.java index 013b219f6..851cf347a 100644 --- a/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/OracleVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-oracle-store/src/test/java/org/springframework/ai/vectorstore/OracleVectorStoreObservationIT.java @@ -27,6 +27,7 @@ import javax.sql.DataSource; import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.OracleVectorStore.OracleVectorStoreDistanceType; @@ -56,6 +57,7 @@ import oracle.jdbc.pool.OracleDataSource; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers public class OracleVectorStoreObservationIT { @@ -109,20 +111,22 @@ public class OracleVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store oracle add") + .hasContextualNameEqualTo("oracle add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.ORACLE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), OracleVectorStore.DEFAULT_TABLE_NAME) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -138,21 +142,24 @@ public class OracleVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store oracle query") + .hasContextualNameEqualTo("oracle query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.ORACLE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), OracleVectorStore.DEFAULT_TABLE_NAME) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/PgVectorStore.java b/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/PgVectorStore.java index 450f20489..ef385c51d 100644 --- a/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/PgVectorStore.java +++ b/vector-stores/spring-ai-pgvector-store/src/main/java/org/springframework/ai/vectorstore/PgVectorStore.java @@ -56,6 +56,7 @@ import io.micrometer.observation.ObservationRegistry; * @author Christian Tzolov * @author Josh Long * @author Muthukumaran Navaneethakrishnan + * @author Thomas Vitale */ public class PgVectorStore extends AbstractObservationVectorStore implements InitializingBean { @@ -570,11 +571,10 @@ public class PgVectorStore extends AbstractObservationVectorStore implements Ini public VectorStoreObservationContext.Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.PG_VECTOR.value(), operationName) - .withDimensions(this.embeddingDimensions()) .withCollectionName(this.vectorTableName) + .withDimensions(this.embeddingDimensions()) .withNamespace(this.schemaName) - .withSimilarityMetric(getSimilarityMetric()) - .withIndexName(this.createIndexMethod.name()); + .withSimilarityMetric(getSimilarityMetric()); } private static Map SIMILARITY_TYPE_MAPPING = Map.of( diff --git a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/PgVectorObservationIT.java b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/PgVectorStoreObservationIT.java similarity index 77% rename from vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/PgVectorObservationIT.java rename to vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/PgVectorStoreObservationIT.java index 966df742a..1f1563318 100644 --- a/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/PgVectorObservationIT.java +++ b/vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/PgVectorStoreObservationIT.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiEmbeddingModel; import org.springframework.ai.openai.api.OpenAiApi; @@ -60,10 +61,11 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; * {@link OpenAiChatModel}. * * @author Christian Tzolov + * @author Thomas Vitale */ @EnabledIfEnvironmentVariable(named = "OPENAI_API_KEY", matches = ".+") @Testcontainers -public class PgVectorObservationIT { +public class PgVectorStoreObservationIT { @Container @SuppressWarnings("resource") @@ -111,18 +113,21 @@ public class PgVectorObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store pg_vector add") + .hasContextualNameEqualTo("pg_vector add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), "pg_vector") - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "public") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + PgVectorStore.DEFAULT_TABLE_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "public") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -138,19 +143,23 @@ public class PgVectorObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store pg_vector query") + .hasContextualNameEqualTo("pg_vector query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), "pg_vector") - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "public") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1536") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + PgVectorStore.DEFAULT_TABLE_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "public") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-pinecone-store/src/main/java/org/springframework/ai/vectorstore/PineconeVectorStore.java b/vector-stores/spring-ai-pinecone-store/src/main/java/org/springframework/ai/vectorstore/PineconeVectorStore.java index 1d5796a64..fdb960299 100644 --- a/vector-stores/spring-ai-pinecone-store/src/main/java/org/springframework/ai/vectorstore/PineconeVectorStore.java +++ b/vector-stores/spring-ai-pinecone-store/src/main/java/org/springframework/ai/vectorstore/PineconeVectorStore.java @@ -72,6 +72,8 @@ public class PineconeVectorStore extends AbstractObservationVectorStore { private final String pineconeNamespace; + private final String pineconeIndexName; + private final String pineconeContentFieldName; private final String pineconeDistanceMetadataFieldName; @@ -276,6 +278,7 @@ public class PineconeVectorStore extends AbstractObservationVectorStore { this.embeddingModel = embeddingModel; this.pineconeNamespace = config.namespace; + this.pineconeIndexName = config.connectionConfig.getIndexName(); this.pineconeContentFieldName = config.contentFieldName; this.pineconeDistanceMetadataFieldName = config.distanceMetadataFieldName; this.pineconeConnection = new PineconeClient(config.clientConfig).connect(config.connectionConfig); @@ -447,6 +450,7 @@ public class PineconeVectorStore extends AbstractObservationVectorStore { public VectorStoreObservationContext.Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.PINECONE.value(), operationName) + .withCollectionName(this.pineconeIndexName) .withDimensions(this.embeddingModel.dimensions()) .withNamespace(this.pineconeNamespace) .withFieldName(this.pineconeContentFieldName); diff --git a/vector-stores/spring-ai-pinecone-store/src/test/java/org/springframework/ai/vectorstore/PineconeVectorStoreObservationIT.java b/vector-stores/spring-ai-pinecone-store/src/test/java/org/springframework/ai/vectorstore/PineconeVectorStoreObservationIT.java index 06cc36cea..5ad55e508 100644 --- a/vector-stores/spring-ai-pinecone-store/src/test/java/org/springframework/ai/vectorstore/PineconeVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-pinecone-store/src/test/java/org/springframework/ai/vectorstore/PineconeVectorStoreObservationIT.java @@ -31,6 +31,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.PineconeVectorStore.PineconeVectorStoreConfig; @@ -49,6 +50,7 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; /** * @author Christian Tzolov + * @author Thomas Vitale */ @EnabledIfEnvironmentVariable(named = "PINECONE_API_KEY", matches = ".+") public class PineconeVectorStoreObservationIT { @@ -104,19 +106,21 @@ public class PineconeVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store pinecone add") + .hasContextualNameEqualTo("pinecone add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.PINECONE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "article") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), PINECONE_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "article") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -136,20 +140,23 @@ public class PineconeVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store pinecone query") + .hasContextualNameEqualTo("pinecone query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.PINECONE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "article") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), PINECONE_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "article") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-qdrant-store/pom.xml b/vector-stores/spring-ai-qdrant-store/pom.xml index f3b5ea9e9..f88c4f84e 100644 --- a/vector-stores/spring-ai-qdrant-store/pom.xml +++ b/vector-stores/spring-ai-qdrant-store/pom.xml @@ -50,13 +50,12 @@ - - org.springframework.ai - spring-ai-azure-openai - ${project.parent.version} - test - true - + + org.springframework.ai + spring-ai-mistral-ai + ${project.parent.version} + test + org.springframework.boot diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java index 55306ea01..2162b23e1 100644 --- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java +++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreIT.java @@ -21,9 +21,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutionException; -import com.azure.ai.openai.OpenAIClient; -import com.azure.ai.openai.OpenAIClientBuilder; -import com.azure.core.credential.AzureKeyCredential; import io.qdrant.client.QdrantClient; import io.qdrant.client.QdrantGrpcClient; import io.qdrant.client.grpc.Collections.Distance; @@ -31,11 +28,12 @@ import io.qdrant.client.grpc.Collections.VectorParams; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.springframework.ai.mistralai.MistralAiEmbeddingModel; +import org.springframework.ai.mistralai.api.MistralAiApi; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.qdrant.QdrantContainer; -import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.vectorstore.SearchRequest; @@ -50,16 +48,16 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Anush Shetty * @author Josh Long * @author Eddú Meléndez + * @author Thomas Vitale * @since 0.8.1 */ @Testcontainers -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_API_KEY", matches = ".+") -@EnabledIfEnvironmentVariable(named = "AZURE_OPENAI_ENDPOINT", matches = ".+") +@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".+") public class QdrantVectorStoreIT { private static final String COLLECTION_NAME = "test_collection"; - private static final int EMBEDDING_DIMENSION = 1536; + private static final int EMBEDDING_DIMENSION = 1024; @Container static QdrantContainer qdrantContainer = new QdrantContainer("qdrant/qdrant:v1.9.2"); @@ -255,15 +253,8 @@ public class QdrantVectorStoreIT { } @Bean - public OpenAIClient openAIClient() { - return new OpenAIClientBuilder().credential(new AzureKeyCredential(System.getenv("AZURE_OPENAI_API_KEY"))) - .endpoint(System.getenv("AZURE_OPENAI_ENDPOINT")) - .buildClient(); - } - - @Bean - public AzureOpenAiEmbeddingModel azureEmbeddingModel(OpenAIClient openAIClient) { - return new AzureOpenAiEmbeddingModel(openAIClient); + public EmbeddingModel embeddingModel() { + return new MistralAiEmbeddingModel(new MistralAiApi(System.getenv("MISTRAL_AI_API_KEY"))); } } diff --git a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java index 4ea591015..be5e19052 100644 --- a/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-qdrant-store/src/test/java/org/springframework/ai/vectorstore/qdrant/QdrantVectorStoreObservationIT.java @@ -26,9 +26,11 @@ import java.util.concurrent.ExecutionException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.springframework.ai.azure.openai.AzureOpenAiEmbeddingModel; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.mistralai.MistralAiEmbeddingModel; +import org.springframework.ai.mistralai.api.MistralAiApi; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; @@ -44,10 +46,6 @@ import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.qdrant.QdrantContainer; -import com.azure.ai.openai.OpenAIClient; -import com.azure.ai.openai.OpenAIClientBuilder; -import com.azure.core.credential.AzureKeyCredential; - import io.micrometer.observation.ObservationRegistry; import io.micrometer.observation.tck.TestObservationRegistry; import io.micrometer.observation.tck.TestObservationRegistryAssert; @@ -58,15 +56,15 @@ import io.qdrant.client.grpc.Collections.VectorParams; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers -@EnabledIfEnvironmentVariable(named = "AZURE_AI_SEARCH_API_KEY", matches = ".+") -@EnabledIfEnvironmentVariable(named = "AZURE_AI_SEARCH_ENDPOINT", matches = ".+") +@EnabledIfEnvironmentVariable(named = "MISTRAL_AI_API_KEY", matches = ".+") public class QdrantVectorStoreObservationIT { private static final String COLLECTION_NAME = "test_collection"; - private static final int EMBEDDING_DIMENSION = 1536; + private static final int EMBEDDING_DIMENSION = 1024; @Container static QdrantContainer qdrantContainer = new QdrantContainer("qdrant/qdrant:v1.9.2"); @@ -119,19 +117,21 @@ public class QdrantVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store qdrant add") + .hasContextualNameEqualTo("qdrant add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.QDRANT.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "test_collection") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1024") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), COLLECTION_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -147,20 +147,23 @@ public class QdrantVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store qdrant query") + .hasContextualNameEqualTo("qdrant query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.QDRANT.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "1536") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "test_collection") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "1024") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), COLLECTION_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); @@ -193,15 +196,8 @@ public class QdrantVectorStoreObservationIT { } @Bean - public OpenAIClient openAIClient() { - return new OpenAIClientBuilder().credential(new AzureKeyCredential(System.getenv("AZURE_OPENAI_API_KEY"))) - .endpoint(System.getenv("AZURE_OPENAI_ENDPOINT")) - .buildClient(); - } - - @Bean - public AzureOpenAiEmbeddingModel azureEmbeddingModel(OpenAIClient openAIClient) { - return new AzureOpenAiEmbeddingModel(openAIClient); + public EmbeddingModel embeddingModel() { + return new MistralAiEmbeddingModel(new MistralAiApi(System.getenv("MISTRAL_AI_API_KEY"))); } } diff --git a/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java index dcf780309..de72bcc9f 100644 --- a/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java +++ b/vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/RedisVectorStore.java @@ -74,6 +74,7 @@ import redis.clients.jedis.search.schemafields.VectorField.VectorAlgorithm; * @author Julien Ruaux * @author Christian Tzolov * @author Eddú Meléndez + * @author Thomas Vitale * @see VectorStore * @see RedisVectorStoreConfig * @see EmbeddingModel @@ -476,10 +477,10 @@ public class RedisVectorStore extends AbstractObservationVectorStore implements public VectorStoreObservationContext.Builder createObservationContextBuilder(String operationName) { return VectorStoreObservationContext.builder(VectorStoreProvider.REDIS.value(), operationName) + .withCollectionName(this.config.indexName) .withDimensions(this.embeddingModel.dimensions()) .withFieldName(this.config.embeddingFieldName) - .withSimilarityMetric(vectorAlgorithm().name()) - .withIndexName(this.config.indexName); + .withSimilarityMetric(vectorAlgorithm().name()); } diff --git a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/RedisVectorStoreObservationIT.java b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/RedisVectorStoreObservationIT.java index 80169d52c..fb2d018cc 100644 --- a/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/RedisVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-redis-store/src/test/java/org/springframework/ai/vectorstore/RedisVectorStoreObservationIT.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.RedisVectorStore.MetadataField; @@ -53,6 +54,7 @@ import redis.clients.jedis.JedisPooled; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers public class RedisVectorStoreObservationIT { @@ -101,20 +103,22 @@ public class RedisVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store redis add") + .hasContextualNameEqualTo("redis add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.REDIS.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "embedding") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "HNSW") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), "spring-ai-index") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + RedisVectorStore.DEFAULT_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "embedding") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "HNSW") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -130,21 +134,24 @@ public class RedisVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store redis query") + .hasContextualNameEqualTo("redis query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.REDIS.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "embedding") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "HNSW") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), "spring-ai-index") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), + RedisVectorStore.DEFAULT_INDEX_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "embedding") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "HNSW") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); diff --git a/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/TypesenseVectorStoreObservationIT.java b/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/TypesenseVectorStoreObservationIT.java index cce8d3646..1a757646c 100644 --- a/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/TypesenseVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-typesense-store/src/test/java/org/springframework/ai/vectorstore/TypesenseVectorStoreObservationIT.java @@ -27,6 +27,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.TypesenseVectorStore.TypesenseVectorStoreConfig; @@ -51,10 +52,13 @@ import io.micrometer.observation.tck.TestObservationRegistryAssert; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers public class TypesenseVectorStoreObservationIT { + private static final String TEST_COLLECTION_NAME = "test_vector_store"; + @Container private static GenericContainer typesenseContainer = new GenericContainer<>("typesense/typesense:26.0") .withExposedPorts(8108) @@ -93,20 +97,21 @@ public class TypesenseVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store typesense add") + .hasContextualNameEqualTo("typesense add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.TYPESENSE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "test_vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "embedding") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), TEST_COLLECTION_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "embedding") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -122,21 +127,23 @@ public class TypesenseVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store typesense query") + .hasContextualNameEqualTo("typesense query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.TYPESENSE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "test_vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "embedding") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "cosine") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), TEST_COLLECTION_NAME) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "embedding") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "cosine") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped(); @@ -158,7 +165,7 @@ public class TypesenseVectorStoreObservationIT { ObservationRegistry observationRegistry) { TypesenseVectorStoreConfig config = TypesenseVectorStoreConfig.builder() - .withCollectionName("test_vector_store") + .withCollectionName(TEST_COLLECTION_NAME) .withEmbeddingDimension(embeddingModel.dimensions()) .build(); diff --git a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreObservationIT.java b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreObservationIT.java index a9b34b2b3..a2228cf35 100644 --- a/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreObservationIT.java +++ b/vector-stores/spring-ai-weaviate-store/src/test/java/org/springframework/ai/vectorstore/WeaviateVectorStoreObservationIT.java @@ -25,6 +25,7 @@ import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.ai.document.Document; import org.springframework.ai.embedding.EmbeddingModel; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.observation.conventions.VectorStoreProvider; import org.springframework.ai.transformers.TransformersEmbeddingModel; import org.springframework.ai.vectorstore.WeaviateVectorStore.WeaviateVectorStoreConfig; @@ -48,6 +49,7 @@ import io.weaviate.client.WeaviateClient; /** * @author Christian Tzolov + * @author Thomas Vitale */ @Testcontainers public class WeaviateVectorStoreObservationIT { @@ -89,20 +91,21 @@ public class WeaviateVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store weaviate add") + .hasContextualNameEqualTo("weaviate add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "add") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.WEAVIATE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "SpringAiWeaviate") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "none") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), "SpringAiWeaviate") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "none") .hasBeenStarted() .hasBeenStopped(); @@ -118,21 +121,23 @@ public class WeaviateVectorStoreObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("vector_store weaviate query") + .hasContextualNameEqualTo("weaviate query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_OPERATION_NAME.asString(), "query") .hasLowCardinalityKeyValue(LowCardinalityKeyNames.DB_SYSTEM.asString(), VectorStoreProvider.WEAVIATE.value()) - .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "vector_store") + .hasLowCardinalityKeyValue(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), + SpringAiKind.VECTOR_STORE.value()) - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.QUERY.asString(), "What is Great Depression") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DIMENSIONS.asString(), "384") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.COLLECTION_NAME.asString(), "SpringAiWeaviate") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.NAMESPACE.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.FIELD_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_METRIC.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.INDEX_NAME.asString(), "none") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.TOP_K.asString(), "1") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.SIMILARITY_THRESHOLD.asString(), "0.0") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_CONTENT.asString(), + "What is Great Depression") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_DIMENSION_COUNT.asString(), "384") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_COLLECTION_NAME.asString(), "SpringAiWeaviate") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_NAMESPACE.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_FIELD_NAME.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_SIMILARITY_METRIC.asString(), "none") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_TOP_K.asString(), "1") + .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_SIMILARITY_THRESHOLD.asString(), + "0.0") .hasBeenStarted() .hasBeenStopped();