From 99a4fec214cb9a2973c3ff265aa646b6fb0145f4 Mon Sep 17 00:00:00 2001 From: Christian Tzolov Date: Fri, 23 Aug 2024 12:36:06 +0200 Subject: [PATCH] Remove the reactive advisor observability support - Add Anthropic WebClient.Builder autoconfiguration to activate the OOTB WebClient obsercabilty support. --- .../ai/anthropic/api/AnthropicApi.java | 14 ++++----- .../AnthropicChatModelObservationIT.java | 29 ++++++++++--------- .../ObservableRequestResponseAdvisor.java | 22 ++------------ .../anthropic/AnthropicAutoConfiguration.java | 6 ++-- 4 files changed, 28 insertions(+), 43 deletions(-) diff --git a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java index 757def3ec..a0cca8a0b 100644 --- a/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java +++ b/models/spring-ai-anthropic/src/main/java/org/springframework/ai/anthropic/api/AnthropicApi.java @@ -91,7 +91,7 @@ public class AnthropicApi { * @param anthropicApiKey Anthropic api Key. */ public AnthropicApi(String baseUrl, String anthropicApiKey) { - this(baseUrl, anthropicApiKey, DEFAULT_ANTHROPIC_VERSION, RestClient.builder(), + this(baseUrl, anthropicApiKey, DEFAULT_ANTHROPIC_VERSION, RestClient.builder(), WebClient.builder(), RetryUtils.DEFAULT_RESPONSE_ERROR_HANDLER); } @@ -103,8 +103,9 @@ public class AnthropicApi { * @param responseErrorHandler Response error handler. */ public AnthropicApi(String baseUrl, String anthropicApiKey, String anthropicVersion, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { - this(baseUrl, anthropicApiKey, anthropicVersion, restClientBuilder, responseErrorHandler, + RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, + ResponseErrorHandler responseErrorHandler) { + this(baseUrl, anthropicApiKey, anthropicVersion, restClientBuilder, webClientBuilder, responseErrorHandler, DEFAULT_ANTHROPIC_BETA_VERSION); } @@ -117,8 +118,8 @@ public class AnthropicApi { * @param anthropicBetaFeatures Anthropic beta features. */ public AnthropicApi(String baseUrl, String anthropicApiKey, String anthropicVersion, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler, - String anthropicBetaFeatures) { + RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, + ResponseErrorHandler responseErrorHandler, String anthropicBetaFeatures) { Consumer jsonContentHeaders = headers -> { headers.add(HEADER_X_API_KEY, anthropicApiKey); @@ -132,8 +133,7 @@ public class AnthropicApi { .defaultStatusHandler(responseErrorHandler) .build(); - this.webClient = WebClient.builder() - .baseUrl(baseUrl) + this.webClient = webClientBuilder.baseUrl(baseUrl) .defaultHeaders(jsonContentHeaders) .defaultStatusHandler(HttpStatusCode::isError, resp -> Mono.just(new RuntimeException("Response exception, Status: [" + resp.statusCode() diff --git a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java index 7f7e79117..6160d763a 100644 --- a/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java +++ b/models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatModelObservationIT.java @@ -15,15 +15,19 @@ */ package org.springframework.ai.anthropic; -import io.micrometer.common.KeyValue; -import io.micrometer.observation.tck.TestObservationRegistry; -import io.micrometer.observation.tck.TestObservationRegistryAssert; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.stream.Collectors; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.anthropic.api.AnthropicApi; import org.springframework.ai.chat.metadata.ChatResponseMetadata; import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.observation.ChatModelObservationDocumentation.HighCardinalityKeyNames; +import org.springframework.ai.chat.observation.ChatModelObservationDocumentation.LowCardinalityKeyNames; import org.springframework.ai.chat.observation.DefaultChatModelObservationConvention; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.function.FunctionCallbackContext; @@ -34,15 +38,12 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Bean; import org.springframework.retry.support.RetryTemplate; + +import io.micrometer.common.KeyValue; +import io.micrometer.observation.tck.TestObservationRegistry; +import io.micrometer.observation.tck.TestObservationRegistryAssert; import reactor.core.publisher.Flux; -import java.util.List; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.ai.chat.observation.ChatModelObservationDocumentation.HighCardinalityKeyNames; -import static org.springframework.ai.chat.observation.ChatModelObservationDocumentation.LowCardinalityKeyNames; - /** * Integration tests for observation instrumentation in {@link AnthropicChatModel}. * @@ -67,7 +68,7 @@ public class AnthropicChatModelObservationIT { @Test void observationForChatOperation() { var options = AnthropicChatOptions.builder() - .withModel(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getValue()) + .withModel(AnthropicApi.ChatModel.CLAUDE_3_HAIKU.getValue()) .withMaxTokens(2048) .withStopSequences(List.of("this-is-the-end")) .withTemperature(0.7f) @@ -89,7 +90,7 @@ public class AnthropicChatModelObservationIT { @Test void observationForStreamingChatOperation() { var options = AnthropicChatOptions.builder() - .withModel(AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getValue()) + .withModel(AnthropicApi.ChatModel.CLAUDE_3_HAIKU.getValue()) .withMaxTokens(2048) .withStopSequences(List.of("this-is-the-end")) .withTemperature(0.7f) @@ -125,12 +126,12 @@ public class AnthropicChatModelObservationIT { .doesNotHaveAnyRemainingCurrentObservation() .hasObservationWithNameEqualTo(DefaultChatModelObservationConvention.DEFAULT_NAME) .that() - .hasContextualNameEqualTo("chat " + AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getValue()) + .hasContextualNameEqualTo("chat " + AnthropicApi.ChatModel.CLAUDE_3_HAIKU.getValue()) .hasLowCardinalityKeyValue(LowCardinalityKeyNames.AI_OPERATION_TYPE.asString(), AiOperationType.CHAT.value()) .hasLowCardinalityKeyValue(LowCardinalityKeyNames.AI_PROVIDER.asString(), AiProvider.ANTHROPIC.value()) .hasLowCardinalityKeyValue(LowCardinalityKeyNames.REQUEST_MODEL.asString(), - AnthropicApi.ChatModel.CLAUDE_3_5_SONNET.getValue()) + AnthropicApi.ChatModel.CLAUDE_3_HAIKU.getValue()) .hasLowCardinalityKeyValue(LowCardinalityKeyNames.RESPONSE_MODEL.asString(), responseMetadata.getModel()) .hasHighCardinalityKeyValue(HighCardinalityKeyNames.REQUEST_FREQUENCY_PENALTY.asString(), KeyValue.NONE_VALUE) diff --git a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/ObservableRequestResponseAdvisor.java b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/ObservableRequestResponseAdvisor.java index f2ad542ea..cbc57f6f1 100644 --- a/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/ObservableRequestResponseAdvisor.java +++ b/spring-ai-core/src/main/java/org/springframework/ai/chat/client/advisor/observation/ObservableRequestResponseAdvisor.java @@ -23,9 +23,7 @@ import org.springframework.ai.chat.model.ChatResponse; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationRegistry; -import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; import reactor.core.publisher.Flux; /** @@ -84,24 +82,8 @@ public class ObservableRequestResponseAdvisor implements RequestResponseAdvisor @Override public Flux adviseResponse(Flux fluxResponse, Map context) { - return Flux.deferContextual(contextView -> { - var observationContext = this.doCreateObservationContextBuilder(AdvisorObservationContext.Type.AFTER) - .withAdvisorResponseContext(context) - .build(); - - Observation observation = AdvisorObservationDocumentation.AI_ADVISOR.observation( - this.customObservationConvention, DEFAULT_OBSERVATION_CONVENTION, () -> observationContext, - this.observationRegistry); - - observation.parentObservation(contextView.getOrDefault(ObservationThreadLocalAccessor.KEY, null)).start(); - - // @formatter:off - return this.targetAdvisor.adviseResponse(fluxResponse, context) - .doOnError(observation::error) - .doFinally(s -> observation.stop()) - .contextWrite(ctx -> ctx.put(ObservationThreadLocalAccessor.KEY, observation)); - // @formatter:on - }); + // NOTE: The reactive observation support is not available yet. + return this.targetAdvisor.adviseResponse(fluxResponse, context); } /** diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/anthropic/AnthropicAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/anthropic/AnthropicAutoConfiguration.java index 3c0cd3f0a..35c507300 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/anthropic/AnthropicAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/anthropic/AnthropicAutoConfiguration.java @@ -38,6 +38,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.retry.support.RetryTemplate; import org.springframework.web.client.ResponseErrorHandler; import org.springframework.web.client.RestClient; +import org.springframework.web.reactive.function.client.WebClient; /** * @author Christian Tzolov @@ -56,10 +57,11 @@ public class AnthropicAutoConfiguration { @Bean @ConditionalOnMissingBean public AnthropicApi anthropicApi(AnthropicConnectionProperties connectionProperties, - RestClient.Builder restClientBuilder, ResponseErrorHandler responseErrorHandler) { + RestClient.Builder restClientBuilder, WebClient.Builder webClientBuilder, + ResponseErrorHandler responseErrorHandler) { return new AnthropicApi(connectionProperties.getBaseUrl(), connectionProperties.getApiKey(), - connectionProperties.getVersion(), restClientBuilder, responseErrorHandler, + connectionProperties.getVersion(), restClientBuilder, webClientBuilder, responseErrorHandler, connectionProperties.getBetaVersion()); }