From 036093a42b719d2506b93bec9dfea3cc977d730a Mon Sep 17 00:00:00 2001 From: Thomas Vitale Date: Thu, 22 Aug 2024 08:23:43 +0200 Subject: [PATCH] Enhance vector store observability support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Consolidate usage of “db.collection.name” attribute to track table name, collection name, index name, document name, or whatever concept a vector database uses to store data. Removed “db.index” that was use sometimes instead of “db.collection.name”. This usage is in line with the OpenTelemetry Semantic Conventions. * Configure query response content to be included as a “span event” instead of a “span attribute” if the backend system supports that, similar to how we do for the model observations. * Structure vector store observation attributes in dedicated enums, including one for the Spring AI Kinds to avoid hard-coding the same value in a lot of places. This follows the OpenTelemetry Semantic Conventions as much as possible. Also, adopt Spring usual non-null-by-default strategy as much as possible. * Align vector store conventions to the chat model ones, and follow alphabetical order for values. This is particularly useful for the convention classes, for which the Micrometer performance of exporting telemetry data improves when key values are added already sorted to the context. * Fix flaky test in Mistral AI. * Improve Qdrant integration tests. Signed-off-by: Thomas Vitale --- .../MistralAiChatModelObservationIT.java | 3 +- .../ChatModelCompletionObservationFilter.java | 3 +- ...ChatModelCompletionObservationHandler.java | 3 +- .../ChatModelObservationContentProcessor.java | 41 ---- ...atModelPromptContentObservationFilter.java | 3 +- ...tModelPromptContentObservationHandler.java | 3 +- .../conventions/AiObservationAttributes.java | 6 +- .../observation/conventions/SpringAiKind.java | 44 +++++ .../VectorStoreObservationAttributes.java | 120 ++++++++++++ .../VectorStoreObservationEventNames.java | 42 ++++ .../VectorStoreSimilarityMetric.java | 45 +++-- .../ai/observation/tracing/TracingHelper.java | 73 +++++++ ...faultVectorStoreObservationConvention.java | 153 +++++++-------- ...ectorStoreObservationContentProcessor.java | 38 ++++ .../VectorStoreObservationContext.java | 183 +++++++++--------- .../VectorStoreObservationDocumentation.java | 135 +++++++------ ...orStoreQueryResponseObservationFilter.java | 21 +- ...rStoreQueryResponseObservationHandler.java | 57 ++++++ .../vectorstore/observation/package-info.java | 22 +++ ...odelCompletionObservationHandlerTests.java | 3 +- ...lPromptContentObservationHandlerTests.java | 3 +- .../tracing/TracingHelperTests.java} | 33 +--- ...VectorStoreObservationConventionTests.java | 38 ++-- ...reQueryResponseObservationFilterTests.java | 5 +- ...eQueryResponseObservationHandlerTests.java | 69 +++++++ ...ctorStoreObservationAutoConfiguration.java | 51 ++++- .../AzureVectorStoreAutoConfigurationIT.java | 7 +- ...ssandraVectorStoreAutoConfigurationIT.java | 7 +- .../ChromaVectorStoreAutoConfigurationIT.java | 11 +- ...csearchVectorStoreAutoConfigurationIT.java | 6 +- ...GemFireVectorStoreAutoConfigurationIT.java | 7 +- .../MilvusVectorStoreAutoConfigurationIT.java | 7 +- ...DBAtlasVectorStoreAutoConfigurationIT.java | 7 +- .../Neo4jVectorStoreAutoConfigurationIT.java | 7 +- .../observation/ObservationTestUtil.java | 4 +- ...toreObservationAutoConfigurationTests.java | 19 +- ...nSearchVectorStoreAutoConfigurationIT.java | 6 +- .../OracleVectorStoreAutoConfigurationIT.java | 7 +- .../PgVectorStoreAutoConfigurationIT.java | 7 +- ...ineconeVectorStoreAutoConfigurationIT.java | 7 +- .../QdrantVectorStoreAutoConfigurationIT.java | 7 +- .../RedisVectorStoreAutoConfigurationIT.java | 7 +- ...pesenseVectorStoreAutoConfigurationIT.java | 7 +- ...eaviateVectorStoreAutoConfigurationIT.java | 7 +- .../vectorstore/azure/AzureVectorStore.java | 7 +- .../azure/AzureVectorStoreObservationIT.java | 49 +++-- .../ai/vectorstore/CassandraVectorStore.java | 6 +- .../CassandraVectorStoreObservationIT.java | 49 +++-- .../ChromaVectorStoreObservationIT.java | 47 +++-- .../vectorstore/ElasticsearchVectorStore.java | 4 +- ...ElasticsearchVectorStoreObservationIT.java | 49 +++-- .../ai/vectorstore/GemFireVectorStore.java | 3 +- .../GemFireVectorStoreObservationIT.java | 53 ++--- .../HanaVectorStoreObservationIT.java | 51 +++-- .../ai/vectorstore/MilvusVectorStore.java | 4 +- .../MilvusVectorStoreObservationIT.java | 51 +++-- .../vectorstore/MongoDBAtlasVectorStore.java | 8 +- .../MongoDbVectorStoreObservationIT.java | 51 ++--- .../ai/vectorstore/Neo4jVectorStore.java | 3 +- .../Neo4jVectorStoreObservationIT.java | 50 +++-- .../ai/vectorstore/OpenSearchVectorStore.java | 5 +- .../OpenSearchVectorStoreObservationIT.java | 49 +++-- .../OracleVectorStoreObservationIT.java | 47 +++-- .../ai/vectorstore/PgVectorStore.java | 6 +- ...T.java => PgVectorStoreObservationIT.java} | 51 +++-- .../ai/vectorstore/PineconeVectorStore.java | 4 + .../PineconeVectorStoreObservationIT.java | 47 +++-- vector-stores/spring-ai-qdrant-store/pom.xml | 13 +- .../qdrant/QdrantVectorStoreIT.java | 23 +-- .../QdrantVectorStoreObservationIT.java | 70 ++++--- .../ai/vectorstore/RedisVectorStore.java | 5 +- .../RedisVectorStoreObservationIT.java | 51 ++--- .../TypesenseVectorStoreObservationIT.java | 53 ++--- .../WeaviateVectorStoreObservationIT.java | 49 ++--- 74 files changed, 1456 insertions(+), 836 deletions(-) create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/SpringAiKind.java create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreObservationAttributes.java create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/observation/conventions/VectorStoreObservationEventNames.java create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/observation/tracing/TracingHelper.java create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreObservationContentProcessor.java create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationHandler.java create mode 100644 spring-ai-core/src/main/java/org/springframework/ai/vectorstore/observation/package-info.java rename spring-ai-core/src/test/java/org/springframework/ai/{chat/observation/ChatModelObservationContentProcessorTests.java => observation/tracing/TracingHelperTests.java} (67%) create mode 100644 spring-ai-core/src/test/java/org/springframework/ai/vectorstore/observation/VectorStoreQueryResponseObservationHandlerTests.java rename vector-stores/spring-ai-pgvector-store/src/test/java/org/springframework/ai/vectorstore/{PgVectorObservationIT.java => PgVectorStoreObservationIT.java} (77%) 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();