diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java index 42c21c5097f..ab38447c416 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/DefaultChatClient.java @@ -77,6 +77,7 @@ * @author Arjen Poutsma * @author Soby Chacko * @author Dariusz Jedrzejczyk + * @author Thomas Vitale * @since 1.0.0 */ public class DefaultChatClient implements ChatClient { @@ -360,8 +361,11 @@ private ChatResponse doGetChatResponse() { private ChatResponse doGetObservableChatResponse(DefaultChatClientRequestSpec inputRequest, String formatParam) { - ChatClientObservationContext observationContext = new ChatClientObservationContext(inputRequest, - formatParam, false); + ChatClientObservationContext observationContext = ChatClientObservationContext.builder() + .withRequest(inputRequest) + .withFormat(formatParam) + .withStream(false) + .build(); var observation = ChatClientObservationDocumentation.AI_CHAT_CLIENT.observation( inputRequest.getCustomObservationConvention(), DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION, @@ -407,8 +411,10 @@ public DefaultStreamResponseSpec(DefaultChatClientRequestSpec request) { private Flux doGetObservableFluxChatResponse(DefaultChatClientRequestSpec inputRequest) { return Flux.deferContextual(contextView -> { - ChatClientObservationContext observationContext = new ChatClientObservationContext(inputRequest, "", - true); + ChatClientObservationContext observationContext = ChatClientObservationContext.builder() + .withRequest(inputRequest) + .withStream(true) + .build(); Observation observation = ChatClientObservationDocumentation.AI_CHAT_CLIENT.observation( inputRequest.getCustomObservationConvention(), DEFAULT_CHAT_CLIENT_OBSERVATION_CONVENTION, diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java index 81acaa6097e..fe5e3b4eaa5 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/DefaultAroundAdvisorChain.java @@ -85,6 +85,7 @@ public AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest) { .withAdvisorType(AdvisorObservationContext.Type.AROUND) .withAdvisedRequest(advisedRequest) .withAdvisorRequestContext(advisedRequest.adviseContext()) + .withOrder(advisor.getOrder()) .build(); return AdvisorObservationDocumentation.AI_ADVISOR @@ -106,6 +107,7 @@ public Flux nextAroundStream(AdvisedRequest advisedRequest) { .withAdvisorType(AdvisorObservationContext.Type.AROUND) .withAdvisedRequest(advisedRequest) .withAdvisorRequestContext(advisedRequest.adviseContext()) + .withOrder(advisor.getOrder()) .build(); var observation = AdvisorObservationDocumentation.AI_ADVISOR.observation(null, diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationContext.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationContext.java index a0303303a7b..9effbf29bd1 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationContext.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationContext.java @@ -20,15 +20,18 @@ import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.api.AdvisedRequest; import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import io.micrometer.observation.Observation; /** + * Context used to store metadata for chat client advisors. + * * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ - public class AdvisorObservationContext extends Observation.Context { public enum Type { @@ -37,35 +40,48 @@ public enum Type { } - private String advisorName; + private final String advisorName; - private Type advisorType; + private final Type advisorType; /** * The {@link AdvisedRequest} data to be advised. Represents the row * {@link ChatClient.ChatClientRequestSpec} data before sealed into a {@link Prompt}. */ + @Nullable private AdvisedRequest advisorRequest; /** * The shared data between the advisors in the chain. It is shared between all request * and response advising points of all advisors in the chain. */ + @Nullable private Map advisorRequestContext; /** * the shared data between the advisors in the chain. It is shared between all request * and response advising points of all advisors in the chain. */ + @Nullable private Map advisorResponseContext; /** * The order of the advisor in the advisor chain. */ - private int order; + private final int order; + + public AdvisorObservationContext(String advisorName, Type advisorType, @Nullable AdvisedRequest advisorRequest, + @Nullable Map advisorRequestContext, @Nullable Map advisorResponseContext, + int order) { + Assert.hasText(advisorName, "advisorName must not be null or empty"); + Assert.notNull(advisorType, "advisorType must not be null"); - public void setAdvisorName(String advisorName) { this.advisorName = advisorName; + this.advisorType = advisorType; + this.advisorRequest = advisorRequest; + this.advisorRequestContext = advisorRequestContext; + this.advisorResponseContext = advisorResponseContext; + this.order = order; } public String getAdvisorName() { @@ -76,31 +92,30 @@ public Type getAdvisorType() { return this.advisorType; } - public void setAdvisorType(Type type) { - this.advisorType = type; - } - + @Nullable public AdvisedRequest getAdvisedRequest() { return this.advisorRequest; } - public void setAdvisedRequest(AdvisedRequest advisedRequest) { + public void setAdvisedRequest(@Nullable AdvisedRequest advisedRequest) { this.advisorRequest = advisedRequest; } + @Nullable public Map getAdvisorRequestContext() { return this.advisorRequestContext; } - public void setAdvisorRequestContext(Map advisorRequestContext) { + public void setAdvisorRequestContext(@Nullable Map advisorRequestContext) { this.advisorRequestContext = advisorRequestContext; } + @Nullable public Map getAdvisorResponseContext() { return this.advisorResponseContext; } - public void setAdvisorResponseContext(Map advisorResponseContext) { + public void setAdvisorResponseContext(@Nullable Map advisorResponseContext) { this.advisorResponseContext = advisorResponseContext; } @@ -108,52 +123,57 @@ public int getOrder() { return this.order; } - public void setOrder(int order) { - this.order = order; - } - public static Builder builder() { return new Builder(); } public static class Builder { - private final AdvisorObservationContext context = new AdvisorObservationContext(); + private String advisorName; + + private Type advisorType; + + private AdvisedRequest advisorRequest; + + private Map advisorRequestContext; + + private Map advisorResponseContext; + + private int order = 0; public Builder withAdvisorName(String advisorName) { - this.context.setAdvisorName(advisorName); + this.advisorName = advisorName; return this; } public Builder withAdvisorType(Type advisorType) { - this.context.setAdvisorType(advisorType); + this.advisorType = advisorType; return this; } public Builder withAdvisedRequest(AdvisedRequest advisedRequest) { - this.context.setAdvisedRequest(advisedRequest); + this.advisorRequest = advisedRequest; return this; } public Builder withAdvisorRequestContext(Map advisorRequestContext) { - this.context.setAdvisorRequestContext(advisorRequestContext); + this.advisorRequestContext = advisorRequestContext; return this; } public Builder withAdvisorResponseContext(Map advisorResponseContext) { - this.context.setAdvisorResponseContext(advisorResponseContext); + this.advisorResponseContext = advisorResponseContext; return this; } public Builder withOrder(int order) { - this.context.setOrder(order); + this.order = order; return this; } public AdvisorObservationContext build() { - Assert.hasText(this.context.advisorName, "The advisorName must not be empty!"); - Assert.notNull(this.context.advisorType, "The advisorType must not be null!"); - return this.context; + return new AdvisorObservationContext(advisorName, advisorType, advisorRequest, advisorRequestContext, + advisorResponseContext, order); } } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationConvention.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationConvention.java index 450a8499a2a..7726c65e99c 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationConvention.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationConvention.java @@ -19,6 +19,8 @@ import io.micrometer.observation.ObservationConvention; /** + * Interface for an {@link ObservationConvention} for chat client advisors. + * * @author Christian Tzolov * @since 1.0.0 */ diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationDocumentation.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationDocumentation.java index c09adad28c7..9d1d4fb17f6 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationDocumentation.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationDocumentation.java @@ -27,7 +27,7 @@ public enum AdvisorObservationDocumentation implements ObservationDocumentation { /** - * AI Chat Client observations + * AI Advisor observations */ AI_ADVISOR { @Override @@ -65,7 +65,7 @@ public String asString() { ADVISOR_TYPE { @Override public String asString() { - return "spring.ai.chat.client.advisor.type"; + return "spring.ai.advisor.type"; } } @@ -74,12 +74,12 @@ public String asString() { public enum HighCardinalityKeyNames implements KeyName { /** - * Chat Model name. + * Advisor name. */ ADVISOR_NAME { @Override public String asString() { - return "spring.ai.chat.client.advisor.name"; + return "spring.ai.advisor.name"; } }, /** @@ -88,7 +88,7 @@ public String asString() { ADVISOR_ORDER { @Override public String asString() { - return "spring.ai.chat.client.advisor.order"; + return "spring.ai.advisor.order"; } } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/DefaultAdvisorObservationConvention.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/DefaultAdvisorObservationConvention.java index 99317e801a0..9e7d5721b7d 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/DefaultAdvisorObservationConvention.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/DefaultAdvisorObservationConvention.java @@ -17,6 +17,7 @@ import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationDocumentation.HighCardinalityKeyNames; import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationDocumentation.LowCardinalityKeyNames; +import org.springframework.ai.observation.conventions.SpringAiKind; import org.springframework.ai.util.ParsingUtils; import org.springframework.lang.Nullable; @@ -27,18 +28,9 @@ * @author Christian Tzolov * @since 1.0.0 */ - public class DefaultAdvisorObservationConvention implements AdvisorObservationConvention { - public static final String DEFAULT_NAME = "spring.ai.chat.client.advisor"; - - private static final String CHAT_CLIENT_ADVISOR_SPRING_AI_KIND = "chat_client_advisor"; - - private static final KeyValue ADVISOR_TYPE_NONE = KeyValue.of(LowCardinalityKeyNames.ADVISOR_TYPE, - KeyValue.NONE_VALUE); - - private static final KeyValue ADVISOR_NAME_NONE = KeyValue.of(HighCardinalityKeyNames.ADVISOR_NAME, - KeyValue.NONE_VALUE); + public static final String DEFAULT_NAME = "spring.ai.advisor"; private final String name; @@ -73,15 +65,12 @@ public KeyValues getLowCardinalityKeyValues(AdvisorObservationContext context) { } protected KeyValue advisorType(AdvisorObservationContext context) { - if (context.getAdvisorType() != null) { - return KeyValue.of(LowCardinalityKeyNames.ADVISOR_TYPE, context.getAdvisorType().name()); - } - return ADVISOR_TYPE_NONE; + return KeyValue.of(LowCardinalityKeyNames.ADVISOR_TYPE, context.getAdvisorType().name()); } protected KeyValue springAiKind() { return KeyValue.of(AdvisorObservationDocumentation.LowCardinalityKeyNames.SPRING_AI_KIND, - CHAT_CLIENT_ADVISOR_SPRING_AI_KIND); + SpringAiKind.ADVISOR.value()); } // ------------------------ @@ -94,14 +83,11 @@ public KeyValues getHighCardinalityKeyValues(AdvisorObservationContext context) } protected KeyValue advisorName(AdvisorObservationContext context) { - if (context.getAdvisorType() != null) { - return KeyValue.of(HighCardinalityKeyNames.ADVISOR_NAME, context.getAdvisorName()); - } - return ADVISOR_NAME_NONE; + return KeyValue.of(HighCardinalityKeyNames.ADVISOR_NAME, context.getAdvisorName()); } protected KeyValue advisorOrder(AdvisorObservationContext context) { return KeyValue.of(HighCardinalityKeyNames.ADVISOR_ORDER, "" + context.getOrder()); } -} \ No newline at end of file +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/package-info.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/package-info.java new file mode 100644 index 00000000000..c84a0a9209d --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/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.chat.client.advisor.observation; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientInputContentObservationFilter.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientInputContentObservationFilter.java index f250d4c0e8e..78f16c9b255 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientInputContentObservationFilter.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientInputContentObservationFilter.java @@ -15,8 +15,7 @@ */ package org.springframework.ai.chat.client.observation; -import java.util.stream.Collectors; - +import org.springframework.ai.observation.tracing.TracingHelper; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -32,72 +31,54 @@ */ public class ChatClientInputContentObservationFilter implements ObservationFilter { - private static final KeyValue CHAT_CLIENT_SYSTEM_TEXT_NONE = KeyValue - .of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_SYSTEM_TEXT, KeyValue.NONE_VALUE); - - private static final KeyValue CHAT_CLIENT_SYSTEM_PARAM_NONE = KeyValue - .of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_SYSTEM_PARAM, KeyValue.NONE_VALUE); - - private static final KeyValue CHAT_CLIENT_USER_TEXT_NONE = KeyValue - .of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_USER_TEXT, KeyValue.NONE_VALUE); - - private static final KeyValue CHAT_CLIENT_USER_PARAM_NONE = KeyValue - .of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_USER_PARAMS, KeyValue.NONE_VALUE); - @Override public Observation.Context map(Observation.Context context) { if (!(context instanceof ChatClientObservationContext chatClientObservationContext)) { return context; } - chatClientObservationContext.addHighCardinalityKeyValue(chatClientSystemText(chatClientObservationContext)) - .addHighCardinalityKeyValue(chatClientSystemParam(chatClientObservationContext)) - .addHighCardinalityKeyValue(chatClientUserText(chatClientObservationContext)) - .addHighCardinalityKeyValue(chatClientUserParam(chatClientObservationContext)); + chatClientSystemText(chatClientObservationContext); + chatClientSystemParams(chatClientObservationContext); + chatClientUserText(chatClientObservationContext); + chatClientUserParams(chatClientObservationContext); return chatClientObservationContext; } - protected KeyValue chatClientSystemText(ChatClientObservationContext context) { - if (!StringUtils.hasText(context.getRequest().getUserText())) { - return CHAT_CLIENT_SYSTEM_TEXT_NONE; + protected void chatClientSystemText(ChatClientObservationContext context) { + if (!StringUtils.hasText(context.getRequest().getSystemText())) { + return; } - return KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_SYSTEM_TEXT, - context.getRequest().getSystemText()); + context.addHighCardinalityKeyValue( + KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_SYSTEM_TEXT, + context.getRequest().getSystemText())); } - protected KeyValue chatClientSystemParam(ChatClientObservationContext context) { + protected void chatClientSystemParams(ChatClientObservationContext context) { if (CollectionUtils.isEmpty(context.getRequest().getSystemParams())) { - return CHAT_CLIENT_SYSTEM_PARAM_NONE; + return; } - return KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_SYSTEM_PARAM, - context.getRequest() - .getSystemParams() - .entrySet() - .stream() - .map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"") - .collect(Collectors.joining(",", "[", "]"))); + context.addHighCardinalityKeyValue( + KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_SYSTEM_PARAM, + TracingHelper.concatenateMaps(context.getRequest().getSystemParams()))); } - protected KeyValue chatClientUserText(ChatClientObservationContext context) { + protected void chatClientUserText(ChatClientObservationContext context) { if (!StringUtils.hasText(context.getRequest().getUserText())) { - return CHAT_CLIENT_USER_TEXT_NONE; + return; } - return KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_USER_TEXT, - context.getRequest().getUserText()); + context.addHighCardinalityKeyValue( + KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_USER_TEXT, + context.getRequest().getUserText())); } - protected KeyValue chatClientUserParam(ChatClientObservationContext context) { + protected void chatClientUserParams(ChatClientObservationContext context) { if (CollectionUtils.isEmpty(context.getRequest().getUserParams())) { - return CHAT_CLIENT_USER_PARAM_NONE; + return; } - return KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_USER_PARAMS, - context.getRequest() - .getUserParams() - .entrySet() - .stream() - .map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"") - .collect(Collectors.joining(",", "[", "]"))); + context.addHighCardinalityKeyValue( + KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_USER_PARAMS, + TracingHelper.concatenateMaps(context.getRequest().getUserParams()))); } } diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationContext.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationContext.java index cb5bf80da16..fbf9557f24d 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationContext.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationContext.java @@ -21,25 +21,30 @@ import org.springframework.ai.observation.conventions.AiProvider; import io.micrometer.observation.Observation; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** + * Context used to store metadata for chat client workflows. + * * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ - public class ChatClientObservationContext extends Observation.Context { - private final boolean stream; - - private final String format; - private final DefaultChatClientRequestSpec request; private final AiOperationMetadata operationMetadata = new AiOperationMetadata(AiOperationType.FRAMEWORK.value(), AiProvider.SPRING_AI.value()); - public ChatClientObservationContext(DefaultChatClientRequestSpec requestSpec, String format, Boolean isStream) { + private final boolean stream; + + @Nullable + private String format; + ChatClientObservationContext(DefaultChatClientRequestSpec requestSpec, String format, boolean isStream) { + Assert.notNull(requestSpec, "requestSpec cannot be null"); this.request = requestSpec; this.format = format; this.stream = isStream; @@ -57,8 +62,49 @@ public boolean isStream() { return this.stream; } + @Nullable public String getFormat() { return this.format; } -} \ No newline at end of file + public void setFormat(@Nullable String format) { + this.format = format; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private DefaultChatClientRequestSpec request; + + private String format; + + private boolean isStream = false; + + private Builder() { + } + + public Builder withRequest(DefaultChatClientRequestSpec request) { + this.request = request; + return this; + } + + public Builder withFormat(String format) { + this.format = format; + return this; + } + + public Builder withStream(boolean isStream) { + this.isStream = isStream; + return this; + } + + public ChatClientObservationContext build() { + return new ChatClientObservationContext(this.request, this.format, this.isStream); + } + + } + +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationConvention.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationConvention.java index fd6790c4844..047608d8ef9 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationConvention.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationConvention.java @@ -19,10 +19,11 @@ import io.micrometer.observation.ObservationConvention; /** + * Interface for an {@link ObservationConvention} for chat client workflows. + * * @author Christian Tzolov * @since 1.0.0 */ - public interface ChatClientObservationConvention extends ObservationConvention { @Override diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationDocumentation.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationDocumentation.java index 875832201c4..09c34bd45d4 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationDocumentation.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/ChatClientObservationDocumentation.java @@ -21,6 +21,8 @@ import io.micrometer.observation.docs.ObservationDocumentation; /** + * Documented conventions for chat client observations. + * * @author Christian Tzolov * @since 1.0.0 */ @@ -88,7 +90,7 @@ public String asString() { CHAT_CLIENT_TOOL_FUNCTION_CALLBACKS { @Override public String asString() { - return "spring.ai.chat.client.tool.functioncallbacks"; + return "spring.ai.chat.client.tool.function.callbacks"; } }, @@ -149,4 +151,4 @@ public String asString() { } -} \ No newline at end of file +} diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConvention.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConvention.java index eca289b73b7..e2ab324778d 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConvention.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConvention.java @@ -15,10 +15,12 @@ */ package org.springframework.ai.chat.client.observation; -import java.util.stream.Collectors; - +import org.springframework.ai.chat.client.advisor.api.Advisor; import org.springframework.ai.chat.client.observation.ChatClientObservationDocumentation.LowCardinalityKeyNames; import org.springframework.ai.chat.observation.ChatModelObservationDocumentation; +import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.observation.conventions.SpringAiKind; +import org.springframework.ai.observation.tracing.TracingHelper; import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; @@ -26,30 +28,16 @@ import io.micrometer.common.KeyValues; /** + * Default conventions to populate observations for chat client workflows. + * * @author Christian Tzolov + * @author Thomas Vitale * @since 1.0.0 */ - public class DefaultChatClientObservationConvention implements ChatClientObservationConvention { public static final String DEFAULT_NAME = "spring.ai.chat.client"; - private static final String CHAT_CLIENT_SPRING_AI_KIND = "chat_client"; - - private static final KeyValue CHAT_CLIENT_TOOL_FUNCTION_CALLBACKS_NONE = KeyValue.of( - ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_TOOL_FUNCTION_CALLBACKS, - KeyValue.NONE_VALUE); - - private static final KeyValue CHAT_CLIENT_TOOL_FUNCTION_NAMES_NONE = KeyValue.of( - ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_TOOL_FUNCTION_NAMES, - KeyValue.NONE_VALUE); - - private static final KeyValue CHAT_CLIENT_ADVISOR_NONE = KeyValue - .of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_ADVISORS, KeyValue.NONE_VALUE); - - private static final KeyValue CHAT_CLIENT_ADVISOR_PARAM_NONE = KeyValue - .of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_ADVISOR_PARAMS, KeyValue.NONE_VALUE); - private final String name; public DefaultChatClientObservationConvention() { @@ -68,88 +56,84 @@ public String getName() { @Override @Nullable public String getContextualName(ChatClientObservationContext context) { - return "%s %s".formatted(context.getOperationMetadata().provider(), CHAT_CLIENT_SPRING_AI_KIND); + return "%s %s".formatted(context.getOperationMetadata().provider(), SpringAiKind.CHAT_CLIENT.value()); } @Override public KeyValues getLowCardinalityKeyValues(ChatClientObservationContext context) { - return KeyValues.of(springAiKind(), aiOperationType(context), aiProvider(context), stream(context)); + return KeyValues.of(aiOperationType(context), aiProvider(context), springAiKind(), stream(context)); } - @Override - public KeyValues getHighCardinalityKeyValues(ChatClientObservationContext context) { - return KeyValues.of(toolFunctionNames(context), toolFunctionCallbacks(context), chatClientAdvisor(context), - chatClientAdvisorParam(context)); + protected KeyValue aiOperationType(ChatClientObservationContext context) { + return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.AI_OPERATION_TYPE, + context.getOperationMetadata().operationType()); + } + + protected KeyValue aiProvider(ChatClientObservationContext context) { + return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.AI_PROVIDER, + context.getOperationMetadata().provider()); } protected KeyValue springAiKind() { return KeyValue.of(ChatClientObservationDocumentation.LowCardinalityKeyNames.SPRING_AI_KIND, - CHAT_CLIENT_SPRING_AI_KIND); + SpringAiKind.CHAT_CLIENT.value()); } protected KeyValue stream(ChatClientObservationContext context) { return KeyValue.of(LowCardinalityKeyNames.STREAM, "" + context.isStream()); } - protected KeyValue aiOperationType(ChatClientObservationContext context) { - return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.AI_OPERATION_TYPE, - context.getOperationMetadata().operationType()); - } - - protected KeyValue aiProvider(ChatClientObservationContext context) { - return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.AI_PROVIDER, - context.getOperationMetadata().provider()); + @Override + public KeyValues getHighCardinalityKeyValues(ChatClientObservationContext context) { + var keyValues = KeyValues.empty(); + keyValues = chatClientAdvisorNames(keyValues, context); + keyValues = chatClientAdvisorParams(keyValues, context); + keyValues = toolFunctionNames(keyValues, context); + keyValues = toolFunctionCallbacks(keyValues, context); + return keyValues; } - protected KeyValue toolFunctionNames(ChatClientObservationContext context) { - if (CollectionUtils.isEmpty(context.getRequest().getFunctionNames())) { - return CHAT_CLIENT_TOOL_FUNCTION_NAMES_NONE; + protected KeyValues chatClientAdvisorNames(KeyValues keyValues, ChatClientObservationContext context) { + if (CollectionUtils.isEmpty(context.getRequest().getAdvisors())) { + return keyValues; } - return KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_TOOL_FUNCTION_NAMES, - context.getRequest() - .getFunctionNames() - .stream() - .map(v -> "\"" + v + "\"") - .collect(Collectors.joining(",", "[", "]"))); + var advisorNames = context.getRequest().getAdvisors().stream().map(Advisor::getName).toList(); + return keyValues.and(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_ADVISORS.asString(), + TracingHelper.concatenateStrings(advisorNames)); } - protected KeyValue toolFunctionCallbacks(ChatClientObservationContext context) { - if (CollectionUtils.isEmpty(context.getRequest().getFunctionCallbacks())) { - return CHAT_CLIENT_TOOL_FUNCTION_CALLBACKS_NONE; + protected KeyValues chatClientAdvisorParams(KeyValues keyValues, ChatClientObservationContext context) { + if (CollectionUtils.isEmpty(context.getRequest().getAdvisorParams())) { + return keyValues; } - - return KeyValue.of( - ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_TOOL_FUNCTION_CALLBACKS, - context.getRequest() - .getFunctionCallbacks() - .stream() - .map(fc -> "\"" + fc.getName() + "\"") - .collect(Collectors.joining(",", "[", "]"))); + var advisorParams = context.getRequest().getAdvisorParams(); + return keyValues.and( + ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_ADVISOR_PARAMS.asString(), + TracingHelper.concatenateMaps(advisorParams)); } - protected KeyValue chatClientAdvisor(ChatClientObservationContext context) { - if (CollectionUtils.isEmpty(context.getRequest().getAdvisors())) { - return CHAT_CLIENT_ADVISOR_NONE; + protected KeyValues toolFunctionNames(KeyValues keyValues, ChatClientObservationContext context) { + if (CollectionUtils.isEmpty(context.getRequest().getFunctionNames())) { + return keyValues; } - return KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_ADVISORS, - context.getRequest() - .getAdvisors() - .stream() - .map(a -> "\"" + a.getName() + "\"") - .collect(Collectors.joining(",", "[", "]"))); + var functionNames = context.getRequest().getFunctionNames(); + return keyValues.and( + ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_TOOL_FUNCTION_NAMES.asString(), + TracingHelper.concatenateStrings(functionNames)); } - protected KeyValue chatClientAdvisorParam(ChatClientObservationContext context) { - if (CollectionUtils.isEmpty(context.getRequest().getAdvisorParams())) { - return CHAT_CLIENT_ADVISOR_PARAM_NONE; + protected KeyValues toolFunctionCallbacks(KeyValues keyValues, ChatClientObservationContext context) { + if (CollectionUtils.isEmpty(context.getRequest().getFunctionCallbacks())) { + return keyValues; } - return KeyValue.of(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_ADVISOR_PARAMS, - context.getRequest() - .getAdvisorParams() - .entrySet() - .stream() - .map(e -> "\"" + e.getKey() + "\":\"" + e.getValue() + "\"") - .collect(Collectors.joining(",", "[", "]"))); + var functionCallbacks = context.getRequest() + .getFunctionCallbacks() + .stream() + .map(FunctionCallback::getName) + .toList(); + return keyValues + .and(ChatClientObservationDocumentation.HighCardinalityKeyNames.CHAT_CLIENT_TOOL_FUNCTION_CALLBACKS + .asString(), TracingHelper.concatenateStrings(functionCallbacks)); } } \ No newline at end of file diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/package-info.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/observation/package-info.java new file mode 100644 index 00000000000..4a91aabb600 --- /dev/null +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/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.chat.client.observation; + +import org.springframework.lang.NonNullApi; +import org.springframework.lang.NonNullFields; 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 index 74a56df2b91..11c70f45952 100644 --- 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 @@ -25,8 +25,9 @@ public enum SpringAiKind { // @formatter:off + // Please, keep the alphabetical sorting. + ADVISOR("advisor"), CHAT_CLIENT("chat_client"), - CHAT_CLIENT_ADVISOR("chat_client_advisor"), VECTOR_STORE("vector_store"); private final String value; 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 index 791547cb66f..fce9a93e841 100644 --- 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 @@ -28,6 +28,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; +import java.util.Map; import java.util.StringJoiner; /** @@ -64,10 +65,16 @@ public static Span extractOtelSpan(@Nullable TracingObservationHandler.TracingCo return null; } + public static String concatenateMaps(Map keyValues) { + var keyValuesJoiner = new StringJoiner(", ", "[", "]"); + keyValues.forEach((key, value) -> keyValuesJoiner.add("\"" + key + "\":\"" + value + "\"")); + return keyValuesJoiner.toString(); + } + public static String concatenateStrings(List strings) { - var promptMessagesJoiner = new StringJoiner(", ", "[", "]"); - strings.forEach(string -> promptMessagesJoiner.add("\"" + string + "\"")); - return promptMessagesJoiner.toString(); + var stringsJoiner = new StringJoiner(", ", "[", "]"); + strings.forEach(string -> stringsJoiner.add("\"" + string + "\"")); + return stringsJoiner.toString(); } } diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationContextTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationContextTests.java index 9b843972346..795d1056faa 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationContextTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/observation/AdvisorObservationContextTests.java @@ -24,6 +24,7 @@ * Unit tests for {@link AdvisorObservationContext}. * * @author Christian Tzolov + * @author Thomas Vitale */ class AdvisorObservationContextTests { @@ -42,14 +43,14 @@ void missingAdvisorName() { assertThatThrownBy(() -> AdvisorObservationContext.builder() .withAdvisorType(AdvisorObservationContext.Type.BEFORE) .build()).isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("The advisorName must not be empty!"); + .hasMessageContaining("advisorName must not be null or empty"); } @Test void missingAdvisorType() { assertThatThrownBy(() -> AdvisorObservationContext.builder().withAdvisorName("MyName").build()) .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("The advisorType must not be null!"); + .hasMessageContaining("advisorType must not be null"); } } diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/observation/DefaultAdvisorObservationConventionTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/observation/DefaultAdvisorObservationConventionTests.java index d6d05146f56..7f9f4e3da02 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/observation/DefaultAdvisorObservationConventionTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/advisor/observation/DefaultAdvisorObservationConventionTests.java @@ -23,11 +23,13 @@ import io.micrometer.common.KeyValue; import io.micrometer.observation.Observation; +import org.springframework.ai.observation.conventions.SpringAiKind; /** * Unit tests for {@link DefaultAdvisorObservationConvention}. * * @author Christian Tzolov + * @author Thomas Vitale */ class DefaultAdvisorObservationConventionTests { @@ -64,8 +66,9 @@ void shouldHaveLowCardinalityKeyValuesWhenDefined() { .withAdvisorType(AdvisorObservationContext.Type.AROUND) .build(); assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)).contains( - KeyValue.of(LowCardinalityKeyNames.ADVISOR_TYPE.asString(), "AROUND"), - KeyValue.of(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "chat_client_advisor")); + KeyValue.of(LowCardinalityKeyNames.ADVISOR_TYPE.asString(), + AdvisorObservationContext.Type.AROUND.name()), + KeyValue.of(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), SpringAiKind.ADVISOR.value())); } @Test diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/ChatClientInputContentObservationFilterTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/ChatClientInputContentObservationFilterTests.java index 586a11fa10f..5480129157f 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/ChatClientInputContentObservationFilterTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/ChatClientInputContentObservationFilterTests.java @@ -33,9 +33,10 @@ import io.micrometer.observation.ObservationRegistry; /** - * Unit tests for {@link ChatClientImportContentObservationFilter}. + * Unit tests for {@link ChatClientInputContentObservationFilter}. * * @author Christian Tzolov + * @author Thomas Vitale */ @ExtendWith(MockitoExtension.class) class ChatClientInputContentObservationFilterTests { @@ -55,14 +56,13 @@ void whenNotSupportedObservationContextThenReturnOriginalContext() { @Test void whenEmptyInputContentThenReturnOriginalContext() { - ObservationRegistry observationRegistry = ObservationRegistry.NOOP; ChatClientObservationConvention customObservationConvention = null; var request = new DefaultChatClientRequestSpec(chatModel, "", Map.of(), "", Map.of(), List.of(), List.of(), List.of(), List.of(), null, List.of(), Map.of(), observationRegistry, customObservationConvention); - var expectedContext = new ChatClientObservationContext(request, "", false); + var expectedContext = ChatClientObservationContext.builder().withRequest(request).build(); var actualContext = observationFilter.map(expectedContext); @@ -71,7 +71,6 @@ void whenEmptyInputContentThenReturnOriginalContext() { @Test void whenWithTextThenAugmentContext() { - ObservationRegistry observationRegistry = ObservationRegistry.NOOP; ChatClientObservationConvention customObservationConvention = null; @@ -79,7 +78,7 @@ void whenWithTextThenAugmentContext() { "sample system text", Map.of("sp1", "sp1v"), List.of(), List.of(), List.of(), List.of(), null, List.of(), Map.of(), observationRegistry, customObservationConvention); - var originalContext = new ChatClientObservationContext(request, "", false); + var originalContext = ChatClientObservationContext.builder().withRequest(request).build(); var augmentedContext = observationFilter.map(originalContext); diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/ChatClientObservationContextTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/ChatClientObservationContextTests.java index 94d74efdc96..61f5f00942a 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/ChatClientObservationContextTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/ChatClientObservationContextTests.java @@ -33,6 +33,7 @@ * Unit tests for {@link ChatClientObservationContext}. * * @author Christian Tzolov + * @author Thomas Vitale */ @ExtendWith(MockitoExtension.class) class ChatClientObservationContextTests { @@ -46,7 +47,7 @@ void whenMandatoryRequestOptionsThenReturn() { var request = new DefaultChatClientRequestSpec(chatModel, "", Map.of(), "", Map.of(), List.of(), List.of(), List.of(), List.of(), null, List.of(), Map.of(), ObservationRegistry.NOOP, null); - var observationContext = new ChatClientObservationContext(request, "", true); + var observationContext = ChatClientObservationContext.builder().withRequest(request).withStream(true).build(); assertThat(observationContext).isNotNull(); } diff --git a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConventionTests.java b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConventionTests.java index 0f36c2c9782..3a470d70864 100644 --- a/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConventionTests.java +++ b/spring-ai-core/src/test/java/org/springframework/ai/chat/client/observation/DefaultChatClientObservationConventionTests.java @@ -38,11 +38,14 @@ import io.micrometer.common.KeyValue; import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; +import org.springframework.ai.observation.conventions.AiProvider; +import org.springframework.ai.observation.conventions.SpringAiKind; /** * Unit tests for {@link DefaultChatClientObservationConvention}. * * @author Christian Tzolov + * @author Thomas Vitale */ @ExtendWith(MockitoExtension.class) class DefaultChatClientObservationConventionTests { @@ -67,14 +70,21 @@ void shouldHaveName() { @Test void shouldHaveContextualName() { - ChatClientObservationContext observationContext = new ChatClientObservationContext(request, "", true); + ChatClientObservationContext observationContext = ChatClientObservationContext.builder() + .withRequest(request) + .withStream(true) + .build(); - assertThat(this.observationConvention.getContextualName(observationContext)).isEqualTo("spring_ai chat_client"); + assertThat(this.observationConvention.getContextualName(observationContext)) + .isEqualTo("%s %s".formatted(AiProvider.SPRING_AI.value(), SpringAiKind.CHAT_CLIENT.value())); } @Test void supportsOnlyChatClientObservationContext() { - ChatClientObservationContext observationContext = new ChatClientObservationContext(request, "", true); + ChatClientObservationContext observationContext = ChatClientObservationContext.builder() + .withRequest(request) + .withStream(true) + .build(); assertThat(this.observationConvention.supportsContext(observationContext)).isTrue(); assertThat(this.observationConvention.supportsContext(new Observation.Context())).isFalse(); @@ -82,7 +92,10 @@ void supportsOnlyChatClientObservationContext() { @Test void shouldHaveRequiredKeyValues() { - ChatClientObservationContext observationContext = new ChatClientObservationContext(request, "", true); + ChatClientObservationContext observationContext = ChatClientObservationContext.builder() + .withRequest(request) + .withStream(true) + .build(); assertThat(this.observationConvention.getLowCardinalityKeyValues(observationContext)).contains( KeyValue.of(LowCardinalityKeyNames.SPRING_AI_KIND.asString(), "chat_client"), @@ -143,38 +156,27 @@ public String call(String functionInput) { @Test void shouldHaveOptionalKeyValues() { - var request = new DefaultChatClientRequestSpec(chatModel, "", Map.of(), "", Map.of(), List.of(dummyFunction("functionCallback1"), dummyFunction("functionCallback2")), List.of(), List.of("function1", "function2"), List.of(), null, List.of(dummyAdvisor("advisor1"), dummyAdvisor("advisor2")), Map.of("advParam1", "advisorParam1Value"), ObservationRegistry.NOOP, null); - ChatClientObservationContext observationContext = new ChatClientObservationContext(request, "json", true); + ChatClientObservationContext observationContext = ChatClientObservationContext.builder() + .withRequest(request) + .withFormat("json") + .withStream(true) + .build(); assertThat(this.observationConvention.getHighCardinalityKeyValues(observationContext)).contains( KeyValue.of(HighCardinalityKeyNames.CHAT_CLIENT_ADVISORS.asString(), - "[\"advisor1\",\"advisor2\",\"CallAroundAdvisor\",\"StreamAroundAdvisor\"]"), + "[\"advisor1\", \"advisor2\", \"CallAroundAdvisor\", \"StreamAroundAdvisor\"]"), KeyValue.of(HighCardinalityKeyNames.CHAT_CLIENT_ADVISOR_PARAMS.asString(), "[\"advParam1\":\"advisorParam1Value\"]"), KeyValue.of(HighCardinalityKeyNames.CHAT_CLIENT_TOOL_FUNCTION_NAMES.asString(), - "[\"function1\",\"function2\"]"), + "[\"function1\", \"function2\"]"), KeyValue.of(HighCardinalityKeyNames.CHAT_CLIENT_TOOL_FUNCTION_CALLBACKS.asString(), - "[\"functionCallback1\",\"functionCallback2\"]")); - } - - static class TestUsage implements Usage { - - @Override - public Long getPromptTokens() { - return 1000L; - } - - @Override - public Long getGenerationTokens() { - return 500L; - } - + "[\"functionCallback1\", \"functionCallback2\"]")); } } 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 fef80b6f128..f892b1fbbc1 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 @@ -118,7 +118,6 @@ public void addAndSearchWithFilters() { .hasObservationWithNameEqualTo(DefaultVectorStoreObservationConvention.DEFAULT_NAME) .that() .hasContextualNameEqualTo("chroma delete") - .hasHighCardinalityKeyValue(HighCardinalityKeyNames.DB_VECTOR_QUERY_FILTER.asString(), "none") .hasBeenStarted() .hasBeenStopped(); observationRegistry.clear();