From e4ee01a8df77aa2add7d119af6176b2187ef1c3e Mon Sep 17 00:00:00 2001 From: wmz7year Date: Mon, 3 Jun 2024 06:51:03 +0800 Subject: [PATCH 1/9] Add Amazon Bedrock Converse API support. --- models/spring-ai-bedrock/pom.xml | 6 + .../ai/bedrock/api/BedrockConverseApi.java | 245 ++++++++++++++++++ .../ai/bedrock/api/BedrockConverseApiIT.java | 89 +++++++ pom.xml | 2 +- .../BedrockConverseApiAutoConfiguration.java | 58 +++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + ...BedrockConverseApiAutoConfigurationIT.java | 54 ++++ 7 files changed, 454 insertions(+), 1 deletion(-) create mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java create mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiIT.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfiguration.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfigurationIT.java diff --git a/models/spring-ai-bedrock/pom.xml b/models/spring-ai-bedrock/pom.xml index e3b79d30bd..ae7913822a 100644 --- a/models/spring-ai-bedrock/pom.xml +++ b/models/spring-ai-bedrock/pom.xml @@ -29,6 +29,12 @@ ${project.parent.version} + + org.springframework.ai + spring-ai-retry + ${project.parent.version} + + org.springframework spring-web diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java new file mode 100644 index 0000000000..b1f69b5284 --- /dev/null +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java @@ -0,0 +1,245 @@ +/* + * Copyright 2023 - 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. + */ +// @formatter:off +package org.springframework.ai.bedrock.api; + +import java.time.Duration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.retry.support.RetryTemplate; +import org.springframework.util.Assert; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Sinks; +import reactor.core.publisher.Sinks.EmitFailureHandler; +import reactor.core.publisher.Sinks.EmitResult; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseRequest; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamRequest; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamResponseHandler; + +/** + * Amazon Bedrock Converse API, It provides the basic functionality to invoke the Bedrock + * AI model and receive the response for streaming and non-streaming requests. + * The Converse API doesn't support any embedding models (such as Titan Embeddings G1 - Text) + * or image generation models (such as Stability AI). + * + *

+ * https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html + *

+ * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html + *

+ * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStream.html + *

+ * https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html + *

+ * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html + * + * @author Wei Jiang + * @since 1.0.0 + */ +public class BedrockConverseApi { + + private static final Logger logger = LoggerFactory.getLogger(BedrockConverseApi.class); + + private final Region region; + + private final BedrockRuntimeClient client; + + private final BedrockRuntimeAsyncClient clientStreaming; + + private final RetryTemplate retryTemplate; + + /** + * Create a new BedrockConverseApi instance using default credentials provider. + * + * @param region The AWS region to use. + */ + public BedrockConverseApi(String region) { + this(ProfileCredentialsProvider.builder().build(), region, Duration.ofMinutes(5)); + } + + /** + * Create a new BedrockConverseApi instance using default credentials provider. + * + * @param region The AWS region to use. + * @param timeout The timeout to use. + */ + public BedrockConverseApi(String region, Duration timeout) { + this(ProfileCredentialsProvider.builder().build(), region, timeout); + } + + /** + * Create a new BedrockConverseApi instance using the provided credentials provider, + * region. + * + * @param credentialsProvider The credentials provider to connect to AWS. + * @param region The AWS region to use. + */ + public BedrockConverseApi(AwsCredentialsProvider credentialsProvider, String region) { + this(credentialsProvider, region, Duration.ofMinutes(5)); + } + + /** + * Create a new BedrockConverseApi instance using the provided credentials provider, + * region. + * + * @param credentialsProvider The credentials provider to connect to AWS. + * @param region The AWS region to use. + * @param timeout Configure the amount of time to allow the client to complete the + * execution of an API call. This timeout covers the entire client execution except + * for marshalling. This includes request handler execution, all HTTP requests + * including retries, unmarshalling, etc. This value should always be positive, if + * present. + */ + public BedrockConverseApi(AwsCredentialsProvider credentialsProvider, String region, Duration timeout) { + this(credentialsProvider, Region.of(region), timeout); + } + + /** + * Create a new BedrockConverseApi instance using the provided credentials provider, + * region. + * + * @param credentialsProvider The credentials provider to connect to AWS. + * @param region The AWS region to use. + * @param timeout Configure the amount of time to allow the client to complete the + * execution of an API call. This timeout covers the entire client execution except + * for marshalling. This includes request handler execution, all HTTP requests + * including retries, unmarshalling, etc. This value should always be positive, if + * present. + */ + public BedrockConverseApi(AwsCredentialsProvider credentialsProvider, Region region, Duration timeout) { + this(credentialsProvider, region, timeout, RetryUtils.DEFAULT_RETRY_TEMPLATE); + } + + /** + * Create a new BedrockConverseApi instance using the provided credentials provider, + * region + * + * @param credentialsProvider The credentials provider to connect to AWS. + * @param region The AWS region to use. + * @param timeout Configure the amount of time to allow the client to complete the + * execution of an API call. This timeout covers the entire client execution except + * for marshalling. This includes request handler execution, all HTTP requests + * including retries, unmarshalling, etc. This value should always be positive, if + * present. + * @param retryTemplate The retry template used to retry the Amazon Bedrock Converse + * API calls. + */ + public BedrockConverseApi(AwsCredentialsProvider credentialsProvider, Region region, Duration timeout, + RetryTemplate retryTemplate) { + Assert.notNull(credentialsProvider, "Credentials provider must not be null"); + Assert.notNull(region, "Region must not be empty"); + Assert.notNull(timeout, "Timeout must not be null"); + Assert.notNull(retryTemplate, "RetryTemplate must not be null"); + + this.region = region; + this.retryTemplate = retryTemplate; + + this.client = BedrockRuntimeClient.builder() + .region(this.region) + .credentialsProvider(credentialsProvider) + .overrideConfiguration(c -> c.apiCallTimeout(timeout)) + .build(); + + this.clientStreaming = BedrockRuntimeAsyncClient.builder() + .region(this.region) + .credentialsProvider(credentialsProvider) + .overrideConfiguration(c -> c.apiCallTimeout(timeout)) + .build(); + } + + /** + * @return The AWS region. + */ + public Region getRegion() { + return this.region; + } + + /** + * Invoke the model and return the response. + * + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html + * https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/bedrockruntime/BedrockRuntimeClient.html#converse + * @param converseRequest Model invocation request. + * @return The model invocation response. + */ + public ConverseResponse converse(ConverseRequest converseRequest) { + Assert.notNull(converseRequest, "'converseRequest' must not be null"); + + return this.retryTemplate.execute(ctx -> { + return client.converse(converseRequest); + }); + } + + /** + * Invoke the model and return the response stream. + * + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html + * https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/bedrockruntime/BedrockRuntimeAsyncClient.html#converseStream + * @param converseStreamRequest Model invocation request. + * @return The model invocation response stream. + */ + public Flux converseStream(ConverseStreamRequest converseStreamRequest) { + Assert.notNull(converseStreamRequest, "'converseStreamRequest' must not be null"); + + return this.retryTemplate.execute(ctx -> { + Sinks.Many eventSink = Sinks.many().multicast().onBackpressureBuffer(); + + ConverseStreamResponseHandler.Visitor visitor = ConverseStreamResponseHandler.Visitor.builder() + .onDefault((output) -> { + logger.debug("Received converse stream output:{}", output); + eventSink.tryEmitNext(output); + }) + .build(); + + ConverseStreamResponseHandler responseHandler = ConverseStreamResponseHandler.builder() + .onEventStream(stream -> stream.subscribe((e) -> e.accept(visitor))) + .onComplete(() -> { + EmitResult emitResult = eventSink.tryEmitComplete(); + + while (!emitResult.isSuccess()) { + logger.debug("Emitting complete:{}", emitResult); + emitResult = eventSink.tryEmitComplete(); + } + + eventSink.emitComplete(EmitFailureHandler.busyLooping(Duration.ofSeconds(3))); + logger.debug("Completed streaming response."); + }) + .onError((error) -> { + logger.error("Error handling Bedrock converse stream response", error); + eventSink.tryEmitError(error); + }) + .build(); + + clientStreaming.converseStream(converseStreamRequest, responseHandler); + + return eventSink.asFlux(); + }); + } + +} +//@formatter:on diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiIT.java new file mode 100644 index 0000000000..77ea8dbb5a --- /dev/null +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiIT.java @@ -0,0 +1,89 @@ +/* + * Copyright 2023 - 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.bedrock.api; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; + +import reactor.core.publisher.Flux; +import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseRequest; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamRequest; +import software.amazon.awssdk.services.bedrockruntime.model.Message; + +/** + * @author Wei Jiang + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +public class BedrockConverseApiIT { + + private BedrockConverseApi converseApi = new BedrockConverseApi(EnvironmentVariableCredentialsProvider.create(), + Region.US_EAST_1.id()); + + @Test + public void testConverse() { + ContentBlock contentBlock = ContentBlock.builder().text("Give me the names of 3 famous pirates?").build(); + + Message message = Message.builder().content(contentBlock).role(ConversationRole.USER).build(); + + ConverseRequest request = ConverseRequest.builder() + .modelId("anthropic.claude-3-sonnet-20240229-v1:0") + .messages(List.of(message)) + .build(); + + ConverseResponse response = converseApi.converse(request); + + assertThat(response).isNotNull(); + assertThat(response.output()).isNotNull(); + assertThat(response.output().message()).isNotNull(); + assertThat(response.output().message().content()).isNotEmpty(); + assertThat(response.output().message().content().get(0).text()).contains("Blackbeard"); + assertThat(response.stopReason()).isNotNull(); + assertThat(response.usage()).isNotNull(); + assertThat(response.usage().inputTokens()).isGreaterThan(10); + assertThat(response.usage().outputTokens()).isGreaterThan(30); + } + + @Test + public void testConverseStream() { + ContentBlock contentBlock = ContentBlock.builder().text("Give me the names of 3 famous pirates?").build(); + + Message message = Message.builder().content(contentBlock).role(ConversationRole.USER).build(); + + ConverseStreamRequest request = ConverseStreamRequest.builder() + .modelId("anthropic.claude-3-sonnet-20240229-v1:0") + .messages(List.of(message)) + .build(); + + Flux responseStream = converseApi.converseStream(request); + + List responseOutputs = responseStream.collectList().block(); + + assertThat(responseOutputs).isNotNull(); + assertThat(responseOutputs).hasSizeGreaterThan(10); + } + +} diff --git a/pom.xml b/pom.xml index f9abde4603..02ff739589 100644 --- a/pom.xml +++ b/pom.xml @@ -136,7 +136,7 @@ 1.0.0-beta.8 1.0.0 4.31.1 - 2.25.3 + 2.25.64 2.16.1 0.26.0 1.17.0 diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfiguration.java new file mode 100644 index 0000000000..8c10040c3a --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfiguration.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.api; + +import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; +import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +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.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.retry.support.RetryTemplate; + +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.regions.providers.AwsRegionProvider; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; + +/** + * {@link AutoConfiguration Auto-configuration} for Bedrock Converse API. + * + * @author Wei Jiang + * @since 1.0.0 + */ +@AutoConfiguration(after = SpringAiRetryAutoConfiguration.class) +@EnableConfigurationProperties({ BedrockAwsConnectionProperties.class }) +@ConditionalOnClass({ BedrockConverseApi.class, BedrockRuntimeClient.class, BedrockRuntimeAsyncClient.class }) +@Import(BedrockAwsConnectionConfiguration.class) +public class BedrockConverseApiAutoConfiguration { + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) + public BedrockConverseApi bedrockConverseApi(AwsCredentialsProvider credentialsProvider, + AwsRegionProvider regionProvider, BedrockAwsConnectionProperties awsProperties, + RetryTemplate retryTemplate) { + return new BedrockConverseApi(credentialsProvider, regionProvider.getRegion(), awsProperties.getTimeout(), + retryTemplate); + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index c744be669c..ddbfd533d8 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -5,6 +5,7 @@ org.springframework.ai.autoconfigure.transformers.TransformersEmbeddingModelAuto org.springframework.ai.autoconfigure.huggingface.HuggingfaceChatAutoConfiguration org.springframework.ai.autoconfigure.vertexai.palm2.VertexAiPalm2AutoConfiguration org.springframework.ai.autoconfigure.vertexai.gemini.VertexAiGeminiAutoConfiguration +org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration org.springframework.ai.autoconfigure.bedrock.jurrasic2.BedrockAi21Jurassic2ChatAutoConfiguration org.springframework.ai.autoconfigure.bedrock.llama.BedrockLlamaChatAutoConfiguration org.springframework.ai.autoconfigure.bedrock.cohere.BedrockCohereChatAutoConfiguration diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfigurationIT.java new file mode 100644 index 0000000000..4142d4a173 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfigurationIT.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.api; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import software.amazon.awssdk.regions.Region; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +public class BedrockConverseApiAutoConfigurationIT { + + @Test + public void autoConfigureBedrockConverseApi() { + new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id()) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class)) + .run((context) -> { + var bedrockConverseApi = context.getBean(BedrockConverseApi.class); + + assertThat(bedrockConverseApi).isNotNull(); + + assertThat(bedrockConverseApi.getRegion()).isEqualTo(Region.US_EAST_1); + }); + } + +} From 49b33260d0471f4d8f79e6f0c4b67eb63e2a32b1 Mon Sep 17 00:00:00 2001 From: wmz7year Date: Mon, 3 Jun 2024 18:01:40 +0800 Subject: [PATCH 2/9] Re-implementing Amazon Bedrock Chat model with Amazon Bedrock Converse API. --- .../bedrock/BedrockChatResponseMetadata.java | 88 +++++ .../ai/bedrock/BedrockUsage.java | 27 +- .../anthropic/AnthropicChatOptions.java | 39 +- .../anthropic/BedrockAnthropicChatModel.java | 126 ++++--- .../api/AnthropicChatBedrockApi.java | 2 + .../anthropic3/Anthropic3ChatOptions.java | 22 +- .../BedrockAnthropic3ChatModel.java | 194 ++++------ .../api/Anthropic3ChatBedrockApi.java | 2 + .../bedrock/api/BedrockConverseApiUtils.java | 311 ++++++++++++++++ .../cohere/BedrockCohereChatModel.java | 132 +++---- .../cohere/BedrockCohereChatOptions.java | 61 ++- .../cohere/api/CohereChatBedrockApi.java | 3 + .../BedrockAi21Jurassic2ChatModel.java | 118 +++--- .../BedrockAi21Jurassic2ChatOptions.java | 45 +-- .../api/Ai21Jurassic2ChatBedrockApi.java | 3 + .../bedrock/llama/BedrockLlamaChatModel.java | 141 +++---- .../llama/BedrockLlamaChatOptions.java | 5 + .../llama/api/LlamaChatBedrockApi.java | 3 + .../bedrock/titan/BedrockTitanChatModel.java | 168 ++++----- .../titan/BedrockTitanChatOptions.java | 76 ++-- .../titan/api/TitanChatBedrockApi.java | 2 + .../BedrockAnthropicChatModelIT.java | 42 ++- .../BedrockAnthropicCreateRequestTests.java | 81 ---- .../api/AnthropicChatBedrockApiIT.java | 1 + .../BedrockAnthropic3ChatModelIT.java | 46 ++- .../BedrockAnthropic3CreateRequestTests.java | 80 ---- .../api/Anthropic3ChatBedrockApiIT.java | 1 + .../ai/bedrock/api/BedrockConverseApiIT.java | 1 + .../api/BedrockConverseApiUtilsIT.java | 346 ++++++++++++++++++ .../BedrockCohereChatCreateRequestTests.java | 106 ------ .../cohere/BedrockCohereChatModelIT.java | 56 ++- .../cohere/api/CohereChatBedrockApiIT.java | 1 + .../BedrockAi21Jurassic2ChatModelIT.java | 63 +++- .../api/Ai21Jurassic2ChatBedrockApiIT.java | 1 + .../llama/BedrockLlamaChatModelIT.java | 41 ++- .../llama/BedrockLlamaCreateRequestTests.java | 67 ---- .../llama/api/LlamaChatBedrockApiIT.java | 1 + ...drockTitanChatModelCreateRequestTests.java | 77 ---- .../titan/BedrockTitanChatModelIT.java | 50 ++- .../titan/api/TitanChatBedrockApiIT.java | 1 + .../modules/ROOT/pages/api/bedrock.adoc | 18 +- .../api/chat/bedrock/bedrock-anthropic.adoc | 15 +- .../api/chat/bedrock/bedrock-anthropic3.adoc | 15 +- .../api/chat/bedrock/bedrock-cohere.adoc | 4 +- .../api/chat/bedrock/bedrock-jurassic2.adoc | 27 +- .../pages/api/chat/bedrock/bedrock-llama.adoc | 6 +- .../pages/api/chat/bedrock/bedrock-titan.adoc | 4 +- ...BedrockAnthropicChatAutoConfiguration.java | 12 +- .../BedrockAnthropicChatProperties.java | 13 +- ...edrockAnthropic3ChatAutoConfiguration.java | 14 +- .../BedrockAnthropic3ChatProperties.java | 19 +- .../BedrockCohereChatAutoConfiguration.java | 14 +- .../cohere/BedrockCohereChatProperties.java | 8 +- ...ockAi21Jurassic2ChatAutoConfiguration.java | 16 +- .../BedrockAi21Jurassic2ChatProperties.java | 11 +- .../BedrockLlamaChatAutoConfiguration.java | 13 +- .../llama/BedrockLlamaChatProperties.java | 11 +- .../BedrockTitanChatAutoConfiguration.java | 15 +- .../titan/BedrockTitanChatProperties.java | 8 +- ...drockAnthropicChatAutoConfigurationIT.java | 22 +- ...rockAnthropic3ChatAutoConfigurationIT.java | 22 +- .../BedrockCohereChatAutoConfigurationIT.java | 38 +- ...kAi21Jurassic2ChatAutoConfigurationIT.java | 35 +- .../BedrockLlamaChatAutoConfigurationIT.java | 20 +- .../BedrockTitanChatAutoConfigurationIT.java | 35 +- 65 files changed, 1777 insertions(+), 1268 deletions(-) create mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockChatResponseMetadata.java create mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtils.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicCreateRequestTests.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3CreateRequestTests.java create mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtilsIT.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatCreateRequestTests.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaCreateRequestTests.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModelCreateRequestTests.java diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockChatResponseMetadata.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockChatResponseMetadata.java new file mode 100644 index 0000000000..27e745cbcf --- /dev/null +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockChatResponseMetadata.java @@ -0,0 +1,88 @@ +/* + * Copyright 2023 - 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.bedrock; + +import java.util.HashMap; + +import org.springframework.ai.chat.metadata.ChatResponseMetadata; +import org.springframework.ai.chat.metadata.EmptyUsage; +import org.springframework.ai.chat.metadata.Usage; + +import software.amazon.awssdk.services.bedrockruntime.model.ConverseMetrics; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamMetadataEvent; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamMetrics; + +/** + * {@link ChatResponseMetadata} implementation for {@literal Amazon Bedrock}. + * + * @author Wei Jiang + * @since 1.0.0 + */ +public class BedrockChatResponseMetadata extends HashMap implements ChatResponseMetadata { + + protected static final String AI_METADATA_STRING = "{ @type: %1$s, id: %2$s, usage: %3$s, latency: %4$sms}"; + + private final String id; + + private final Usage usage; + + private final Long latencyMs; + + public static BedrockChatResponseMetadata from(ConverseResponse response) { + String requestId = response.responseMetadata().requestId(); + + BedrockUsage usage = BedrockUsage.from(response.usage()); + + ConverseMetrics metrics = response.metrics(); + + return new BedrockChatResponseMetadata(requestId, usage, metrics.latencyMs()); + } + + public static BedrockChatResponseMetadata from(ConverseStreamMetadataEvent converseStreamMetadataEvent) { + BedrockUsage usage = BedrockUsage.from(converseStreamMetadataEvent.usage()); + + ConverseStreamMetrics metrics = converseStreamMetadataEvent.metrics(); + + return new BedrockChatResponseMetadata(null, usage, metrics.latencyMs()); + } + + protected BedrockChatResponseMetadata(String id, BedrockUsage usage, Long latencyMs) { + this.id = id; + this.usage = usage; + this.latencyMs = latencyMs; + } + + public String getId() { + return this.id; + } + + public Long getLatencyMs() { + return latencyMs; + } + + @Override + public Usage getUsage() { + Usage usage = this.usage; + return usage != null ? usage : new EmptyUsage(); + } + + @Override + public String toString() { + return AI_METADATA_STRING.formatted(getClass().getName(), getId(), getUsage(), getLatencyMs()); + } + +} diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockUsage.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockUsage.java index 6eb31aa28e..6271e12715 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockUsage.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockUsage.java @@ -19,42 +19,49 @@ import org.springframework.ai.chat.metadata.Usage; import org.springframework.util.Assert; +import software.amazon.awssdk.services.bedrockruntime.model.TokenUsage; + /** * {@link Usage} implementation for Bedrock API. * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ public class BedrockUsage implements Usage { public static BedrockUsage from(AmazonBedrockInvocationMetrics usage) { - return new BedrockUsage(usage); + return new BedrockUsage(usage.inputTokenCount().longValue(), usage.outputTokenCount().longValue()); } - private final AmazonBedrockInvocationMetrics usage; + public static BedrockUsage from(TokenUsage usage) { + Assert.notNull(usage, "'TokenUsage' must not be null."); - protected BedrockUsage(AmazonBedrockInvocationMetrics usage) { - Assert.notNull(usage, "OpenAI Usage must not be null"); - this.usage = usage; + return new BedrockUsage(usage.inputTokens().longValue(), usage.outputTokens().longValue()); } - protected AmazonBedrockInvocationMetrics getUsage() { - return this.usage; + private final Long inputTokens; + + private final Long outputTokens; + + protected BedrockUsage(Long inputTokens, Long outputTokens) { + this.inputTokens = inputTokens; + this.outputTokens = outputTokens; } @Override public Long getPromptTokens() { - return getUsage().inputTokenCount().longValue(); + return inputTokens; } @Override public Long getGenerationTokens() { - return getUsage().outputTokenCount().longValue(); + return outputTokens; } @Override public String toString() { - return getUsage().toString(); + return "BedrockUsage [inputTokens=" + inputTokens + ", outputTokens=" + outputTokens + "]"; } } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/AnthropicChatOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/AnthropicChatOptions.java index d0d5a5a2ce..57b6f4ccb3 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/AnthropicChatOptions.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/AnthropicChatOptions.java @@ -25,7 +25,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** + * Java {@link ChatOptions} for the Bedrock Anthropic chat generative model chat options. + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-text-completion.html + * * @author Christian Tzolov + * @author Wei Jiang */ @JsonInclude(Include.NON_NULL) public class AnthropicChatOptions implements ChatOptions { @@ -44,7 +48,7 @@ public class AnthropicChatOptions implements ChatOptions { * reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate. We * recommend a limit of 4,000 tokens for optimal performance. */ - private @JsonProperty("max_tokens_to_sample") Integer maxTokensToSample; + private @JsonProperty("max_tokens") Integer maxTokens; /** * Specify the number of token choices the generative uses to generate the next token. @@ -62,11 +66,6 @@ public class AnthropicChatOptions implements ChatOptions { * generating further tokens. The returned text doesn't contain the stop sequence. */ private @JsonProperty("stop_sequences") List stopSequences; - - /** - * The version of the generative to use. The default value is bedrock-2023-05-31. - */ - private @JsonProperty("anthropic_version") String anthropicVersion; // @formatter:on public static Builder builder() { @@ -82,8 +81,8 @@ public Builder withTemperature(Float temperature) { return this; } - public Builder withMaxTokensToSample(Integer maxTokensToSample) { - this.options.setMaxTokensToSample(maxTokensToSample); + public Builder withMaxTokens(Integer maxTokens) { + this.options.setMaxTokens(maxTokens); return this; } @@ -102,11 +101,6 @@ public Builder withStopSequences(List stopSequences) { return this; } - public Builder withAnthropicVersion(String anthropicVersion) { - this.options.setAnthropicVersion(anthropicVersion); - return this; - } - public AnthropicChatOptions build() { return this.options; } @@ -122,12 +116,12 @@ public void setTemperature(Float temperature) { this.temperature = temperature; } - public Integer getMaxTokensToSample() { - return this.maxTokensToSample; + public Integer getMaxTokens() { + return maxTokens; } - public void setMaxTokensToSample(Integer maxTokensToSample) { - this.maxTokensToSample = maxTokensToSample; + public void setMaxTokens(Integer maxTokens) { + this.maxTokens = maxTokens; } @Override @@ -156,21 +150,12 @@ public void setStopSequences(List stopSequences) { this.stopSequences = stopSequences; } - public String getAnthropicVersion() { - return this.anthropicVersion; - } - - public void setAnthropicVersion(String anthropicVersion) { - this.anthropicVersion = anthropicVersion; - } - public static AnthropicChatOptions fromOptions(AnthropicChatOptions fromOptions) { return builder().withTemperature(fromOptions.getTemperature()) - .withMaxTokensToSample(fromOptions.getMaxTokensToSample()) + .withMaxTokens(fromOptions.getMaxTokens()) .withTopK(fromOptions.getTopK()) .withTopP(fromOptions.getTopP()) .withStopSequences(fromOptions.getStopSequences()) - .withAnthropicVersion(fromOptions.getAnthropicVersion()) .build(); } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicChatModel.java index 9ea5af4f88..7c561349b3 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicChatModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicChatModel.java @@ -15,111 +15,117 @@ */ package org.springframework.ai.bedrock.anthropic; -import java.util.List; - import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; import reactor.core.publisher.Flux; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; -import org.springframework.ai.bedrock.MessageToPromptConverter; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatRequest; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatResponse; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.api.BedrockConverseApiUtils; import org.springframework.ai.chat.model.StreamingChatModel; -import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.model.ModelDescription; +import org.springframework.util.Assert; /** * Java {@link ChatModel} and {@link StreamingChatModel} for the Bedrock Anthropic chat - * generative. + * generative model. * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ public class BedrockAnthropicChatModel implements ChatModel, StreamingChatModel { - private final AnthropicChatBedrockApi anthropicChatApi; + private final String modelId; + + private final BedrockConverseApi converseApi; private final AnthropicChatOptions defaultOptions; - public BedrockAnthropicChatModel(AnthropicChatBedrockApi chatApi) { - this(chatApi, - AnthropicChatOptions.builder() - .withTemperature(0.8f) - .withMaxTokensToSample(500) - .withTopK(10) - .withAnthropicVersion(AnthropicChatBedrockApi.DEFAULT_ANTHROPIC_VERSION) - .build()); + public BedrockAnthropicChatModel(BedrockConverseApi converseApi) { + this(converseApi, AnthropicChatOptions.builder().withTemperature(0.8f).withTopK(10).build()); } - public BedrockAnthropicChatModel(AnthropicChatBedrockApi chatApi, AnthropicChatOptions options) { - this.anthropicChatApi = chatApi; + public BedrockAnthropicChatModel(BedrockConverseApi converseApi, AnthropicChatOptions options) { + this(AnthropicChatModel.CLAUDE_V2.id(), converseApi, options); + } + + public BedrockAnthropicChatModel(String modelId, BedrockConverseApi converseApi, AnthropicChatOptions options) { + Assert.notNull(modelId, "modelId must not be null."); + Assert.notNull(converseApi, "BedrockConverseApi must not be null."); + Assert.notNull(options, "AnthropicChatOptions must not be null."); + + this.modelId = modelId; + this.converseApi = converseApi; this.defaultOptions = options; } @Override public ChatResponse call(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); - AnthropicChatRequest request = createRequest(prompt); + var request = BedrockConverseApiUtils.createConverseRequest(modelId, prompt, defaultOptions); - AnthropicChatResponse response = this.anthropicChatApi.chatCompletion(request); + ConverseResponse response = this.converseApi.converse(request); - return new ChatResponse(List.of(new Generation(response.completion()))); + return BedrockConverseApiUtils.convertConverseResponse(response); } @Override public Flux stream(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); - AnthropicChatRequest request = createRequest(prompt); + var request = BedrockConverseApiUtils.createConverseStreamRequest(modelId, prompt, defaultOptions); - Flux fluxResponse = this.anthropicChatApi.chatCompletionStream(request); + Flux fluxResponse = this.converseApi.converseStream(request); - return fluxResponse.map(response -> { - String stopReason = response.stopReason() != null ? response.stopReason() : null; - var generation = new Generation(response.completion()); - if (response.amazonBedrockInvocationMetrics() != null) { - generation = generation.withGenerationMetadata( - ChatGenerationMetadata.from(stopReason, response.amazonBedrockInvocationMetrics())); - } - return new ChatResponse(List.of(generation)); - }); + return fluxResponse.map(output -> BedrockConverseApiUtils.convertConverseStreamOutput(output)); + } + + @Override + public ChatOptions getDefaultOptions() { + return AnthropicChatOptions.fromOptions(this.defaultOptions); } /** - * Accessible for testing. + * Anthropic models version. */ - AnthropicChatRequest createRequest(Prompt prompt) { - - // Related to: https://github.com/spring-projects/spring-ai/issues/404 - final String promptValue = MessageToPromptConverter.create("\n").toPrompt(prompt.getInstructions()); - - AnthropicChatRequest request = AnthropicChatRequest.builder(promptValue).build(); - - if (this.defaultOptions != null) { - request = ModelOptionsUtils.merge(request, this.defaultOptions, AnthropicChatRequest.class); + public enum AnthropicChatModel implements ModelDescription { + + /** + * anthropic.claude-instant-v1 + */ + CLAUDE_INSTANT_V1("anthropic.claude-instant-v1"), + /** + * anthropic.claude-v2 + */ + CLAUDE_V2("anthropic.claude-v2"), + /** + * anthropic.claude-v2:1 + */ + CLAUDE_V21("anthropic.claude-v2:1"); + + private final String id; + + /** + * @return The model id. + */ + public String id() { + return id; } - if (prompt.getOptions() != null) { - if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { - AnthropicChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, - ChatOptions.class, AnthropicChatOptions.class); - request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, AnthropicChatRequest.class); - } - else { - throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " - + prompt.getOptions().getClass().getSimpleName()); - } + AnthropicChatModel(String value) { + this.id = value; } - return request; - } + @Override + public String getModelName() { + return this.id; + } - @Override - public ChatOptions getDefaultOptions() { - return AnthropicChatOptions.fromOptions(this.defaultOptions); } } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java index cd85fb7a5e..3313b563db 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java @@ -33,11 +33,13 @@ import org.springframework.util.Assert; /** + * @deprecated Use {@link BedrockConverseApi} instead. * @author Christian Tzolov * @author Wei Jiang * @since 0.8.0 */ // @formatter:off +@Deprecated public class AnthropicChatBedrockApi extends AbstractBedrockApi { diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/Anthropic3ChatOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/Anthropic3ChatOptions.java index b4995683a3..98fe80bb8d 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/Anthropic3ChatOptions.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/Anthropic3ChatOptions.java @@ -23,7 +23,11 @@ import java.util.List; /** + * Java {@link ChatOptions} for the Bedrock Anthropic chat generative model chat options. + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-text-completion.html + * * @author Ben Middleton + * @author Wei Jiang * @since 1.0.0 */ @JsonInclude(Include.NON_NULL) @@ -62,10 +66,6 @@ public class Anthropic3ChatOptions implements ChatOptions { */ private @JsonProperty("stop_sequences") List stopSequences; - /** - * The version of the generative to use. The default value is bedrock-2023-05-31. - */ - private @JsonProperty("anthropic_version") String anthropicVersion; // @formatter:on public static Builder builder() { @@ -101,11 +101,6 @@ public Builder withStopSequences(List stopSequences) { return this; } - public Builder withAnthropicVersion(String anthropicVersion) { - this.options.setAnthropicVersion(anthropicVersion); - return this; - } - public Anthropic3ChatOptions build() { return this.options; } @@ -155,21 +150,12 @@ public void setStopSequences(List stopSequences) { this.stopSequences = stopSequences; } - public String getAnthropicVersion() { - return this.anthropicVersion; - } - - public void setAnthropicVersion(String anthropicVersion) { - this.anthropicVersion = anthropicVersion; - } - public static Anthropic3ChatOptions fromOptions(Anthropic3ChatOptions fromOptions) { return builder().withTemperature(fromOptions.getTemperature()) .withMaxTokens(fromOptions.getMaxTokens()) .withTopK(fromOptions.getTopK()) .withTopP(fromOptions.getTopP()) .withStopSequences(fromOptions.getStopSequences()) - .withAnthropicVersion(fromOptions.getAnthropicVersion()) .build(); } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java index e84ad25204..bb7d1b9690 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java @@ -15,181 +15,119 @@ */ package org.springframework.ai.bedrock.anthropic3; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; - import reactor.core.publisher.Flux; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatRequest; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatResponse; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatStreamingResponse.StreamingType; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.ChatCompletionMessage; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.ChatCompletionMessage.Role; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.MediaContent; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.api.BedrockConverseApiUtils; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.model.StreamingChatModel; -import org.springframework.ai.chat.messages.Message; -import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; -import org.springframework.util.CollectionUtils; +import org.springframework.ai.model.ModelDescription; +import org.springframework.util.Assert; /** - * Java {@link ChatModel} and {@link StreamingChatModel} for the Bedrock Anthropic chat - * generative. + * Java {@link ChatModel} and {@link StreamingChatModel} for the Bedrock Anthropic3 chat + * generative model. * * @author Ben Middleton * @author Christian Tzolov + * @author Wei Jiang * @since 1.0.0 */ public class BedrockAnthropic3ChatModel implements ChatModel, StreamingChatModel { - private final Anthropic3ChatBedrockApi anthropicChatApi; + private final String modelId; + + private final BedrockConverseApi converseApi; private final Anthropic3ChatOptions defaultOptions; - public BedrockAnthropic3ChatModel(Anthropic3ChatBedrockApi chatApi) { - this(chatApi, - Anthropic3ChatOptions.builder() - .withTemperature(0.8f) - .withMaxTokens(500) - .withTopK(10) - .withAnthropicVersion(Anthropic3ChatBedrockApi.DEFAULT_ANTHROPIC_VERSION) - .build()); + public BedrockAnthropic3ChatModel(BedrockConverseApi converseApi) { + this(converseApi, + Anthropic3ChatOptions.builder().withTemperature(0.8f).withMaxTokens(500).withTopK(10).build()); + } + + public BedrockAnthropic3ChatModel(BedrockConverseApi converseApi, Anthropic3ChatOptions options) { + this(Anthropic3ChatModel.CLAUDE_V3_SONNET.id(), converseApi, options); } - public BedrockAnthropic3ChatModel(Anthropic3ChatBedrockApi chatApi, Anthropic3ChatOptions options) { - this.anthropicChatApi = chatApi; + public BedrockAnthropic3ChatModel(String modelId, BedrockConverseApi converseApi, Anthropic3ChatOptions options) { + Assert.notNull(modelId, "modelId must not be null."); + Assert.notNull(converseApi, "BedrockConverseApi must not be null."); + Assert.notNull(options, "Anthropic3ChatOptions must not be null."); + + this.modelId = modelId; + this.converseApi = converseApi; this.defaultOptions = options; } @Override public ChatResponse call(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); - AnthropicChatRequest request = createRequest(prompt); + var request = BedrockConverseApiUtils.createConverseRequest(modelId, prompt, defaultOptions); - AnthropicChatResponse response = this.anthropicChatApi.chatCompletion(request); + ConverseResponse response = this.converseApi.converse(request); - return new ChatResponse(List.of(new Generation(response.content().get(0).text()))); + return BedrockConverseApiUtils.convertConverseResponse(response); } @Override public Flux stream(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); - AnthropicChatRequest request = createRequest(prompt); - - Flux fluxResponse = this.anthropicChatApi - .chatCompletionStream(request); + var request = BedrockConverseApiUtils.createConverseStreamRequest(modelId, prompt, defaultOptions); - AtomicReference inputTokens = new AtomicReference<>(0); - return fluxResponse.map(response -> { - if (response.type() == StreamingType.MESSAGE_START) { - inputTokens.set(response.message().usage().inputTokens()); - } - String content = response.type() == StreamingType.CONTENT_BLOCK_DELTA ? response.delta().text() : ""; + Flux fluxResponse = this.converseApi.converseStream(request); - var generation = new Generation(content); - - if (response.type() == StreamingType.MESSAGE_DELTA) { - generation = generation.withGenerationMetadata(ChatGenerationMetadata - .from(response.delta().stopReason(), new Anthropic3ChatBedrockApi.AnthropicUsage(inputTokens.get(), - response.usage().outputTokens()))); - } + return fluxResponse.map(output -> BedrockConverseApiUtils.convertConverseStreamOutput(output)); + } - return new ChatResponse(List.of(generation)); - }); + @Override + public ChatOptions getDefaultOptions() { + return Anthropic3ChatOptions.fromOptions(this.defaultOptions); } /** - * Accessible for testing. + * Anthropic3 models version. */ - AnthropicChatRequest createRequest(Prompt prompt) { - - AnthropicChatRequest request = AnthropicChatRequest.builder(toAnthropicMessages(prompt)) - .withSystem(toAnthropicSystemContext(prompt)) - .build(); - - if (this.defaultOptions != null) { - request = ModelOptionsUtils.merge(request, this.defaultOptions, AnthropicChatRequest.class); + public enum Anthropic3ChatModel implements ModelDescription { + + /** + * anthropic.claude-3-sonnet-20240229-v1:0 + */ + CLAUDE_V3_SONNET("anthropic.claude-3-sonnet-20240229-v1:0"), + /** + * anthropic.claude-3-haiku-20240307-v1:0 + */ + CLAUDE_V3_HAIKU("anthropic.claude-3-haiku-20240307-v1:0"), + /** + * anthropic.claude-3-opus-20240229-v1:0 + */ + CLAUDE_V3_OPUS("anthropic.claude-3-opus-20240229-v1:0"); + + private final String id; + + /** + * @return The model id. + */ + public String id() { + return id; } - if (prompt.getOptions() != null) { - if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { - Anthropic3ChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, - ChatOptions.class, Anthropic3ChatOptions.class); - request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, AnthropicChatRequest.class); - } - else { - throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " - + prompt.getOptions().getClass().getSimpleName()); - } + Anthropic3ChatModel(String value) { + this.id = value; } - return request; - } - - /** - * Extracts system context from prompt. - * @param prompt The prompt. - * @return The system context. - */ - private String toAnthropicSystemContext(Prompt prompt) { - - return prompt.getInstructions() - .stream() - .filter(m -> m.getMessageType() == MessageType.SYSTEM) - .map(Message::getContent) - .collect(Collectors.joining(System.lineSeparator())); - } - - /** - * Extracts list of messages from prompt. - * @param prompt The prompt. - * @return The list of {@link ChatCompletionMessage}. - */ - private List toAnthropicMessages(Prompt prompt) { - - return prompt.getInstructions() - .stream() - .filter(m -> m.getMessageType() == MessageType.USER || m.getMessageType() == MessageType.ASSISTANT) - .map(message -> { - List contents = new ArrayList<>(List.of(new MediaContent(message.getContent()))); - if (!CollectionUtils.isEmpty(message.getMedia())) { - List mediaContent = message.getMedia() - .stream() - .map(media -> new MediaContent(media.getMimeType().toString(), - this.fromMediaData(media.getData()))) - .toList(); - contents.addAll(mediaContent); - } - return new ChatCompletionMessage(contents, Role.valueOf(message.getMessageType().name())); - }) - .toList(); - } - - private String fromMediaData(Object mediaData) { - if (mediaData instanceof byte[] bytes) { - return Base64.getEncoder().encodeToString(bytes); - } - else if (mediaData instanceof String text) { - return text; + @Override + public String getModelName() { + return this.id; } - else { - throw new IllegalArgumentException("Unsupported media data type: " + mediaData.getClass().getSimpleName()); - } - } - @Override - public ChatOptions getDefaultOptions() { - return Anthropic3ChatOptions.fromOptions(this.defaultOptions); } } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java index 8b5b29ed1e..dce8c9ccba 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java @@ -39,12 +39,14 @@ * * It is meant to replace the previous Chat API, which is now deprecated. * + * @deprecated Use {@link BedrockConverseApi} instead. * @author Ben Middleton * @author Christian Tzolov * @author Wei Jiang * @since 1.0.0 */ // @formatter:off +@Deprecated public class Anthropic3ChatBedrockApi extends AbstractBedrockApi { diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtils.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtils.java new file mode 100644 index 0000000000..8cb921cd64 --- /dev/null +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtils.java @@ -0,0 +1,311 @@ +/* + * Copyright 2023 - 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. + */ +//@formatter:off +package org.springframework.ai.bedrock.api; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.ai.bedrock.BedrockChatResponseMetadata; +import org.springframework.ai.chat.messages.MessageType; +import org.springframework.ai.chat.metadata.ChatGenerationMetadata; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.ModelOptions; +import org.springframework.util.Assert; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.core.document.Document; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlockDeltaEvent; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlockStopEvent; +import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseRequest; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamMetadataEvent; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamRequest; +import software.amazon.awssdk.services.bedrockruntime.model.ImageBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ImageSource; +import software.amazon.awssdk.services.bedrockruntime.model.Message; +import software.amazon.awssdk.services.bedrockruntime.model.MessageStartEvent; +import software.amazon.awssdk.services.bedrockruntime.model.MessageStopEvent; +import software.amazon.awssdk.services.bedrockruntime.model.SystemContentBlock; + +/** + * Amazon Bedrock Converse API utils. + * + * @author Wei Jiang + * @since 1.0.0 + */ +public class BedrockConverseApiUtils { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Convert {@link Prompt} to {@link ConverseRequest} with model id and options. It + * will merge default options and runtime options to converse inference parameters. + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_RequestSyntax + * + * @param modelId The Amazon Bedrock Model Id. + * @param prompt The prompt that needs to convert. + * @return Amazon Bedrock Converse request. + */ + public static ConverseRequest createConverseRequest(String modelId, Prompt prompt) { + return createConverseRequest(modelId, prompt, null); + } + + /** + * Convert {@link Prompt} to {@link ConverseRequest} with model id and options. It + * will merge default options and runtime options to converse inference parameters. + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_RequestSyntax + * + * @param modelId The Amazon Bedrock Model Id. + * @param prompt The prompt that needs to convert. + * @param defaultOptions The default options needs to convert. + * @return Amazon Bedrock Converse request. + */ + public static ConverseRequest createConverseRequest(String modelId, Prompt prompt, ChatOptions defaultOptions) { + Assert.notNull(modelId, "'modelId' must not be null."); + Assert.notNull(prompt, "'prompt' must not be null."); + + List systemMessages = getPromptSystemContentBlocks(prompt); + + List userMessages = getPromptMessages(prompt); + + Document additionalModelRequestFields = getChatOptionsAdditionalModelRequestFields(defaultOptions, + prompt.getOptions()); + + return ConverseRequest.builder() + .modelId(modelId) + .messages(userMessages) + .system(systemMessages) + .additionalModelRequestFields(additionalModelRequestFields) + .build(); + } + + /** + * Convert {@link Prompt} to {@link ConverseStreamRequest} with model id and options. + * It will merge default options and runtime options to converse inference parameters. + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStream.html + * + * @param modelId The Amazon Bedrock Model Id. + * @param prompt The prompt that needs to convert. + * @param defaultOptions The default options needs to convert. + * @return Amazon Bedrock Converse stream request. + */ + public static ConverseStreamRequest createConverseStreamRequest(String modelId, Prompt prompt, + ChatOptions defaultOptions) { + Assert.notNull(modelId, "'modelId' must not be null."); + Assert.notNull(prompt, "'prompt' must not be null."); + + List systemMessages = getPromptSystemContentBlocks(prompt); + + List userMessages = getPromptMessages(prompt); + + Document additionalModelRequestFields = getChatOptionsAdditionalModelRequestFields(defaultOptions, + prompt.getOptions()); + + return ConverseStreamRequest.builder() + .modelId(modelId) + .messages(userMessages) + .system(systemMessages) + .additionalModelRequestFields(additionalModelRequestFields) + .build(); + } + + /** + * Convert {@link ConverseResponse} to {@link ChatResponse} includes model output, + * stopReason, usage, metrics etc. + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_ResponseSyntax + * + * @param response The Bedrock Converse response. + * @return The ChatResponse entity. + */ + public static ChatResponse convertConverseResponse(ConverseResponse response) { + Assert.notNull(response, "'response' must not be null."); + + String stopReason = response.stopReasonAsString(); + + List generations = response.output() + .message() + .content() + .stream() + .map(content -> new Generation(content.text()) + .withGenerationMetadata(ChatGenerationMetadata.from(stopReason, null))) + .toList(); + + return new ChatResponse(generations, BedrockChatResponseMetadata.from(response)); + } + + /** + * Convert {@link ConverseStreamOutput} to {@link ChatResponse} includes model output, + * stopReason, usage, metrics etc. + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_ResponseSyntax + * + * @param output The Bedrock Converse stream output. + * @return The ChatResponse entity. + */ + public static ChatResponse convertConverseStreamOutput(ConverseStreamOutput output) { + if (output instanceof MessageStartEvent) { + return new ChatResponse(List.of()); + } else if (output instanceof ContentBlockDeltaEvent contentBlockDeltaEvent) { + return new ChatResponse(List.of(new Generation(contentBlockDeltaEvent.delta().text()))); + } else if (output instanceof ContentBlockStopEvent) { + return new ChatResponse(List.of()); + } else if (output instanceof MessageStopEvent messageStopEvent) { + ChatGenerationMetadata metadata = ChatGenerationMetadata.from(messageStopEvent.stopReasonAsString(), null); + + return new ChatResponse(List.of(new Generation("").withGenerationMetadata(metadata))); + } else if (output instanceof ConverseStreamMetadataEvent converseStreamMetadataEvent) { + return new ChatResponse(List.of(), BedrockChatResponseMetadata.from(converseStreamMetadataEvent)); + } else { + return new ChatResponse(List.of()); + } + } + + private static List getPromptSystemContentBlocks(Prompt prompt) { + return prompt.getInstructions() + .stream() + .filter(message -> message.getMessageType() == MessageType.SYSTEM) + .map(instruction -> SystemContentBlock.builder().text(instruction.getContent()).build()) + .toList(); + } + + private static List getPromptMessages(Prompt prompt) { + return prompt.getInstructions() + .stream() + .filter(message -> message.getMessageType() == MessageType.USER + || message.getMessageType() == MessageType.ASSISTANT) + .map(instruction -> Message.builder() + .content(getInstructionContents(instruction)) + .role(instruction.getMessageType() == MessageType.USER ? ConversationRole.USER + : ConversationRole.ASSISTANT) + .build()) + .toList(); + } + + private static List getInstructionContents(org.springframework.ai.chat.messages.Message instruction) { + List contents = new ArrayList<>(); + + ContentBlock textContentBlock = ContentBlock.builder().text(instruction.getContent()).build(); + + contents.add(textContentBlock); + + List mediaContentBlocks = instruction.getMedia() + .stream() + .map(media -> ContentBlock.builder() + .image(ImageBlock.builder() + .format(media.getMimeType().getSubtype()) + .source(ImageSource.fromBytes(SdkBytes.fromByteArray(getContentMediaData(media.getData())))) + .build()) + .build()) + .toList(); + + contents.addAll(mediaContentBlocks); + + return contents; + } + + private static byte[] getContentMediaData(Object mediaData) { + if (mediaData instanceof byte[] bytes) { + return bytes; + } + else if (mediaData instanceof String text) { + return text.getBytes(); + } + else { + throw new IllegalArgumentException("Unsupported media data type: " + mediaData.getClass().getSimpleName()); + } + } + + @SuppressWarnings("unchecked") + private static Document getChatOptionsAdditionalModelRequestFields(ChatOptions defaultOptions, + ModelOptions promptOptions) { + if (defaultOptions == null && promptOptions == null) { + return null; + } + + Map attributes = new HashMap<>(); + + if (defaultOptions != null) { + Map options = objectMapper.convertValue(defaultOptions, Map.class); + + attributes.putAll(options); + } + + if (promptOptions != null) { + if (promptOptions instanceof ChatOptions runtimeOptions) { + Map options = objectMapper.convertValue(runtimeOptions, Map.class); + + attributes.putAll(options); + } else { + throw new IllegalArgumentException( + "Prompt options are not of type ChatOptions:" + promptOptions.getClass().getSimpleName()); + } + } + + return convertAttributesToDocument(attributes); + } + + private static Document convertAttributesToDocument(Map attributes) { + Map attr = attributes.entrySet() + .stream() + .collect(Collectors.toMap(e -> e.getKey(), e -> convertAttributeValueToDocument(e.getValue()))); + + return Document.fromMap(attr); + } + + @SuppressWarnings("unchecked") + private static Document convertAttributeValueToDocument(Object value) { + if (value == null) { + return Document.fromNull(); + } else if (value instanceof String stringValue) { + return Document.fromString(stringValue); + } else if (value instanceof Boolean booleanValue) { + return Document.fromBoolean(booleanValue); + } else if (value instanceof Integer integerValue) { + return Document.fromNumber(integerValue); + } else if (value instanceof Long longValue) { + return Document.fromNumber(longValue); + } else if (value instanceof Float floatValue) { + return Document.fromNumber(floatValue); + } else if (value instanceof Double doubleValue) { + return Document.fromNumber(doubleValue); + } else if (value instanceof BigDecimal bigDecimalValue) { + return Document.fromNumber(bigDecimalValue); + } else if (value instanceof BigInteger bigIntegerValue) { + return Document.fromNumber(bigIntegerValue); + } else if (value instanceof List listValue) { + return Document.fromList(listValue.stream().map(v -> convertAttributeValueToDocument(v)).toList()); + } else if (value instanceof Map mapValue) { + return convertAttributesToDocument(mapValue); + } else { + throw new IllegalArgumentException("Unsupported value type:" + value.getClass().getSimpleName()); + } + } + +} +//@formatter:on diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModel.java index 5c4be40b8d..41019abb15 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModel.java @@ -15,103 +15,74 @@ */ package org.springframework.ai.bedrock.cohere; -import java.util.List; - import reactor.core.publisher.Flux; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; -import org.springframework.ai.bedrock.BedrockUsage; -import org.springframework.ai.bedrock.MessageToPromptConverter; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatResponse; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.api.BedrockConverseApiUtils; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.model.StreamingChatModel; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; -import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.model.ModelDescription; import org.springframework.util.Assert; /** + * Java {@link ChatModel} and {@link StreamingChatModel} for the Bedrock Cohere chat + * generative model. + * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ public class BedrockCohereChatModel implements ChatModel, StreamingChatModel { - private final CohereChatBedrockApi chatApi; + private final String modelId; + + private final BedrockConverseApi converseApi; private final BedrockCohereChatOptions defaultOptions; - public BedrockCohereChatModel(CohereChatBedrockApi chatApi) { - this(chatApi, BedrockCohereChatOptions.builder().build()); + public BedrockCohereChatModel(BedrockConverseApi converseApi) { + this(converseApi, BedrockCohereChatOptions.builder().build()); } - public BedrockCohereChatModel(CohereChatBedrockApi chatApi, BedrockCohereChatOptions options) { - Assert.notNull(chatApi, "CohereChatBedrockApi must not be null"); + public BedrockCohereChatModel(BedrockConverseApi converseApi, BedrockCohereChatOptions options) { + this(CohereChatModel.COHERE_COMMAND_V14.id(), converseApi, options); + } + + public BedrockCohereChatModel(String modelId, BedrockConverseApi converseApi, BedrockCohereChatOptions options) { + Assert.notNull(modelId, "modelId must not be null."); + Assert.notNull(converseApi, "BedrockConverseApi must not be null."); Assert.notNull(options, "BedrockCohereChatOptions must not be null"); - this.chatApi = chatApi; + this.modelId = modelId; + this.converseApi = converseApi; this.defaultOptions = options; } @Override public ChatResponse call(Prompt prompt) { - CohereChatResponse response = this.chatApi.chatCompletion(this.createRequest(prompt, false)); - List generations = response.generations().stream().map(g -> { - return new Generation(g.text()); - }).toList(); + Assert.notNull(prompt, "Prompt must not be null."); + + var request = BedrockConverseApiUtils.createConverseRequest(modelId, prompt, defaultOptions); + + ConverseResponse response = this.converseApi.converse(request); - return new ChatResponse(generations); + return BedrockConverseApiUtils.convertConverseResponse(response); } @Override public Flux stream(Prompt prompt) { - return this.chatApi.chatCompletionStream(this.createRequest(prompt, true)).map(g -> { - if (g.isFinished()) { - String finishReason = g.finishReason().name(); - Usage usage = BedrockUsage.from(g.amazonBedrockInvocationMetrics()); - return new ChatResponse(List - .of(new Generation("").withGenerationMetadata(ChatGenerationMetadata.from(finishReason, usage)))); - } - return new ChatResponse(List.of(new Generation(g.text()))); - }); - } + Assert.notNull(prompt, "Prompt must not be null."); - /** - * Test access. - */ - CohereChatRequest createRequest(Prompt prompt, boolean stream) { - final String promptValue = MessageToPromptConverter.create().toPrompt(prompt.getInstructions()); - - var request = CohereChatRequest.builder(promptValue) - .withTemperature(this.defaultOptions.getTemperature()) - .withTopP(this.defaultOptions.getTopP()) - .withTopK(this.defaultOptions.getTopK()) - .withMaxTokens(this.defaultOptions.getMaxTokens()) - .withStopSequences(this.defaultOptions.getStopSequences()) - .withReturnLikelihoods(this.defaultOptions.getReturnLikelihoods()) - .withStream(stream) - .withNumGenerations(this.defaultOptions.getNumGenerations()) - .withLogitBias(this.defaultOptions.getLogitBias()) - .withTruncate(this.defaultOptions.getTruncate()) - .build(); - - if (prompt.getOptions() != null) { - if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { - BedrockCohereChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, - ChatOptions.class, BedrockCohereChatOptions.class); - request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, CohereChatRequest.class); - } - else { - throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " - + prompt.getOptions().getClass().getSimpleName()); - } - } + var request = BedrockConverseApiUtils.createConverseStreamRequest(modelId, prompt, defaultOptions); + + Flux fluxResponse = this.converseApi.converseStream(request); - return request; + return fluxResponse.map(output -> BedrockConverseApiUtils.convertConverseStreamOutput(output)); } @Override @@ -119,4 +90,39 @@ public ChatOptions getDefaultOptions() { return BedrockCohereChatOptions.fromOptions(this.defaultOptions); } + /** + * Cohere models version. + */ + public enum CohereChatModel implements ModelDescription { + + /** + * cohere.command-light-text-v14 + */ + COHERE_COMMAND_LIGHT_V14("cohere.command-light-text-v14"), + + /** + * cohere.command-text-v14 + */ + COHERE_COMMAND_V14("cohere.command-text-v14"); + + private final String id; + + /** + * @return The model id. + */ + public String id() { + return id; + } + + CohereChatModel(String value) { + this.id = value; + } + + @Override + public String getModelName() { + return this.id; + } + + } + } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatOptions.java index e0ab181cc9..f2d88efb24 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatOptions.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatOptions.java @@ -21,13 +21,15 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest.LogitBias; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest.ReturnLikelihoods; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest.Truncate; import org.springframework.ai.chat.prompt.ChatOptions; /** + * Java {@link ChatOptions} for the Bedrock Cohere Command chat generative model chat + * options. + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command.html + * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ @JsonInclude(Include.NON_NULL) @@ -213,6 +215,59 @@ public void setTruncate(Truncate truncate) { this.truncate = truncate; } + /** + * Specify how and if the token likelihoods are returned with the response. + */ + public static enum ReturnLikelihoods { + + /** + * Only return likelihoods for generated tokens. + */ + GENERATION, + /** + * Return likelihoods for all tokens. + */ + ALL, + /** + * (Default) Don't return any likelihoods. + */ + NONE + + } + + /** + * Specifies how the API handles inputs longer than the maximum token length. If you + * specify START or END, the model discards the input until the remaining input is + * exactly the maximum input token length for the model. + */ + public enum Truncate { + + /** + * Returns an error when the input exceeds the maximum input token length. + */ + NONE, + /** + * Discard the start of the input. + */ + START, + /** + * (Default) Discards the end of the input. + */ + END + + } + + /** + * Prevents the model from generating unwanted tokens or incentivize the model to + * include desired tokens. + * + * @param token The token likelihoods. + * @param bias A float between -10 and 10. + */ + @JsonInclude(Include.NON_NULL) + public record LogitBias(@JsonProperty("token") Integer token, @JsonProperty("bias") Float bias) { + } + public static BedrockCohereChatOptions fromOptions(BedrockCohereChatOptions fromOptions) { return builder().withTemperature(fromOptions.getTemperature()) .withTopP(fromOptions.getTopP()) diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java index 766271b87c..03904cdcca 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java @@ -37,10 +37,13 @@ * Java client for the Bedrock Cohere chat model. * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere.html * + * @deprecated Use {@link BedrockConverseApi} instead. + * * @author Christian Tzolov * @author Wei Jiang * @since 0.8.0 */ +@Deprecated public class CohereChatBedrockApi extends AbstractBedrockApi { diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatModel.java index 13518fd512..f18d9957c2 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatModel.java @@ -16,40 +16,34 @@ package org.springframework.ai.bedrock.jurassic2; -import org.springframework.ai.bedrock.MessageToPromptConverter; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatRequest; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.api.BedrockConverseApiUtils; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.model.ModelDescription; import org.springframework.util.Assert; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; + /** * Java {@link ChatModel} for the Bedrock Jurassic2 chat generative model. * * @author Ahmed Yousri + * @author Wei Jiang * @since 1.0.0 */ public class BedrockAi21Jurassic2ChatModel implements ChatModel { - private final Ai21Jurassic2ChatBedrockApi chatApi; - - private final BedrockAi21Jurassic2ChatOptions defaultOptions; + private final String modelId; - public BedrockAi21Jurassic2ChatModel(Ai21Jurassic2ChatBedrockApi chatApi, BedrockAi21Jurassic2ChatOptions options) { - Assert.notNull(chatApi, "Ai21Jurassic2ChatBedrockApi must not be null"); - Assert.notNull(options, "BedrockAi21Jurassic2ChatOptions must not be null"); + private final BedrockConverseApi converseApi; - this.chatApi = chatApi; - this.defaultOptions = options; - } + private final BedrockAi21Jurassic2ChatOptions defaultOptions; - public BedrockAi21Jurassic2ChatModel(Ai21Jurassic2ChatBedrockApi chatApi) { - this(chatApi, + public BedrockAi21Jurassic2ChatModel(BedrockConverseApi converseApi) { + this(converseApi, BedrockAi21Jurassic2ChatOptions.builder() .withTemperature(0.8f) .withTopP(0.9f) @@ -57,72 +51,70 @@ public BedrockAi21Jurassic2ChatModel(Ai21Jurassic2ChatBedrockApi chatApi) { .build()); } - @Override - public ChatResponse call(Prompt prompt) { - var request = createRequest(prompt); - var response = this.chatApi.chatCompletion(request); - - return new ChatResponse(response.completions() - .stream() - .map(completion -> new Generation(completion.data().text()) - .withGenerationMetadata(ChatGenerationMetadata.from(completion.finishReason().reason(), null))) - .toList()); + public BedrockAi21Jurassic2ChatModel(BedrockConverseApi converseApi, BedrockAi21Jurassic2ChatOptions options) { + this(Ai21Jurassic2ChatModel.AI21_J2_MID_V1.id(), converseApi, options); } - private Ai21Jurassic2ChatRequest createRequest(Prompt prompt) { + public BedrockAi21Jurassic2ChatModel(String modelId, BedrockConverseApi converseApi, + BedrockAi21Jurassic2ChatOptions options) { + Assert.notNull(modelId, "modelId must not be null."); + Assert.notNull(converseApi, "BedrockConverseApi must not be null."); + Assert.notNull(options, "BedrockAi21Jurassic2ChatOptions must not be null."); - final String promptValue = MessageToPromptConverter.create().toPrompt(prompt.getInstructions()); + this.modelId = modelId; + this.converseApi = converseApi; + this.defaultOptions = options; + } - Ai21Jurassic2ChatRequest request = Ai21Jurassic2ChatRequest.builder(promptValue).build(); + @Override + public ChatResponse call(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); - if (prompt.getOptions() != null) { - if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { - BedrockAi21Jurassic2ChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, - ChatOptions.class, BedrockAi21Jurassic2ChatOptions.class); - request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, Ai21Jurassic2ChatRequest.class); - } - else { - throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " - + prompt.getOptions().getClass().getSimpleName()); - } - } + var request = BedrockConverseApiUtils.createConverseRequest(modelId, prompt, defaultOptions); - if (this.defaultOptions != null) { - request = ModelOptionsUtils.merge(request, this.defaultOptions, Ai21Jurassic2ChatRequest.class); - } + ConverseResponse response = this.converseApi.converse(request); - return request; + return BedrockConverseApiUtils.convertConverseResponse(response); } - public static Builder builder(Ai21Jurassic2ChatBedrockApi chatApi) { - return new Builder(chatApi); + @Override + public ChatOptions getDefaultOptions() { + return BedrockAi21Jurassic2ChatOptions.fromOptions(this.defaultOptions); } - public static class Builder { + /** + * Ai21 Jurassic2 models version. + */ + public enum Ai21Jurassic2ChatModel implements ModelDescription { - private final Ai21Jurassic2ChatBedrockApi chatApi; + /** + * ai21.j2-mid-v1 + */ + AI21_J2_MID_V1("ai21.j2-mid-v1"), - private BedrockAi21Jurassic2ChatOptions options; + /** + * ai21.j2-ultra-v1 + */ + AI21_J2_ULTRA_V1("ai21.j2-ultra-v1"); - public Builder(Ai21Jurassic2ChatBedrockApi chatApi) { - this.chatApi = chatApi; - } + private final String id; - public Builder withOptions(BedrockAi21Jurassic2ChatOptions options) { - this.options = options; - return this; + /** + * @return The model id. + */ + public String id() { + return id; } - public BedrockAi21Jurassic2ChatModel build() { - return new BedrockAi21Jurassic2ChatModel(chatApi, - options != null ? options : BedrockAi21Jurassic2ChatOptions.builder().build()); + Ai21Jurassic2ChatModel(String value) { + this.id = value; } - } + @Override + public String getModelName() { + return this.id; + } - @Override - public ChatOptions getDefaultOptions() { - return BedrockAi21Jurassic2ChatOptions.fromOptions(this.defaultOptions); } } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatOptions.java index c165c61c1e..bab4d0b03b 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatOptions.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatOptions.java @@ -18,23 +18,22 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + import org.springframework.ai.chat.prompt.ChatOptions; /** - * Request body for the /complete endpoint of the Jurassic-2 API. + * Java {@link ChatOptions} for the Bedrock Jurassic-2 chat generative model chat options. + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jurassic2.html * * @author Ahmed Yousri + * @author Wei Jiang * @since 1.0.0 */ @JsonInclude(JsonInclude.Include.NON_NULL) public class BedrockAi21Jurassic2ChatOptions implements ChatOptions { - /** - * The text which the model is requested to continue. - */ - @JsonProperty("prompt") - private String prompt; - /** * Number of completions to sample and return. */ @@ -75,7 +74,7 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions { * Stops decoding if any of the strings is generated. */ @JsonProperty("stopSequences") - private String[] stopSequences; + private List stopSequences; /** * Penalty object for frequency. @@ -97,22 +96,6 @@ public class BedrockAi21Jurassic2ChatOptions implements ChatOptions { // Getters and setters - /** - * Gets the prompt text for the model to continue. - * @return The prompt text. - */ - public String getPrompt() { - return prompt; - } - - /** - * Sets the prompt text for the model to continue. - * @param prompt The prompt text. - */ - public void setPrompt(String prompt) { - this.prompt = prompt; - } - /** * Gets the number of completions to sample and return. * @return The number of results. @@ -216,7 +199,7 @@ public void setTopK(Integer topK) { * Gets the stop sequences for stopping decoding if any of the strings is generated. * @return The stop sequences. */ - public String[] getStopSequences() { + public List getStopSequences() { return stopSequences; } @@ -224,7 +207,7 @@ public String[] getStopSequences() { * Sets the stop sequences for stopping decoding if any of the strings is generated. * @param stopSequences The stop sequences. */ - public void setStopSequences(String[] stopSequences) { + public void setStopSequences(List stopSequences) { this.stopSequences = stopSequences; } @@ -284,11 +267,6 @@ public static class Builder { private final BedrockAi21Jurassic2ChatOptions request = new BedrockAi21Jurassic2ChatOptions(); - public Builder withPrompt(String prompt) { - request.setPrompt(prompt); - return this; - } - public Builder withNumResults(Integer numResults) { request.setNumResults(numResults); return this; @@ -314,7 +292,7 @@ public Builder withTopP(Float topP) { return this; } - public Builder withStopSequences(String[] stopSequences) { + public Builder withStopSequences(List stopSequences) { request.setStopSequences(stopSequences); return this; } @@ -414,8 +392,7 @@ public Penalty build() { } public static BedrockAi21Jurassic2ChatOptions fromOptions(BedrockAi21Jurassic2ChatOptions fromOptions) { - return builder().withPrompt(fromOptions.getPrompt()) - .withNumResults(fromOptions.getNumResults()) + return builder().withNumResults(fromOptions.getNumResults()) .withMaxTokens(fromOptions.getMaxTokens()) .withMinTokens(fromOptions.getMinTokens()) .withTemperature(fromOptions.getTemperature()) diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java index fecf70fa4e..0c7cb2bf24 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java @@ -36,10 +36,13 @@ * Java client for the Bedrock Jurassic2 chat model. * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jurassic2.html * + * @deprecated Use {@link BedrockConverseApi} instead. + * * @author Christian Tzolov * @author Wei Jiang * @since 0.8.0 */ +@Deprecated public class Ai21Jurassic2ChatBedrockApi extends AbstractBedrockApi { diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModel.java index ab9f5c7b72..71fd5b3496 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModel.java @@ -15,28 +15,23 @@ */ package org.springframework.ai.bedrock.llama; -import java.util.List; - -import reactor.core.publisher.Flux; - -import org.springframework.ai.bedrock.MessageToPromptConverter; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatRequest; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatResponse; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.api.BedrockConverseApiUtils; import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.model.StreamingChatModel; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; -import org.springframework.ai.chat.metadata.Usage; +import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.model.ModelDescription; import org.springframework.util.Assert; +import reactor.core.publisher.Flux; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; + /** * Java {@link ChatModel} and {@link StreamingChatModel} for the Bedrock Llama chat - * generative. + * generative model. * * @author Christian Tzolov * @author Wei Jiang @@ -44,95 +39,101 @@ */ public class BedrockLlamaChatModel implements ChatModel, StreamingChatModel { - private final LlamaChatBedrockApi chatApi; + private final String modelId; + + private final BedrockConverseApi converseApi; private final BedrockLlamaChatOptions defaultOptions; - public BedrockLlamaChatModel(LlamaChatBedrockApi chatApi) { - this(chatApi, + public BedrockLlamaChatModel(BedrockConverseApi converseApi) { + this(converseApi, BedrockLlamaChatOptions.builder().withTemperature(0.8f).withTopP(0.9f).withMaxGenLen(100).build()); } - public BedrockLlamaChatModel(LlamaChatBedrockApi chatApi, BedrockLlamaChatOptions options) { - Assert.notNull(chatApi, "LlamaChatBedrockApi must not be null"); - Assert.notNull(options, "BedrockLlamaChatOptions must not be null"); + public BedrockLlamaChatModel(BedrockConverseApi converseApi, BedrockLlamaChatOptions options) { + this(LlamaChatModel.LLAMA3_70B_INSTRUCT_V1.id(), converseApi, options); + } + + public BedrockLlamaChatModel(String modelId, BedrockConverseApi converseApi, BedrockLlamaChatOptions options) { + Assert.notNull(modelId, "modelId must not be null."); + Assert.notNull(converseApi, "BedrockConverseApi must not be null."); + Assert.notNull(options, "BedrockLlamaChatOptions must not be null."); - this.chatApi = chatApi; + this.modelId = modelId; + this.converseApi = converseApi; this.defaultOptions = options; } @Override public ChatResponse call(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); - var request = createRequest(prompt); + var request = BedrockConverseApiUtils.createConverseRequest(modelId, prompt, defaultOptions); - LlamaChatResponse response = this.chatApi.chatCompletion(request); + ConverseResponse response = this.converseApi.converse(request); - return new ChatResponse(List.of(new Generation(response.generation()).withGenerationMetadata( - ChatGenerationMetadata.from(response.stopReason().name(), extractUsage(response))))); + return BedrockConverseApiUtils.convertConverseResponse(response); } @Override public Flux stream(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); - var request = createRequest(prompt); + var request = BedrockConverseApiUtils.createConverseStreamRequest(modelId, prompt, defaultOptions); - Flux fluxResponse = this.chatApi.chatCompletionStream(request); + Flux fluxResponse = this.converseApi.converseStream(request); - return fluxResponse.map(response -> { - String stopReason = response.stopReason() != null ? response.stopReason().name() : null; - return new ChatResponse(List.of(new Generation(response.generation()) - .withGenerationMetadata(ChatGenerationMetadata.from(stopReason, extractUsage(response))))); - }); + return fluxResponse.map(output -> BedrockConverseApiUtils.convertConverseStreamOutput(output)); } - private Usage extractUsage(LlamaChatResponse response) { - return new Usage() { - - @Override - public Long getPromptTokens() { - return response.promptTokenCount().longValue(); - } - - @Override - public Long getGenerationTokens() { - return response.generationTokenCount().longValue(); - } - }; + @Override + public ChatOptions getDefaultOptions() { + return BedrockLlamaChatOptions.fromOptions(this.defaultOptions); } /** - * Accessible for testing. + * Llama models version. */ - LlamaChatRequest createRequest(Prompt prompt) { - - final String promptValue = MessageToPromptConverter.create().toPrompt(prompt.getInstructions()); - - LlamaChatRequest request = LlamaChatRequest.builder(promptValue).build(); - - if (this.defaultOptions != null) { - request = ModelOptionsUtils.merge(request, this.defaultOptions, LlamaChatRequest.class); + public enum LlamaChatModel implements ModelDescription { + + /** + * meta.llama2-13b-chat-v1 + */ + LLAMA2_13B_CHAT_V1("meta.llama2-13b-chat-v1"), + + /** + * meta.llama2-70b-chat-v1 + */ + LLAMA2_70B_CHAT_V1("meta.llama2-70b-chat-v1"), + + /** + * meta.llama3-8b-instruct-v1:0 + */ + LLAMA3_8B_INSTRUCT_V1("meta.llama3-8b-instruct-v1:0"), + + /** + * meta.llama3-70b-instruct-v1:0 + */ + LLAMA3_70B_INSTRUCT_V1("meta.llama3-70b-instruct-v1:0"); + + private final String id; + + /** + * @return The model id. + */ + public String id() { + return id; } - if (prompt.getOptions() != null) { - if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { - BedrockLlamaChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, - ChatOptions.class, BedrockLlamaChatOptions.class); - - request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, LlamaChatRequest.class); - } - else { - throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " - + prompt.getOptions().getClass().getSimpleName()); - } + LlamaChatModel(String value) { + this.id = value; } - return request; - } + @Override + public String getModelName() { + return this.id; + } - @Override - public ChatOptions getDefaultOptions() { - return BedrockLlamaChatOptions.fromOptions(this.defaultOptions); } } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatOptions.java index 4d6c0a6e04..5753cf4d2d 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatOptions.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatOptions.java @@ -23,7 +23,12 @@ import org.springframework.ai.chat.prompt.ChatOptions; /** + * Java {@link ChatOptions} for the Bedrock Cohere Command chat generative model chat + * options. + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html + * * @author Christian Tzolov + * @author Wei Jiang */ @JsonInclude(Include.NON_NULL) public class BedrockLlamaChatOptions implements ChatOptions { diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java index 16af9735ed..6e52e42c29 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java @@ -35,10 +35,13 @@ * Java client for the Bedrock Llama chat model. * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html * + * @deprecated Use {@link BedrockConverseApi} instead. + * * @author Christian Tzolov * @author Wei Jiang * @since 0.8.0 */ +@Deprecated public class LlamaChatBedrockApi extends AbstractBedrockApi { diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModel.java index 77b91e09f8..f6026d02df 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModel.java @@ -15,140 +15,120 @@ */ package org.springframework.ai.bedrock.titan; -import java.util.List; - import reactor.core.publisher.Flux; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; -import org.springframework.ai.bedrock.MessageToPromptConverter; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatRequest; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponse; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponseChunk; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.api.BedrockConverseApiUtils; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.model.StreamingChatModel; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; -import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.model.ModelDescription; import org.springframework.util.Assert; /** + * Java {@link ChatModel} and {@link StreamingChatModel} for the Bedrock Titan chat + * generative model. + * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ public class BedrockTitanChatModel implements ChatModel, StreamingChatModel { - private final TitanChatBedrockApi chatApi; + private final String modelId; + + private final BedrockConverseApi converseApi; private final BedrockTitanChatOptions defaultOptions; - public BedrockTitanChatModel(TitanChatBedrockApi chatApi) { - this(chatApi, BedrockTitanChatOptions.builder().withTemperature(0.8f).build()); + public BedrockTitanChatModel(BedrockConverseApi converseApi) { + this(converseApi, BedrockTitanChatOptions.builder().withTemperature(0.8f).build()); + } + + public BedrockTitanChatModel(BedrockConverseApi converseApi, BedrockTitanChatOptions defaultOptions) { + this(TitanChatModel.TITAN_TEXT_EXPRESS_V1.id(), converseApi, defaultOptions); } - public BedrockTitanChatModel(TitanChatBedrockApi chatApi, BedrockTitanChatOptions defaultOptions) { - Assert.notNull(chatApi, "ChatApi must not be null"); - Assert.notNull(defaultOptions, "DefaultOptions must not be null"); - this.chatApi = chatApi; + public BedrockTitanChatModel(String modelId, BedrockConverseApi converseApi, + BedrockTitanChatOptions defaultOptions) { + Assert.notNull(modelId, "modelId must not be null."); + Assert.notNull(converseApi, "BedrockConverseApi must not be null."); + Assert.notNull(defaultOptions, "BedrockTitanChatOptions must not be null"); + + this.modelId = modelId; + this.converseApi = converseApi; this.defaultOptions = defaultOptions; } @Override public ChatResponse call(Prompt prompt) { - TitanChatResponse response = this.chatApi.chatCompletion(this.createRequest(prompt)); - List generations = response.results().stream().map(result -> { - return new Generation(result.outputText()); - }).toList(); + Assert.notNull(prompt, "Prompt must not be null."); + + var request = BedrockConverseApiUtils.createConverseRequest(modelId, prompt, defaultOptions); + + ConverseResponse response = this.converseApi.converse(request); - return new ChatResponse(generations); + return BedrockConverseApiUtils.convertConverseResponse(response); } @Override public Flux stream(Prompt prompt) { - return this.chatApi.chatCompletionStream(this.createRequest(prompt)).map(chunk -> { - - Generation generation = new Generation(chunk.outputText()); - - if (chunk.amazonBedrockInvocationMetrics() != null) { - String completionReason = chunk.completionReason().name(); - generation = generation.withGenerationMetadata( - ChatGenerationMetadata.from(completionReason, chunk.amazonBedrockInvocationMetrics())); - } - else if (chunk.inputTextTokenCount() != null && chunk.totalOutputTextTokenCount() != null) { - String completionReason = chunk.completionReason().name(); - generation = generation - .withGenerationMetadata(ChatGenerationMetadata.from(completionReason, extractUsage(chunk))); - - } - return new ChatResponse(List.of(generation)); - }); - } - - /** - * Test access. - */ - TitanChatRequest createRequest(Prompt prompt) { - final String promptValue = MessageToPromptConverter.create().toPrompt(prompt.getInstructions()); + Assert.notNull(prompt, "Prompt must not be null."); - var requestBuilder = TitanChatRequest.builder(promptValue); + var request = BedrockConverseApiUtils.createConverseStreamRequest(modelId, prompt, defaultOptions); - if (this.defaultOptions != null) { - requestBuilder = update(requestBuilder, this.defaultOptions); - } - - if (prompt.getOptions() != null) { - if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { - BedrockTitanChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, - ChatOptions.class, BedrockTitanChatOptions.class); - - requestBuilder = update(requestBuilder, updatedRuntimeOptions); - } - else { - throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " - + prompt.getOptions().getClass().getSimpleName()); - } - } + Flux fluxResponse = this.converseApi.converseStream(request); - return requestBuilder.build(); + return fluxResponse.map(output -> BedrockConverseApiUtils.convertConverseStreamOutput(output)); } - private TitanChatRequest.Builder update(TitanChatRequest.Builder builder, BedrockTitanChatOptions options) { - if (options.getTemperature() != null) { - builder.withTemperature(options.getTemperature()); - } - if (options.getTopP() != null) { - builder.withTopP(options.getTopP()); - } - if (options.getMaxTokenCount() != null) { - builder.withMaxTokenCount(options.getMaxTokenCount()); - } - if (options.getStopSequences() != null) { - builder.withStopSequences(options.getStopSequences()); - } - return builder; + @Override + public ChatOptions getDefaultOptions() { + return BedrockTitanChatOptions.fromOptions(this.defaultOptions); } - private Usage extractUsage(TitanChatResponseChunk response) { - return new Usage() { + /** + * Titan models version. + */ + public enum TitanChatModel implements ModelDescription { + + /** + * amazon.titan-text-lite-v1 + */ + TITAN_TEXT_LITE_V1("amazon.titan-text-lite-v1"), + + /** + * amazon.titan-text-express-v1 + */ + TITAN_TEXT_EXPRESS_V1("amazon.titan-text-express-v1"), + + /** + * amazon.titan-text-premier-v1:0 + */ + TITAN_TEXT_PREMIER_V1("amazon.titan-text-premier-v1:0"); + + private final String id; + + /** + * @return The model id. + */ + public String id() { + return id; + } - @Override - public Long getPromptTokens() { - return response.inputTextTokenCount().longValue(); - } + TitanChatModel(String value) { + this.id = value; + } - @Override - public Long getGenerationTokens() { - return response.totalOutputTextTokenCount().longValue(); - } - }; - } + @Override + public String getModelName() { + return this.id; + } - @Override - public ChatOptions getDefaultOptions() { - return BedrockTitanChatOptions.fromOptions(this.defaultOptions); } } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatOptions.java index d53126a0b7..6c07f26475 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatOptions.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatOptions.java @@ -17,6 +17,7 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; @@ -25,33 +26,47 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** + * Java {@link ChatOptions} for the Bedrock Titan chat generative model chat options. + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html + * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ @JsonInclude(Include.NON_NULL) public class BedrockTitanChatOptions implements ChatOptions { - // @formatter:off /** - * The temperature value controls the randomness of the generated text. + * The Titan chat model text generation config. */ - private @JsonProperty("temperature") Float temperature; + private @JsonProperty("textGenerationConfig") TextGenerationConfig textGenerationConfig = new TextGenerationConfig(); - /** - * The topP value controls the diversity of the generated text. Use a lower value to ignore less probable options. - */ - private @JsonProperty("topP") Float topP; + @JsonInclude(Include.NON_NULL) + public static class TextGenerationConfig { - /** - * Maximum number of tokens to generate. - */ - private @JsonProperty("maxTokenCount") Integer maxTokenCount; + // @formatter:off + /** + * The temperature value controls the randomness of the generated text. + */ + private @JsonProperty(value = "temperature") Float temperature; - /** - * A list of tokens that the model should stop generating after. - */ - private @JsonProperty("stopSequences") List stopSequences; - // @formatter:on + /** + * The topP value controls the diversity of the generated text. Use a lower value to ignore less probable options. + */ + private @JsonProperty("topP") Float topP; + + /** + * Maximum number of tokens to generate. + */ + private @JsonProperty("maxTokenCount") Integer maxTokenCount; + + /** + * A list of tokens that the model should stop generating after. + */ + private @JsonProperty("stopSequences") List stopSequences; + // @formatter:on + + } public static Builder builder() { return new Builder(); @@ -62,22 +77,22 @@ public static class Builder { private BedrockTitanChatOptions options = new BedrockTitanChatOptions(); public Builder withTemperature(Float temperature) { - this.options.temperature = temperature; + this.options.textGenerationConfig.temperature = temperature; return this; } public Builder withTopP(Float topP) { - this.options.topP = topP; + this.options.textGenerationConfig.topP = topP; return this; } public Builder withMaxTokenCount(Integer maxTokenCount) { - this.options.maxTokenCount = maxTokenCount; + this.options.textGenerationConfig.maxTokenCount = maxTokenCount; return this; } public Builder withStopSequences(List stopSequences) { - this.options.stopSequences = stopSequences; + this.options.textGenerationConfig.stopSequences = stopSequences; return this; } @@ -87,39 +102,44 @@ public BedrockTitanChatOptions build() { } + @JsonIgnore public Float getTemperature() { - return temperature; + return this.textGenerationConfig.temperature; } public void setTemperature(Float temperature) { - this.temperature = temperature; + this.textGenerationConfig.temperature = temperature; } + @JsonIgnore public Float getTopP() { - return topP; + return this.textGenerationConfig.topP; } public void setTopP(Float topP) { - this.topP = topP; + this.textGenerationConfig.topP = topP; } public Integer getMaxTokenCount() { - return maxTokenCount; + return this.textGenerationConfig.maxTokenCount; } + @JsonIgnore public void setMaxTokenCount(Integer maxTokenCount) { - this.maxTokenCount = maxTokenCount; + this.textGenerationConfig.maxTokenCount = maxTokenCount; } + @JsonIgnore public List getStopSequences() { - return stopSequences; + return this.textGenerationConfig.stopSequences; } public void setStopSequences(List stopSequences) { - this.stopSequences = stopSequences; + this.textGenerationConfig.stopSequences = stopSequences; } @Override + @JsonIgnore public Integer getTopK() { throw new UnsupportedOperationException("Bedrock Titan Chat does not support the 'TopK' option."); } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java index ce1842adf3..c7b954c34f 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java @@ -39,10 +39,12 @@ *

* https://docs.aws.amazon.com/bedrock/latest/userguide/titan-text-models.html * + * @deprecated Use {@link BedrockConverseApi} instead. * @author Christian Tzolov * @author Wei Jiang * @since 0.8.0 */ +@Deprecated // @formatter:off public class TitanChatBedrockApi extends AbstractBedrockApi { diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicChatModelIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicChatModelIT.java index b2b09fc8ec..5e18efc9b5 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicChatModelIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicChatModelIT.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; @@ -30,12 +29,13 @@ import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.regions.Region; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi; +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.chat.prompt.SystemPromptTemplate; @@ -198,19 +198,47 @@ void beanStreamOutputConverterRecords() { assertThat(actorsFilms.movies()).hasSize(5); } + @Test + void chatResponseUsage() { + Prompt prompt = new Prompt("Who are you?"); + + ChatResponse response = chatModel.call(prompt); + + Usage usage = response.getMetadata().getUsage(); + assertThat(usage).isNotNull(); + assertThat(usage.getPromptTokens()).isGreaterThan(1); + assertThat(usage.getGenerationTokens()).isGreaterThan(1); + } + + @Test + void chatOptions() { + AnthropicChatOptions options = AnthropicChatOptions.builder() + .withTemperature(0.5F) + .withMaxTokens(100) + .withTopK(10) + .withTopP(0.5F) + .withStopSequences(List.of("stop sequences")) + .build(); + + Prompt prompt = new Prompt("Who are you?", options); + ChatResponse response = chatModel.call(prompt); + String content = response.getResult().getOutput().getContent(); + + assertThat(content).isNotNull(); + } + @SpringBootConfiguration public static class TestConfiguration { @Bean - public AnthropicChatBedrockApi anthropicApi() { - return new AnthropicChatBedrockApi(AnthropicChatBedrockApi.AnthropicChatModel.CLAUDE_V2.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), + public BedrockConverseApi converseApi() { + return new BedrockConverseApi(EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), Duration.ofMinutes(2)); } @Bean - public BedrockAnthropicChatModel anthropicChatModel(AnthropicChatBedrockApi anthropicApi) { - return new BedrockAnthropicChatModel(anthropicApi); + public BedrockAnthropicChatModel anthropicChatModel(BedrockConverseApi converseApi) { + return new BedrockAnthropicChatModel(converseApi); } } diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicCreateRequestTests.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicCreateRequestTests.java deleted file mode 100644 index c8b5cbe859..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicCreateRequestTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.anthropic; - -import java.time.Duration; -import java.util.List; - -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatModel; -import org.springframework.ai.chat.prompt.Prompt; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -public class BedrockAnthropicCreateRequestTests { - - private AnthropicChatBedrockApi anthropicChatApi = new AnthropicChatBedrockApi(AnthropicChatModel.CLAUDE_V2.id(), - Region.US_EAST_1.id(), Duration.ofMillis(1000L)); - - @Test - public void createRequestWithChatOptions() { - - var client = new BedrockAnthropicChatModel(anthropicChatApi, - AnthropicChatOptions.builder() - .withTemperature(66.6f) - .withTopK(66) - .withTopP(0.66f) - .withMaxTokensToSample(666) - .withAnthropicVersion("X.Y.Z") - .withStopSequences(List.of("stop1", "stop2")) - .build()); - - var request = client.createRequest(new Prompt("Test message content")); - - assertThat(request.prompt()).isNotEmpty(); - assertThat(request.temperature()).isEqualTo(66.6f); - assertThat(request.topK()).isEqualTo(66); - assertThat(request.topP()).isEqualTo(0.66f); - assertThat(request.maxTokensToSample()).isEqualTo(666); - assertThat(request.anthropicVersion()).isEqualTo("X.Y.Z"); - assertThat(request.stopSequences()).containsExactly("stop1", "stop2"); - - request = client.createRequest(new Prompt("Test message content", - AnthropicChatOptions.builder() - .withTemperature(99.9f) - .withTopP(0.99f) - .withMaxTokensToSample(999) - .withAnthropicVersion("zzz") - .withStopSequences(List.of("stop3", "stop4")) - .build() - - )); - - assertThat(request.prompt()).isNotEmpty(); - assertThat(request.temperature()).isEqualTo(99.9f); - assertThat(request.topK()).as("unchanged from the default options").isEqualTo(66); - assertThat(request.topP()).isEqualTo(0.99f); - assertThat(request.maxTokensToSample()).isEqualTo(999); - assertThat(request.anthropicVersion()).isEqualTo("zzz"); - assertThat(request.stopSequences()).containsExactly("stop3", "stop4"); - } - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApiIT.java index 334efa48ff..35803fc6ba 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApiIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApiIT.java @@ -37,6 +37,7 @@ /** * @author Christian Tzolov */ +@Deprecated @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") public class AnthropicChatBedrockApiIT { diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModelIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModelIT.java index 7e31a8f017..75441e7696 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModelIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModelIT.java @@ -22,7 +22,6 @@ import java.util.Map; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.slf4j.Logger; @@ -31,13 +30,14 @@ import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.regions.Region; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi; +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Media; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.chat.prompt.SystemPromptTemplate; @@ -208,7 +208,7 @@ void multiModalityTest() throws IOException { var imageData = new ClassPathResource("/test.png"); - var userMessage = new UserMessage("Explain what do you see o this picture?", + var userMessage = new UserMessage("Explain what do you see on this picture?", List.of(new Media(MimeTypeUtils.IMAGE_PNG, imageData))); var response = chatModel.call(new Prompt(List.of(userMessage))); @@ -217,19 +217,47 @@ void multiModalityTest() throws IOException { assertThat(response.getResult().getOutput().getContent()).contains("bananas", "apple", "basket"); } + @Test + void chatResponseUsage() { + Prompt prompt = new Prompt("Who are you?"); + + ChatResponse response = chatModel.call(prompt); + + Usage usage = response.getMetadata().getUsage(); + assertThat(usage).isNotNull(); + assertThat(usage.getPromptTokens()).isGreaterThan(1); + assertThat(usage.getGenerationTokens()).isGreaterThan(1); + } + + @Test + void chatOptions() { + Anthropic3ChatOptions options = Anthropic3ChatOptions.builder() + .withTemperature(0.5F) + .withMaxTokens(100) + .withTopK(10) + .withTopP(0.5F) + .withStopSequences(List.of("stop sequences")) + .build(); + + Prompt prompt = new Prompt("Who are you?", options); + ChatResponse response = chatModel.call(prompt); + String content = response.getResult().getOutput().getContent(); + + assertThat(content).isNotNull(); + } + @SpringBootConfiguration public static class TestConfiguration { @Bean - public Anthropic3ChatBedrockApi anthropicApi() { - return new Anthropic3ChatBedrockApi(Anthropic3ChatBedrockApi.AnthropicChatModel.CLAUDE_V3_SONNET.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), - Duration.ofMinutes(5)); + public BedrockConverseApi converseApi() { + return new BedrockConverseApi(EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), + Duration.ofMinutes(2)); } @Bean - public BedrockAnthropic3ChatModel anthropicChatModel(Anthropic3ChatBedrockApi anthropicApi) { - return new BedrockAnthropic3ChatModel(anthropicApi); + public BedrockAnthropic3ChatModel anthropicChatModel(BedrockConverseApi converseApi) { + return new BedrockAnthropic3ChatModel(converseApi); } } diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3CreateRequestTests.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3CreateRequestTests.java deleted file mode 100644 index 31486f9e93..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3CreateRequestTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.anthropic3; - -import org.junit.jupiter.api.Test; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatModel; -import org.springframework.ai.chat.prompt.Prompt; -import software.amazon.awssdk.regions.Region; - -import java.time.Duration; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -public class BedrockAnthropic3CreateRequestTests { - - private Anthropic3ChatBedrockApi anthropicChatApi = new Anthropic3ChatBedrockApi(AnthropicChatModel.CLAUDE_V2.id(), - Region.EU_CENTRAL_1.id(), Duration.ofMillis(1000L)); - - @Test - public void createRequestWithChatOptions() { - - var client = new BedrockAnthropic3ChatModel(anthropicChatApi, - Anthropic3ChatOptions.builder() - .withTemperature(66.6f) - .withTopK(66) - .withTopP(0.66f) - .withMaxTokens(666) - .withAnthropicVersion("X.Y.Z") - .withStopSequences(List.of("stop1", "stop2")) - .build()); - - var request = client.createRequest(new Prompt("Test message content")); - - assertThat(request.messages()).isNotEmpty(); - assertThat(request.temperature()).isEqualTo(66.6f); - assertThat(request.topK()).isEqualTo(66); - assertThat(request.topP()).isEqualTo(0.66f); - assertThat(request.maxTokens()).isEqualTo(666); - assertThat(request.anthropicVersion()).isEqualTo("X.Y.Z"); - assertThat(request.stopSequences()).containsExactly("stop1", "stop2"); - - request = client.createRequest(new Prompt("Test message content", - Anthropic3ChatOptions.builder() - .withTemperature(99.9f) - .withTopP(0.99f) - .withMaxTokens(999) - .withAnthropicVersion("zzz") - .withStopSequences(List.of("stop3", "stop4")) - .build() - - )); - - assertThat(request.messages()).isNotEmpty(); - assertThat(request.temperature()).isEqualTo(99.9f); - assertThat(request.topK()).as("unchanged from the default options").isEqualTo(66); - assertThat(request.topP()).isEqualTo(0.99f); - assertThat(request.maxTokens()).isEqualTo(999); - assertThat(request.anthropicVersion()).isEqualTo("zzz"); - assertThat(request.stopSequences()).containsExactly("stop3", "stop4"); - } - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApiIT.java index 15ab3dd0f5..33f03d15d2 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApiIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApiIT.java @@ -41,6 +41,7 @@ /** * @author Ben Middleton */ +@Deprecated @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") public class Anthropic3ChatBedrockApiIT { diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiIT.java index 77ea8dbb5a..da8a6ef1d2 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiIT.java @@ -35,6 +35,7 @@ /** * @author Wei Jiang + * @since 1.0.0 */ @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtilsIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtilsIT.java new file mode 100644 index 0000000000..7c79bd1314 --- /dev/null +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtilsIT.java @@ -0,0 +1,346 @@ +/* + * Copyright 2023 - 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.bedrock.api; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.SystemMessage; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import software.amazon.awssdk.core.document.Document; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock.Type; +import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseRequest; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamRequest; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +public class BedrockConverseApiUtilsIT { + + private static final String FAKE_MODEL_ID = "FAKE_MODEL_ID"; + + @Test + public void testCreateConverseRequestWithNoOptions() { + Prompt prompt = new Prompt("hello world"); + + ConverseRequest converseRequest = BedrockConverseApiUtils.createConverseRequest(FAKE_MODEL_ID, prompt); + + assertThat(converseRequest).isNotNull(); + assertThat(converseRequest.system()).isEmpty(); + assertThat(converseRequest.inferenceConfig()).isNull(); + assertThat(converseRequest.toolConfig()).isNull(); + assertThat(converseRequest.additionalModelRequestFields()).isNull(); + assertThat(converseRequest.additionalModelResponseFieldPaths()).isEmpty(); + assertThat(converseRequest.modelId()).isEqualTo(FAKE_MODEL_ID); + assertThat(converseRequest.messages()).hasSize(1); + assertThat(converseRequest.messages().get(0).content()).hasSize(1); + assertThat(converseRequest.messages().get(0).role()).isEqualTo(ConversationRole.USER); + assertThat(converseRequest.messages().get(0).content().get(0).text()).isEqualTo("hello world"); + assertThat(converseRequest.messages().get(0).content().get(0).type()).isEqualTo(Type.TEXT); + } + + @Test + public void testCreateConverseRequestWithMultipleMessagesAndNoOptions() { + Prompt prompt = new Prompt(List.of(new UserMessage("hello world1"), new UserMessage("hello world2"))); + + ConverseRequest converseRequest = BedrockConverseApiUtils.createConverseRequest(FAKE_MODEL_ID, prompt); + + assertThat(converseRequest).isNotNull(); + assertThat(converseRequest.system()).isEmpty(); + assertThat(converseRequest.inferenceConfig()).isNull(); + assertThat(converseRequest.toolConfig()).isNull(); + assertThat(converseRequest.additionalModelRequestFields()).isNull(); + assertThat(converseRequest.additionalModelResponseFieldPaths()).isEmpty(); + assertThat(converseRequest.modelId()).isEqualTo(FAKE_MODEL_ID); + assertThat(converseRequest.messages()).hasSize(2); + assertThat(converseRequest.messages().get(0).content()).hasSize(1); + assertThat(converseRequest.messages().get(0).role()).isEqualTo(ConversationRole.USER); + assertThat(converseRequest.messages().get(0).content().get(0).text()).isEqualTo("hello world1"); + assertThat(converseRequest.messages().get(0).content().get(0).type()).isEqualTo(Type.TEXT); + assertThat(converseRequest.messages().get(1).content()).hasSize(1); + assertThat(converseRequest.messages().get(1).role()).isEqualTo(ConversationRole.USER); + assertThat(converseRequest.messages().get(1).content().get(0).text()).isEqualTo("hello world2"); + assertThat(converseRequest.messages().get(1).content().get(0).type()).isEqualTo(Type.TEXT); + } + + @Test + public void testCreateConverseRequestWithMultipleMessageRolesAndNoOptions() { + Prompt prompt = new Prompt(List.of(new UserMessage("hello world1"), new AssistantMessage("hello world2"))); + + ConverseRequest converseRequest = BedrockConverseApiUtils.createConverseRequest(FAKE_MODEL_ID, prompt); + + assertThat(converseRequest).isNotNull(); + assertThat(converseRequest.system()).isEmpty(); + assertThat(converseRequest.inferenceConfig()).isNull(); + assertThat(converseRequest.toolConfig()).isNull(); + assertThat(converseRequest.additionalModelRequestFields()).isNull(); + assertThat(converseRequest.additionalModelResponseFieldPaths()).isEmpty(); + assertThat(converseRequest.modelId()).isEqualTo(FAKE_MODEL_ID); + assertThat(converseRequest.messages()).hasSize(2); + assertThat(converseRequest.messages().get(0).content()).hasSize(1); + assertThat(converseRequest.messages().get(0).role()).isEqualTo(ConversationRole.USER); + assertThat(converseRequest.messages().get(0).content().get(0).text()).isEqualTo("hello world1"); + assertThat(converseRequest.messages().get(0).content().get(0).type()).isEqualTo(Type.TEXT); + assertThat(converseRequest.messages().get(1).content()).hasSize(1); + assertThat(converseRequest.messages().get(1).role()).isEqualTo(ConversationRole.ASSISTANT); + assertThat(converseRequest.messages().get(1).content().get(0).text()).isEqualTo("hello world2"); + assertThat(converseRequest.messages().get(1).content().get(0).type()).isEqualTo(Type.TEXT); + } + + @Test + public void testCreateConverseRequestWithSystemMessageAndNoOptions() { + Prompt prompt = new Prompt( + List.of(new UserMessage("hello world"), new SystemMessage("example system message"))); + + ConverseRequest converseRequest = BedrockConverseApiUtils.createConverseRequest(FAKE_MODEL_ID, prompt); + + assertThat(converseRequest).isNotNull(); + assertThat(converseRequest.inferenceConfig()).isNull(); + assertThat(converseRequest.toolConfig()).isNull(); + assertThat(converseRequest.additionalModelRequestFields()).isNull(); + assertThat(converseRequest.additionalModelResponseFieldPaths()).isEmpty(); + assertThat(converseRequest.modelId()).isEqualTo(FAKE_MODEL_ID); + assertThat(converseRequest.messages()).hasSize(1); + assertThat(converseRequest.messages().get(0).content()).hasSize(1); + assertThat(converseRequest.messages().get(0).role()).isEqualTo(ConversationRole.USER); + assertThat(converseRequest.messages().get(0).content().get(0).text()).isEqualTo("hello world"); + assertThat(converseRequest.messages().get(0).content().get(0).type()).isEqualTo(Type.TEXT); + assertThat(converseRequest.system()).hasSize(1); + assertThat(converseRequest.system().get(0).text()).isEqualTo("example system message"); + } + + @Test + public void testOptionsToAdditionalModelRequestFields() { + Prompt prompt = new Prompt("hello world"); + + ConverseRequest converseRequest = BedrockConverseApiUtils.createConverseRequest(FAKE_MODEL_ID, prompt, + new MockChatOptions()); + + Document requestFields = converseRequest.additionalModelRequestFields(); + + assertThat(converseRequest).isNotNull(); + assertThat(converseRequest.system()).isEmpty(); + assertThat(converseRequest.inferenceConfig()).isNull(); + assertThat(converseRequest.toolConfig()).isNull(); + assertThat(requestFields).isNotNull(); + assertThat(requestFields.asMap()).hasSize(12); + assertThat(requestFields.asMap().get("temperature").asNumber().floatValue()).isEqualTo(0.1F); + assertThat(requestFields.asMap().get("top_p").asNumber().floatValue()).isEqualTo(0.2F); + assertThat(requestFields.asMap().get("top_k").asNumber().intValue()).isEqualTo(3); + assertThat(requestFields.asMap().get("string_value").asString()).isEqualTo("stringValue"); + assertThat(requestFields.asMap().get("boolean_value").asBoolean()).isEqualTo(true); + assertThat(requestFields.asMap().get("long_value").asNumber().longValue()).isEqualTo(4); + assertThat(requestFields.asMap().get("float_value").asNumber().floatValue()).isEqualTo(0.5F); + assertThat(requestFields.asMap().get("double_value").asNumber().doubleValue()).isEqualTo(0.6); + assertThat(requestFields.asMap().get("big_decimal_value").asNumber().bigDecimalValue()) + .isEqualTo(BigDecimal.valueOf(7)); + assertThat(requestFields.asMap().get("big_intege_value").asNumber().bigDecimalValue().intValue()).isEqualTo(8); + assertThat(requestFields.asMap().get("list_value").asList()).hasSize(2); + assertThat(requestFields.asMap().get("list_value").asList().get(0).asString()).isEqualTo("hello"); + assertThat(requestFields.asMap().get("map_value").asMap()).hasSize(1); + assertThat(requestFields.asMap().get("map_value").asMap().get("hello").asString()).isEqualTo("world"); + } + + @Test + public void testCreateConverseRequestWithRuntimeOptions() { + MockChatOptions runtimeOptions = new MockChatOptions(); + runtimeOptions.setTemperature(50F); + + Prompt prompt = new Prompt("hello world", runtimeOptions); + + ConverseRequest converseRequest = BedrockConverseApiUtils.createConverseRequest(FAKE_MODEL_ID, prompt, + new MockChatOptions()); + + Document requestFields = converseRequest.additionalModelRequestFields(); + + assertThat(converseRequest).isNotNull(); + assertThat(converseRequest.system()).isEmpty(); + assertThat(converseRequest.inferenceConfig()).isNull(); + assertThat(converseRequest.toolConfig()).isNull(); + assertThat(requestFields).isNotNull(); + assertThat(requestFields.asMap()).hasSize(12); + assertThat(requestFields.asMap().get("temperature").asNumber().floatValue()).isEqualTo(50F); + assertThat(requestFields.asMap().get("top_p").asNumber().floatValue()).isEqualTo(0.2F); + } + + @Test + public void testCreateConverseStreamRequestWithRuntimeOptions() { + MockChatOptions runtimeOptions = new MockChatOptions(); + runtimeOptions.setTemperature(50F); + + Prompt prompt = new Prompt("hello world", runtimeOptions); + + ConverseStreamRequest converseStreamRequest = BedrockConverseApiUtils.createConverseStreamRequest(FAKE_MODEL_ID, + prompt, new MockChatOptions()); + + Document requestFields = converseStreamRequest.additionalModelRequestFields(); + + assertThat(converseStreamRequest).isNotNull(); + assertThat(converseStreamRequest.system()).isEmpty(); + assertThat(converseStreamRequest.inferenceConfig()).isNull(); + assertThat(converseStreamRequest.additionalModelResponseFieldPaths()).isEmpty(); + assertThat(converseStreamRequest.modelId()).isEqualTo(FAKE_MODEL_ID); + assertThat(converseStreamRequest.messages()).hasSize(1); + assertThat(converseStreamRequest.messages().get(0).content()).hasSize(1); + assertThat(converseStreamRequest.messages().get(0).role()).isEqualTo(ConversationRole.USER); + assertThat(converseStreamRequest.messages().get(0).content().get(0).text()).isEqualTo("hello world"); + assertThat(converseStreamRequest.messages().get(0).content().get(0).type()).isEqualTo(Type.TEXT); + assertThat(requestFields.asMap()).hasSize(12); + assertThat(requestFields.asMap().get("temperature").asNumber().floatValue()).isEqualTo(50F); + assertThat(requestFields.asMap().get("top_p").asNumber().floatValue()).isEqualTo(0.2F); + } + + class MockChatOptions implements ChatOptions { + + private @JsonProperty("temperature") Float temperature = 0.1F; + + private @JsonProperty("top_p") Float topP = 0.2F; + + private @JsonProperty("top_k") Integer topK = 3; + + private @JsonProperty("string_value") String stringValue = "stringValue"; + + private @JsonProperty("boolean_value") Boolean booleanValue = true; + + private @JsonProperty("long_value") Long longValue = 4L; + + private @JsonProperty("float_value") Float floatValue = 0.5F; + + private @JsonProperty("double_value") Double doubleValue = 0.6; + + private @JsonProperty("big_decimal_value") BigDecimal bigDecimalValue = BigDecimal.valueOf(7); + + private @JsonProperty("big_intege_value") BigInteger bigIntegerValue = BigInteger.valueOf(8); + + private @JsonProperty("list_value") List listValue = List.of("hello", "world"); + + private @JsonProperty("map_value") Map mapValue = Map.of("hello", "world"); + + @Override + public Float getTemperature() { + return temperature; + } + + @Override + public Float getTopP() { + return topP; + } + + @Override + public Integer getTopK() { + return topK; + } + + public String getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + public Boolean getBooleanValue() { + return booleanValue; + } + + public void setBooleanValue(Boolean booleanValue) { + this.booleanValue = booleanValue; + } + + public Long getLongValue() { + return longValue; + } + + public void setLongValue(Long longValue) { + this.longValue = longValue; + } + + public Float getFloatValue() { + return floatValue; + } + + public void setFloatValue(Float floatValue) { + this.floatValue = floatValue; + } + + public Double getDoubleValue() { + return doubleValue; + } + + public void setDoubleValue(Double doubleValue) { + this.doubleValue = doubleValue; + } + + public BigDecimal getBigDecimalValue() { + return bigDecimalValue; + } + + public void setBigDecimalValue(BigDecimal bigDecimalValue) { + this.bigDecimalValue = bigDecimalValue; + } + + public BigInteger getBigIntegerValue() { + return bigIntegerValue; + } + + public void setBigIntegerValue(BigInteger bigIntegerValue) { + this.bigIntegerValue = bigIntegerValue; + } + + public List getListValue() { + return listValue; + } + + public void setListValue(List listValue) { + this.listValue = listValue; + } + + public Map getMapValue() { + return mapValue; + } + + public void setMapValue(Map mapValue) { + this.mapValue = mapValue; + } + + public void setTemperature(Float temperature) { + this.temperature = temperature; + } + + public void setTopP(Float topP) { + this.topP = topP; + } + + public void setTopK(Integer topK) { + this.topK = topK; + } + + } + +} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatCreateRequestTests.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatCreateRequestTests.java deleted file mode 100644 index c757efe04a..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatCreateRequestTests.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.cohere; - -import java.time.Duration; -import java.util.List; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatModel; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest.LogitBias; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest.ReturnLikelihoods; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest.Truncate; -import org.springframework.ai.chat.prompt.Prompt; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -public class BedrockCohereChatCreateRequestTests { - - private CohereChatBedrockApi chatApi = new CohereChatBedrockApi(CohereChatModel.COHERE_COMMAND_V14.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), - Duration.ofMinutes(2)); - - @Test - public void createRequestWithChatOptions() { - - var client = new BedrockCohereChatModel(chatApi, - BedrockCohereChatOptions.builder() - .withTemperature(66.6f) - .withTopK(66) - .withTopP(0.66f) - .withMaxTokens(678) - .withStopSequences(List.of("stop1", "stop2")) - .withReturnLikelihoods(ReturnLikelihoods.ALL) - .withNumGenerations(3) - .withLogitBias(new LogitBias("t", 6.6f)) - .withTruncate(Truncate.END) - .build()); - - CohereChatRequest request = client.createRequest(new Prompt("Test message content"), true); - - assertThat(request.prompt()).isNotEmpty(); - assertThat(request.stream()).isTrue(); - - assertThat(request.temperature()).isEqualTo(66.6f); - assertThat(request.topK()).isEqualTo(66); - assertThat(request.topP()).isEqualTo(0.66f); - assertThat(request.maxTokens()).isEqualTo(678); - assertThat(request.stopSequences()).containsExactly("stop1", "stop2"); - assertThat(request.returnLikelihoods()).isEqualTo(ReturnLikelihoods.ALL); - assertThat(request.numGenerations()).isEqualTo(3); - assertThat(request.logitBias()).isEqualTo(new LogitBias("t", 6.6f)); - assertThat(request.truncate()).isEqualTo(Truncate.END); - - request = client.createRequest(new Prompt("Test message content", - BedrockCohereChatOptions.builder() - .withTemperature(99.9f) - .withTopK(99) - .withTopP(0.99f) - .withMaxTokens(888) - .withStopSequences(List.of("stop3", "stop4")) - .withReturnLikelihoods(ReturnLikelihoods.GENERATION) - .withNumGenerations(13) - .withLogitBias(new LogitBias("t", 9.9f)) - .withTruncate(Truncate.START) - .build()), - false - - ); - - assertThat(request.prompt()).isNotEmpty(); - assertThat(request.stream()).isFalse(); - - assertThat(request.temperature()).isEqualTo(99.9f); - assertThat(request.topK()).isEqualTo(99); - assertThat(request.topP()).isEqualTo(0.99f); - assertThat(request.maxTokens()).isEqualTo(888); - assertThat(request.stopSequences()).containsExactly("stop3", "stop4"); - assertThat(request.returnLikelihoods()).isEqualTo(ReturnLikelihoods.GENERATION); - assertThat(request.numGenerations()).isEqualTo(13); - assertThat(request.logitBias()).isEqualTo(new LogitBias("t", 9.9f)); - assertThat(request.truncate()).isEqualTo(Truncate.START); - } - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModelIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModelIT.java index 5da9f8670d..ec520357f2 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModelIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModelIT.java @@ -21,23 +21,22 @@ import java.util.Map; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import reactor.core.publisher.Flux; import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.regions.Region; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatModel; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.cohere.BedrockCohereChatOptions.ReturnLikelihoods; +import org.springframework.ai.bedrock.cohere.BedrockCohereChatOptions.Truncate; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; -import org.springframework.ai.chat.prompt.SystemPromptTemplate; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.ai.converter.ListOutputConverter; import org.springframework.ai.converter.MapOutputConverter; @@ -92,12 +91,8 @@ void multipleStreamAttempts() { @Test void roleTest() { String request = "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."; - String name = "Bob"; - String voice = "pirate"; UserMessage userMessage = new UserMessage(request); - SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource); - Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice)); - Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); + Prompt prompt = new Prompt(List.of(userMessage)); ChatResponse response = chatModel.call(prompt); assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard"); } @@ -194,19 +189,50 @@ void beanStreamOutputConverterRecords() { assertThat(actorsFilms.movies()).hasSize(5); } + @Test + void chatResponseUsage() { + Prompt prompt = new Prompt("Who are you?"); + + ChatResponse response = chatModel.call(prompt); + + Usage usage = response.getMetadata().getUsage(); + assertThat(usage).isNotNull(); + assertThat(usage.getPromptTokens()).isGreaterThan(1); + assertThat(usage.getGenerationTokens()).isGreaterThan(1); + } + + @Test + void chatOptions() { + BedrockCohereChatOptions options = BedrockCohereChatOptions.builder() + .withTemperature(0.5F) + .withTopP(0.5F) + .withTopK(100) + .withMaxTokens(100) + .withStopSequences(List.of("stop sequences")) + .withReturnLikelihoods(ReturnLikelihoods.ALL) + .withNumGenerations(1) + .withTruncate(Truncate.START) + .build(); + + Prompt prompt = new Prompt("Who are you?", options); + ChatResponse response = chatModel.call(prompt); + String content = response.getResult().getOutput().getContent(); + + assertThat(content).isNotNull(); + } + @SpringBootConfiguration public static class TestConfiguration { @Bean - public CohereChatBedrockApi cohereApi() { - return new CohereChatBedrockApi(CohereChatModel.COHERE_COMMAND_V14.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), + public BedrockConverseApi converseApi() { + return new BedrockConverseApi(EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), Duration.ofMinutes(2)); } @Bean - public BedrockCohereChatModel cohereChatModel(CohereChatBedrockApi cohereApi) { - return new BedrockCohereChatModel(cohereApi); + public BedrockCohereChatModel cohereChatModel(BedrockConverseApi converseApi) { + return new BedrockCohereChatModel(converseApi); } } diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApiIT.java index 540a6bd2bf..dc903faeba 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApiIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApiIT.java @@ -35,6 +35,7 @@ /** * @author Christian Tzolov */ +@Deprecated @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") public class CohereChatBedrockApiIT { diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatModelIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatModelIT.java index 5ab957e799..a3370c6b3c 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatModelIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatModelIT.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.ai.bedrock.jurassic2; import java.time.Duration; @@ -21,18 +20,20 @@ import java.util.List; import java.util.Map; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.regions.Region; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatModel.Ai21Jurassic2ChatModel; +import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatOptions.Penalty; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.chat.prompt.SystemPromptTemplate; @@ -61,10 +62,7 @@ class BedrockAi21Jurassic2ChatModelIT { void roleTest() { UserMessage userMessage = new UserMessage( "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."); - SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource); - Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate")); - - Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); + Prompt prompt = new Prompt(List.of(userMessage)); ChatResponse response = chatModel.call(prompt); @@ -130,34 +128,63 @@ void mapOutputConverter() { @Test void simpleChatResponse() { UserMessage userMessage = new UserMessage("Tell me a joke about AI."); - SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource); - Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate")); - Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); + Prompt prompt = new Prompt(List.of(userMessage)); ChatResponse response = chatModel.call(prompt); assertThat(response.getResult().getOutput().getContent()).contains("AI"); } + @Test + void chatResponseUsage() { + Prompt prompt = new Prompt("Who are you?"); + + ChatResponse response = chatModel.call(prompt); + + Usage usage = response.getMetadata().getUsage(); + assertThat(usage).isNotNull(); + assertThat(usage.getPromptTokens()).isGreaterThan(1); + assertThat(usage.getGenerationTokens()).isGreaterThan(1); + } + + @Test + void chatOptions() { + BedrockAi21Jurassic2ChatOptions options = BedrockAi21Jurassic2ChatOptions.builder() + .withNumResults(1) + .withMaxTokens(100) + .withMinTokens(1) + .withTemperature(0.5F) + .withTopP(0.5F) + .withTopK(20) + .withStopSequences(List.of("stop sequences")) + .withFrequencyPenalty(Penalty.builder().scale(1F).build()) + .withPresencePenalty(Penalty.builder().scale(1F).build()) + .withCountPenalty(Penalty.builder().scale(1F).build()) + .build(); + + Prompt prompt = new Prompt("Who are you?", options); + ChatResponse response = chatModel.call(prompt); + String content = response.getResult().getOutput().getContent(); + + assertThat(content).isNotNull(); + } + @SpringBootConfiguration public static class TestConfiguration { @Bean - public Ai21Jurassic2ChatBedrockApi jurassic2ChatBedrockApi() { - return new Ai21Jurassic2ChatBedrockApi( - Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatModel.AI21_J2_MID_V1.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), + public BedrockConverseApi converseApi() { + return new BedrockConverseApi(EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), Duration.ofMinutes(2)); } @Bean - public BedrockAi21Jurassic2ChatModel bedrockAi21Jurassic2ChatModel( - Ai21Jurassic2ChatBedrockApi jurassic2ChatBedrockApi) { - return new BedrockAi21Jurassic2ChatModel(jurassic2ChatBedrockApi, + public BedrockAi21Jurassic2ChatModel bedrockAi21Jurassic2ChatModel(BedrockConverseApi converseApi) { + return new BedrockAi21Jurassic2ChatModel(Ai21Jurassic2ChatModel.AI21_J2_MID_V1.id(), converseApi, BedrockAi21Jurassic2ChatOptions.builder() .withTemperature(0.5f) - .withMaxTokens(100) + .withMaxTokens(500) .withTopP(0.9f) .build()); } diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApiIT.java index 8525471d14..b184528daf 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApiIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApiIT.java @@ -31,6 +31,7 @@ /** * @author Christian Tzolov */ +@Deprecated @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") public class Ai21Jurassic2ChatBedrockApiIT { diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModelIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModelIT.java index 416b397783..3058d36095 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModelIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModelIT.java @@ -21,20 +21,19 @@ import java.util.Map; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import reactor.core.publisher.Flux; import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.regions.Region; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatModel; +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; import org.springframework.ai.chat.prompt.SystemPromptTemplate; @@ -195,19 +194,45 @@ void beanStreamOutputConverterRecords() { assertThat(actorsFilms.movies()).hasSize(5); } + @Test + void chatResponseUsage() { + Prompt prompt = new Prompt("Who are you?"); + + ChatResponse response = chatModel.call(prompt); + + Usage usage = response.getMetadata().getUsage(); + assertThat(usage).isNotNull(); + assertThat(usage.getPromptTokens()).isGreaterThan(1); + assertThat(usage.getGenerationTokens()).isGreaterThan(1); + } + + @Test + void chatOptions() { + BedrockLlamaChatOptions options = BedrockLlamaChatOptions.builder() + .withTemperature(0.5F) + .withTopP(0.5F) + .withMaxGenLen(100) + .build(); + + Prompt prompt = new Prompt("Who are you?", options); + ChatResponse response = chatModel.call(prompt); + String content = response.getResult().getOutput().getContent(); + + assertThat(content).isNotNull(); + } + @SpringBootConfiguration public static class TestConfiguration { @Bean - public LlamaChatBedrockApi llamaApi() { - return new LlamaChatBedrockApi(LlamaChatModel.LLAMA3_70B_INSTRUCT_V1.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), + public BedrockConverseApi converseApi() { + return new BedrockConverseApi(EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), Duration.ofMinutes(2)); } @Bean - public BedrockLlamaChatModel llamaChatModel(LlamaChatBedrockApi llamaApi) { - return new BedrockLlamaChatModel(llamaApi, + public BedrockLlamaChatModel llamaChatModel(BedrockConverseApi converseApi) { + return new BedrockLlamaChatModel(converseApi, BedrockLlamaChatOptions.builder().withTemperature(0.5f).withMaxGenLen(100).withTopP(0.9f).build()); } diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaCreateRequestTests.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaCreateRequestTests.java deleted file mode 100644 index 4bd48680d2..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaCreateRequestTests.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.llama; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; - -import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatModel; -import org.springframework.ai.chat.prompt.Prompt; - -import java.time.Duration; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @author Wei Jiang - */ -@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") -public class BedrockLlamaCreateRequestTests { - - private LlamaChatBedrockApi api = new LlamaChatBedrockApi(LlamaChatModel.LLAMA3_70B_INSTRUCT_V1.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), - Duration.ofMinutes(2)); - - @Test - public void createRequestWithChatOptions() { - - var client = new BedrockLlamaChatModel(api, - BedrockLlamaChatOptions.builder().withTemperature(66.6f).withMaxGenLen(666).withTopP(0.66f).build()); - - var request = client.createRequest(new Prompt("Test message content")); - - assertThat(request.prompt()).isNotEmpty(); - assertThat(request.temperature()).isEqualTo(66.6f); - assertThat(request.topP()).isEqualTo(0.66f); - assertThat(request.maxGenLen()).isEqualTo(666); - - request = client.createRequest(new Prompt("Test message content", - BedrockLlamaChatOptions.builder().withTemperature(99.9f).withMaxGenLen(999).withTopP(0.99f).build())); - - assertThat(request.prompt()).isNotEmpty(); - assertThat(request.temperature()).isEqualTo(99.9f); - assertThat(request.topP()).isEqualTo(0.99f); - assertThat(request.maxGenLen()).isEqualTo(999); - } - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApiIT.java index 5b4587358f..f1c6f07fcb 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApiIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApiIT.java @@ -36,6 +36,7 @@ * @author Christian Tzolov * @author Wei Jiang */ +@Deprecated @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") public class LlamaChatBedrockApiIT { diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModelCreateRequestTests.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModelCreateRequestTests.java deleted file mode 100644 index af0522de63..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModelCreateRequestTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.titan; - -import java.time.Duration; -import java.util.List; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatModel; -import org.springframework.ai.chat.prompt.Prompt; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -public class BedrockTitanChatModelCreateRequestTests { - - private TitanChatBedrockApi api = new TitanChatBedrockApi(TitanChatModel.TITAN_TEXT_EXPRESS_V1.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), - Duration.ofMinutes(2)); - - @Test - public void createRequestWithChatOptions() { - - var model = new BedrockTitanChatModel(api, - BedrockTitanChatOptions.builder() - .withTemperature(66.6f) - .withTopP(0.66f) - .withMaxTokenCount(666) - .withStopSequences(List.of("stop1", "stop2")) - .build()); - - var request = model.createRequest(new Prompt("Test message content")); - - assertThat(request.inputText()).isNotEmpty(); - assertThat(request.textGenerationConfig().temperature()).isEqualTo(66.6f); - assertThat(request.textGenerationConfig().topP()).isEqualTo(0.66f); - assertThat(request.textGenerationConfig().maxTokenCount()).isEqualTo(666); - assertThat(request.textGenerationConfig().stopSequences()).containsExactly("stop1", "stop2"); - - request = model.createRequest(new Prompt("Test message content", - BedrockTitanChatOptions.builder() - .withTemperature(99.9f) - .withTopP(0.99f) - .withMaxTokenCount(999) - .withStopSequences(List.of("stop3", "stop4")) - .build() - - )); - - assertThat(request.inputText()).isNotEmpty(); - assertThat(request.textGenerationConfig().temperature()).isEqualTo(99.9f); - assertThat(request.textGenerationConfig().topP()).isEqualTo(0.99f); - assertThat(request.textGenerationConfig().maxTokenCount()).isEqualTo(999); - assertThat(request.textGenerationConfig().stopSequences()).containsExactly("stop3", "stop4"); - } - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModelIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModelIT.java index 97085c2c2b..f89edc9799 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModelIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModelIT.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; @@ -29,16 +28,14 @@ import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.regions.Region; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatModel; +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.messages.AssistantMessage; -import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.metadata.Usage; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.PromptTemplate; -import org.springframework.ai.chat.prompt.SystemPromptTemplate; import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.ai.converter.ListOutputConverter; import org.springframework.ai.converter.MapOutputConverter; @@ -93,12 +90,8 @@ void multipleStreamAttempts() { @Test void roleTest() { String request = "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."; - String name = "Bob"; - String voice = "pirate"; UserMessage userMessage = new UserMessage(request); - SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource); - Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice)); - Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); + Prompt prompt = new Prompt(List.of(userMessage)); ChatResponse response = chatModel.call(prompt); assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard"); } @@ -200,19 +193,46 @@ void beanStreamOutputConverterRecords() { assertThat(actorsFilms.movies()).hasSize(5); } + @Test + void chatResponseUsage() { + Prompt prompt = new Prompt("Who are you?"); + + ChatResponse response = chatModel.call(prompt); + + Usage usage = response.getMetadata().getUsage(); + assertThat(usage).isNotNull(); + assertThat(usage.getPromptTokens()).isGreaterThan(1); + assertThat(usage.getGenerationTokens()).isGreaterThan(1); + } + + @Test + void chatOptions() { + BedrockTitanChatOptions options = BedrockTitanChatOptions.builder() + .withTemperature(0.5F) + .withTopP(0.5F) + .withMaxTokenCount(100) + .withStopSequences(List.of("stop sequences")) + .build(); + + Prompt prompt = new Prompt("Who are you?", options); + ChatResponse response = chatModel.call(prompt); + String content = response.getResult().getOutput().getContent(); + + assertThat(content).isNotNull(); + } + @SpringBootConfiguration public static class TestConfiguration { @Bean - public TitanChatBedrockApi titanApi() { - return new TitanChatBedrockApi(TitanChatModel.TITAN_TEXT_PREMIER_V1.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), + public BedrockConverseApi converseApi() { + return new BedrockConverseApi(EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), Duration.ofMinutes(2)); } @Bean - public BedrockTitanChatModel titanChatModel(TitanChatBedrockApi titanApi) { - return new BedrockTitanChatModel(titanApi); + public BedrockTitanChatModel titanChatModel(BedrockConverseApi converseApi) { + return new BedrockTitanChatModel(converseApi); } } diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApiIT.java index e7bb1f8bff..d83969df2b 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApiIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApiIT.java @@ -34,6 +34,7 @@ /** * @author Christian Tzolov */ +@Deprecated @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") public class TitanChatBedrockApiIT { diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc index f8b2b2062a..8671dea707 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc @@ -21,8 +21,8 @@ Then add the Spring Boot Starter dependency to your project's Maven `pom.xml` bu [source,xml] ---- - spring-ai-bedrock-ai-spring-boot-starter - org.springframework.ai + org.springframework.ai + spring-ai-bedrock-ai-spring-boot-starter ---- @@ -83,15 +83,13 @@ Here are the supported `` and `` combinations: [cols="|,|,|,|"] |==== -| Model | Chat | Chat Streaming | Embedding - -| llama | Yes | Yes | No -| jurassic2 | Yes | No | No -| cohere | Yes | Yes | Yes +| Model | Chat | Chat Streaming | Embedding | anthropic 2 | Yes | Yes | No -| anthropic 3 | Yes | Yes | No -| jurassic2 (WIP) | Yes | No | No -| titan | Yes | Yes | Yes (however, no batch support) +| anthropic 3 | Yes | Yes | No +| cohere | Yes | Yes | Yes +| jurassic2 | Yes | No | No +| llama | Yes | Yes | No +| titan | Yes | Yes | Yes (however, no batch support) |==== For example, to enable the Bedrock Llama chat model, you need to set `spring.ai.bedrock.llama.chat.enabled=true`. diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic.adoc index dde93f185d..7657adb1e7 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic.adoc @@ -81,13 +81,12 @@ The prefix `spring.ai.bedrock.anthropic.chat` is the property prefix that config | Property | Description | Default | spring.ai.bedrock.anthropic.chat.enable | Enable Bedrock Anthropic chat model. Disabled by default | false -| spring.ai.bedrock.anthropic.chat.model | The model id to use. See the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java[AnthropicChatModel] for the supported models. | anthropic.claude-v2 -| spring.ai.bedrock.anthropic.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | 0.8 -| spring.ai.bedrock.anthropic.chat.options.topP | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default +| spring.ai.bedrock.anthropic.chat.model | The model id to use. See the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java[AnthropicChatModel] for the supported models. | `anthropic.claude-v2` +| spring.ai.bedrock.anthropic.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | AWS Bedrock default +| spring.ai.bedrock.anthropic.chat.options.maxTokens | Specify the maximum number of tokens to use in the generated response. Note that the models may stop before reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate. We recommend a limit of 4,000 tokens for optimal performance. | AWS Bedrock default | spring.ai.bedrock.anthropic.chat.options.topK | Specify the number of token choices the generative uses to generate the next token. | AWS Bedrock default -| spring.ai.bedrock.anthropic.chat.options.stopSequences | Configure up to four sequences that the generative recognizes. After a stop sequence, the generative stops generating further tokens. The returned text doesn't contain the stop sequence. | 10 -| spring.ai.bedrock.anthropic.chat.options.anthropicVersion | The version of the generative to use. | bedrock-2023-05-31 -| spring.ai.bedrock.anthropic.chat.options.maxTokensToSample | Specify the maximum number of tokens to use in the generated response. Note that the models may stop before reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate. We recommend a limit of 4,000 tokens for optimal performance. | 500 +| spring.ai.bedrock.anthropic.chat.options.topP | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default +| spring.ai.bedrock.anthropic.chat.options.stopSequences | Configure up to four sequences that the generative recognizes. After a stop sequence, the generative stops generating further tokens. The returned text doesn't contain the stop sequence. | AWS Bedrock default |==== Look at the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java[AnthropicChatModel] for other model IDs. @@ -205,10 +204,10 @@ AnthropicChatBedrockApi anthropicApi = new AnthropicChatBedrockApi( BedrockAnthropicChatModel chatModel = new BedrockAnthropicChatModel(anthropicApi, AnthropicChatOptions.builder() .withTemperature(0.6f) + .withMaxTokens(100) .withTopK(10) .withTopP(0.8f) - .withMaxTokensToSample(100) - .withAnthropicVersion(AnthropicChatBedrockApi.DEFAULT_ANTHROPIC_VERSION) + .withStopSequences(List.of("stop sequences")) .build()); ChatResponse response = chatModel.call( diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc index c03b1d7da0..590dbf7908 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc @@ -79,12 +79,11 @@ The prefix `spring.ai.bedrock.anthropic3.chat` is the property prefix that confi | spring.ai.bedrock.anthropic3.chat.enable | Enable Bedrock Anthropic chat model. Disabled by default | false | spring.ai.bedrock.anthropic3.chat.model | The model id to use. Supports the `anthropic.claude-3-sonnet-20240229-v1:0`,`anthropic.claude-3-haiku-20240307-v1:0` and the legacy `anthropic.claude-v2`, `anthropic.claude-v2:1` and `anthropic.claude-instant-v1` models for both synchronous and streaming responses. | `anthropic.claude-3-sonnet-20240229-v1:0` -| spring.ai.bedrock.anthropic3.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | 0.8 -| spring.ai.bedrock.anthropic3.chat.options.top-p | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default -| spring.ai.bedrock.anthropic3.chat.options.top-k | Specify the number of token choices the generative uses to generate the next token. | AWS Bedrock default -| spring.ai.bedrock.anthropic3.chat.options.stop-sequences | Configure up to four sequences that the generative recognizes. After a stop sequence, the generative stops generating further tokens. The returned text doesn't contain the stop sequence. | 10 -| spring.ai.bedrock.anthropic3.chat.options.anthropic-version | The version of the generative to use. | bedrock-2023-05-31 -| spring.ai.bedrock.anthropic3.chat.options.max-tokens | Specify the maximum number of tokens to use in the generated response. Note that the models may stop before reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate. We recommend a limit of 4,000 tokens for optimal performance. | 500 +| spring.ai.bedrock.anthropic3.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | AWS Bedrock default +| spring.ai.bedrock.anthropic3.chat.options.maxTokens | Specify the maximum number of tokens to use in the generated response. Note that the models may stop before reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate. We recommend a limit of 4,000 tokens for optimal performance. | AWS Bedrock default +| spring.ai.bedrock.anthropic3.chat.options.topK | Specify the number of token choices the generative uses to generate the next token. | AWS Bedrock default +| spring.ai.bedrock.anthropic3.chat.options.topP | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default +| spring.ai.bedrock.anthropic3.chat.options.stopSequences | Configure up to four sequences that the generative recognizes. After a stop sequence, the generative stops generating further tokens. The returned text doesn't contain the stop sequence. | AWS Bedrock default |==== Look at the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java[AnthropicChatModel] for other model IDs. @@ -246,10 +245,10 @@ Anthropic3ChatBedrockApi anthropicApi = new Anthropic3ChatBedrockApi( BedrockAnthropic3ChatModel chatModel = new BedrockAnthropic3ChatModel(anthropicApi, AnthropicChatOptions.builder() .withTemperature(0.6f) + .withMaxTokens(100) .withTopK(10) .withTopP(0.8f) - .withMaxTokensToSample(100) - .withAnthropicVersion(AnthropicChatBedrockApi.DEFAULT_ANTHROPIC_VERSION) + .withStopSequences(List.of("stop sequences")) .build()); ChatResponse response = chatModel.call( diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-cohere.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-cohere.adoc index c4345a46c4..85a66b36e9 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-cohere.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-cohere.adoc @@ -71,8 +71,8 @@ The prefix `spring.ai.bedrock.cohere.chat` is the property prefix that configure | Property | Description | Default | spring.ai.bedrock.cohere.chat.enabled | Enable or disable support for Cohere | false -| spring.ai.bedrock.cohere.chat.model | The model id to use. See the https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java#L326C14-L326C29[CohereChatModel] for the supported models. | cohere.command-text-v14 -| spring.ai.bedrock.cohere.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | 0.7 +| spring.ai.bedrock.cohere.chat.model | The model id to use. See the https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java#L326C14-L326C29[CohereChatModel] for the supported models. | `cohere.command-text-v14` +| spring.ai.bedrock.cohere.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | AWS Bedrock default | spring.ai.bedrock.cohere.chat.options.topP | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default | spring.ai.bedrock.cohere.chat.options.topK | Specify the number of token choices the model uses to generate the next token | AWS Bedrock default | spring.ai.bedrock.cohere.chat.options.maxTokens | Specify the maximum number of tokens to use in the generated response. | AWS Bedrock default diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-jurassic2.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-jurassic2.adoc index d1d8956ce5..55e813cbdb 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-jurassic2.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-jurassic2.adoc @@ -71,10 +71,17 @@ The prefix `spring.ai.bedrock.jurassic2.chat` is the property prefix that config | Property | Description | Default | spring.ai.bedrock.jurassic2.chat.enabled | Enable or disable support for Jurassic-2 | false -| spring.ai.bedrock.jurassic2.chat.model | The model id to use (See Below) | ai21.j2-mid-v1 -| spring.ai.bedrock.jurassic2.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0], inclusive. A value closer to 1.0 will produce responses that are more varied, while a value closer to 0.0 will typically result in less surprising responses from the model. This value specifies default to be used by the backend while making the call to the model. | 0.7 -| spring.ai.bedrock.jurassic2.chat.options.top-p | The maximum cumulative probability of tokens to consider when sampling. The model uses combined Top-k and nucleus sampling. Nucleus sampling considers the smallest set of tokens whose probability sum is at least topP. | AWS Bedrock default -| spring.ai.bedrock.jurassic2.chat.options.max-tokens | Specify the maximum number of tokens to use in the generated response. The model truncates the response once the generated text exceeds maxTokens. | 500 +| spring.ai.bedrock.jurassic2.chat.model | The model id to use | `ai21.j2-mid-v1` +| spring.ai.bedrock.jurassic2.chat.options.numResults | Number of completions to sample and return | AWS Bedrock default +| spring.ai.bedrock.jurassic2.chat.options.maxTokens | The maximum number of tokens to generate per result | AWS Bedrock default +| spring.ai.bedrock.jurassic2.chat.options.minTokens | The minimum number of tokens to generate per result | AWS Bedrock default +| spring.ai.bedrock.jurassic2.chat.options.temperature | Modifies the distribution from which tokens are sampled | AWS Bedrock default +| spring.ai.bedrock.jurassic2.chat.options.topP | Sample tokens from the corresponding top percentile of probability mass | AWS Bedrock default +| spring.ai.bedrock.jurassic2.chat.options.topKReturn | Return the top-K (topKReturn) alternative tokens | AWS Bedrock default +| spring.ai.bedrock.jurassic2.chat.options.stopSequences | Stops decoding if any of the strings is generated | AWS Bedrock default +| spring.ai.bedrock.jurassic2.chat.options.frequencyPenalty | Penalty object for frequency | AWS Bedrock default +| spring.ai.bedrock.jurassic2.chat.options.presencePenalty | Penalty object for presence | AWS Bedrock default +| spring.ai.bedrock.jurassic2.chat.options.countPenalty | Penalty object for count | AWS Bedrock default |==== Look at https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java#L164[Ai21Jurassic2ChatBedrockApi#Ai21Jurassic2ChatModel] for other model IDs. The other value supported is `ai21.j2-ultra-v1`. @@ -183,9 +190,17 @@ Ai21Jurassic2ChatBedrockApi api = new Ai21Jurassic2ChatBedrockApi(Ai21Jurassic2C BedrockAi21Jurassic2ChatModel chatModel = new BedrockAi21Jurassic2ChatModel(api, BedrockAi21Jurassic2ChatOptions.builder() - .withTemperature(0.5f) + .withNumResults(1) .withMaxTokens(100) - .withTopP(0.9f).build()); + .withMinTokens(1) + .withTemperature(0.5F) + .withTopP(0.5F) + .withTopK(20) + .withStopSequences(List.of("stop sequences")) + .withFrequencyPenalty(Penalty.builder().scale(1F).build()) + .withPresencePenalty(Penalty.builder().scale(1F).build()) + .withCountPenalty(Penalty.builder().scale(1F).build()) + .build(); ChatResponse response = chatModel.call( new Prompt("Generate the names of 5 famous pirates.")); diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-llama.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-llama.adoc index d8a0c63476..a7c526f453 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-llama.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-llama.adoc @@ -76,10 +76,10 @@ The prefix `spring.ai.bedrock.llama.chat` is the property prefix that configures | Property | Description | Default | spring.ai.bedrock.llama.chat.enabled | Enable or disable support for Llama | false -| spring.ai.bedrock.llama.chat.model | The model id to use (See Below) | meta.llama3-70b-instruct-v1:0 -| spring.ai.bedrock.llama.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0], inclusive. A value closer to 1.0 will produce responses that are more varied, while a value closer to 0.0 will typically result in less surprising responses from the model. This value specifies default to be used by the backend while making the call to the model. | 0.7 +| spring.ai.bedrock.llama.chat.model | The model id to use (See Below) | `meta.llama3-70b-instruct-v1:0` +| spring.ai.bedrock.llama.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0], inclusive. A value closer to 1.0 will produce responses that are more varied, while a value closer to 0.0 will typically result in less surprising responses from the model. This value specifies default to be used by the backend while making the call to the model. | AWS Bedrock default | spring.ai.bedrock.llama.chat.options.top-p | The maximum cumulative probability of tokens to consider when sampling. The model uses combined Top-k and nucleus sampling. Nucleus sampling considers the smallest set of tokens whose probability sum is at least topP. | AWS Bedrock default -| spring.ai.bedrock.llama.chat.options.max-gen-len | Specify the maximum number of tokens to use in the generated response. The model truncates the response once the generated text exceeds maxGenLen. | 300 +| spring.ai.bedrock.llama.chat.options.max-gen-len | Specify the maximum number of tokens to use in the generated response. The model truncates the response once the generated text exceeds maxGenLen. | AWS Bedrock default |==== Look at https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java#L164[LlamaChatBedrockApi#LlamaChatModel] for other model IDs. The other value supported is `meta.llama2-13b-chat-v1`. diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-titan.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-titan.adoc index a1e57a3579..86a59c4c7a 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-titan.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-titan.adoc @@ -72,8 +72,8 @@ The prefix `spring.ai.bedrock.titan.chat` is the property prefix that configures | Property | Description | Default | spring.ai.bedrock.titan.chat.enable | Enable Bedrock Titan chat model. Disabled by default | false -| spring.ai.bedrock.titan.chat.model | The model id to use. See the link:https://github.com/spring-projects/spring-ai/blob/4839a6175cd1ec89498b97d3efb6647022c3c7cb/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java#L220[TitanChatBedrockApi#TitanChatModel] for the supported models. | amazon.titan-text-lite-v1 -| spring.ai.bedrock.titan.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | 0.7 +| spring.ai.bedrock.titan.chat.model | The model id to use. See the link:https://github.com/spring-projects/spring-ai/blob/4839a6175cd1ec89498b97d3efb6647022c3c7cb/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java#L220[TitanChatBedrockApi#TitanChatModel] for the supported models. | `amazon.titan-text-express-v1` +| spring.ai.bedrock.titan.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | AWS Bedrock default | spring.ai.bedrock.titan.chat.options.topP | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default | spring.ai.bedrock.titan.chat.options.stopSequences | Configure up to four sequences that the generative recognizes. After a stop sequence, the generative stops generating further tokens. The returned text doesn't contain the stop sequence. | AWS Bedrock default | spring.ai.bedrock.titan.chat.options.maxTokenCount | Specify the maximum number of tokens to use in the generated response. Note that the models may stop before reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate. We recommend a limit of 4,000 tokens for optimal performance. | AWS Bedrock default diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfiguration.java index 3e30324546..9a7112c8ef 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfiguration.java @@ -18,8 +18,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.bedrock.anthropic.BedrockAnthropicChatModel; import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi; +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -40,8 +42,8 @@ * @author Wei Jiang * @since 0.8.0 */ -@AutoConfiguration -@ConditionalOnClass(AnthropicChatBedrockApi.class) +@AutoConfiguration(after = BedrockConverseApiAutoConfiguration.class) +@ConditionalOnClass(BedrockConverseApi.class) @EnableConfigurationProperties({ BedrockAnthropicChatProperties.class, BedrockAwsConnectionProperties.class }) @ConditionalOnProperty(prefix = BedrockAnthropicChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") @Import(BedrockAwsConnectionConfiguration.class) @@ -58,10 +60,10 @@ public AnthropicChatBedrockApi anthropicApi(AwsCredentialsProvider credentialsPr } @Bean - @ConditionalOnBean(AnthropicChatBedrockApi.class) - public BedrockAnthropicChatModel anthropicChatModel(AnthropicChatBedrockApi anthropicApi, + @ConditionalOnBean(BedrockConverseApi.class) + public BedrockAnthropicChatModel anthropicChatModel(BedrockConverseApi converseApi, BedrockAnthropicChatProperties properties) { - return new BedrockAnthropicChatModel(anthropicApi, properties.getOptions()); + return new BedrockAnthropicChatModel(properties.getModel(), converseApi, properties.getOptions()); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatProperties.java index e9b2636773..d1850b2708 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatProperties.java @@ -15,10 +15,8 @@ */ package org.springframework.ai.autoconfigure.bedrock.anthropic; -import java.util.List; - import org.springframework.ai.bedrock.anthropic.AnthropicChatOptions; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatModel; +import org.springframework.ai.bedrock.anthropic.BedrockAnthropicChatModel.AnthropicChatModel; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.util.Assert; @@ -27,6 +25,7 @@ * Configuration properties for Bedrock Anthropic. * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ @ConfigurationProperties(BedrockAnthropicChatProperties.CONFIG_PREFIX) @@ -46,12 +45,7 @@ public class BedrockAnthropicChatProperties { private String model = AnthropicChatModel.CLAUDE_V2.id(); @NestedConfigurationProperty - private AnthropicChatOptions options = AnthropicChatOptions.builder() - .withTemperature(0.7f) - .withMaxTokensToSample(300) - .withTopK(10) - .withStopSequences(List.of("\n\nHuman:")) - .build(); + private AnthropicChatOptions options = AnthropicChatOptions.builder().build(); public boolean isEnabled() { return this.enabled; @@ -75,7 +69,6 @@ public AnthropicChatOptions getOptions() { public void setOptions(AnthropicChatOptions options) { Assert.notNull(options, "AnthropicChatOptions must not be null"); - Assert.notNull(options.getTemperature(), "AnthropicChatOptions.temperature must not be null"); this.options = options; } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java index 3e53f026b2..6c1272bfb8 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java @@ -18,8 +18,10 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel; import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi; +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -32,7 +34,7 @@ import software.amazon.awssdk.regions.providers.AwsRegionProvider; /** - * {@link AutoConfiguration Auto-configuration} for Bedrock Anthropic Chat Client. + * {@link AutoConfiguration Auto-configuration} for Bedrock Anthropic3 Chat Client. * * Leverages the Spring Cloud AWS to resolve the {@link AwsCredentialsProvider}. * @@ -40,8 +42,8 @@ * @author Wei Jiang * @since 0.8.0 */ -@AutoConfiguration -@ConditionalOnClass(Anthropic3ChatBedrockApi.class) +@AutoConfiguration(after = BedrockConverseApiAutoConfiguration.class) +@ConditionalOnClass(BedrockConverseApi.class) @EnableConfigurationProperties({ BedrockAnthropic3ChatProperties.class, BedrockAwsConnectionProperties.class }) @ConditionalOnProperty(prefix = BedrockAnthropic3ChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") @Import(BedrockAwsConnectionConfiguration.class) @@ -58,10 +60,10 @@ public Anthropic3ChatBedrockApi anthropic3Api(AwsCredentialsProvider credentials } @Bean - @ConditionalOnBean(Anthropic3ChatBedrockApi.class) - public BedrockAnthropic3ChatModel anthropic3ChatModel(Anthropic3ChatBedrockApi anthropicApi, + @ConditionalOnBean(BedrockConverseApi.class) + public BedrockAnthropic3ChatModel anthropic3ChatModel(BedrockConverseApi converseApi, BedrockAnthropic3ChatProperties properties) { - return new BedrockAnthropic3ChatModel(anthropicApi, properties.getOptions()); + return new BedrockAnthropic3ChatModel(properties.getModel(), converseApi, properties.getOptions()); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatProperties.java index 71086b0d66..413a209a81 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatProperties.java @@ -15,9 +15,8 @@ */ package org.springframework.ai.autoconfigure.bedrock.anthropic3; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi; import org.springframework.ai.bedrock.anthropic3.Anthropic3ChatOptions; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatModel; +import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel.Anthropic3ChatModel; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.util.Assert; @@ -26,6 +25,7 @@ * Configuration properties for Bedrock Anthropic Claude 3. * * @author Christian Tzolov + * @author Wei Jiang * @since 1.0.0 */ @ConfigurationProperties(BedrockAnthropic3ChatProperties.CONFIG_PREFIX) @@ -39,19 +39,13 @@ public class BedrockAnthropic3ChatProperties { private boolean enabled = false; /** - * The generative id to use. See the {@link AnthropicChatModel} for the supported + * The generative id to use. See the {@link Anthropic3ChatModel} for the supported * models. */ - private String model = AnthropicChatModel.CLAUDE_V3_SONNET.id(); + private String model = Anthropic3ChatModel.CLAUDE_V3_SONNET.id(); @NestedConfigurationProperty - private Anthropic3ChatOptions options = Anthropic3ChatOptions.builder() - .withTemperature(0.7f) - .withMaxTokens(300) - .withTopK(10) - .withAnthropicVersion(Anthropic3ChatBedrockApi.DEFAULT_ANTHROPIC_VERSION) - // .withStopSequences(List.of("\n\nHuman:")) - .build(); + private Anthropic3ChatOptions options = Anthropic3ChatOptions.builder().build(); public boolean isEnabled() { return this.enabled; @@ -74,8 +68,7 @@ public Anthropic3ChatOptions getOptions() { } public void setOptions(Anthropic3ChatOptions options) { - Assert.notNull(options, "AnthropicChatOptions must not be null"); - Assert.notNull(options.getTemperature(), "AnthropicChatOptions.temperature must not be null"); + Assert.notNull(options, "Anthropic3ChatOptions must not be null"); this.options = options; } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfiguration.java index 896078e5bc..cc15a767ae 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfiguration.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.bedrock.cohere.BedrockCohereChatModel; import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -34,12 +36,14 @@ /** * {@link AutoConfiguration Auto-configuration} for Bedrock Cohere Chat Client. * + * Leverages the Spring Cloud AWS to resolve the {@link AwsCredentialsProvider}. + * * @author Christian Tzolov * @author Wei Jiang * @since 0.8.0 */ -@AutoConfiguration -@ConditionalOnClass(CohereChatBedrockApi.class) +@AutoConfiguration(after = BedrockConverseApiAutoConfiguration.class) +@ConditionalOnClass(BedrockConverseApi.class) @EnableConfigurationProperties({ BedrockCohereChatProperties.class, BedrockAwsConnectionProperties.class }) @ConditionalOnProperty(prefix = BedrockCohereChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") @Import(BedrockAwsConnectionConfiguration.class) @@ -56,11 +60,11 @@ public CohereChatBedrockApi cohereChatApi(AwsCredentialsProvider credentialsProv } @Bean - @ConditionalOnBean(CohereChatBedrockApi.class) - public BedrockCohereChatModel cohereChatModel(CohereChatBedrockApi cohereChatApi, + @ConditionalOnBean(BedrockConverseApi.class) + public BedrockCohereChatModel cohereChatModel(BedrockConverseApi converseApi, BedrockCohereChatProperties properties) { - return new BedrockCohereChatModel(cohereChatApi, properties.getOptions()); + return new BedrockCohereChatModel(properties.getModel(), converseApi, properties.getOptions()); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatProperties.java index 0381d591b9..ea1766d9a5 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatProperties.java @@ -16,14 +16,16 @@ package org.springframework.ai.autoconfigure.bedrock.cohere; import org.springframework.ai.bedrock.cohere.BedrockCohereChatOptions; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi; +import org.springframework.ai.bedrock.cohere.BedrockCohereChatModel.CohereChatModel; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.util.Assert; /** * Bedrock Cohere Chat autoconfiguration properties. * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ @ConfigurationProperties(BedrockCohereChatProperties.CONFIG_PREFIX) @@ -39,7 +41,7 @@ public class BedrockCohereChatProperties { /** * Bedrock Cohere Chat generative name. Defaults to 'cohere-command-v14'. */ - private String model = CohereChatBedrockApi.CohereChatModel.COHERE_COMMAND_V14.id(); + private String model = CohereChatModel.COHERE_COMMAND_V14.id(); @NestedConfigurationProperty private BedrockCohereChatOptions options = BedrockCohereChatOptions.builder().build(); @@ -65,6 +67,8 @@ public BedrockCohereChatOptions getOptions() { } public void setOptions(BedrockCohereChatOptions options) { + Assert.notNull(options, "BedrockCohereChatOptions must not be null"); + this.options = options; } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatAutoConfiguration.java index 8ad3c0bb1a..4da85c90df 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatAutoConfiguration.java @@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatModel; import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -35,12 +37,14 @@ /** * {@link AutoConfiguration Auto-configuration} for Bedrock Jurassic2 Chat Client. * + * Leverages the Spring Cloud AWS to resolve the {@link AwsCredentialsProvider}. + * * @author Ahmed Yousri * @author Wei Jiang * @since 1.0.0 */ -@AutoConfiguration -@ConditionalOnClass(Ai21Jurassic2ChatBedrockApi.class) +@AutoConfiguration(after = BedrockConverseApiAutoConfiguration.class) +@ConditionalOnClass(BedrockConverseApi.class) @EnableConfigurationProperties({ BedrockAi21Jurassic2ChatProperties.class, BedrockAwsConnectionProperties.class }) @ConditionalOnProperty(prefix = BedrockAi21Jurassic2ChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") @@ -58,13 +62,11 @@ public Ai21Jurassic2ChatBedrockApi ai21Jurassic2ChatBedrockApi(AwsCredentialsPro } @Bean - @ConditionalOnBean(Ai21Jurassic2ChatBedrockApi.class) - public BedrockAi21Jurassic2ChatModel jurassic2ChatModel(Ai21Jurassic2ChatBedrockApi ai21Jurassic2ChatBedrockApi, + @ConditionalOnBean(BedrockConverseApi.class) + public BedrockAi21Jurassic2ChatModel jurassic2ChatModel(BedrockConverseApi converseApi, BedrockAi21Jurassic2ChatProperties properties) { - return BedrockAi21Jurassic2ChatModel.builder(ai21Jurassic2ChatBedrockApi) - .withOptions(properties.getOptions()) - .build(); + return new BedrockAi21Jurassic2ChatModel(properties.getModel(), converseApi, properties.getOptions()); } } \ No newline at end of file diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatProperties.java index eccd7e0c9e..a29656e958 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatProperties.java @@ -17,14 +17,16 @@ package org.springframework.ai.autoconfigure.bedrock.jurrasic2; import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatOptions; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatModel; +import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatModel.Ai21Jurassic2ChatModel; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.util.Assert; /** * Configuration properties for Bedrock Ai21Jurassic2. * * @author Ahmed Yousri + * @author Wei Jiang * @since 1.0.0 */ @ConfigurationProperties(BedrockAi21Jurassic2ChatProperties.CONFIG_PREFIX) @@ -44,10 +46,7 @@ public class BedrockAi21Jurassic2ChatProperties { private String model = Ai21Jurassic2ChatModel.AI21_J2_MID_V1.id(); @NestedConfigurationProperty - private BedrockAi21Jurassic2ChatOptions options = BedrockAi21Jurassic2ChatOptions.builder() - .withTemperature(0.7f) - .withMaxTokens(500) - .build(); + private BedrockAi21Jurassic2ChatOptions options = BedrockAi21Jurassic2ChatOptions.builder().build(); public boolean isEnabled() { return this.enabled; @@ -70,6 +69,8 @@ public BedrockAi21Jurassic2ChatOptions getOptions() { } public void setOptions(BedrockAi21Jurassic2ChatOptions options) { + Assert.notNull(options, "BedrockAi21Jurassic2ChatOptions must not be null"); + this.options = options; } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfiguration.java index 6e105b8f26..ad5c1d3c3d 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfiguration.java @@ -16,12 +16,15 @@ package org.springframework.ai.autoconfigure.bedrock.llama; import com.fasterxml.jackson.databind.ObjectMapper; + +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.bedrock.llama.BedrockLlamaChatModel; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.regions.providers.AwsRegionProvider; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -41,8 +44,8 @@ * @author Wei Jiang * @since 0.8.0 */ -@AutoConfiguration -@ConditionalOnClass(LlamaChatBedrockApi.class) +@AutoConfiguration(after = BedrockConverseApiAutoConfiguration.class) +@ConditionalOnClass(BedrockConverseApi.class) @EnableConfigurationProperties({ BedrockLlamaChatProperties.class, BedrockAwsConnectionProperties.class }) @ConditionalOnProperty(prefix = BedrockLlamaChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") @Import(BedrockAwsConnectionConfiguration.class) @@ -58,10 +61,10 @@ public LlamaChatBedrockApi llamaApi(AwsCredentialsProvider credentialsProvider, } @Bean - @ConditionalOnBean(LlamaChatBedrockApi.class) - public BedrockLlamaChatModel llamaChatModel(LlamaChatBedrockApi llamaApi, BedrockLlamaChatProperties properties) { + @ConditionalOnBean(BedrockConverseApi.class) + public BedrockLlamaChatModel llamaChatModel(BedrockConverseApi converseApi, BedrockLlamaChatProperties properties) { - return new BedrockLlamaChatModel(llamaApi, properties.getOptions()); + return new BedrockLlamaChatModel(properties.getModel(), converseApi, properties.getOptions()); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatProperties.java index 048b7dde2b..8f3eb3d666 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatProperties.java @@ -16,14 +16,16 @@ package org.springframework.ai.autoconfigure.bedrock.llama; import org.springframework.ai.bedrock.llama.BedrockLlamaChatOptions; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatModel; +import org.springframework.ai.bedrock.llama.BedrockLlamaChatModel.LlamaChatModel; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.util.Assert; /** * Configuration properties for Bedrock Llama. * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ @ConfigurationProperties(BedrockLlamaChatProperties.CONFIG_PREFIX) @@ -42,10 +44,7 @@ public class BedrockLlamaChatProperties { private String model = LlamaChatModel.LLAMA3_70B_INSTRUCT_V1.id(); @NestedConfigurationProperty - private BedrockLlamaChatOptions options = BedrockLlamaChatOptions.builder() - .withTemperature(0.7f) - .withMaxGenLen(300) - .build(); + private BedrockLlamaChatOptions options = BedrockLlamaChatOptions.builder().build(); public boolean isEnabled() { return this.enabled; @@ -68,6 +67,8 @@ public BedrockLlamaChatOptions getOptions() { } public void setOptions(BedrockLlamaChatOptions options) { + Assert.notNull(options, "BedrockLlamaChatOptions must not be null"); + this.options = options; } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfiguration.java index 0115967fe5..746ae52435 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfiguration.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.bedrock.titan.BedrockTitanChatModel; import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -34,12 +36,14 @@ /** * {@link AutoConfiguration Auto-configuration} for Bedrock Titan Chat Client. * + * Leverages the Spring Cloud AWS to resolve the {@link AwsCredentialsProvider}. + * * @author Christian Tzolov * @author Wei Jiang * @since 0.8.0 */ -@AutoConfiguration -@ConditionalOnClass(TitanChatBedrockApi.class) +@AutoConfiguration(after = BedrockConverseApiAutoConfiguration.class) +@ConditionalOnClass(BedrockConverseApi.class) @EnableConfigurationProperties({ BedrockTitanChatProperties.class, BedrockAwsConnectionProperties.class }) @ConditionalOnProperty(prefix = BedrockTitanChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") @Import(BedrockAwsConnectionConfiguration.class) @@ -56,11 +60,10 @@ public TitanChatBedrockApi titanChatBedrockApi(AwsCredentialsProvider credential } @Bean - @ConditionalOnBean(TitanChatBedrockApi.class) - public BedrockTitanChatModel titanChatModel(TitanChatBedrockApi titanChatApi, - BedrockTitanChatProperties properties) { + @ConditionalOnBean(BedrockConverseApi.class) + public BedrockTitanChatModel titanChatModel(BedrockConverseApi converseApi, BedrockTitanChatProperties properties) { - return new BedrockTitanChatModel(titanChatApi, properties.getOptions()); + return new BedrockTitanChatModel(properties.getModel(), converseApi, properties.getOptions()); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatProperties.java index b196e9797a..56afb7462b 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatProperties.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatProperties.java @@ -16,14 +16,16 @@ package org.springframework.ai.autoconfigure.bedrock.titan; import org.springframework.ai.bedrock.titan.BedrockTitanChatOptions; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatModel; +import org.springframework.ai.bedrock.titan.BedrockTitanChatModel.TitanChatModel; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.util.Assert; /** * Bedrock Titan Chat autoconfiguration properties. * * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ @ConfigurationProperties(BedrockTitanChatProperties.CONFIG_PREFIX) @@ -42,7 +44,7 @@ public class BedrockTitanChatProperties { private String model = TitanChatModel.TITAN_TEXT_EXPRESS_V1.id(); @NestedConfigurationProperty - private BedrockTitanChatOptions options = BedrockTitanChatOptions.builder().withTemperature(0.7f).build(); + private BedrockTitanChatOptions options = BedrockTitanChatOptions.builder().build(); public boolean isEnabled() { return enabled; @@ -65,6 +67,8 @@ public BedrockTitanChatOptions getOptions() { } public void setOptions(BedrockTitanChatOptions options) { + Assert.notNull(options, "BedrockTitanChatOptions must not be null"); + this.options = options; } diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfigurationIT.java index 4137e33ce6..293fe48c49 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfigurationIT.java @@ -22,12 +22,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.bedrock.anthropic.BedrockAnthropicChatModel; +import org.springframework.ai.bedrock.anthropic.BedrockAnthropicChatModel.AnthropicChatModel; import org.springframework.ai.chat.messages.AssistantMessage; import reactor.core.publisher.Flux; import software.amazon.awssdk.regions.Region; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatModel; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; @@ -41,6 +43,7 @@ /** * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @@ -51,10 +54,11 @@ public class BedrockAnthropicChatAutoConfigurationIT { .withPropertyValues("spring.ai.bedrock.anthropic.chat.enabled=true", "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), - "spring.ai.bedrock.aws.region=" + Region.EU_CENTRAL_1.id(), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), "spring.ai.bedrock.anthropic.chat.model=" + AnthropicChatModel.CLAUDE_V2.id(), "spring.ai.bedrock.anthropic.chat.options.temperature=0.5") - .withConfiguration(AutoConfigurations.of(BedrockAnthropicChatAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropicChatAutoConfiguration.class)); private final Message systemMessage = new SystemPromptTemplate(""" You are a helpful AI assistant. Your name is {name}. @@ -106,7 +110,8 @@ public void propertiesTest() { "spring.ai.bedrock.anthropic.chat.model=MODEL_XYZ", "spring.ai.bedrock.aws.region=" + Region.EU_CENTRAL_1.id(), "spring.ai.bedrock.anthropic.chat.options.temperature=0.55") - .withConfiguration(AutoConfigurations.of(BedrockAnthropicChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropicChatAutoConfiguration.class)) .run(context -> { var anthropicChatProperties = context.getBean(BedrockAnthropicChatProperties.class); var awsProperties = context.getBean(BedrockAwsConnectionProperties.class); @@ -127,7 +132,8 @@ public void chatCompletionDisabled() { // It is disabled by default new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BedrockAnthropicChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropicChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockAnthropicChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockAnthropicChatModel.class)).isEmpty(); @@ -135,7 +141,8 @@ public void chatCompletionDisabled() { // Explicitly enable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.anthropic.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockAnthropicChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropicChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockAnthropicChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(BedrockAnthropicChatModel.class)).isNotEmpty(); @@ -143,7 +150,8 @@ public void chatCompletionDisabled() { // Explicitly disable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.anthropic.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockAnthropicChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropicChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockAnthropicChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockAnthropicChatModel.class)).isEmpty(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfigurationIT.java index 3defe79b3b..8976cfcdc3 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfigurationIT.java @@ -22,12 +22,14 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel; +import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel.Anthropic3ChatModel; import org.springframework.ai.chat.messages.AssistantMessage; import reactor.core.publisher.Flux; import software.amazon.awssdk.regions.Region; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatModel; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; @@ -41,6 +43,7 @@ /** * @author Christian Tzolov + * @author Wei Jiang * @since 1.0.0 */ @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @@ -52,9 +55,10 @@ public class BedrockAnthropic3ChatAutoConfigurationIT { "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), - "spring.ai.bedrock.anthropic3.chat.model=" + AnthropicChatModel.CLAUDE_V3_SONNET.id(), + "spring.ai.bedrock.anthropic3.chat.model=" + Anthropic3ChatModel.CLAUDE_V3_SONNET.id(), "spring.ai.bedrock.anthropic3.chat.options.temperature=0.5") - .withConfiguration(AutoConfigurations.of(BedrockAnthropic3ChatAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropic3ChatAutoConfiguration.class)); private final Message systemMessage = new SystemPromptTemplate(""" You are a helpful AI assistant. Your name is {name}. @@ -106,7 +110,8 @@ public void propertiesTest() { "spring.ai.bedrock.anthropic3.chat.model=MODEL_XYZ", "spring.ai.bedrock.aws.region=" + Region.EU_CENTRAL_1.id(), "spring.ai.bedrock.anthropic3.chat.options.temperature=0.55") - .withConfiguration(AutoConfigurations.of(BedrockAnthropic3ChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropic3ChatAutoConfiguration.class)) .run(context -> { var anthropicChatProperties = context.getBean(BedrockAnthropic3ChatProperties.class); var awsProperties = context.getBean(BedrockAwsConnectionProperties.class); @@ -127,7 +132,8 @@ public void chatCompletionDisabled() { // It is disabled by default new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BedrockAnthropic3ChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropic3ChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockAnthropic3ChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockAnthropic3ChatModel.class)).isEmpty(); @@ -135,7 +141,8 @@ public void chatCompletionDisabled() { // Explicitly enable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.anthropic3.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockAnthropic3ChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropic3ChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockAnthropic3ChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(BedrockAnthropic3ChatModel.class)).isNotEmpty(); @@ -143,7 +150,8 @@ public void chatCompletionDisabled() { // Explicitly disable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.anthropic3.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockAnthropic3ChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropic3ChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockAnthropic3ChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockAnthropic3ChatModel.class)).isEmpty(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfigurationIT.java index 83b487c901..f419748eb2 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfigurationIT.java @@ -16,25 +16,24 @@ package org.springframework.ai.autoconfigure.bedrock.cohere; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.bedrock.cohere.BedrockCohereChatModel; +import org.springframework.ai.bedrock.cohere.BedrockCohereChatModel.CohereChatModel; +import org.springframework.ai.bedrock.cohere.BedrockCohereChatOptions.ReturnLikelihoods; +import org.springframework.ai.bedrock.cohere.BedrockCohereChatOptions.Truncate; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.messages.AssistantMessage; import reactor.core.publisher.Flux; import software.amazon.awssdk.regions.Region; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatModel; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest.ReturnLikelihoods; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest.Truncate; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.SystemPromptTemplate; -import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -43,6 +42,7 @@ /** * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @@ -57,14 +57,8 @@ public class BedrockCohereChatAutoConfigurationIT { "spring.ai.bedrock.cohere.chat.model=" + CohereChatModel.COHERE_COMMAND_V14.id(), "spring.ai.bedrock.cohere.chat.options.temperature=0.5", "spring.ai.bedrock.cohere.chat.options.maxTokens=500") - .withConfiguration(AutoConfigurations.of(BedrockCohereChatAutoConfiguration.class)); - - private final Message systemMessage = new SystemPromptTemplate(""" - You are a helpful AI assistant. Your name is {name}. - You are an AI assistant that helps people find information. - Your name is {name} - You should reply to the user's request with your name and also in the style of a {voice}. - """).createMessage(Map.of("name", "Bob", "voice", "pirate")); + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereChatAutoConfiguration.class)); private final UserMessage userMessage = new UserMessage( "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."); @@ -73,7 +67,7 @@ public class BedrockCohereChatAutoConfigurationIT { public void chatCompletion() { contextRunner.run(context -> { BedrockCohereChatModel cohereChatModel = context.getBean(BedrockCohereChatModel.class); - ChatResponse response = cohereChatModel.call(new Prompt(List.of(userMessage, systemMessage))); + ChatResponse response = cohereChatModel.call(new Prompt(List.of(userMessage))); assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard"); }); } @@ -84,7 +78,7 @@ public void chatCompletionStreaming() { BedrockCohereChatModel cohereChatModel = context.getBean(BedrockCohereChatModel.class); - Flux response = cohereChatModel.stream(new Prompt(List.of(userMessage, systemMessage))); + Flux response = cohereChatModel.stream(new Prompt(List.of(userMessage))); List responses = response.collectList().block(); assertThat(responses.size()).isGreaterThan(2); @@ -115,7 +109,8 @@ public void propertiesTest() { "spring.ai.bedrock.cohere.chat.options.numGenerations=3", "spring.ai.bedrock.cohere.chat.options.truncate=START", "spring.ai.bedrock.cohere.chat.options.maxTokens=123") - .withConfiguration(AutoConfigurations.of(BedrockCohereChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(BedrockCohereChatProperties.class); var aswProperties = context.getBean(BedrockAwsConnectionProperties.class); @@ -143,7 +138,8 @@ public void chatCompletionDisabled() { // It is disabled by default new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BedrockCohereChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockCohereChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockCohereChatModel.class)).isEmpty(); @@ -151,7 +147,8 @@ public void chatCompletionDisabled() { // Explicitly enable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.cohere.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockCohereChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockCohereChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(BedrockCohereChatModel.class)).isNotEmpty(); @@ -159,7 +156,8 @@ public void chatCompletionDisabled() { // Explicitly disable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.cohere.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockCohereChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockCohereChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockCohereChatModel.class)).isEmpty(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/jurassic2/BedrockAi21Jurassic2ChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/jurassic2/BedrockAi21Jurassic2ChatAutoConfigurationIT.java index ace30a03d1..3ff218ddb6 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/jurassic2/BedrockAi21Jurassic2ChatAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/jurassic2/BedrockAi21Jurassic2ChatAutoConfigurationIT.java @@ -19,26 +19,26 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.autoconfigure.bedrock.jurrasic2.BedrockAi21Jurassic2ChatAutoConfiguration; import org.springframework.ai.autoconfigure.bedrock.jurrasic2.BedrockAi21Jurassic2ChatProperties; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatModel; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi; +import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatModel.Ai21Jurassic2ChatModel; import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.SystemPromptTemplate; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import software.amazon.awssdk.regions.Region; import java.util.List; -import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; /** * @author Ahmed Yousri + * @author Wei Jiang * @since 1.0.0 */ @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @@ -50,18 +50,11 @@ public class BedrockAi21Jurassic2ChatAutoConfigurationIT { "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), - "spring.ai.bedrock.jurassic2.chat.model=" - + Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatModel.AI21_J2_ULTRA_V1.id(), + "spring.ai.bedrock.jurassic2.chat.model=" + Ai21Jurassic2ChatModel.AI21_J2_ULTRA_V1.id(), "spring.ai.bedrock.jurassic2.chat.options.temperature=0.5", "spring.ai.bedrock.jurassic2.chat.options.maxGenLen=500") - .withConfiguration(AutoConfigurations.of(BedrockAi21Jurassic2ChatAutoConfiguration.class)); - - private final Message systemMessage = new SystemPromptTemplate(""" - You are a helpful AI assistant. Your name is {name}. - You are an AI assistant that helps people find information. - Your name is {name} - You should reply to the user's request with your name and also in the style of a {voice}. - """).createMessage(Map.of("name", "Bob", "voice", "pirate")); + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAi21Jurassic2ChatAutoConfiguration.class)); private final UserMessage userMessage = new UserMessage( "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."); @@ -70,7 +63,7 @@ public class BedrockAi21Jurassic2ChatAutoConfigurationIT { public void chatCompletion() { contextRunner.run(context -> { BedrockAi21Jurassic2ChatModel ai21Jurassic2ChatModel = context.getBean(BedrockAi21Jurassic2ChatModel.class); - ChatResponse response = ai21Jurassic2ChatModel.call(new Prompt(List.of(userMessage, systemMessage))); + ChatResponse response = ai21Jurassic2ChatModel.call(new Prompt(List.of(userMessage))); assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard"); }); } @@ -85,7 +78,8 @@ public void propertiesTest() { "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), "spring.ai.bedrock.jurassic2.chat.options.temperature=0.55", "spring.ai.bedrock.jurassic2.chat.options.maxTokens=123") - .withConfiguration(AutoConfigurations.of(BedrockAi21Jurassic2ChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAi21Jurassic2ChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(BedrockAi21Jurassic2ChatProperties.class); var awsProperties = context.getBean(BedrockAwsConnectionProperties.class); @@ -107,7 +101,8 @@ public void chatCompletionDisabled() { // It is disabled by default new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BedrockAi21Jurassic2ChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAi21Jurassic2ChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatModel.class)).isEmpty(); @@ -115,7 +110,8 @@ public void chatCompletionDisabled() { // Explicitly enable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.jurassic2.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockAi21Jurassic2ChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAi21Jurassic2ChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatModel.class)).isNotEmpty(); @@ -123,7 +119,8 @@ public void chatCompletionDisabled() { // Explicitly disable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.jurassic2.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockAi21Jurassic2ChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAi21Jurassic2ChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatModel.class)).isEmpty(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfigurationIT.java index f1ed73b8b1..2b68bea15d 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfigurationIT.java @@ -22,13 +22,15 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.bedrock.llama.BedrockLlamaChatModel; +import org.springframework.ai.bedrock.llama.BedrockLlamaChatModel.LlamaChatModel; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.messages.AssistantMessage; import reactor.core.publisher.Flux; import software.amazon.awssdk.regions.Region; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatModel; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.chat.prompt.SystemPromptTemplate; @@ -56,7 +58,8 @@ public class BedrockLlamaChatAutoConfigurationIT { "spring.ai.bedrock.llama.chat.model=" + LlamaChatModel.LLAMA3_70B_INSTRUCT_V1.id(), "spring.ai.bedrock.llama.chat.options.temperature=0.5", "spring.ai.bedrock.llama.chat.options.maxGenLen=500") - .withConfiguration(AutoConfigurations.of(BedrockLlamaChatAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockLlamaChatAutoConfiguration.class)); private final Message systemMessage = new SystemPromptTemplate(""" You are a helpful AI assistant. Your name is {name}. @@ -109,7 +112,8 @@ public void propertiesTest() { "spring.ai.bedrock.aws.region=" + Region.EU_CENTRAL_1.id(), "spring.ai.bedrock.llama.chat.options.temperature=0.55", "spring.ai.bedrock.llama.chat.options.maxGenLen=123") - .withConfiguration(AutoConfigurations.of(BedrockLlamaChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockLlamaChatAutoConfiguration.class)) .run(context -> { var llamaChatProperties = context.getBean(BedrockLlamaChatProperties.class); var awsProperties = context.getBean(BedrockAwsConnectionProperties.class); @@ -130,7 +134,9 @@ public void propertiesTest() { public void chatCompletionDisabled() { // It is disabled by default - new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(BedrockLlamaChatAutoConfiguration.class)) + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockLlamaChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockLlamaChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockLlamaChatModel.class)).isEmpty(); @@ -138,7 +144,8 @@ public void chatCompletionDisabled() { // Explicitly enable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.llama.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockLlamaChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockLlamaChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockLlamaChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(BedrockLlamaChatModel.class)).isNotEmpty(); @@ -146,7 +153,8 @@ public void chatCompletionDisabled() { // Explicitly disable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.llama.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockLlamaChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockLlamaChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockLlamaChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockLlamaChatModel.class)).isEmpty(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfigurationIT.java index 94a2fda1b6..2724e12667 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfigurationIT.java @@ -16,7 +16,6 @@ package org.springframework.ai.autoconfigure.bedrock.titan; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; @@ -27,12 +26,12 @@ import software.amazon.awssdk.regions.Region; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.bedrock.titan.BedrockTitanChatModel; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatModel; +import org.springframework.ai.bedrock.titan.BedrockTitanChatModel.TitanChatModel; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.chat.prompt.SystemPromptTemplate; -import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -41,6 +40,7 @@ /** * @author Christian Tzolov + * @author Wei Jiang * @since 0.8.0 */ @EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") @@ -55,14 +55,8 @@ public class BedrockTitanChatAutoConfigurationIT { "spring.ai.bedrock.titan.chat.model=" + TitanChatModel.TITAN_TEXT_EXPRESS_V1.id(), "spring.ai.bedrock.titan.chat.options.temperature=0.5", "spring.ai.bedrock.titan.chat.options.maxTokenCount=500") - .withConfiguration(AutoConfigurations.of(BedrockTitanChatAutoConfiguration.class)); - - private final Message systemMessage = new SystemPromptTemplate(""" - You are a helpful AI assistant. Your name is {name}. - You are an AI assistant that helps people find information. - Your name is {name} - You should reply to the user's request with your name and also in the style of a {voice}. - """).createMessage(Map.of("name", "Bob", "voice", "pirate")); + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockTitanChatAutoConfiguration.class)); private final UserMessage userMessage = new UserMessage( "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."); @@ -71,7 +65,7 @@ public class BedrockTitanChatAutoConfigurationIT { public void chatCompletion() { contextRunner.run(context -> { BedrockTitanChatModel chatModel = context.getBean(BedrockTitanChatModel.class); - ChatResponse response = chatModel.call(new Prompt(List.of(userMessage, systemMessage))); + ChatResponse response = chatModel.call(new Prompt(List.of(userMessage))); assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard"); }); } @@ -82,7 +76,7 @@ public void chatCompletionStreaming() { BedrockTitanChatModel chatModel = context.getBean(BedrockTitanChatModel.class); - Flux response = chatModel.stream(new Prompt(List.of(userMessage, systemMessage))); + Flux response = chatModel.stream(new Prompt(List.of(userMessage))); List responses = response.collectList().block(); assertThat(responses.size()).isGreaterThan(1); @@ -110,7 +104,8 @@ public void propertiesTest() { "spring.ai.bedrock.titan.chat.options.topP=0.55", "spring.ai.bedrock.titan.chat.options.stopSequences=END1,END2", "spring.ai.bedrock.titan.chat.options.maxTokenCount=123") - .withConfiguration(AutoConfigurations.of(BedrockTitanChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockTitanChatAutoConfiguration.class)) .run(context -> { var chatProperties = context.getBean(BedrockTitanChatProperties.class); var aswProperties = context.getBean(BedrockAwsConnectionProperties.class); @@ -134,7 +129,9 @@ public void propertiesTest() { public void chatCompletionDisabled() { // It is disabled by default - new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(BedrockTitanChatAutoConfiguration.class)) + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockTitanChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockTitanChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockTitanChatModel.class)).isEmpty(); @@ -142,7 +139,8 @@ public void chatCompletionDisabled() { // Explicitly enable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.titan.chat.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockTitanChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockTitanChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockTitanChatProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(BedrockTitanChatModel.class)).isNotEmpty(); @@ -150,7 +148,8 @@ public void chatCompletionDisabled() { // Explicitly disable the chat auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.titan.chat.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockTitanChatAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockTitanChatAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockTitanChatProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockTitanChatModel.class)).isEmpty(); From 89f9c89ca2edeee8e8abc5cb06707e2cf6026988 Mon Sep 17 00:00:00 2001 From: wmz7year Date: Wed, 5 Jun 2024 08:35:11 +0800 Subject: [PATCH 3/9] Amazon Bedrock Chat adds tool support. --- ...BedrockConverseChatGenerationMetadata.java | 86 ++++++++ .../anthropic3/Anthropic3ChatOptions.java | 76 ++++++- .../BedrockAnthropic3ChatModel.java | 181 +++++++++++++++- .../ai/bedrock/api/BedrockConverseApi.java | 125 ++++++++++++ .../bedrock/api/BedrockConverseApiUtils.java | 193 ++++++++++++------ .../ai/bedrock/MockWeatherService.java | 89 ++++++++ .../BedrockAnthropic3ChatModelIT.java | 27 +++ .../src/main/antora/modules/ROOT/nav.adoc | 1 + .../functions/anthropic-chat-functions.adoc | 2 +- .../bedrock-anthropic3-chat-functions.adoc | 187 +++++++++++++++++ .../modules/ROOT/pages/api/functions.adoc | 3 +- ...edrockAnthropic3ChatAutoConfiguration.java | 26 ++- .../tool/FunctionCallWithFunctionBeanIT.java | 113 ++++++++++ .../FunctionCallWithPromptFunctionIT.java | 88 ++++++++ .../anthropic3/tool/MockWeatherService.java | 91 +++++++++ 15 files changed, 1205 insertions(+), 83 deletions(-) create mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockConverseChatGenerationMetadata.java create mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/MockWeatherService.java create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-anthropic3-chat-functions.adoc create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/FunctionCallWithFunctionBeanIT.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/FunctionCallWithPromptFunctionIT.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/MockWeatherService.java diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockConverseChatGenerationMetadata.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockConverseChatGenerationMetadata.java new file mode 100644 index 0000000000..f1088a644c --- /dev/null +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/BedrockConverseChatGenerationMetadata.java @@ -0,0 +1,86 @@ +/* + * Copyright 2023 - 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.bedrock; + +import org.springframework.ai.chat.metadata.ChatGenerationMetadata; + +import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; +import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; +import software.amazon.awssdk.services.bedrockruntime.model.Message; +import software.amazon.awssdk.services.bedrockruntime.model.MessageStopEvent; + +/** + * Amazon Bedrock Chat model converse interface generation metadata, encapsulating + * information on the completion. + * + * @author Wei Jiang + * @since 1.0.0 + */ +public class BedrockConverseChatGenerationMetadata implements ChatGenerationMetadata { + + private String stopReason; + + private Message message; + + private ConverseStreamOutput event; + + public BedrockConverseChatGenerationMetadata(String stopReason, ConverseStreamOutput event) { + super(); + + this.stopReason = stopReason; + this.event = event; + } + + public BedrockConverseChatGenerationMetadata(String stopReason, Message message) { + super(); + + this.stopReason = stopReason; + this.message = message; + } + + public static BedrockConverseChatGenerationMetadata from(ConverseResponse response, Message message) { + return new BedrockConverseChatGenerationMetadata(response.stopReasonAsString(), message); + } + + public static BedrockConverseChatGenerationMetadata from(ConverseStreamOutput event) { + String stopReason = null; + + if (event instanceof MessageStopEvent messageStopEvent) { + stopReason = messageStopEvent.stopReasonAsString(); + } + + return new BedrockConverseChatGenerationMetadata(stopReason, event); + } + + @Override + public T getContentFilterMetadata() { + return null; + } + + @Override + public String getFinishReason() { + return stopReason; + } + + public Message getMessage() { + return message; + } + + public ConverseStreamOutput getEvent() { + return event; + } + +} diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/Anthropic3ChatOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/Anthropic3ChatOptions.java index 98fe80bb8d..208925f6d6 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/Anthropic3ChatOptions.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/Anthropic3ChatOptions.java @@ -15,12 +15,20 @@ */ package org.springframework.ai.bedrock.anthropic3; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.util.Assert; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Java {@link ChatOptions} for the Bedrock Anthropic chat generative model chat options. @@ -31,7 +39,7 @@ * @since 1.0.0 */ @JsonInclude(Include.NON_NULL) -public class Anthropic3ChatOptions implements ChatOptions { +public class Anthropic3ChatOptions implements ChatOptions, FunctionCallingOptions { // @formatter:off /** @@ -66,6 +74,31 @@ public class Anthropic3ChatOptions implements ChatOptions { */ private @JsonProperty("stop_sequences") List stopSequences; + /** + * Tool Function Callbacks to register with the ChatModel. For Prompt + * Options the functionCallbacks are automatically enabled for the duration of the + * prompt execution. For Default Options the functionCallbacks are registered but + * disabled by default. Use the enableFunctions to set the functions from the registry + * to be used by the ChatModel chat completion requests. + */ + @NestedConfigurationProperty + @JsonIgnore + private List functionCallbacks = new ArrayList<>(); + + /** + * List of functions, identified by their names, to configure for function calling in + * the chat completion requests. Functions with those names must exist in the + * functionCallbacks registry. The {@link #functionCallbacks} from the PromptOptions + * are automatically enabled for the duration of the prompt execution. + * + * Note that function enabled with the default options are enabled for all chat + * completion requests. This could impact the token count and the billing. If the + * functions is set in a prompt options, then the enabled functions are only active + * for the duration of this prompt execution. + */ + @NestedConfigurationProperty + @JsonIgnore + private Set functions = new HashSet<>(); // @formatter:on public static Builder builder() { @@ -101,6 +134,23 @@ public Builder withStopSequences(List stopSequences) { return this; } + public Builder withFunctionCallbacks(List functionCallbacks) { + this.options.functionCallbacks = functionCallbacks; + return this; + } + + public Builder withFunctions(Set functionNames) { + Assert.notNull(functionNames, "Function names must not be null"); + this.options.functions = functionNames; + return this; + } + + public Builder withFunction(String functionName) { + Assert.hasText(functionName, "Function name must not be empty"); + this.options.functions.add(functionName); + return this; + } + public Anthropic3ChatOptions build() { return this.options; } @@ -150,12 +200,36 @@ public void setStopSequences(List stopSequences) { this.stopSequences = stopSequences; } + @Override + public List getFunctionCallbacks() { + return this.functionCallbacks; + } + + @Override + public void setFunctionCallbacks(List functionCallbacks) { + Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null"); + this.functionCallbacks = functionCallbacks; + } + + @Override + public Set getFunctions() { + return this.functions; + } + + @Override + public void setFunctions(Set functions) { + Assert.notNull(functions, "Function must not be null"); + this.functions = functions; + } + public static Anthropic3ChatOptions fromOptions(Anthropic3ChatOptions fromOptions) { return builder().withTemperature(fromOptions.getTemperature()) .withMaxTokens(fromOptions.getMaxTokens()) .withTopK(fromOptions.getTopK()) .withTopP(fromOptions.getTopP()) .withStopSequences(fromOptions.getStopSequences()) + .withFunctionCallbacks(fromOptions.getFunctionCallbacks()) + .withFunctions(fromOptions.getFunctions()) .build(); } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java index bb7d1b9690..a260736ddd 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java @@ -16,18 +16,41 @@ package org.springframework.ai.bedrock.anthropic3; import reactor.core.publisher.Flux; -import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; -import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; +import software.amazon.awssdk.services.bedrockruntime.model.Message; +import software.amazon.awssdk.services.bedrockruntime.model.StopReason; +import software.amazon.awssdk.services.bedrockruntime.model.Tool; +import software.amazon.awssdk.services.bedrockruntime.model.ToolConfiguration; +import software.amazon.awssdk.services.bedrockruntime.model.ToolInputSchema; +import software.amazon.awssdk.services.bedrockruntime.model.ToolResultBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ToolResultContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ToolResultStatus; +import software.amazon.awssdk.services.bedrockruntime.model.ToolSpecification; +import software.amazon.awssdk.services.bedrockruntime.model.ToolUseBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock.Type; +import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.ai.bedrock.BedrockConverseChatGenerationMetadata; import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.api.BedrockConverseApi.BedrockConverseRequest; import org.springframework.ai.bedrock.api.BedrockConverseApiUtils; import org.springframework.ai.chat.model.ChatModel; import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.model.StreamingChatModel; import org.springframework.ai.chat.prompt.ChatOptions; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.model.ModelDescription; +import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.model.function.AbstractFunctionCallSupport; +import org.springframework.ai.model.function.FunctionCallbackContext; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; /** * Java {@link ChatModel} and {@link StreamingChatModel} for the Bedrock Anthropic3 chat @@ -38,7 +61,9 @@ * @author Wei Jiang * @since 1.0.0 */ -public class BedrockAnthropic3ChatModel implements ChatModel, StreamingChatModel { +public class BedrockAnthropic3ChatModel + extends AbstractFunctionCallSupport + implements ChatModel, StreamingChatModel { private final String modelId; @@ -56,6 +81,13 @@ public BedrockAnthropic3ChatModel(BedrockConverseApi converseApi, Anthropic3Chat } public BedrockAnthropic3ChatModel(String modelId, BedrockConverseApi converseApi, Anthropic3ChatOptions options) { + this(modelId, converseApi, options, null); + } + + public BedrockAnthropic3ChatModel(String modelId, BedrockConverseApi converseApi, Anthropic3ChatOptions options, + FunctionCallbackContext functionCallbackContext) { + super(functionCallbackContext); + Assert.notNull(modelId, "modelId must not be null."); Assert.notNull(converseApi, "BedrockConverseApi must not be null."); Assert.notNull(options, "Anthropic3ChatOptions must not be null."); @@ -69,22 +101,75 @@ public BedrockAnthropic3ChatModel(String modelId, BedrockConverseApi converseApi public ChatResponse call(Prompt prompt) { Assert.notNull(prompt, "Prompt must not be null."); - var request = BedrockConverseApiUtils.createConverseRequest(modelId, prompt, defaultOptions); + var request = createBedrockConverseRequest(prompt); - ConverseResponse response = this.converseApi.converse(request); - - return BedrockConverseApiUtils.convertConverseResponse(response); + return this.callWithFunctionSupport(request); } @Override public Flux stream(Prompt prompt) { Assert.notNull(prompt, "Prompt must not be null."); - var request = BedrockConverseApiUtils.createConverseStreamRequest(modelId, prompt, defaultOptions); + var request = createBedrockConverseRequest(prompt); + + return converseApi.converseStream(request); + } + + private BedrockConverseRequest createBedrockConverseRequest(Prompt prompt) { + var request = BedrockConverseApiUtils.createBedrockConverseRequest(modelId, prompt, defaultOptions); + + ToolConfiguration toolConfiguration = createToolConfiguration(prompt); + + return BedrockConverseRequest.from(request).withToolConfiguration(toolConfiguration).build(); + } + + private ToolConfiguration createToolConfiguration(Prompt prompt) { + Set functionsForThisRequest = new HashSet<>(); + + if (this.defaultOptions != null) { + Set promptEnabledFunctions = this.handleFunctionCallbackConfigurations(this.defaultOptions, + !IS_RUNTIME_CALL); + functionsForThisRequest.addAll(promptEnabledFunctions); + } + + if (prompt.getOptions() != null) { + if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { + Anthropic3ChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, + ChatOptions.class, Anthropic3ChatOptions.class); - Flux fluxResponse = this.converseApi.converseStream(request); + Set defaultEnabledFunctions = this.handleFunctionCallbackConfigurations(updatedRuntimeOptions, + IS_RUNTIME_CALL); + functionsForThisRequest.addAll(defaultEnabledFunctions); + } + else { + throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " + + prompt.getOptions().getClass().getSimpleName()); + } + } - return fluxResponse.map(output -> BedrockConverseApiUtils.convertConverseStreamOutput(output)); + if (!CollectionUtils.isEmpty(functionsForThisRequest)) { + return ToolConfiguration.builder().tools(getFunctionTools(functionsForThisRequest)).build(); + } + + return null; + } + + private List getFunctionTools(Set functionNames) { + return this.resolveFunctionCallbacks(functionNames).stream().map(functionCallback -> { + var description = functionCallback.getDescription(); + var name = functionCallback.getName(); + String inputSchema = functionCallback.getInputTypeSchema(); + + return Tool.builder() + .toolSpec(ToolSpecification.builder() + .name(name) + .description(description) + .inputSchema(ToolInputSchema.builder() + .json(BedrockConverseApiUtils.convertObjectToDocument(ModelOptionsUtils.jsonToMap(inputSchema))) + .build()) + .build()) + .build(); + }).toList(); } @Override @@ -92,6 +177,82 @@ public ChatOptions getDefaultOptions() { return Anthropic3ChatOptions.fromOptions(this.defaultOptions); } + @Override + protected BedrockConverseRequest doCreateToolResponseRequest(BedrockConverseRequest previousRequest, + Message responseMessage, List conversationHistory) { + List toolToUseList = responseMessage.content() + .stream() + .filter(content -> content.type() == Type.TOOL_USE) + .map(content -> content.toolUse()) + .toList(); + + List toolResults = new ArrayList<>(); + + for (ToolUseBlock toolToUse : toolToUseList) { + var functionCallId = toolToUse.toolUseId(); + var functionName = toolToUse.name(); + var functionArguments = toolToUse.input().unwrap(); + + if (!this.functionCallbackRegister.containsKey(functionName)) { + throw new IllegalStateException("No function callback found for function name: " + functionName); + } + + String functionResponse = this.functionCallbackRegister.get(functionName) + .call(ModelOptionsUtils.toJsonString(functionArguments)); + + toolResults.add(ToolResultBlock.builder() + .toolUseId(functionCallId) + .status(ToolResultStatus.SUCCESS) + .content(ToolResultContentBlock.builder().text(functionResponse).build()) + .build()); + } + + // Add the function response to the conversation. + Message toolResultMessage = Message.builder() + .content(toolResults.stream().map(toolResult -> ContentBlock.fromToolResult(toolResult)).toList()) + .role(ConversationRole.USER) + .build(); + conversationHistory.add(toolResultMessage); + + // Recursively call chatCompletionWithTools until the model doesn't call a + // functions anymore. + return BedrockConverseRequest.from(previousRequest).withMessages(conversationHistory).build(); + } + + @Override + protected List doGetUserMessages(BedrockConverseRequest request) { + return request.messages(); + } + + @Override + protected Message doGetToolResponseMessage(ChatResponse response) { + Generation result = response.getResult(); + + var metadata = (BedrockConverseChatGenerationMetadata) result.getMetadata(); + + return metadata.getMessage(); + } + + @Override + protected ChatResponse doChatCompletion(BedrockConverseRequest request) { + return converseApi.converse(request); + } + + @Override + protected Flux doChatCompletionStream(BedrockConverseRequest request) { + throw new UnsupportedOperationException("Streaming function calling is not supported."); + } + + @Override + protected boolean isToolFunctionCall(ChatResponse response) { + Generation result = response.getResult(); + if (result == null) { + return false; + } + + return StopReason.fromValue(result.getMetadata().getFinishReason()) == StopReason.TOOL_USE; + } + /** * Anthropic3 models version. */ diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java index b1f69b5284..e5a4be2f58 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java @@ -17,9 +17,11 @@ package org.springframework.ai.bedrock.api; import java.time.Duration; +import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.retry.RetryUtils; import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; @@ -30,6 +32,7 @@ import reactor.core.publisher.Sinks.EmitResult; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; +import software.amazon.awssdk.core.document.Document; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; @@ -38,6 +41,9 @@ import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamOutput; import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamRequest; import software.amazon.awssdk.services.bedrockruntime.model.ConverseStreamResponseHandler; +import software.amazon.awssdk.services.bedrockruntime.model.Message; +import software.amazon.awssdk.services.bedrockruntime.model.SystemContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ToolConfiguration; /** * Amazon Bedrock Converse API, It provides the basic functionality to invoke the Bedrock @@ -177,6 +183,25 @@ public Region getRegion() { return this.region; } + /** + * Invoke the model and return the response. + * + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html + * https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/bedrockruntime/BedrockRuntimeClient.html#converse + * @param bedrockConverseRequest Model invocation request. + * @return The model invocation response. + */ + public ChatResponse converse(BedrockConverseRequest bedrockConverseRequest) { + Assert.notNull(bedrockConverseRequest, "'bedrockConverseRequest' must not be null"); + + ConverseRequest converseRequest = BedrockConverseApiUtils.createConverseRequest(bedrockConverseRequest); + + ConverseResponse converseResponse = converse(converseRequest); + + return BedrockConverseApiUtils.convertConverseResponse(converseResponse); + } + /** * Invoke the model and return the response. * @@ -194,6 +219,26 @@ public ConverseResponse converse(ConverseRequest converseRequest) { }); } + /** + * Invoke the model and return the response stream. + * + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html + * https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/bedrockruntime/BedrockRuntimeAsyncClient.html#converseStream + * @param bedrockConverseRequest Model invocation request. + * @return The model invocation response stream. + */ + public Flux converseStream(BedrockConverseRequest bedrockConverseRequest) { + Assert.notNull(bedrockConverseRequest, "'bedrockConverseRequest' must not be null"); + + ConverseStreamRequest converseStreamRequest = BedrockConverseApiUtils + .createConverseStreamRequest(bedrockConverseRequest); + + return converseStream(converseStreamRequest) + .map(output -> BedrockConverseApiUtils.convertConverseStreamOutput(output)); + + } + /** * Invoke the model and return the response stream. * @@ -241,5 +286,85 @@ public Flux converseStream(ConverseStreamRequest converseS }); } + /** + * BedrockConverseRequest encapsulates the request parameters for the Amazon Bedrock + * Converse Api. + * + * @param modelId The Amazon Bedrock Model Id. + * @param messages The messages that you want to send to the model. + * @param systemMessages The system prompt to pass to the model. + * @param additionalModelRequestFields Additional inference parameters that the model + * supports, beyond the base set of inference parameters that Converse supports in the + * inferenceConfig field. + * @param toolConfiguration Configuration information for the tools that the model can + * use when generating a response. + */ + public record BedrockConverseRequest(String modelId, List messages, + List systemMessages, Document additionalModelRequestFields, + ToolConfiguration toolConfiguration) { + + public BedrockConverseRequest(String modelId, List messages, List systemMessages, + Document additionalModelRequestFields) { + this(modelId, messages, systemMessages, additionalModelRequestFields, null); + } + + public static Builder from(BedrockConverseRequest request) { + return new Builder(request); + } + + public static class Builder { + + private String modelId; + + private List messages; + + private List systemMessages; + + private Document additionalModelRequestFields; + + private ToolConfiguration toolConfiguration; + + private Builder(BedrockConverseRequest request) { + this.modelId = request.modelId(); + this.messages = request.messages(); + this.systemMessages = request.systemMessages(); + this.additionalModelRequestFields = request.additionalModelRequestFields(); + this.toolConfiguration = request.toolConfiguration(); + } + + public Builder withModelId(String modelId) { + this.modelId = modelId; + return this; + } + + public Builder withMessages(List messages) { + this.messages = messages; + return this; + } + + public Builder withSystemMessages(List systemMessages) { + this.systemMessages = systemMessages; + return this; + } + + public Builder withAdditionalModelRequestFields(Document additionalModelRequestFields) { + this.additionalModelRequestFields = additionalModelRequestFields; + return this; + } + + public Builder withToolConfiguration(ToolConfiguration toolConfiguration) { + this.toolConfiguration = toolConfiguration; + return this; + } + + public BedrockConverseRequest build() { + return new BedrockConverseRequest(modelId, messages, systemMessages, additionalModelRequestFields, + toolConfiguration); + } + + } + + } + } //@formatter:on diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtils.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtils.java index 8cb921cd64..b7df79ec7d 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtils.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApiUtils.java @@ -22,11 +22,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; +import org.springframework.ai.bedrock.BedrockConverseChatGenerationMetadata; import org.springframework.ai.bedrock.BedrockChatResponseMetadata; +import org.springframework.ai.bedrock.api.BedrockConverseApi.BedrockConverseRequest; import org.springframework.ai.chat.messages.MessageType; -import org.springframework.ai.chat.metadata.ChatGenerationMetadata; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; import org.springframework.ai.chat.prompt.ChatOptions; @@ -40,7 +42,6 @@ import software.amazon.awssdk.core.document.Document; import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; import software.amazon.awssdk.services.bedrockruntime.model.ContentBlockDeltaEvent; -import software.amazon.awssdk.services.bedrockruntime.model.ContentBlockStopEvent; import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole; import software.amazon.awssdk.services.bedrockruntime.model.ConverseRequest; import software.amazon.awssdk.services.bedrockruntime.model.ConverseResponse; @@ -50,9 +51,9 @@ import software.amazon.awssdk.services.bedrockruntime.model.ImageBlock; import software.amazon.awssdk.services.bedrockruntime.model.ImageSource; import software.amazon.awssdk.services.bedrockruntime.model.Message; -import software.amazon.awssdk.services.bedrockruntime.model.MessageStartEvent; import software.amazon.awssdk.services.bedrockruntime.model.MessageStopEvent; import software.amazon.awssdk.services.bedrockruntime.model.SystemContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock.Type; /** * Amazon Bedrock Converse API utils. @@ -61,9 +62,32 @@ * @since 1.0.0 */ public class BedrockConverseApiUtils { - private static final ObjectMapper objectMapper = new ObjectMapper(); + /** + * Convert {@link Prompt} to {@link ConverseRequest} with model id and options. It + * will merge default options and runtime options to converse inference parameters. + * + * @param modelId The Amazon Bedrock Model Id. + * @param prompt The prompt that needs to convert. + * @param defaultOptions The default options needs to convert. + * @return Amazon Bedrock Converse encapsulates request. + */ + public static BedrockConverseRequest createBedrockConverseRequest(String modelId, Prompt prompt, + ChatOptions defaultOptions) { + Assert.notNull(modelId, "'modelId' must not be null."); + Assert.notNull(prompt, "'prompt' must not be null."); + + List messages = getInstructionsMessages(prompt.getInstructions()); + + List systemMessages = getPromptSystemContentBlocks(prompt); + + Document additionalModelRequestFields = getChatOptionsAdditionalModelRequestFields(defaultOptions, + prompt.getOptions()); + + return new BedrockConverseRequest(modelId, messages, systemMessages, additionalModelRequestFields); + } + /** * Convert {@link Prompt} to {@link ConverseRequest} with model id and options. It * will merge default options and runtime options to converse inference parameters. @@ -88,24 +112,45 @@ public static ConverseRequest createConverseRequest(String modelId, Prompt promp * @return Amazon Bedrock Converse request. */ public static ConverseRequest createConverseRequest(String modelId, Prompt prompt, ChatOptions defaultOptions) { - Assert.notNull(modelId, "'modelId' must not be null."); - Assert.notNull(prompt, "'prompt' must not be null."); - - List systemMessages = getPromptSystemContentBlocks(prompt); + BedrockConverseRequest bedrockConverseRequest = createBedrockConverseRequest(modelId, prompt, defaultOptions); - List userMessages = getPromptMessages(prompt); + return createConverseRequest(bedrockConverseRequest); + } - Document additionalModelRequestFields = getChatOptionsAdditionalModelRequestFields(defaultOptions, - prompt.getOptions()); + /** + * Convert {@link Prompt} to {@link ConverseRequest} with model id and options. It + * will merge default options and runtime options to converse inference parameters. + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html#API_runtime_Converse_RequestSyntax + * + * @param bedrockConverseRequest The Amazon Bedrock Converse encapsulates request. + * @return Amazon Bedrock Converse request. + */ + public static ConverseRequest createConverseRequest(BedrockConverseRequest bedrockConverseRequest) { + Assert.notNull(bedrockConverseRequest, "'bedrockConverseRequest' must not be null."); return ConverseRequest.builder() - .modelId(modelId) - .messages(userMessages) - .system(systemMessages) - .additionalModelRequestFields(additionalModelRequestFields) + .modelId(bedrockConverseRequest.modelId()) + .messages(bedrockConverseRequest.messages()) + .system(bedrockConverseRequest.systemMessages()) + .additionalModelRequestFields(bedrockConverseRequest.additionalModelRequestFields()) + .toolConfig(bedrockConverseRequest.toolConfiguration()) .build(); } + /** + * Convert {@link Prompt} to {@link ConverseStreamRequest} with model id and options. + * It will merge default options and runtime options to converse inference parameters. + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStream.html + * + * @param modelId The Amazon Bedrock Model Id. + * @param prompt The prompt that needs to convert. + * @param defaultOptions The default options needs to convert. + * @return Amazon Bedrock Converse stream request. + */ + public static ConverseStreamRequest createConverseStreamRequest(String modelId, Prompt prompt) { + return createConverseStreamRequest(modelId, prompt, null); + } + /** * Convert {@link Prompt} to {@link ConverseStreamRequest} with model id and options. * It will merge default options and runtime options to converse inference parameters. @@ -118,21 +163,28 @@ public static ConverseRequest createConverseRequest(String modelId, Prompt promp */ public static ConverseStreamRequest createConverseStreamRequest(String modelId, Prompt prompt, ChatOptions defaultOptions) { - Assert.notNull(modelId, "'modelId' must not be null."); - Assert.notNull(prompt, "'prompt' must not be null."); + BedrockConverseRequest bedrockConverseRequest = createBedrockConverseRequest(modelId, prompt, defaultOptions); - List systemMessages = getPromptSystemContentBlocks(prompt); - - List userMessages = getPromptMessages(prompt); + return createConverseStreamRequest(bedrockConverseRequest); + } - Document additionalModelRequestFields = getChatOptionsAdditionalModelRequestFields(defaultOptions, - prompt.getOptions()); + /** + * Convert {@link Prompt} to {@link ConverseStreamRequest} with model id and options. + * It will merge default options and runtime options to converse inference parameters. + * https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStream.html + * + * @param bedrockConverseRequest The Amazon Bedrock Converse encapsulates request. + * @return Amazon Bedrock Converse stream request. + */ + public static ConverseStreamRequest createConverseStreamRequest(BedrockConverseRequest bedrockConverseRequest) { + Assert.notNull(bedrockConverseRequest, "'bedrockConverseRequest' must not be null."); return ConverseStreamRequest.builder() - .modelId(modelId) - .messages(userMessages) - .system(systemMessages) - .additionalModelRequestFields(additionalModelRequestFields) + .modelId(bedrockConverseRequest.modelId()) + .messages(bedrockConverseRequest.messages()) + .system(bedrockConverseRequest.systemMessages()) + .additionalModelRequestFields(bedrockConverseRequest.additionalModelRequestFields()) + .toolConfig(bedrockConverseRequest.toolConfiguration()) .build(); } @@ -147,17 +199,14 @@ public static ConverseStreamRequest createConverseStreamRequest(String modelId, public static ChatResponse convertConverseResponse(ConverseResponse response) { Assert.notNull(response, "'response' must not be null."); - String stopReason = response.stopReasonAsString(); + Message message = response.output().message(); - List generations = response.output() - .message() - .content() - .stream() - .map(content -> new Generation(content.text()) - .withGenerationMetadata(ChatGenerationMetadata.from(stopReason, null))) - .toList(); + String text = getConverseResponseTextContent(message.content()); + + Generation generation = new Generation(text) + .withGenerationMetadata(BedrockConverseChatGenerationMetadata.from(response, message)); - return new ChatResponse(generations, BedrockChatResponseMetadata.from(response)); + return new ChatResponse(List.of(generation), BedrockChatResponseMetadata.from(response)); } /** @@ -169,23 +218,31 @@ public static ChatResponse convertConverseResponse(ConverseResponse response) { * @return The ChatResponse entity. */ public static ChatResponse convertConverseStreamOutput(ConverseStreamOutput output) { - if (output instanceof MessageStartEvent) { - return new ChatResponse(List.of()); - } else if (output instanceof ContentBlockDeltaEvent contentBlockDeltaEvent) { - return new ChatResponse(List.of(new Generation(contentBlockDeltaEvent.delta().text()))); - } else if (output instanceof ContentBlockStopEvent) { - return new ChatResponse(List.of()); + if (output instanceof ContentBlockDeltaEvent contentBlockDeltaEvent) { + Generation generation = new Generation(contentBlockDeltaEvent.delta().text()) + .withGenerationMetadata(BedrockConverseChatGenerationMetadata.from(contentBlockDeltaEvent)); + + return new ChatResponse(List.of(generation)); } else if (output instanceof MessageStopEvent messageStopEvent) { - ChatGenerationMetadata metadata = ChatGenerationMetadata.from(messageStopEvent.stopReasonAsString(), null); + var metadata = BedrockConverseChatGenerationMetadata.from(messageStopEvent); return new ChatResponse(List.of(new Generation("").withGenerationMetadata(metadata))); } else if (output instanceof ConverseStreamMetadataEvent converseStreamMetadataEvent) { return new ChatResponse(List.of(), BedrockChatResponseMetadata.from(converseStreamMetadataEvent)); } else { - return new ChatResponse(List.of()); + Generation generation = new Generation("") + .withGenerationMetadata(BedrockConverseChatGenerationMetadata.from(output)); + + return new ChatResponse(List.of(generation)); } } + private static String getConverseResponseTextContent(List contents) { + Optional optional = contents.stream().filter(content -> content.type() == Type.TEXT).findFirst(); + + return optional.isPresent() ? optional.get().text() : ""; + } + private static List getPromptSystemContentBlocks(Prompt prompt) { return prompt.getInstructions() .stream() @@ -194,19 +251,21 @@ private static List getPromptSystemContentBlocks(Prompt prom .toList(); } - private static List getPromptMessages(Prompt prompt) { - return prompt.getInstructions() - .stream() + public static List getInstructionsMessages( + List instructions) { + return instructions.stream() .filter(message -> message.getMessageType() == MessageType.USER || message.getMessageType() == MessageType.ASSISTANT) - .map(instruction -> Message.builder() - .content(getInstructionContents(instruction)) - .role(instruction.getMessageType() == MessageType.USER ? ConversationRole.USER - : ConversationRole.ASSISTANT) - .build()) + .map(instruction -> createMessage(getInstructionContents(instruction), + instruction.getMessageType() == MessageType.USER ? ConversationRole.USER + : ConversationRole.ASSISTANT)) .toList(); } + public static Message createMessage(List contentBlocks, ConversationRole role) { + return Message.builder().content(contentBlocks).role(role).build(); + } + private static List getInstructionContents(org.springframework.ai.chat.messages.Message instruction) { List contents = new ArrayList<>(); @@ -232,11 +291,9 @@ private static List getInstructionContents(org.springframework.ai. private static byte[] getContentMediaData(Object mediaData) { if (mediaData instanceof byte[] bytes) { return bytes; - } - else if (mediaData instanceof String text) { + } else if (mediaData instanceof String text) { return text.getBytes(); - } - else { + } else { throw new IllegalArgumentException("Unsupported media data type: " + mediaData.getClass().getSimpleName()); } } @@ -267,19 +324,11 @@ private static Document getChatOptionsAdditionalModelRequestFields(ChatOptions d } } - return convertAttributesToDocument(attributes); - } - - private static Document convertAttributesToDocument(Map attributes) { - Map attr = attributes.entrySet() - .stream() - .collect(Collectors.toMap(e -> e.getKey(), e -> convertAttributeValueToDocument(e.getValue()))); - - return Document.fromMap(attr); + return convertObjectToDocument(attributes); } @SuppressWarnings("unchecked") - private static Document convertAttributeValueToDocument(Object value) { + public static Document convertObjectToDocument(Object value) { if (value == null) { return Document.fromNull(); } else if (value instanceof String stringValue) { @@ -299,13 +348,21 @@ private static Document convertAttributeValueToDocument(Object value) { } else if (value instanceof BigInteger bigIntegerValue) { return Document.fromNumber(bigIntegerValue); } else if (value instanceof List listValue) { - return Document.fromList(listValue.stream().map(v -> convertAttributeValueToDocument(v)).toList()); + return Document.fromList(listValue.stream().map(v -> convertObjectToDocument(v)).toList()); } else if (value instanceof Map mapValue) { - return convertAttributesToDocument(mapValue); + return convertMapToDocument(mapValue); } else { throw new IllegalArgumentException("Unsupported value type:" + value.getClass().getSimpleName()); } } + private static Document convertMapToDocument(Map value) { + Map attr = value.entrySet() + .stream() + .collect(Collectors.toMap(e -> e.getKey(), e -> convertObjectToDocument(e.getValue()))); + + return Document.fromMap(attr); + } + } -//@formatter:on +//@formatter:on \ No newline at end of file diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/MockWeatherService.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/MockWeatherService.java new file mode 100644 index 0000000000..78f41f210a --- /dev/null +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/MockWeatherService.java @@ -0,0 +1,89 @@ +/* + * Copyright 2023 - 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.bedrock; + +import java.util.function.Function; + +import com.fasterxml.jackson.annotation.JsonClassDescription; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; + +/** + * @author Christian Tzolov + */ +public class MockWeatherService implements Function { + + /** + * Weather Function request. + */ + @JsonInclude(Include.NON_NULL) + @JsonClassDescription("Weather API request") + public record Request(@JsonProperty(required = true, + value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, + @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { + } + + /** + * Temperature units. + */ + public enum Unit { + + /** + * Celsius. + */ + C("metric"), + /** + * Fahrenheit. + */ + F("imperial"); + + /** + * Human readable unit name. + */ + public final String unitName; + + private Unit(String text) { + this.unitName = text; + } + + } + + /** + * Weather Function response. + */ + public record Response(double temp, Unit unit) { + } + + @Override + public Response apply(Request request) { + + double temperature = 0; + if (request.location().contains("Paris")) { + temperature = 15; + } + else if (request.location().contains("Tokyo")) { + temperature = 10; + } + else if (request.location().contains("San Francisco")) { + temperature = 30; + } + + return new Response(temperature, Unit.C); + } + +} \ No newline at end of file diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModelIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModelIT.java index 75441e7696..3540d6a705 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModelIT.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModelIT.java @@ -17,6 +17,7 @@ import java.io.IOException; import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -30,6 +31,7 @@ import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; import software.amazon.awssdk.regions.Region; +import org.springframework.ai.bedrock.MockWeatherService; import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.model.Generation; @@ -44,6 +46,7 @@ import org.springframework.ai.converter.BeanOutputConverter; import org.springframework.ai.converter.ListOutputConverter; import org.springframework.ai.converter.MapOutputConverter; +import org.springframework.ai.model.function.FunctionCallbackWrapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; @@ -246,6 +249,30 @@ void chatOptions() { assertThat(content).isNotNull(); } + @Test + void functionCallTest() { + UserMessage userMessage = new UserMessage( + "What's the weather like in San Francisco, Tokyo and Paris? Return the result in Celsius."); + + List messages = new ArrayList<>(List.of(userMessage)); + + var promptOptions = Anthropic3ChatOptions.builder() + .withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService()) + .withName("getCurrentWeather") + .withDescription("Get the weather in location. Return temperature in 36°F or 36°C format.") + .build())) + .build(); + + ChatResponse response = chatModel.call(new Prompt(messages, promptOptions)); + + logger.info("Response: {}", response); + + Generation generation = response.getResult(); + assertThat(generation.getOutput().getContent()).containsAnyOf("30.0", "30"); + assertThat(generation.getOutput().getContent()).containsAnyOf("10.0", "10"); + assertThat(generation.getOutput().getContent()).containsAnyOf("15.0", "15"); + } + @SpringBootConfiguration public static class TestConfiguration { diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc index 120a8e5c8b..9475388b43 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc @@ -11,6 +11,7 @@ **** xref:api/chat/functions/azure-open-ai-chat-functions.adoc[Function Calling] *** xref:api/bedrock-chat.adoc[Amazon Bedrock] **** xref:api/chat/bedrock/bedrock-anthropic3.adoc[Anthropic3] +***** xref:api/chat/functions/bedrock/bedrock-anthropic3-chat-functions.adoc[Function Calling] **** xref:api/chat/bedrock/bedrock-anthropic.adoc[Anthropic2] **** xref:api/chat/bedrock/bedrock-llama.adoc[Llama] **** xref:api/chat/bedrock/bedrock-cohere.adoc[Cohere] diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/anthropic-chat-functions.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/anthropic-chat-functions.adoc index fea58dc1d0..1e13b223fd 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/anthropic-chat-functions.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/anthropic-chat-functions.adoc @@ -6,7 +6,7 @@ The `claude-3-opus`, `claude-3-sonnet` and `claude-3-haiku` link:https://docs.an The Anthropic API does not call the function directly; instead, the model generates JSON that you can use to call the function in your code and return the result back to the model to complete the conversation. -NOTE: As of April 4th, 2024, streaming is not yet supported for function calling and Tool use is not yet available on third-party platforms like Vertex AI or AWS Bedrock, but is coming soon. +NOTE: As of April 4th, 2024, streaming is not yet supported for function calling and Tool use is not yet available on third-party platforms like Vertex AI, but is coming soon. Spring AI provides flexible and user-friendly ways to register and call custom functions. In general, the custom functions need to provide a function `name`, `description`, and the function call `signature` (as JSON schema) to let the model know what arguments the function expects. diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-anthropic3-chat-functions.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-anthropic3-chat-functions.adoc new file mode 100644 index 0000000000..ca6d7201ac --- /dev/null +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-anthropic3-chat-functions.adoc @@ -0,0 +1,187 @@ += Bedrock Anthropic 3 Function Calling + +You can register custom Java functions with the `BedrockAnthropic3ChatModel` and have the Bedrock Anthropic 3 models intelligently choose to output a JSON object containing arguments to call one or many of the registered functions. +This allows you to connect the LLM capabilities with external tools and APIs. + +The Bedrock Anthropic 3 API does not call the function directly; instead, the model generates JSON that you can use to call the function in your code and return the result back to the model to complete the conversation. + +Spring AI provides flexible and user-friendly ways to register and call custom functions. +In general, the custom functions need to provide a function `name`, `description`, and the function call `signature` (as JSON schema) to let the model know what arguments the function expects. +The `description` helps the model to understand when to call the function. + +As a developer, you need to implement a function that takes the function call arguments sent from the AI model, and respond with the result back to the model. +Your function can in turn invoke other 3rd party services to provide the results. + +Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`. + +Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code. +The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions. + +== How it works + +Suppose we want the AI model to respond with information that it does not have, for example the current temperature at a given location. + +We can provide the AI model with metadata about our own functions that it can use to retrieve that information as it processes your prompt. + +For example, if during the processing of a prompt, the AI Model determines that it needs additional information about the temperature in a given location, it will start a server side generated request/response interaction. The AI Model invokes a client side function. +The AI Model provides method invocation details as JSON and it is the responsibility of the client to execute that function and return the response. + +Spring AI greatly simplifies the code you need to write to support function invocation. +It brokers the function invocation conversation for you. +You can simply provide your function definition as a `@Bean` and then provide the bean name of the function in your prompt options. +You can also reference multiple function bean names in your prompt. + +== Quick Start + +Let's create a chatbot that answer questions by calling our own function. +To support the response of the chatbot, we will register our own function that takes a location and returns the current weather in that location. + +When the response to the prompt to the model needs to answer a question such as `"What’s the weather like in Boston?"` the AI model will invoke the client providing the location value as an argument to be passed to the function. This RPC-like data is passed as JSON. + +Our function can some SaaS based weather service API and returns the weather response back to the model to complete the conversation. +In this example we will use a simple implementation named `MockWeatherService` that hard codes the temperature for various locations. + +The following `MockWeatherService.java` represents the weather service API: + +[source,java] +---- +public class MockWeatherService implements Function { + + public enum Unit { C, F } + public record Request(String location, Unit unit) {} + public record Response(double temp, Unit unit) {} + + public Response apply(Request request) { + return new Response(30.0, Unit.C); + } +} +---- + +=== Registering Functions as Beans + +With the link:../bedrock/bedrock-anthropic3.html#_auto_configuration[BedrockAnthropic3ChatModel Auto-Configuration] you have multiple ways to register custom functions as beans in the Spring context. + +We start with describing the most POJO friendly options. + +==== Plain Java Functions + +In this approach you define `@Beans` in your application context as you would any other Spring managed object. + +Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` wrapper that adds the logic for it being invoked via the AI model. +The name of the `@Bean` is passed as a `ChatOption`. + + +[source,java] +---- +@Configuration +static class Config { + + @Bean + @Description("Get the weather in location") // function description + public Function weatherFunction1() { + return new MockWeatherService(); + } + ... +} +---- + +The `@Description` annotation is optional and provides a function description (2) that helps the model understand when to call the function. +It is an important property to set to help the AI model determine what client side function to invoke. + +Another option to provide the description of the function is to use the `@JsonClassDescription` annotation on the `MockWeatherService.Request` to provide the function description: + +[source,java] +---- + +@Configuration +static class Config { + + @Bean + public Function currentWeather3() { // (1) bean name as function name. + return new MockWeatherService(); + } + ... +} + +@JsonClassDescription("Get the weather in location") // (2) function description +public record Request(String location, Unit unit) {} +---- + +It is a best practice to annotate the request object with information such that the generated JSON schema of that function is as descriptive as possible to help the AI model pick the correct function to invoke. + +The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/anthropic/tool/FunctionCallWithFunctionBeanIT.java.java[FunctionCallWithFunctionBeanIT.java] demonstrates this approach. + + +==== FunctionCallback Wrapper + +Another way to register a function is to create a `FunctionCallbackWrapper` wrapper like this: + +[source,java] +---- +@Configuration +static class Config { + + @Bean + public FunctionCallback weatherFunctionInfo() { + + return new FunctionCallbackWrapper<>("CurrentWeather", // (1) function name + "Get the weather in location", // (2) function description + (response) -> "" + response.temp() + response.unit(), // (3) Response Converter + new MockWeatherService()); // function code + } + ... +} +---- + +It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `BedrockAnthropic3ChatModel`. +It also provides a description (2) and an optional response converter (3) to convert the response into a text as expected by the model. + +NOTE: By default, the response converter does a JSON serialization of the Response object. + +NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class. + +=== Specifying functions in Chat Options + +To let the model know and call your `CurrentWeather` function you need to enable it in your prompt requests: + +[source,java] +---- +BedrockAnthropic3ChatModel chatModel = ... + +UserMessage userMessage = new UserMessage("What's the weather like in Paris?"); + +ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), + Anthropic3ChatOptions.builder().withFunction("CurrentWeather").build())); // (1) Enable the function + +logger.info("Response: {}", response); +---- + +// NOTE: You can can have multiple functions registered in your `ChatModel` but only those enabled in the prompt request will be considered for the function calling. + +Above user question will trigger 3 calls to `CurrentWeather` function (one for each city) and produce the final response. + +=== Register/Call Functions with Prompt Options + +In addition to the auto-configuration you can register callback functions, dynamically, with your Prompt requests: + +[source,java] +---- +BedrockAnthropic3ChatModel chatModel = ... + +UserMessage userMessage = new UserMessage("What's the weather like in Paris?"); + +var promptOptions = Anthropic3ChatOptions.builder() + .withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>( + "CurrentWeather", // name + "Get the weather in location", // function description + new MockWeatherService()))) // function code + .build(); + +ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); +---- + +NOTE: The in-prompt registered functions are enabled by default for the duration of this request. + +This approach allows to dynamically chose different functions to be called based on the user input. + +The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/anthropic/tool/FunctionCallWithPromptFunctionIT.java[FunctionCallWithPromptFunctionIT.java] integration test provides a complete example of how to register a function with the `BedrockAnthropic3ChatModel` and use it in a prompt request. \ No newline at end of file diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc index b351d4b594..c704e83220 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc @@ -13,4 +13,5 @@ Spring AI currently supports Function invocation for the following AI Models * Mistral AI: Refer to the xref:api/chat/functions/mistralai-chat-functions.adoc[Mistral AI function invocation docs]. * Anthropic Claude: Refer to the xref:api/chat/functions/anthropic-chat-functions.adoc[Anthropic Claude function invocation docs]. * MiniMax : Refer to the xref:api/chat/functions/minimax-chat-functions.adoc[MiniMax function invocation docs]. -* ZhiPu AI : Refer to the xref:api/chat/functions/zhipuai-chat-functions.adoc[ZhiPu AI function invocation docs]. \ No newline at end of file +* ZhiPu AI : Refer to the xref:api/chat/functions/zhipuai-chat-functions.adoc[ZhiPu AI function invocation docs]. +* Amazon Bedrock Anthropic 3 : Refer to the xref:api/chat/functions/bedrock/bedrock-anthropic3-chat-functions.adoc[Amazon Bedrock Anthropic3 function invocation docs]. \ No newline at end of file diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java index 6c1272bfb8..8728f8db0a 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java @@ -16,20 +16,28 @@ package org.springframework.ai.autoconfigure.bedrock.anthropic3; import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.List; + import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel; import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi; import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.model.function.FunctionCallbackContext; 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.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.util.CollectionUtils; + import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.regions.providers.AwsRegionProvider; @@ -62,8 +70,22 @@ public Anthropic3ChatBedrockApi anthropic3Api(AwsCredentialsProvider credentials @Bean @ConditionalOnBean(BedrockConverseApi.class) public BedrockAnthropic3ChatModel anthropic3ChatModel(BedrockConverseApi converseApi, - BedrockAnthropic3ChatProperties properties) { - return new BedrockAnthropic3ChatModel(properties.getModel(), converseApi, properties.getOptions()); + BedrockAnthropic3ChatProperties properties, FunctionCallbackContext functionCallbackContext, + List toolFunctionCallbacks) { + if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) { + properties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks); + } + + return new BedrockAnthropic3ChatModel(properties.getModel(), converseApi, properties.getOptions(), + functionCallbackContext); + } + + @Bean + @ConditionalOnMissingBean + public FunctionCallbackContext springAiFunctionManager(ApplicationContext context) { + FunctionCallbackContext manager = new FunctionCallbackContext(); + manager.setApplicationContext(context); + return manager; } } diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/FunctionCallWithFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/FunctionCallWithFunctionBeanIT.java new file mode 100644 index 0000000000..1770adad78 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/FunctionCallWithFunctionBeanIT.java @@ -0,0 +1,113 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.anthropic3.tool; + +import java.util.List; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.autoconfigure.bedrock.anthropic3.BedrockAnthropic3ChatAutoConfiguration; +import org.springframework.ai.autoconfigure.bedrock.anthropic3.tool.MockWeatherService.Request; +import org.springframework.ai.autoconfigure.bedrock.anthropic3.tool.MockWeatherService.Response; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.anthropic3.Anthropic3ChatOptions; +import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel; +import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel.Anthropic3ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Description; + +import software.amazon.awssdk.regions.Region; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +class FunctionCallWithFunctionBeanIT { + + private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionBeanIT.class); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.anthropic3.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), + "spring.ai.bedrock.anthropic3.chat.model=" + Anthropic3ChatModel.CLAUDE_V3_SONNET.id(), + "spring.ai.bedrock.anthropic3.chat.options.temperature=0.5") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropic3ChatAutoConfiguration.class)) + .withUserConfiguration(Config.class); + + @Test + void functionCallTest() { + + contextRunner.run(context -> { + + BedrockAnthropic3ChatModel chatModel = context.getBean(BedrockAnthropic3ChatModel.class); + + var userMessage = new UserMessage( + "What's the weather like in San Francisco, Tokyo and Paris? Return the result in Celsius."); + + ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), + Anthropic3ChatOptions.builder().withFunction("weatherFunction").build())); + + logger.info("Response: {}", response); + + assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15"); + + response = chatModel.call(new Prompt(List.of(userMessage), + Anthropic3ChatOptions.builder().withFunction("weatherFunction3").build())); + + logger.info("Response: {}", response); + + assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15"); + + }); + } + + @Configuration + static class Config { + + @Bean + @Description("Get the weather in location. Return temperature in 36°F or 36°C format.") + public Function weatherFunction() { + return new MockWeatherService(); + } + + // Relies on the Request's JsonClassDescription annotation to provide the + // function description. + @Bean + public Function weatherFunction3() { + MockWeatherService weatherService = new MockWeatherService(); + return (weatherService::apply); + } + + } + +} \ No newline at end of file diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/FunctionCallWithPromptFunctionIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/FunctionCallWithPromptFunctionIT.java new file mode 100644 index 0000000000..571a324f4c --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/FunctionCallWithPromptFunctionIT.java @@ -0,0 +1,88 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.anthropic3.tool; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.ai.autoconfigure.bedrock.anthropic3.BedrockAnthropic3ChatAutoConfiguration; +import org.springframework.ai.autoconfigure.bedrock.anthropic3.tool.FunctionCallWithFunctionBeanIT.Config; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.anthropic3.Anthropic3ChatOptions; +import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel; +import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel.Anthropic3ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.function.FunctionCallbackWrapper; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import software.amazon.awssdk.regions.Region; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +public class FunctionCallWithPromptFunctionIT { + + private final Logger logger = LoggerFactory.getLogger(FunctionCallWithPromptFunctionIT.class); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.anthropic3.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), + "spring.ai.bedrock.anthropic3.chat.model=" + Anthropic3ChatModel.CLAUDE_V3_SONNET.id(), + "spring.ai.bedrock.anthropic3.chat.options.temperature=0.5") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockAnthropic3ChatAutoConfiguration.class)) + .withUserConfiguration(Config.class); + + @Test + void functionCallTest() { + contextRunner.run(context -> { + + BedrockAnthropic3ChatModel chatModel = context.getBean(BedrockAnthropic3ChatModel.class); + + UserMessage userMessage = new UserMessage( + "What's the weather like in San Francisco, Tokyo and Paris? Return the result in Celsius."); + + var promptOptions = Anthropic3ChatOptions.builder() + .withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService()) + .withName("CurrentWeatherService") + .withDescription("Get the weather in location. Return temperature in 36°F or 36°C format.") + .build())) + .build(); + + ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); + + logger.info("Response: {}", response); + + assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15"); + }); + } + +} \ No newline at end of file diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/MockWeatherService.java new file mode 100644 index 0000000000..711f642e55 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/tool/MockWeatherService.java @@ -0,0 +1,91 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.anthropic3.tool; + +import java.util.function.Function; + +import com.fasterxml.jackson.annotation.JsonClassDescription; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; + +/** + * Mock 3rd party weather service. + * + * @author Wei Jiang + */ +public class MockWeatherService implements Function { + + /** + * Weather Function request. + */ + @JsonInclude(Include.NON_NULL) + @JsonClassDescription("Get the weather in location. Return temperature in 36°F or 36°C format.") + public record Request(@JsonProperty(required = true, + value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, + @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { + } + + /** + * Temperature units. + */ + public enum Unit { + + /** + * Celsius. + */ + C("metric"), + /** + * Fahrenheit. + */ + F("imperial"); + + /** + * Human readable unit name. + */ + public final String unitName; + + private Unit(String text) { + this.unitName = text; + } + + } + + /** + * Weather Function response. + */ + public record Response(double temperature, double feels_like, double temp_min, double temp_max, int pressure, + int humidity, Unit unit) { + } + + @Override + public Response apply(Request request) { + double temperature = 0; + if (request.location().contains("Paris")) { + temperature = 15; + } + else if (request.location().contains("Tokyo")) { + temperature = 10; + } + else if (request.location().contains("San Francisco")) { + temperature = 30; + } + + return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); + } + +} \ No newline at end of file From 678389f2131eae5ea157e45d1d319be077434367 Mon Sep 17 00:00:00 2001 From: wmz7year Date: Sat, 8 Jun 2024 19:05:35 +0800 Subject: [PATCH 4/9] Amazon Bedrock AI models remove the old API components, use BedrockConverseApi instead. --- .../api/AnthropicChatBedrockApi.java | 278 ---------- .../api/Anthropic3ChatBedrockApi.java | 502 ------------------ .../ai/bedrock/aot/BedrockRuntimeHints.java | 18 - .../cohere/api/CohereChatBedrockApi.java | 416 --------------- .../api/Ai21Jurassic2ChatBedrockApi.java | 416 --------------- .../llama/api/LlamaChatBedrockApi.java | 262 --------- .../titan/api/TitanChatBedrockApi.java | 317 ----------- .../api/AnthropicChatBedrockApiIT.java | 94 ---- .../api/Anthropic3ChatBedrockApiIT.java | 145 ----- .../bedrock/aot/BedrockRuntimeHintsTests.java | 9 +- .../cohere/api/CohereChatBedrockApiIT.java | 148 ------ .../api/Ai21Jurassic2ChatBedrockApiIT.java | 65 --- .../llama/api/LlamaChatBedrockApiIT.java | 84 --- .../titan/api/TitanChatBedrockApiIT.java | 69 --- .../bedrock/bedrock-anthropic-chat-api.png | Bin 173787 -> 0 bytes .../bedrock-cohere-chat-low-level-api.jpg | Bin 452454 -> 0 bytes .../images/bedrock/bedrock-llama-chat-api.jpg | Bin 123735 -> 0 bytes .../bedrock-titan-chat-low-level-api.jpg | Bin 243012 -> 0 bytes .../api/chat/bedrock/bedrock-anthropic.adoc | 44 +- .../api/chat/bedrock/bedrock-anthropic3.adoc | 40 +- .../api/chat/bedrock/bedrock-cohere.adoc | 70 +-- .../api/chat/bedrock/bedrock-jurassic2.adoc | 39 +- .../pages/api/chat/bedrock/bedrock-llama.adoc | 49 +- .../pages/api/chat/bedrock/bedrock-titan.adoc | 47 +- ...BedrockAnthropicChatAutoConfiguration.java | 14 - ...edrockAnthropic3ChatAutoConfiguration.java | 14 - .../BedrockCohereChatAutoConfiguration.java | 14 - ...ockAi21Jurassic2ChatAutoConfiguration.java | 14 - .../BedrockLlamaChatAutoConfiguration.java | 14 - .../BedrockTitanChatAutoConfiguration.java | 14 - 30 files changed, 32 insertions(+), 3164 deletions(-) delete mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java delete mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java delete mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java delete mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java delete mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java delete mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApiIT.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApiIT.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApiIT.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApiIT.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApiIT.java delete mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApiIT.java delete mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-anthropic-chat-api.png delete mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-cohere-chat-low-level-api.jpg delete mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-llama-chat-api.jpg delete mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-titan-chat-low-level-api.jpg diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java deleted file mode 100644 index 3313b563db..0000000000 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.anthropic.api; - -import java.time.Duration; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import reactor.core.publisher.Flux; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatRequest; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatResponse; -import org.springframework.ai.bedrock.api.AbstractBedrockApi; -import org.springframework.ai.model.ModelDescription; -import org.springframework.util.Assert; - -/** - * @deprecated Use {@link BedrockConverseApi} instead. - * @author Christian Tzolov - * @author Wei Jiang - * @since 0.8.0 - */ -// @formatter:off -@Deprecated -public class AnthropicChatBedrockApi extends - AbstractBedrockApi { - - public static final String PROMPT_TEMPLATE = "\n\nHuman:%s\n\nAssistant:"; - - /** - * Default version of the Anthropic chat model. - */ - public static final String DEFAULT_ANTHROPIC_VERSION = "bedrock-2023-05-31"; - - - /** - * Create a new AnthropicChatBedrockApi instance using the default credentials provider chain, the default object. - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param region The AWS region to use. - */ - public AnthropicChatBedrockApi(String modelId, String region) { - super(modelId, region); - } - - /** - * Create a new AnthropicChatBedrockApi instance using the default credentials provider chain, the default object. - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param region The AWS region to use. - * @param timeout The timeout to use. - */ - public AnthropicChatBedrockApi(String modelId, String region, Duration timeout) { - super(modelId, region, timeout); - } - - /** - * Create a new AnthropicChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - */ - public AnthropicChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper) { - super(modelId, credentialsProvider, region, objectMapper); - } - - /** - * Create a new AnthropicChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public AnthropicChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * Create a new AnthropicChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public AnthropicChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, Region region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - // https://github.com/build-on-aws/amazon-bedrock-java-examples/blob/main/example_code/bedrock-runtime/src/main/java/aws/community/examples/InvokeBedrockStreamingAsync.java - - // https://docs.anthropic.com/claude/reference/complete_post - - // https://docs.aws.amazon.com/bedrock/latest/userguide/br-product-ids.html - - // Anthropic Claude models: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html - - /** - * AnthropicChatRequest encapsulates the request parameters for the Anthropic chat model. - * https://docs.anthropic.com/claude/reference/complete_post - * - * @param prompt The prompt to use for the chat. - * @param temperature (default 0.5) The temperature to use for the chat. You should either alter temperature or - * top_p, but not both. - * @param maxTokensToSample (default 200) Specify the maximum number of tokens to use in the generated response. - * Note that the models may stop before reaching this maximum. This parameter only specifies the absolute maximum - * number of tokens to generate. We recommend a limit of 4,000 tokens for optimal performance. - * @param topK (default 250) Specify the number of token choices the model uses to generate the next token. - * @param topP (default 1) Nucleus sampling to specify the cumulative probability of the next token in range [0,1]. - * In nucleus sampling, we compute the cumulative distribution over all the options for each subsequent token in - * decreasing probability order and cut it off once it reaches a particular probability specified by top_p. You - * should either alter temperature or top_p, but not both. - * @param stopSequences (defaults to "\n\nHuman:") Configure up to four sequences that the model recognizes. After a - * stop sequence, the model stops generating further tokens. The returned text doesn't contain the stop sequence. - * @param anthropicVersion The version of the model to use. The default value is bedrock-2023-05-31. - */ - @JsonInclude(Include.NON_NULL) - public record AnthropicChatRequest( - @JsonProperty("prompt") String prompt, - @JsonProperty("temperature") Float temperature, - @JsonProperty("max_tokens_to_sample") Integer maxTokensToSample, - @JsonProperty("top_k") Integer topK, - @JsonProperty("top_p") Float topP, - @JsonProperty("stop_sequences") List stopSequences, - @JsonProperty("anthropic_version") String anthropicVersion) { - - public static Builder builder(String prompt) { - return new Builder(prompt); - } - - public static class Builder { - private final String prompt; - private Float temperature;// = 0.7f; - private Integer maxTokensToSample;// = 500; - private Integer topK;// = 10; - private Float topP; - private List stopSequences; - private String anthropicVersion; - - private Builder(String prompt) { - this.prompt = prompt; - } - - public Builder withTemperature(Float temperature) { - this.temperature = temperature; - return this; - } - - public Builder withMaxTokensToSample(Integer maxTokensToSample) { - this.maxTokensToSample = maxTokensToSample; - return this; - } - - public Builder withTopK(Integer topK) { - this.topK = topK; - return this; - } - - public Builder withTopP(Float tpoP) { - this.topP = tpoP; - return this; - } - - public Builder withStopSequences(List stopSequences) { - this.stopSequences = stopSequences; - return this; - } - - public Builder withAnthropicVersion(String anthropicVersion) { - this.anthropicVersion = anthropicVersion; - return this; - } - - public AnthropicChatRequest build() { - return new AnthropicChatRequest( - prompt, - temperature, - maxTokensToSample, - topK, - topP, - stopSequences, - anthropicVersion - ); - } - } - } - - /** - * AnthropicChatResponse encapsulates the response parameters for the Anthropic chat model. - * - * @param completion The generated text. - * @param stopReason The reason the model stopped generating text. - * @param stop The stop sequence that caused the model to stop generating text. - * @param amazonBedrockInvocationMetrics Metrics about the model invocation. - */ - @JsonInclude(Include.NON_NULL) - public record AnthropicChatResponse( - @JsonProperty("type") String type, - @JsonProperty("completion") String completion, - @JsonProperty("stop_reason") String stopReason, - @JsonProperty("stop") String stop, - @JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) { - } - - /** - * Anthropic models version. - */ - public enum AnthropicChatModel implements ModelDescription { - /** - * anthropic.claude-instant-v1 - */ - CLAUDE_INSTANT_V1("anthropic.claude-instant-v1"), - /** - * anthropic.claude-v2 - */ - CLAUDE_V2("anthropic.claude-v2"), - /** - * anthropic.claude-v2:1 - */ - CLAUDE_V21("anthropic.claude-v2:1"); - - private final String id; - - /** - * @return The model id. - */ - public String id() { - return id; - } - - AnthropicChatModel(String value) { - this.id = value; - } - - @Override - public String getModelName() { - return this.id; - } - } - - @Override - public AnthropicChatResponse chatCompletion(AnthropicChatRequest anthropicRequest) { - Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null"); - return this.internalInvocation(anthropicRequest, AnthropicChatResponse.class); - } - - @Override - public Flux chatCompletionStream(AnthropicChatRequest anthropicRequest) { - Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null"); - return this.internalInvocationStream(anthropicRequest, AnthropicChatResponse.class); - } - -} -// @formatter:on \ No newline at end of file diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java deleted file mode 100644 index dce8c9ccba..0000000000 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java +++ /dev/null @@ -1,502 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.anthropic3.api; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatRequest; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatResponse; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatStreamingResponse; -import org.springframework.ai.bedrock.api.AbstractBedrockApi; -import org.springframework.ai.model.ModelDescription; -import org.springframework.util.Assert; -import reactor.core.publisher.Flux; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import java.time.Duration; -import java.util.List; - -/** - * Based on Bedrock's Anthropic - * Claude Messages API. - * - * It is meant to replace the previous Chat API, which is now deprecated. - * - * @deprecated Use {@link BedrockConverseApi} instead. - * @author Ben Middleton - * @author Christian Tzolov - * @author Wei Jiang - * @since 1.0.0 - */ -// @formatter:off -@Deprecated -public class Anthropic3ChatBedrockApi extends - AbstractBedrockApi { - - /** - * Default version of the Anthropic chat model. - */ - public static final String DEFAULT_ANTHROPIC_VERSION = "bedrock-2023-05-31"; - - /** - * Create a new AnthropicChatBedrockApi instance using the default credentials provider chain, the default object. - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param region The AWS region to use. - */ - public Anthropic3ChatBedrockApi(String modelId, String region) { - super(modelId, region); - } - - /** - * Create a new AnthropicChatBedrockApi instance using the default credentials provider chain, the default object. - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param region The AWS region to use. - * @param timeout The timeout to use. - */ - public Anthropic3ChatBedrockApi(String modelId, String region, Duration timeout) { - super(modelId, region, timeout); - } - - /** - * Create a new AnthropicChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - */ - public Anthropic3ChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper) { - super(modelId, credentialsProvider, region, objectMapper); - } - - /** - * Create a new AnthropicChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public Anthropic3ChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * Create a new AnthropicChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link AnthropicChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public Anthropic3ChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, Region region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - // https://github.com/build-on-aws/amazon-bedrock-java-examples/blob/main/example_code/bedrock-runtime/src/main/java/aws/community/examples/InvokeBedrockStreamingAsync.java - - // https://docs.anthropic.com/claude/reference/complete_post - - // https://docs.aws.amazon.com/bedrock/latest/userguide/br-product-ids.html - - // Anthropic Claude models: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html - - /** - * AnthropicChatRequest encapsulates the request parameters for the Anthropic messages model. - * https://docs.anthropic.com/claude/reference/messages_post - * - * @param messages A list of messages comprising the conversation so far. - * @param system A system prompt, providing context and instructions to Claude, such as specifying a particular goal - * or role. - * @param temperature (default 0.5) The temperature to use for the chat. You should either alter temperature or - * top_p, but not both. - * @param maxTokens (default 200) Specify the maximum number of tokens to use in the generated response. - * Note that the models may stop before reaching this maximum. This parameter only specifies the absolute maximum - * number of tokens to generate. We recommend a limit of 4,000 tokens for optimal performance. - * @param topK (default 250) Specify the number of token choices the model uses to generate the next token. - * @param topP (default 1) Nucleus sampling to specify the cumulative probability of the next token in range [0,1]. - * In nucleus sampling, we compute the cumulative distribution over all the options for each subsequent token in - * decreasing probability order and cut it off once it reaches a particular probability specified by top_p. You - * should either alter temperature or top_p, but not both. - * @param stopSequences (defaults to "\n\nHuman:") Configure up to four sequences that the model recognizes. After a - * stop sequence, the model stops generating further tokens. The returned text doesn't contain the stop sequence. - * @param anthropicVersion The version of the model to use. The default value is bedrock-2023-05-31. - */ - @JsonInclude(Include.NON_NULL) - public record AnthropicChatRequest( - @JsonProperty("messages") List messages, - @JsonProperty("system") String system, - @JsonProperty("temperature") Float temperature, - @JsonProperty("max_tokens") Integer maxTokens, - @JsonProperty("top_k") Integer topK, - @JsonProperty("top_p") Float topP, - @JsonProperty("stop_sequences") List stopSequences, - @JsonProperty("anthropic_version") String anthropicVersion) { - - public static Builder builder(List messages) { - return new Builder(messages); - } - - public static class Builder { - private final List messages; - private String system; - private Float temperature;// = 0.7f; - private Integer maxTokens;// = 500; - private Integer topK;// = 10; - private Float topP; - private List stopSequences; - private String anthropicVersion; - - private Builder(List messages) { - this.messages = messages; - } - - public Builder withSystem(String system) { - this.system = system; - return this; - } - public Builder withTemperature(Float temperature) { - this.temperature = temperature; - return this; - } - - public Builder withMaxTokens(Integer maxTokens) { - this.maxTokens = maxTokens; - return this; - } - - public Builder withTopK(Integer topK) { - this.topK = topK; - return this; - } - - public Builder withTopP(Float tpoP) { - this.topP = tpoP; - return this; - } - - public Builder withStopSequences(List stopSequences) { - this.stopSequences = stopSequences; - return this; - } - - public Builder withAnthropicVersion(String anthropicVersion) { - this.anthropicVersion = anthropicVersion; - return this; - } - - public AnthropicChatRequest build() { - return new AnthropicChatRequest( - messages, - system, - temperature, - maxTokens, - topK, - topP, - stopSequences, - anthropicVersion - ); - } - } - } - - /** - * @param type the content type can be "text" or "image". - * @param source The source of the media content. Applicable for "image" types only. - * @param text The text of the message. Applicable for "text" types only. - * @param index The index of the content block. Applicable only for streaming - * responses. - */ - @JsonInclude(Include.NON_NULL) - public record MediaContent( // @formatter:off - @JsonProperty("type") Type type, - @JsonProperty("source") Source source, - @JsonProperty("text") String text, - @JsonProperty("index") Integer index // applicable only for streaming responses. - ) { - // @formatter:on - - public MediaContent(String mediaType, String data) { - this(new Source(mediaType, data)); - } - - public MediaContent(Source source) { - this(Type.IMAGE, source, null, null); - } - - public MediaContent(String text) { - this(Type.TEXT, null, text, null); - } - - /** - * The type of this message. - */ - public enum Type { - - /** - * Text message. - */ - @JsonProperty("text") - TEXT, - /** - * Image message. - */ - @JsonProperty("image") - IMAGE - - } - - /** - * The source of the media content. (Applicable for "image" types only) - * - * @param type The type of the media content. Only "base64" is supported at the - * moment. - * @param mediaType The media type of the content. For example, "image/png" or - * "image/jpeg". - * @param data The base64-encoded data of the content. - */ - @JsonInclude(Include.NON_NULL) - public record Source( // @formatter:off - @JsonProperty("type") String type, - @JsonProperty("media_type") String mediaType, - @JsonProperty("data") String data) { - // @formatter:on - - public Source(String mediaType, String data) { - this("base64", mediaType, data); - } - } - } - - /** - * Message comprising the conversation. - * - * @param content The contents of the message. - * @param role The role of the messages author. Could be one of the {@link Role} - * types. - */ - @JsonInclude(Include.NON_NULL) - public record ChatCompletionMessage(@JsonProperty("content") List content, - @JsonProperty("role") Role role) { - - /** - * The role of the author of this message. - */ - public enum Role { - - /** - * User message. - */ - @JsonProperty("user") - USER, - /** - * Assistant message. - */ - @JsonProperty("assistant") - ASSISTANT - - } - } - - /** - * Encapsulates the metrics about the model invocation. - * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html#model-parameters-anthropic-claude-messages-request-response - * - * @param inputTokens The number of tokens in the input prompt. - * @param outputTokens The number of tokens in the generated text. - */ - @JsonInclude(Include.NON_NULL) - public record AnthropicUsage(@JsonProperty("input_tokens") Integer inputTokens, - @JsonProperty("output_tokens") Integer outputTokens) { - } - - /** - * AnthropicChatResponse encapsulates the response parameters for the Anthropic - * messages model. - * - * @param id The unique response identifier. - * @param model The ID for the Anthropic Claude model that made the request. - * @param type The type of the response. - * @param role The role of the response. - * @param content The list of generated text. - * @param stopReason The reason the model stopped generating text: end_turn – The - * model reached a natural stopping point. max_tokens – The generated text exceeded - * the value of the max_tokens input field or exceeded the maximum number of tokens - * that the model supports. stop_sequence – The model generated one of the stop - * sequences that you specified in the stop_sequences input field. - * @param stopSequence The stop sequence that caused the model to stop generating - * text. - * @param usage Metrics about the model invocation. - */ - @JsonInclude(Include.NON_NULL) - public record AnthropicChatResponse(// formatter:off - @JsonProperty("id") String id, @JsonProperty("model") String model, @JsonProperty("type") String type, - @JsonProperty("role") String role, @JsonProperty("content") List content, - @JsonProperty("stop_reason") String stopReason, @JsonProperty("stop_sequence") String stopSequence, - @JsonProperty("usage") AnthropicUsage usage, - @JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) { // formatter:on - } - - /** - * AnthropicChatStreamingResponse encapsulates the streaming response parameters for - * the Anthropic messages model. - * https://docs.anthropic.com/claude/reference/messages-streaming - * - * @param type The streaming type. - * @param message The message details that made the request. - * @param index The delta index. - * @param contentBlock The generated text. - * @param delta The delta. - * @param usage The usage data. - */ - @JsonInclude(Include.NON_NULL) - public record AnthropicChatStreamingResponse(// formatter:off - @JsonProperty("type") StreamingType type, @JsonProperty("message") AnthropicChatResponse message, - @JsonProperty("index") Integer index, @JsonProperty("content_block") MediaContent contentBlock, - @JsonProperty("delta") Delta delta, @JsonProperty("usage") AnthropicUsage usage, - @JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) { // formatter:on - - /** - * The streaming type of this message. - */ - public enum StreamingType { - - /** - * Message start. - */ - @JsonProperty("message_start") - MESSAGE_START, - /** - * Content block start. - */ - @JsonProperty("content_block_start") - CONTENT_BLOCK_START, - /** - * Ping. - */ - @JsonProperty("ping") - PING, - /** - * Content block delta. - */ - @JsonProperty("content_block_delta") - CONTENT_BLOCK_DELTA, - /** - * Content block stop. - */ - @JsonProperty("content_block_stop") - CONTENT_BLOCK_STOP, - /** - * Message delta. - */ - @JsonProperty("message_delta") - MESSAGE_DELTA, - /** - * Message stop. - */ - @JsonProperty("message_stop") - MESSAGE_STOP - - } - - /** - * Encapsulates a delta. - * https://docs.anthropic.com/claude/reference/messages-streaming * - * - * @param type The type of the message. - * @param text The text message. - * @param stopReason The stop reason. - * @param stopSequence The stop sequence. - */ - @JsonInclude(Include.NON_NULL) - public record Delta(@JsonProperty("type") String type, @JsonProperty("text") String text, - @JsonProperty("stop_reason") String stopReason, @JsonProperty("stop_sequence") String stopSequence) { - } - } - - /** - * Anthropic models version. - */ - public enum AnthropicChatModel implements ModelDescription { - - /** - * anthropic.claude-instant-v1 - */ - CLAUDE_INSTANT_V1("anthropic.claude-instant-v1"), - /** - * anthropic.claude-v2 - */ - CLAUDE_V2("anthropic.claude-v2"), - /** - * anthropic.claude-v2:1 - */ - CLAUDE_V21("anthropic.claude-v2:1"), - /** - * anthropic.claude-3-sonnet-20240229-v1:0 - */ - CLAUDE_V3_SONNET("anthropic.claude-3-sonnet-20240229-v1:0"), - /** - * anthropic.claude-3-haiku-20240307-v1:0 - */ - CLAUDE_V3_HAIKU("anthropic.claude-3-haiku-20240307-v1:0"), - /** - * anthropic.claude-3-opus-20240229-v1:0 - */ - CLAUDE_V3_OPUS("anthropic.claude-3-opus-20240229-v1:0"); - - private final String id; - - /** - * @return The model id. - */ - public String id() { - return id; - } - - AnthropicChatModel(String value) { - this.id = value; - } - - @Override - public String getModelName() { - return this.id; - } - - } - - @Override - public AnthropicChatResponse chatCompletion(AnthropicChatRequest anthropicRequest) { - Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null"); - return this.internalInvocation(anthropicRequest, AnthropicChatResponse.class); - } - - @Override - public Flux chatCompletionStream(AnthropicChatRequest anthropicRequest) { - Assert.notNull(anthropicRequest, "'anthropicRequest' must not be null"); - return this.internalInvocationStream(anthropicRequest, AnthropicChatStreamingResponse.class); - } - -} -// @formatter:on diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/aot/BedrockRuntimeHints.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/aot/BedrockRuntimeHints.java index 7db24b3b8c..4317437494 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/aot/BedrockRuntimeHints.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/aot/BedrockRuntimeHints.java @@ -16,20 +16,14 @@ package org.springframework.ai.bedrock.aot; import org.springframework.ai.bedrock.anthropic.AnthropicChatOptions; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi; import org.springframework.ai.bedrock.anthropic3.Anthropic3ChatOptions; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi; import org.springframework.ai.bedrock.api.AbstractBedrockApi; import org.springframework.ai.bedrock.cohere.BedrockCohereChatOptions; import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingOptions; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi; import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi; import org.springframework.ai.bedrock.llama.BedrockLlamaChatOptions; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi; import org.springframework.ai.bedrock.titan.BedrockTitanChatOptions; import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingOptions; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi; import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; @@ -53,11 +47,7 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) { var mcs = MemberCategory.values(); for (var tr : findJsonAnnotatedClassesInPackage(AbstractBedrockApi.class)) hints.reflection().registerType(tr, mcs); - for (var tr : findJsonAnnotatedClassesInPackage(Ai21Jurassic2ChatBedrockApi.class)) - hints.reflection().registerType(tr, mcs); - for (var tr : findJsonAnnotatedClassesInPackage(CohereChatBedrockApi.class)) - hints.reflection().registerType(tr, mcs); for (var tr : findJsonAnnotatedClassesInPackage(BedrockCohereChatOptions.class)) hints.reflection().registerType(tr, mcs); for (var tr : findJsonAnnotatedClassesInPackage(CohereEmbeddingBedrockApi.class)) @@ -65,13 +55,9 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) { for (var tr : findJsonAnnotatedClassesInPackage(BedrockCohereEmbeddingOptions.class)) hints.reflection().registerType(tr, mcs); - for (var tr : findJsonAnnotatedClassesInPackage(LlamaChatBedrockApi.class)) - hints.reflection().registerType(tr, mcs); for (var tr : findJsonAnnotatedClassesInPackage(BedrockLlamaChatOptions.class)) hints.reflection().registerType(tr, mcs); - for (var tr : findJsonAnnotatedClassesInPackage(TitanChatBedrockApi.class)) - hints.reflection().registerType(tr, mcs); for (var tr : findJsonAnnotatedClassesInPackage(BedrockTitanChatOptions.class)) hints.reflection().registerType(tr, mcs); for (var tr : findJsonAnnotatedClassesInPackage(BedrockTitanEmbeddingOptions.class)) @@ -79,13 +65,9 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) { for (var tr : findJsonAnnotatedClassesInPackage(TitanEmbeddingBedrockApi.class)) hints.reflection().registerType(tr, mcs); - for (var tr : findJsonAnnotatedClassesInPackage(AnthropicChatBedrockApi.class)) - hints.reflection().registerType(tr, mcs); for (var tr : findJsonAnnotatedClassesInPackage(AnthropicChatOptions.class)) hints.reflection().registerType(tr, mcs); - for (var tr : findJsonAnnotatedClassesInPackage(Anthropic3ChatBedrockApi.class)) - hints.reflection().registerType(tr, mcs); for (var tr : findJsonAnnotatedClassesInPackage(Anthropic3ChatOptions.class)) hints.reflection().registerType(tr, mcs); } diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java deleted file mode 100644 index 03904cdcca..0000000000 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright 2023 - 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. - */ -// @formatter:off -package org.springframework.ai.bedrock.cohere.api; - -import java.time.Duration; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import reactor.core.publisher.Flux; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.api.AbstractBedrockApi; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatResponse; -import org.springframework.ai.model.ModelDescription; -import org.springframework.util.Assert; - -/** - * Java client for the Bedrock Cohere chat model. - * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere.html - * - * @deprecated Use {@link BedrockConverseApi} instead. - * - * @author Christian Tzolov - * @author Wei Jiang - * @since 0.8.0 - */ -@Deprecated -public class CohereChatBedrockApi extends - AbstractBedrockApi { - - /** - * Create a new CohereChatBedrockApi instance using the default credentials provider chain, the default object - * mapper, default temperature and topP values. - * - * @param modelId The model id to use. See the {@link CohereChatModel} for the supported models. - * @param region The AWS region to use. - */ - public CohereChatBedrockApi(String modelId, String region) { - super(modelId, region); - } - - /** - * Create a new CohereChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link CohereChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - */ - public CohereChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper) { - super(modelId, credentialsProvider, region, objectMapper); - } - - /** - * Create a new CohereChatBedrockApi instance using the default credentials provider chain, the default object - * mapper, default temperature and topP values. - * - * @param modelId The model id to use. See the {@link CohereChatModel} for the supported models. - * @param region The AWS region to use. - * @param timeout The timeout to use. - */ - public CohereChatBedrockApi(String modelId, String region, Duration timeout) { - super(modelId, region, timeout); - } - - /** - * Create a new CohereChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link CohereChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public CohereChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * Create a new CohereChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link CohereChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public CohereChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, Region region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * CohereChatRequest encapsulates the request parameters for the Cohere command model. - * - * @param prompt The input prompt to generate the response from. - * @param temperature (optional) Use a lower value to decrease randomness in the response. - * @param topP (optional) Use a lower value to ignore less probable options. Set to 0 or 1.0 to disable. - * @param topK (optional) Specify the number of token choices the model uses to generate the next token. - * @param maxTokens (optional) Specify the maximum number of tokens to use in the generated response. - * @param stopSequences (optional) Configure up to four sequences that the model recognizes. After a stop sequence, - * the model stops generating further tokens. The returned text doesn't contain the stop sequence. - * @param returnLikelihoods (optional) Specify how and if the token likelihoods are returned with the response. - * @param stream (optional) Specify true to return the response piece-by-piece in real-time and false to return the - * complete response after the process finishes. - * @param numGenerations (optional) The maximum number of generations that the model should return. - * @param logitBias (optional) prevents the model from generating unwanted tokens or incentivize the model to - * include desired tokens. The format is {token_id: bias} where bias is a float between -10 and 10. Tokens can be - * obtained from text using any tokenization service, such as Cohere’s Tokenize endpoint. - * @param truncate (optional) Specifies how the API handles inputs longer than the maximum token length. - */ - @JsonInclude(Include.NON_NULL) - public record CohereChatRequest( - @JsonProperty("prompt") String prompt, - @JsonProperty("temperature") Float temperature, - @JsonProperty("p") Float topP, - @JsonProperty("k") Integer topK, - @JsonProperty("max_tokens") Integer maxTokens, - @JsonProperty("stop_sequences") List stopSequences, - @JsonProperty("return_likelihoods") ReturnLikelihoods returnLikelihoods, - @JsonProperty("stream") boolean stream, - @JsonProperty("num_generations") Integer numGenerations, - @JsonProperty("logit_bias") LogitBias logitBias, - @JsonProperty("truncate") Truncate truncate) { - - /** - * Prevents the model from generating unwanted tokens or incentivize the model to include desired tokens. - * - * @param token The token likelihoods. - * @param bias A float between -10 and 10. - */ - @JsonInclude(Include.NON_NULL) - public record LogitBias( - @JsonProperty("token") String token, - @JsonProperty("bias") Float bias) { - } - - /** - * (optional) Specify how and if the token likelihoods are returned with the response. - */ - public enum ReturnLikelihoods { - /** - * Only return likelihoods for generated tokens. - */ - GENERATION, - /** - * Return likelihoods for all tokens. - */ - ALL, - /** - * (Default) Don't return any likelihoods. - */ - NONE - } - - /** - * Specifies how the API handles inputs longer than the maximum token length. If you specify START or END, the - * model discards the input until the remaining input is exactly the maximum input token length for the model. - */ - public enum Truncate { - /** - * Returns an error when the input exceeds the maximum input token length. - */ - NONE, - /** - * Discard the start of the input. - */ - START, - /** - * (Default) Discards the end of the input. - */ - END - } - - /** - * Get CohereChatRequest builder. - * @param prompt compulsory request prompt parameter. - * @return CohereChatRequest builder. - */ - public static Builder builder(String prompt) { - return new Builder(prompt); - } - - /** - * Builder for the CohereChatRequest. - */ - public static class Builder { - private final String prompt; - private Float temperature; - private Float topP; - private Integer topK; - private Integer maxTokens; - private List stopSequences; - private ReturnLikelihoods returnLikelihoods; - private boolean stream; - private Integer numGenerations; - private LogitBias logitBias; - private Truncate truncate; - - public Builder(String prompt) { - this.prompt = prompt; - } - - public Builder withTemperature(Float temperature) { - this.temperature = temperature; - return this; - } - - public Builder withTopP(Float topP) { - this.topP = topP; - return this; - } - - public Builder withTopK(Integer topK) { - this.topK = topK; - return this; - } - - public Builder withMaxTokens(Integer maxTokens) { - this.maxTokens = maxTokens; - return this; - } - - public Builder withStopSequences(List stopSequences) { - this.stopSequences = stopSequences; - return this; - } - - public Builder withReturnLikelihoods(ReturnLikelihoods returnLikelihoods) { - this.returnLikelihoods = returnLikelihoods; - return this; - } - - public Builder withStream(boolean stream) { - this.stream = stream; - return this; - } - - public Builder withNumGenerations(Integer numGenerations) { - this.numGenerations = numGenerations; - return this; - } - - public Builder withLogitBias(LogitBias logitBias) { - this.logitBias = logitBias; - return this; - } - - public Builder withTruncate(Truncate truncate) { - this.truncate = truncate; - return this; - } - - public CohereChatRequest build() { - return new CohereChatRequest( - prompt, - temperature, - topP, - topK, - maxTokens, - stopSequences, - returnLikelihoods, - stream, - numGenerations, - logitBias, - truncate - ); - } - } - } - - /** - * CohereChatResponse encapsulates the response parameters for the Cohere command model. - * - * @param id An identifier for the request (always returned). - * @param prompt The prompt from the input request. (Always returned). - * @param generations A list of generated results along with the likelihoods for tokens requested. (Always - * returned). - */ - @JsonInclude(Include.NON_NULL) - public record CohereChatResponse( - @JsonProperty("id") String id, - @JsonProperty("prompt") String prompt, - @JsonProperty("generations") List generations) { - - /** - * Generated result along with the likelihoods for tokens requested. - * - * @param id An identifier for the generation. (Always returned). - * @param likelihood The likelihood of the output. The value is the average of the token likelihoods in - * token_likelihoods. Returned if you specify the return_likelihoods input parameter. - * @param tokenLikelihoods An array of per token likelihoods. Returned if you specify the return_likelihoods - * input parameter. - * @param finishReason states the reason why the model finished generating tokens. - * @param isFinished A boolean field used only when stream is true, signifying whether or not there are - * additional tokens that will be generated as part of the streaming response. (Not always returned). - * @param text The generated text. - * @param index In a streaming response, use to determine which generation a given token belongs to. When only - * one response is streamed, all tokens belong to the same generation and index is not returned. index therefore - * is only returned in a streaming request with a value for num_generations that is larger than one. - * @param amazonBedrockInvocationMetrics Encapsulates the metrics about the model invocation. - */ - @JsonInclude(Include.NON_NULL) - public record Generation( - @JsonProperty("id") String id, - @JsonProperty("likelihood") Float likelihood, - @JsonProperty("token_likelihoods") List tokenLikelihoods, - @JsonProperty("finish_reason") FinishReason finishReason, - @JsonProperty("is_finished") Boolean isFinished, - @JsonProperty("text") String text, - @JsonProperty("index") Integer index, - @JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) { - - /** - * @param token The token. - * @param likelihood The likelihood of the token. - */ - @JsonInclude(Include.NON_NULL) - public record TokenLikelihood( - @JsonProperty("token") String token, - @JsonProperty("likelihood") Float likelihood) { - } - - /** - * The reason the response finished being generated. - */ - public enum FinishReason { - /** - * The model sent back a finished reply. - */ - COMPLETE, - /** - * The reply was cut off because the model reached the maximum number of tokens for its context length. - */ - MAX_TOKENS, - /** - * Something went wrong when generating the reply. - */ - ERROR, - /** - * the model generated a reply that was deemed toxic. finish_reason is returned only when - * is_finished=true. (Not always returned). - */ - ERROR_TOXIC - } - } - } - - /** - * Cohere models version. - */ - public enum CohereChatModel implements ModelDescription { - - /** - * cohere.command-light-text-v14 - */ - COHERE_COMMAND_LIGHT_V14("cohere.command-light-text-v14"), - - /** - * cohere.command-text-v14 - */ - COHERE_COMMAND_V14("cohere.command-text-v14"); - - private final String id; - - /** - * @return The model id. - */ - public String id() { - return id; - } - - CohereChatModel(String value) { - this.id = value; - } - - @Override - public String getModelName() { - return this.id; - } - } - - @Override - public CohereChatResponse chatCompletion(CohereChatRequest request) { - Assert.isTrue(!request.stream(), "The request must be configured to return the complete response!"); - return this.internalInvocation(request, CohereChatResponse.class); - } - - @Override - public Flux chatCompletionStream(CohereChatRequest request) { - Assert.isTrue(request.stream(), "The request must be configured to stream the response!"); - return this.internalInvocationStream(request, CohereChatResponse.Generation.class); - } -} -// @formatter:on \ No newline at end of file diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java deleted file mode 100644 index 0c7cb2bf24..0000000000 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright 2023 - 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. - */ -// @formatter:off -package org.springframework.ai.bedrock.jurassic2.api; - -import java.time.Duration; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.springframework.ai.bedrock.api.AbstractBedrockApi; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatRequest; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatResponse; -import org.springframework.ai.model.ModelDescription; - -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -/** - * Java client for the Bedrock Jurassic2 chat model. - * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jurassic2.html - * - * @deprecated Use {@link BedrockConverseApi} instead. - * - * @author Christian Tzolov - * @author Wei Jiang - * @since 0.8.0 - */ -@Deprecated -public class Ai21Jurassic2ChatBedrockApi extends - AbstractBedrockApi { - - /** - * Create a new Ai21Jurassic2ChatBedrockApi instance using the default credentials provider chain, the default - * object mapper, default temperature and topP values. - * - * @param modelId The model id to use. See the {@link Ai21Jurassic2ChatModel} for the supported models. - * @param region The AWS region to use. - */ - public Ai21Jurassic2ChatBedrockApi(String modelId, String region) { - super(modelId, region); - } - - - /** - * Create a new Ai21Jurassic2ChatBedrockApi instance. - * - * @param modelId The model id to use. See the {@link Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - */ - public Ai21Jurassic2ChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper) { - super(modelId, credentialsProvider, region, objectMapper); - } - - /** - * Create a new Ai21Jurassic2ChatBedrockApi instance using the default credentials provider chain, the default - * object mapper, default temperature and topP values. - * - * @param modelId The model id to use. See the {@link Ai21Jurassic2ChatModel} for the supported models. - * @param region The AWS region to use. - * @param timeout The timeout to use. - */ - public Ai21Jurassic2ChatBedrockApi(String modelId, String region, Duration timeout) { - super(modelId, region, timeout); - } - - - /** - * Create a new Ai21Jurassic2ChatBedrockApi instance. - * - * @param modelId The model id to use. See the {@link Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public Ai21Jurassic2ChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * Create a new Ai21Jurassic2ChatBedrockApi instance. - * - * @param modelId The model id to use. See the {@link Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public Ai21Jurassic2ChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, Region region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * AI21 Jurassic2 chat request parameters. - * - * @param prompt The prompt to use for the chat. - * @param temperature The temperature value controls the randomness of the generated text. - * @param topP The topP value controls the diversity of the generated text. Use a lower value to ignore less - * probable options. - * @param maxTokens Specify the maximum number of tokens to use in the generated response. - * @param stopSequences Configure stop sequences that the model recognizes and after which it stops generating - * further tokens. Press the Enter key to insert a newline character in a stop sequence. Use the Tab key to finish - * inserting a stop sequence. - * @param countPenalty Control repetition in the generated response. Use a higher value to lower the probability of - * generating new tokens that already appear at least once in the prompt or in the completion. Proportional to the - * number of appearances. - * @param presencePenalty Control repetition in the generated response. Use a higher value to lower the probability - * of generating new tokens that already appear at least once in the prompt or in the completion. - * @param frequencyPenalty Control repetition in the generated response. Use a high value to lower the probability - * of generating new tokens that already appear at least once in the prompt or in the completion. The value is - * proportional to the frequency of the token appearances (normalized to text length). - */ - @JsonInclude(Include.NON_NULL) - public record Ai21Jurassic2ChatRequest( - @JsonProperty("prompt") String prompt, - @JsonProperty("temperature") Float temperature, - @JsonProperty("topP") Float topP, - @JsonProperty("maxTokens") Integer maxTokens, - @JsonProperty("stopSequences") List stopSequences, - @JsonProperty("countPenalty") IntegerScalePenalty countPenalty, - @JsonProperty("presencePenalty") FloatScalePenalty presencePenalty, - @JsonProperty("frequencyPenalty") IntegerScalePenalty frequencyPenalty) { - - /** - * Penalty with integer scale value. - * - * @param scale The scale value controls the strength of the penalty. Use a higher value to lower the - * probability of generating new tokens that already appear at least once in the prompt or in the completion. - * @param applyToWhitespaces Reduce the probability of repetition of special characters. A true value applies - * the penalty to whitespaces and new lines. - * @param applyToPunctuations Reduce the probability of repetition of special characters. A true value applies - * the penalty to punctuations. - * @param applyToNumbers Reduce the probability of repetition of special characters. A true value applies the - * penalty to numbers. - * @param applyToStopwords Reduce the probability of repetition of special characters. A true value applies the - * penalty to stopwords. - * @param applyToEmojis Reduce the probability of repetition of special characters. A true value applies the - * penalty to emojis. - */ - @JsonInclude(Include.NON_NULL) - public record IntegerScalePenalty( - @JsonProperty("scale") Integer scale, - @JsonProperty("applyToWhitespaces") boolean applyToWhitespaces, - @JsonProperty("applyToPunctuations") boolean applyToPunctuations, - @JsonProperty("applyToNumbers") boolean applyToNumbers, - @JsonProperty("applyToStopwords") boolean applyToStopwords, - @JsonProperty("applyToEmojis") boolean applyToEmojis) { - } - - /** - * Penalty with float scale value. - * - * @param scale The scale value controls the strength of the penalty. Use a higher value to lower the - * probability of generating new tokens that already appear at least once in the prompt or in the completion. - * @param applyToWhitespaces Reduce the probability of repetition of special characters. A true value applies - * the penalty to whitespaces and new lines. - * @param applyToPunctuations Reduce the probability of repetition of special characters. A true value applies - * the penalty to punctuations. - * @param applyToNumbers Reduce the probability of repetition of special characters. A true value applies the - * penalty to numbers. - * @param applyToStopwords Reduce the probability of repetition of special characters. A true value applies the - * penalty to stopwords. - * @param applyToEmojis Reduce the probability of repetition of special characters. A true value applies the - * penalty to emojis. - */ - @JsonInclude(Include.NON_NULL) - public record FloatScalePenalty(@JsonProperty("scale") Float scale, - @JsonProperty("applyToWhitespaces") boolean applyToWhitespaces, - @JsonProperty("applyToPunctuations") boolean applyToPunctuations, - @JsonProperty("applyToNumbers") boolean applyToNumbers, - @JsonProperty("applyToStopwords") boolean applyToStopwords, - @JsonProperty("applyToEmojis") boolean applyToEmojis) { - } - - - - public static Builder builder(String prompt) { - return new Builder(prompt); - } - public static class Builder { - private String prompt; - private Float temperature; - private Float topP; - private Integer maxTokens; - private List stopSequences; - private IntegerScalePenalty countPenalty; - private FloatScalePenalty presencePenalty; - private IntegerScalePenalty frequencyPenalty; - - public Builder(String prompt) { - this.prompt = prompt; - } - - public Builder withTemperature(Float temperature) { - this.temperature = temperature; - return this; - } - - public Builder withTopP(Float topP) { - this.topP = topP; - return this; - } - - public Builder withMaxTokens(Integer maxTokens) { - this.maxTokens = maxTokens; - return this; - } - - public Builder withStopSequences(List stopSequences) { - this.stopSequences = stopSequences; - return this; - } - - public Builder withCountPenalty(IntegerScalePenalty countPenalty) { - this.countPenalty = countPenalty; - return this; - } - - public Builder withPresencePenalty(FloatScalePenalty presencePenalty) { - this.presencePenalty = presencePenalty; - return this; - } - - public Builder withFrequencyPenalty(IntegerScalePenalty frequencyPenalty) { - this.frequencyPenalty = frequencyPenalty; - return this; - } - - public Ai21Jurassic2ChatRequest build() { - return new Ai21Jurassic2ChatRequest( - prompt, - temperature, - topP, - maxTokens, - stopSequences, - countPenalty, - presencePenalty, - frequencyPenalty - ); - } - } - } - - /** - * Ai21 Jurassic2 chat response. - * https://docs.ai21.com/reference/j2-complete-api-ref#response - * - * @param id The unique identifier of the response. - * @param prompt The prompt used for the chat. - * @param amazonBedrockInvocationMetrics The metrics about the model invocation. - */ - @JsonInclude(Include.NON_NULL) - public record Ai21Jurassic2ChatResponse( - @JsonProperty("id") String id, - @JsonProperty("prompt") Prompt prompt, - @JsonProperty("completions") List completions, - @JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) { - - /** - */ - @JsonInclude(Include.NON_NULL) - public record Completion( - @JsonProperty("data") Prompt data, - @JsonProperty("finishReason") FinishReason finishReason) { - } - - /** - * Provides detailed information about each token in both the prompt and the completions. - * - * @param generatedToken The generatedToken fields. - * @param topTokens The topTokens field is a list of the top K alternative tokens for this position, sorted by - * probability, according to the topKReturn request parameter. If topKReturn is set to 0, this field will be - * null. - * @param textRange The textRange field indicates the start and end offsets of the token in the decoded text - * string. - */ - @JsonInclude(Include.NON_NULL) - public record Token( - @JsonProperty("generatedToken") GeneratedToken generatedToken, - @JsonProperty("topTokens") List topTokens, - @JsonProperty("textRange") TextRange textRange) { - } - - /** - * The generatedToken fields. - * - * @param token TThe string representation of the token. - * @param logprob The predicted log probability of the token after applying the sampling parameters as a float - * value. - * @param rawLogprob The raw predicted log probability of the token as a float value. For the indifferent values - * (namely, temperature=1, topP=1) we get raw_logprob=logprob. - */ - @JsonInclude(Include.NON_NULL) - public record GeneratedToken( - @JsonProperty("token") String token, - @JsonProperty("logprob") Float logprob, - @JsonProperty("raw_logprob") Float rawLogprob) { - - } - - /** - * The topTokens field is a list of the top K alternative tokens for this position, sorted by probability, - * according to the topKReturn request parameter. If topKReturn is set to 0, this field will be null. - * - * @param token The string representation of the alternative token. - * @param logprob The predicted log probability of the alternative token. - */ - @JsonInclude(Include.NON_NULL) - public record TopToken( - @JsonProperty("token") String token, - @JsonProperty("logprob") Float logprob) { - } - - /** - * The textRange field indicates the start and end offsets of the token in the decoded text string. - * - * @param start The starting index of the token in the decoded text string. - * @param end The ending index of the token in the decoded text string. - */ - @JsonInclude(Include.NON_NULL) - public record TextRange( - @JsonProperty("start") Integer start, - @JsonProperty("end") Integer end) { - } - - /** - * The prompt includes the raw text, the tokens with their log probabilities, and the top-K alternative tokens - * at each position, if requested. - * - * @param text The raw text of the prompt. - * @param tokens Provides detailed information about each token in both the prompt and the completions. - */ - @JsonInclude(Include.NON_NULL) - public record Prompt( - @JsonProperty("text") String text, - @JsonProperty("tokens") List tokens) { - } - - /** - * Explains why the generation process was halted for a specific completion. - * - * @param reason The reason field indicates the reason for the completion to stop. - * - */ - @JsonInclude(Include.NON_NULL) - public record FinishReason( - @JsonProperty("reason") String reason, - @JsonProperty("length") String length, - @JsonProperty("sequence") String sequence) { - } - } - - /** - * Ai21 Jurassic2 models version. - */ - public enum Ai21Jurassic2ChatModel implements ModelDescription { - - /** - * ai21.j2-mid-v1 - */ - AI21_J2_MID_V1("ai21.j2-mid-v1"), - - /** - * ai21.j2-ultra-v1 - */ - AI21_J2_ULTRA_V1("ai21.j2-ultra-v1"); - - private final String id; - - /** - * @return The model id. - */ - public String id() { - return id; - } - - Ai21Jurassic2ChatModel(String value) { - this.id = value; - } - - @Override - public String getModelName() { - return this.id; - } - } - - @Override - public Ai21Jurassic2ChatResponse chatCompletion(Ai21Jurassic2ChatRequest request) { - return this.internalInvocation(request, Ai21Jurassic2ChatResponse.class); - } - - -} -// @formatter:on \ No newline at end of file diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java deleted file mode 100644 index 6e52e42c29..0000000000 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.llama.api; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import reactor.core.publisher.Flux; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.api.AbstractBedrockApi; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatRequest; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatResponse; -import org.springframework.ai.model.ModelDescription; - -import java.time.Duration; - -// @formatter:off -/** - * Java client for the Bedrock Llama chat model. - * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html - * - * @deprecated Use {@link BedrockConverseApi} instead. - * - * @author Christian Tzolov - * @author Wei Jiang - * @since 0.8.0 - */ -@Deprecated -public class LlamaChatBedrockApi extends - AbstractBedrockApi { - - /** - * Create a new LlamaChatBedrockApi instance using the default credentials provider chain, the default object - * mapper, default temperature and topP values. - * - * @param modelId The model id to use. See the {@link LlamaChatModel} for the supported models. - * @param region The AWS region to use. - */ - public LlamaChatBedrockApi(String modelId, String region) { - super(modelId, region); - } - - /** - * Create a new LlamaChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link LlamaChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - */ - public LlamaChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper) { - super(modelId, credentialsProvider, region, objectMapper); - } - - /** - * Create a new LlamaChatBedrockApi instance using the default credentials provider chain, the default object - * mapper, default temperature and topP values. - * - * @param modelId The model id to use. See the {@link LlamaChatModel} for the supported models. - * @param region The AWS region to use. - * @param timeout The timeout to use. - */ - public LlamaChatBedrockApi(String modelId, String region, Duration timeout) { - super(modelId, region, timeout); - } - - /** - * Create a new LlamaChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link LlamaChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public LlamaChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * Create a new LlamaChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link LlamaChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public LlamaChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, Region region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * LlamaChatRequest encapsulates the request parameters for the Meta Llama chat model. - * - * @param prompt The prompt to use for the chat. - * @param temperature The temperature value controls the randomness of the generated text. Use a lower value to - * decrease randomness in the response. - * @param topP The topP value controls the diversity of the generated text. Use a lower value to ignore less - * probable options. Set to 0 or 1.0 to disable. - * @param maxGenLen The maximum length of the generated text. - */ - @JsonInclude(Include.NON_NULL) - public record LlamaChatRequest( - @JsonProperty("prompt") String prompt, - @JsonProperty("temperature") Float temperature, - @JsonProperty("top_p") Float topP, - @JsonProperty("max_gen_len") Integer maxGenLen) { - - /** - * Create a new LlamaChatRequest builder. - * @param prompt compulsory prompt parameter. - * @return a new LlamaChatRequest builder. - */ - public static Builder builder(String prompt) { - return new Builder(prompt); - } - - public static class Builder { - private String prompt; - private Float temperature; - private Float topP; - private Integer maxGenLen; - - public Builder(String prompt) { - this.prompt = prompt; - } - - public Builder withTemperature(Float temperature) { - this.temperature = temperature; - return this; - } - - public Builder withTopP(Float topP) { - this.topP = topP; - return this; - } - - public Builder withMaxGenLen(Integer maxGenLen) { - this.maxGenLen = maxGenLen; - return this; - } - - public LlamaChatRequest build() { - return new LlamaChatRequest( - prompt, - temperature, - topP, - maxGenLen - ); - } - } - } - - /** - * LlamaChatResponse encapsulates the response parameters for the Meta Llama chat model. - * - * @param generation The generated text. - * @param promptTokenCount The number of tokens in the prompt. - * @param generationTokenCount The number of tokens in the response. - * @param stopReason The reason why the response stopped generating text. Possible values are: (1) stop – The model - * has finished generating text for the input prompt. (2) length – The length of the tokens for the generated text - * exceeds the value of max_gen_len in the call. The response is truncated to max_gen_len tokens. Consider - * increasing the value of max_gen_len and trying again. - */ - @JsonInclude(Include.NON_NULL) - public record LlamaChatResponse( - @JsonProperty("generation") String generation, - @JsonProperty("prompt_token_count") Integer promptTokenCount, - @JsonProperty("generation_token_count") Integer generationTokenCount, - @JsonProperty("stop_reason") StopReason stopReason, - @JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) { - - /** - * The reason the response finished being generated. - */ - public enum StopReason { - /** - * The model has finished generating text for the input prompt. - */ - @JsonProperty("stop") STOP, - /** - * The response was truncated because of the response length you set. - */ - @JsonProperty("length") LENGTH - } - } - - /** - * Llama models version. - */ - public enum LlamaChatModel implements ModelDescription { - - /** - * meta.llama2-13b-chat-v1 - */ - LLAMA2_13B_CHAT_V1("meta.llama2-13b-chat-v1"), - - /** - * meta.llama2-70b-chat-v1 - */ - LLAMA2_70B_CHAT_V1("meta.llama2-70b-chat-v1"), - - /** - * meta.llama3-8b-instruct-v1:0 - */ - LLAMA3_8B_INSTRUCT_V1("meta.llama3-8b-instruct-v1:0"), - - /** - * meta.llama3-70b-instruct-v1:0 - */ - LLAMA3_70B_INSTRUCT_V1("meta.llama3-70b-instruct-v1:0"); - - private final String id; - - /** - * @return The model id. - */ - public String id() { - return id; - } - - LlamaChatModel(String value) { - this.id = value; - } - - @Override - public String getModelName() { - return this.id; - } - } - - @Override - public LlamaChatResponse chatCompletion(LlamaChatRequest request) { - return this.internalInvocation(request, LlamaChatResponse.class); - } - - @Override - public Flux chatCompletionStream(LlamaChatRequest request) { - return this.internalInvocationStream(request, LlamaChatResponse.class); - } -} -// @formatter:on \ No newline at end of file diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java deleted file mode 100644 index c7b954c34f..0000000000 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.titan.api; - -import java.time.Duration; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import reactor.core.publisher.Flux; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.api.AbstractBedrockApi; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatRequest; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponse; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponse.CompletionReason; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponseChunk; -import org.springframework.ai.model.ModelDescription; - -/** - * Java client for the Bedrock Titan chat model. - * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html - *

- * https://docs.aws.amazon.com/bedrock/latest/userguide/titan-text-models.html - * - * @deprecated Use {@link BedrockConverseApi} instead. - * @author Christian Tzolov - * @author Wei Jiang - * @since 0.8.0 - */ -@Deprecated -// @formatter:off -public class TitanChatBedrockApi extends - AbstractBedrockApi { - - /** - * Create a new TitanChatBedrockApi instance using the default credentials provider chain, the default object mapper. - * - * @param modelId The model id to use. See the {@link TitanChatModel} for the supported models. - * @param region The AWS region to use. - */ - public TitanChatBedrockApi(String modelId, String region) { - super(modelId, region); - } - - /** - * Create a new TitanChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link TitanChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - */ - public TitanChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper) { - super(modelId, credentialsProvider, region, objectMapper); - } - - /** - * Create a new TitanChatBedrockApi instance using the default credentials provider chain, the default object mapper. - * - * @param modelId The model id to use. See the {@link TitanChatModel} for the supported models. - * @param region The AWS region to use. - * @param timeout The timeout to use. - */ - public TitanChatBedrockApi(String modelId, String region, Duration timeout) { - super(modelId, region, timeout); - } - - /** - * Create a new TitanChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link TitanChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public TitanChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, String region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * Create a new TitanChatBedrockApi instance using the provided credentials provider, region and object mapper. - * - * @param modelId The model id to use. See the {@link TitanChatModel} for the supported models. - * @param credentialsProvider The credentials provider to connect to AWS. - * @param region The AWS region to use. - * @param objectMapper The object mapper to use for JSON serialization and deserialization. - * @param timeout The timeout to use. - */ - public TitanChatBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, Region region, - ObjectMapper objectMapper, Duration timeout) { - super(modelId, credentialsProvider, region, objectMapper, timeout); - } - - /** - * TitanChatRequest encapsulates the request parameters for the Titan chat model. - * - * @param inputText The prompt to use for the chat. - * @param textGenerationConfig The text generation configuration. - */ - @JsonInclude(Include.NON_NULL) - public record TitanChatRequest( - @JsonProperty("inputText") String inputText, - @JsonProperty("textGenerationConfig") TextGenerationConfig textGenerationConfig) { - - /** - * Titan request text generation configuration. - * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html - * - * @param temperature The temperature value controls the randomness of the generated text. - * @param topP The topP value controls the diversity of the generated text. Use a lower value to ignore less - * probable options. - * @param maxTokenCount The maximum number of tokens to generate. - * @param stopSequences A list of sequences to stop the generation at. Specify character sequences to indicate - * where the model should stop. Use the | (pipe) character to separate different sequences (maximum 20 - * characters). - */ - @JsonInclude(Include.NON_NULL) - public record TextGenerationConfig( - @JsonProperty("temperature") Float temperature, - @JsonProperty("topP") Float topP, - @JsonProperty("maxTokenCount") Integer maxTokenCount, - @JsonProperty("stopSequences") List stopSequences) { - } - - /** - * Create a new TitanChatRequest builder. - * @param inputText The prompt to use for the chat. - * @return A new TitanChatRequest builder. - */ - public static Builder builder(String inputText) { - return new Builder(inputText); - } - - public static class Builder { - private final String inputText; - private Float temperature; - private Float topP; - private Integer maxTokenCount; - private List stopSequences; - - public Builder(String inputText) { - this.inputText = inputText; - } - - public Builder withTemperature(Float temperature) { - this.temperature = temperature; - return this; - } - - public Builder withTopP(Float topP) { - this.topP = topP; - return this; - } - - public Builder withMaxTokenCount(Integer maxTokenCount) { - this.maxTokenCount = maxTokenCount; - return this; - } - - public Builder withStopSequences(List stopSequences) { - this.stopSequences = stopSequences; - return this; - } - - public TitanChatRequest build() { - - if (this.temperature == null && this.topP == null && this.maxTokenCount == null - && this.stopSequences == null) { - return new TitanChatRequest(this.inputText, null); - } else { - return new TitanChatRequest(this.inputText, - new TextGenerationConfig( - this.temperature, - this.topP, - this.maxTokenCount, - this.stopSequences - )); - } - } - } - } - - /** - * TitanChatResponse encapsulates the response parameters for the Titan chat model. - * - * @param inputTextTokenCount The number of tokens in the input text. - * @param results The list of generated responses. - */ - @JsonInclude(Include.NON_NULL) - public record TitanChatResponse( - @JsonProperty("inputTextTokenCount") Integer inputTextTokenCount, - @JsonProperty("results") List results) { - - /** - * Titan response result. - * - * @param tokenCount The number of tokens in the generated text. - * @param outputText The generated text. - * @param completionReason The reason the response finished being generated. - */ - @JsonInclude(Include.NON_NULL) - public record Result( - @JsonProperty("tokenCount") Integer tokenCount, - @JsonProperty("outputText") String outputText, - @JsonProperty("completionReason") CompletionReason completionReason) { - } - - /** - * The reason the response finished being generated. - */ - public enum CompletionReason { - /** - * The response was fully generated. - */ - FINISH, - - /** - * The response was truncated because of the response length you set. - */ - LENGTH, - - /** - * The response was truncated because of restrictions. - */ - CONTENT_FILTERED - } - } - - /** - * Titan chat model streaming response. - * - * @param outputText The generated text in this chunk. - * @param index The index of the chunk in the streaming response. - * @param inputTextTokenCount The number of tokens in the prompt. - * @param totalOutputTextTokenCount The number of tokens in the response. - * @param completionReason The reason the response finished being generated. - */ - @JsonInclude(Include.NON_NULL) - public record TitanChatResponseChunk( - @JsonProperty("outputText") String outputText, - @JsonProperty("index") Integer index, - @JsonProperty("inputTextTokenCount") Integer inputTextTokenCount, - @JsonProperty("totalOutputTextTokenCount") Integer totalOutputTextTokenCount, - @JsonProperty("completionReason") CompletionReason completionReason, - @JsonProperty("amazon-bedrock-invocationMetrics") AmazonBedrockInvocationMetrics amazonBedrockInvocationMetrics) { - } - - /** - * Titan models version. - */ - public enum TitanChatModel implements ModelDescription { - - /** - * amazon.titan-text-lite-v1 - */ - TITAN_TEXT_LITE_V1("amazon.titan-text-lite-v1"), - - /** - * amazon.titan-text-express-v1 - */ - TITAN_TEXT_EXPRESS_V1("amazon.titan-text-express-v1"), - - /** - * amazon.titan-text-premier-v1:0 - */ - TITAN_TEXT_PREMIER_V1("amazon.titan-text-premier-v1:0"); - - private final String id; - - /** - * @return The model id. - */ - public String id() { - return id; - } - - TitanChatModel(String value) { - this.id = value; - } - - @Override - public String getModelName() { - return this.id; - } - } - - @Override - public TitanChatResponse chatCompletion(TitanChatRequest request) { - return this.internalInvocation(request, TitanChatResponse.class); - } - - @Override - public Flux chatCompletionStream(TitanChatRequest request) { - return this.internalInvocationStream(request, TitanChatResponseChunk.class); - } -} -// @formatter:on diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApiIT.java deleted file mode 100644 index 35803fc6ba..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApiIT.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.anthropic.api; - -import java.time.Duration; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; -import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatRequest; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatResponse; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi.AnthropicChatModel; - -import static org.assertj.core.api.Assertions.assertThat;; - -/** - * @author Christian Tzolov - */ -@Deprecated -@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") -public class AnthropicChatBedrockApiIT { - - private final Logger logger = LoggerFactory.getLogger(AnthropicChatBedrockApiIT.class); - - private AnthropicChatBedrockApi anthropicChatApi = new AnthropicChatBedrockApi(AnthropicChatModel.CLAUDE_V2.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), - Duration.ofMinutes(2)); - - @Test - public void chatCompletion() { - - AnthropicChatRequest request = AnthropicChatRequest - .builder(String.format(AnthropicChatBedrockApi.PROMPT_TEMPLATE, "Name 3 famous pirates")) - .withTemperature(0.8f) - .withMaxTokensToSample(300) - .withTopK(10) - .build(); - - AnthropicChatResponse response = anthropicChatApi.chatCompletion(request); - - System.out.println(response.completion()); - assertThat(response).isNotNull(); - assertThat(response.completion()).isNotEmpty(); - assertThat(response.completion()).contains("Blackbeard"); - assertThat(response.stopReason()).isEqualTo("stop_sequence"); - assertThat(response.stop()).isEqualTo("\n\nHuman:"); - assertThat(response.amazonBedrockInvocationMetrics()).isNull(); - - logger.info("" + response); - } - - @Test - public void chatCompletionStream() { - - AnthropicChatRequest request = AnthropicChatRequest - .builder(String.format(AnthropicChatBedrockApi.PROMPT_TEMPLATE, "Name 3 famous pirates")) - .withTemperature(0.8f) - .withMaxTokensToSample(300) - .withTopK(10) - .withStopSequences(List.of("\n\nHuman:")) - .build(); - - Flux responseStream = anthropicChatApi.chatCompletionStream(request); - - List responses = responseStream.collectList().block(); - assertThat(responses).isNotNull(); - assertThat(responses).hasSizeGreaterThan(10); - assertThat(responses.stream().map(AnthropicChatResponse::completion).collect(Collectors.joining())) - .contains("Blackbeard"); - } - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApiIT.java deleted file mode 100644 index 33f03d15d2..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApiIT.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.anthropic3.api; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatModel; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatRequest; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatResponse; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.AnthropicChatStreamingResponse.StreamingType; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.MediaContent; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.ChatCompletionMessage; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.ChatCompletionMessage.Role; -import reactor.core.publisher.Flux; -import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import java.time.Duration; -import java.util.List; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi.DEFAULT_ANTHROPIC_VERSION; - -/** - * @author Ben Middleton - */ -@Deprecated -@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") -public class Anthropic3ChatBedrockApiIT { - - private final Logger logger = LoggerFactory.getLogger(Anthropic3ChatBedrockApiIT.class); - - private Anthropic3ChatBedrockApi anthropicChatApi = new Anthropic3ChatBedrockApi( - AnthropicChatModel.CLAUDE_INSTANT_V1.id(), EnvironmentVariableCredentialsProvider.create(), - Region.US_EAST_1.id(), new ObjectMapper(), Duration.ofMinutes(2)); - - @Test - public void chatCompletion() { - - MediaContent anthropicMessage = new MediaContent("Name 3 famous pirates"); - ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage(List.of(anthropicMessage), Role.USER); - AnthropicChatRequest request = AnthropicChatRequest.builder(List.of(chatCompletionMessage)) - .withTemperature(0.8f) - .withMaxTokens(300) - .withTopK(10) - .withAnthropicVersion(DEFAULT_ANTHROPIC_VERSION) - .build(); - - AnthropicChatResponse response = anthropicChatApi.chatCompletion(request); - - logger.info("" + response.content()); - - assertThat(response).isNotNull(); - assertThat(response.content().get(0).text()).isNotEmpty(); - assertThat(response.content().get(0).text()).contains("Blackbeard"); - assertThat(response.stopReason()).isEqualTo("end_turn"); - assertThat(response.stopSequence()).isNull(); - assertThat(response.usage().inputTokens()).isGreaterThan(10); - assertThat(response.usage().outputTokens()).isGreaterThan(100); - - logger.info("" + response); - } - - @Test - public void chatMultiCompletion() { - - MediaContent anthropicInitialMessage = new MediaContent("Name 3 famous pirates"); - ChatCompletionMessage chatCompletionInitialMessage = new ChatCompletionMessage(List.of(anthropicInitialMessage), - Role.USER); - - MediaContent anthropicAssistantMessage = new MediaContent( - "Here are 3 famous pirates: Blackbeard, Calico Jack, Henry Morgan"); - ChatCompletionMessage chatCompletionAssistantMessage = new ChatCompletionMessage( - List.of(anthropicAssistantMessage), Role.ASSISTANT); - - MediaContent anthropicFollowupMessage = new MediaContent("Why are they famous?"); - ChatCompletionMessage chatCompletionFollowupMessage = new ChatCompletionMessage( - List.of(anthropicFollowupMessage), Role.USER); - - AnthropicChatRequest request = AnthropicChatRequest - .builder(List.of(chatCompletionInitialMessage, chatCompletionAssistantMessage, - chatCompletionFollowupMessage)) - .withTemperature(0.8f) - .withMaxTokens(400) - .withTopK(10) - .withAnthropicVersion(DEFAULT_ANTHROPIC_VERSION) - .build(); - - AnthropicChatResponse response = anthropicChatApi.chatCompletion(request); - - logger.info("" + response.content()); - assertThat(response).isNotNull(); - assertThat(response.content().get(0).text()).isNotEmpty(); - assertThat(response.content().get(0).text()).contains("Blackbeard"); - assertThat(response.stopReason()).isEqualTo("end_turn"); - assertThat(response.stopSequence()).isNull(); - assertThat(response.usage().inputTokens()).isGreaterThan(30); - assertThat(response.usage().outputTokens()).isGreaterThan(200); - - logger.info("" + response); - } - - @Test - public void chatCompletionStream() { - MediaContent anthropicMessage = new MediaContent("Name 3 famous pirates"); - ChatCompletionMessage chatCompletionMessage = new ChatCompletionMessage(List.of(anthropicMessage), Role.USER); - - AnthropicChatRequest request = AnthropicChatRequest.builder(List.of(chatCompletionMessage)) - .withTemperature(0.8f) - .withMaxTokens(300) - .withTopK(10) - .withAnthropicVersion(DEFAULT_ANTHROPIC_VERSION) - .build(); - - Flux responseStream = anthropicChatApi - .chatCompletionStream(request); - - List responses = responseStream.collectList().block(); - assertThat(responses).isNotNull(); - assertThat(responses).hasSizeGreaterThan(10); - assertThat(responses.stream() - .filter(message -> message.type() == StreamingType.CONTENT_BLOCK_DELTA) - .map(message -> message.delta().text()) - .collect(Collectors.joining())).contains("Blackbeard"); - } - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/aot/BedrockRuntimeHintsTests.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/aot/BedrockRuntimeHintsTests.java index 92060ef5bf..60000b27f8 100644 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/aot/BedrockRuntimeHintsTests.java +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/aot/BedrockRuntimeHintsTests.java @@ -16,12 +16,7 @@ package org.springframework.ai.bedrock.aot; import org.junit.jupiter.api.Test; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi; import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi; import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeReference; @@ -42,9 +37,7 @@ void registerHints() { BedrockRuntimeHints bedrockRuntimeHints = new BedrockRuntimeHints(); bedrockRuntimeHints.registerHints(runtimeHints, null); - List classList = Arrays.asList(Ai21Jurassic2ChatBedrockApi.class, CohereChatBedrockApi.class, - CohereEmbeddingBedrockApi.class, LlamaChatBedrockApi.class, TitanChatBedrockApi.class, - TitanEmbeddingBedrockApi.class, AnthropicChatBedrockApi.class); + List classList = Arrays.asList(CohereEmbeddingBedrockApi.class, TitanEmbeddingBedrockApi.class); for (Class aClass : classList) { Set jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage(aClass); diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApiIT.java deleted file mode 100644 index dc903faeba..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApiIT.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.cohere.api; - -import java.time.Duration; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatModel; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatRequest.Truncate; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatResponse; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi.CohereChatResponse.Generation.FinishReason; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy;; - -/** - * @author Christian Tzolov - */ -@Deprecated -@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") -public class CohereChatBedrockApiIT { - - private CohereChatBedrockApi cohereChatApi = new CohereChatBedrockApi(CohereChatModel.COHERE_COMMAND_V14.id(), - Region.US_EAST_1.id(), Duration.ofMinutes(2)); - - @Test - public void requestBuilder() { - - CohereChatRequest request1 = new CohereChatRequest( - "What is the capital of Bulgaria and what is the size? What it the national anthem?", 0.5f, 0.9f, 15, - 40, List.of("END"), CohereChatRequest.ReturnLikelihoods.ALL, false, 1, null, Truncate.NONE); - - var request2 = CohereChatRequest - .builder("What is the capital of Bulgaria and what is the size? What it the national anthem?") - .withTemperature(0.5f) - .withTopP(0.9f) - .withTopK(15) - .withMaxTokens(40) - .withStopSequences(List.of("END")) - .withReturnLikelihoods(CohereChatRequest.ReturnLikelihoods.ALL) - .withStream(false) - .withNumGenerations(1) - .withLogitBias(null) - .withTruncate(Truncate.NONE) - .build(); - - assertThat(request1).isEqualTo(request2); - } - - @Test - public void chatCompletion() { - - var request = CohereChatRequest - .builder("What is the capital of Bulgaria and what is the size? What it the national anthem?") - .withStream(false) - .withTemperature(0.5f) - .withTopP(0.8f) - .withTopK(15) - .withMaxTokens(100) - .withStopSequences(List.of("END")) - .withReturnLikelihoods(CohereChatRequest.ReturnLikelihoods.ALL) - .withNumGenerations(3) - .withLogitBias(null) - .withTruncate(Truncate.NONE) - .build(); - - CohereChatResponse response = cohereChatApi.chatCompletion(request); - - assertThat(response).isNotNull(); - assertThat(response.prompt()).isEqualTo(request.prompt()); - assertThat(response.generations()).hasSize(request.numGenerations()); - assertThat(response.generations().get(0).text()).isNotEmpty(); - } - - @Test - public void chatCompletionStream() { - - var request = CohereChatRequest - .builder("What is the capital of Bulgaria and what is the size? What it the national anthem?") - .withStream(true) - .withTemperature(0.5f) - .withTopP(0.8f) - .withTopK(15) - .withMaxTokens(100) - .withStopSequences(List.of("END")) - .withReturnLikelihoods(CohereChatRequest.ReturnLikelihoods.ALL) - .withNumGenerations(3) - .withLogitBias(null) - .withTruncate(Truncate.NONE) - .build(); - - Flux responseStream = cohereChatApi.chatCompletionStream(request); - List responses = responseStream.collectList().block(); - - assertThat(responses).isNotNull(); - assertThat(responses).hasSizeGreaterThan(10); - assertThat(responses.get(0).text()).isNotEmpty(); - - CohereChatResponse.Generation lastResponse = responses.get(responses.size() - 1); - assertThat(lastResponse.text()).isNull(); - assertThat(lastResponse.isFinished()).isTrue(); - assertThat(lastResponse.finishReason()).isEqualTo(FinishReason.MAX_TOKENS); - assertThat(lastResponse.amazonBedrockInvocationMetrics()).isNotNull(); - } - - @Test - public void testStreamConfigurations() { - var streamRequest = CohereChatRequest - .builder("What is the capital of Bulgaria and what is the size? What it the national anthem?") - .withStream(true) - .build(); - - assertThatThrownBy(() -> cohereChatApi.chatCompletion(streamRequest)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("The request must be configured to return the complete response!"); - - var notStreamRequest = CohereChatRequest - .builder("What is the capital of Bulgaria and what is the size? What it the national anthem?") - .withStream(false) - .build(); - - assertThatThrownBy(() -> cohereChatApi.chatCompletionStream(notStreamRequest)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("The request must be configured to stream the response!"); - - } - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApiIT.java deleted file mode 100644 index b184528daf..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApiIT.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.jurassic2.api; - -import java.time.Duration; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatModel; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatRequest; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -@Deprecated -@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") -public class Ai21Jurassic2ChatBedrockApiIT { - - Ai21Jurassic2ChatBedrockApi api = new Ai21Jurassic2ChatBedrockApi(Ai21Jurassic2ChatModel.AI21_J2_ULTRA_V1.id(), - Region.US_EAST_1.id(), Duration.ofMinutes(2)); - - @Test - public void chatCompletion() { - Ai21Jurassic2ChatRequest request = new Ai21Jurassic2ChatRequest("Give me the names of 3 famous pirates?", 0.9f, - 0.9f, 100, null, // List.of("END"), - new Ai21Jurassic2ChatRequest.IntegerScalePenalty(1, true, true, true, true, true), - new Ai21Jurassic2ChatRequest.FloatScalePenalty(0.5f, true, true, true, true, true), - new Ai21Jurassic2ChatRequest.IntegerScalePenalty(1, true, true, true, true, true)); - - Ai21Jurassic2ChatResponse response = api.chatCompletion(request); - - assertThat(response).isNotNull(); - assertThat(response.completions()).isNotEmpty(); - assertThat(response.amazonBedrockInvocationMetrics()).isNull(); - - String responseContent = response.completions() - .stream() - .map(c -> c.data().text()) - .collect(Collectors.joining(System.lineSeparator())); - assertThat(responseContent).contains("Blackbeard"); - } - - // Note: Ai21Jurassic2 doesn't support streaming yet! - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApiIT.java deleted file mode 100644 index f1c6f07fcb..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApiIT.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.llama.api; - -import java.time.Duration; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatModel; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatRequest; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi.LlamaChatResponse; - -import com.fasterxml.jackson.databind.ObjectMapper; - -import reactor.core.publisher.Flux; -import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; -import software.amazon.awssdk.regions.Region; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - * @author Wei Jiang - */ -@Deprecated -@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") -public class LlamaChatBedrockApiIT { - - private LlamaChatBedrockApi llamaChatApi = new LlamaChatBedrockApi(LlamaChatModel.LLAMA3_70B_INSTRUCT_V1.id(), - EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper(), - Duration.ofMinutes(2)); - - @Test - public void chatCompletion() { - - LlamaChatRequest request = LlamaChatRequest.builder("Hello, my name is") - .withTemperature(0.9f) - .withTopP(0.9f) - .withMaxGenLen(20) - .build(); - - LlamaChatResponse response = llamaChatApi.chatCompletion(request); - - System.out.println(response.generation()); - assertThat(response).isNotNull(); - assertThat(response.generation()).isNotEmpty(); - assertThat(response.generationTokenCount()).isGreaterThan(10); - assertThat(response.generationTokenCount()).isLessThanOrEqualTo(20); - assertThat(response.stopReason()).isNotNull(); - assertThat(response.amazonBedrockInvocationMetrics()).isNull(); - } - - @Test - public void chatCompletionStream() { - - LlamaChatRequest request = new LlamaChatRequest("Hello, my name is", 0.9f, 0.9f, 20); - Flux responseStream = llamaChatApi.chatCompletionStream(request); - List responses = responseStream.collectList().block(); - - assertThat(responses).isNotNull(); - assertThat(responses).hasSizeGreaterThan(10); - assertThat(responses.get(0).generation()).isNotEmpty(); - - LlamaChatResponse lastResponse = responses.get(responses.size() - 1); - assertThat(lastResponse.stopReason()).isNotNull(); - assertThat(lastResponse.amazonBedrockInvocationMetrics()).isNotNull(); - } - -} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApiIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApiIT.java deleted file mode 100644 index d83969df2b..0000000000 --- a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApiIT.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2023 - 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.bedrock.titan.api; - -import java.time.Duration; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; -import software.amazon.awssdk.regions.Region; - -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatModel; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatRequest; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponse; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi.TitanChatResponseChunk; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Christian Tzolov - */ -@Deprecated -@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") -@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") -public class TitanChatBedrockApiIT { - - TitanChatBedrockApi titanBedrockApi = new TitanChatBedrockApi(TitanChatModel.TITAN_TEXT_EXPRESS_V1.id(), - Region.EU_CENTRAL_1.id(), Duration.ofMinutes(2)); - - TitanChatRequest titanChatRequest = TitanChatRequest.builder("Give me the names of 3 famous pirates?") - .withTemperature(0.5f) - .withTopP(0.9f) - .withMaxTokenCount(100) - .withStopSequences(List.of("|")) - .build(); - - @Test - public void chatCompletion() { - TitanChatResponse response = titanBedrockApi.chatCompletion(titanChatRequest); - assertThat(response.results()).hasSize(1); - assertThat(response.results().get(0).outputText()).contains("Blackbeard"); - } - - @Test - public void chatCompletionStream() { - Flux response = titanBedrockApi.chatCompletionStream(titanChatRequest); - List results = response.collectList().block(); - - assertThat(results.stream() - .map(TitanChatResponseChunk::outputText) - .collect(Collectors.joining(System.lineSeparator()))).contains("Blackbeard"); - } - -} diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-anthropic-chat-api.png b/spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-anthropic-chat-api.png deleted file mode 100644 index c1f83541ec6ef98c08aa2f533460693a4a213fce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173787 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

l4l2$j&lY`0+?y#ggl)fCY z`r?4R>iV8vX{|;7y3Lfmhf^kuy9fm01!h}=&7HZSjE1K7q)*X^m^Xw0`K|Ma03vtr z{h48b_B}Hq;CF}g3fk7l3@Z|Mi6MTQfCqrW!dyb5mnvUdJ9w>|3l`nwIl;%-cFONi z!b!yej%#?LC4dca;NT|2|D7Ywi0ox)sFOp-M|C9>*nRD^k@~THA91EgE!Pe7MJy(9 z*r&zUMb$xaWQtIlBAp`=tZ0VOh=&w6Cs(_S6yfiTksM*d#N#DcV2BjAWWd7bV#Bf< z5B>dE1LH^%VNwLqt3Zme#5>{00BiCJO&uI*jEqrzNf``Nx*T9-UR+0J%khxCbcu6q z>!e6RNf>*NQF&o|6*H=+P;L{-Wd0T{t`#cU|rj+UZe zvj$(^r&%whipm5;pq6W>4ZGs-b!LH*2TbTe-I%dS$O~qK^$J317D0vr1U0gm<5!jx zLm>xgCna(Fy`pCR43^e)x-TgYdE3JnDL4}9dvgY(hZr7P;{zn9#K<|aoe2eMZ zky;`0GjNa?gQ%s3jYc%6!&m5MAsFAv=WXu?rUT0 z4lNXT(c;M%IS$elQXF#7>r8SCpFbP4>;Dtzh;09ihEM(IREhkdEQ>;K^fZ4(Exj+``^R%;i)f$JNL(QHGTnWFpWL>Zz6vB;7As=J7=2RBwwP!FJO^GsB9ffy^1$Rw#2dl0Jpsz+HQjc`}){NZZK~( z=o~!Xg(%1jmwae(>teu8Db=!!rhb)T=3@fOTZ>um^e z5*gneCQeTjc8YLnKC@VU{{pZESY+Z(kn{uGsQIm6FtLGyaqBmxH~|Cad{sVZtCl>9 zoiyZ>AXP1%INz;GGXN1Q*su(RqS$P4ATf5%V)^k}?SBhLIO7n3?j9cScZ(Xk(Riqy z>oGT#KFd{^DZF04tzTw6wdzvR0`FCzR(~d~>N0UVt4R@bqlkQnTkuZ_=u=L^r@ANf z29hE|*`OCjPEiQR+#pbfn{DQ>8hU8rj0VQ6Asuq-m*QSTJ`%>I_8=HzjWt1{3sM0%ftP4$wBDRH$-R!mr#3RDj7cCPo(GScV@xzeIxAjK7p$Z;NR>V`-?c^7y5qKkEY}icO?ZCGDzDzJzs9{!%^7 zGn=q6%J5{;YfTZN*q6YTqJ|N++KIr`-Lu362brHfq<_*T27R!nyGd#-?X==@CruTl z=?-5ADc9jk$`pWo zOhaHj4>fqaS4=>?w|}=oi6;(D-H?Sm*cU@a5PdnQnI{Sooy@yPs(=9dF9xWmHmpa1 zl*-SrY;IzFLueVE_`P4UnGPB?s6N#*=8)jU6azm(8a;^mLkT017%ka2+#^i)RBz#_^A)2al zs(Tzl46Yxj5t!h~j-T zp#=8^yh3{J#qEinOcBhoGywi=E>2M+RV6Z?QUaPS%+@RpQi|l5gi9;wY>kviNU>K) zT-vNmzkJNyVkBy)5SM$-v+1ljtfS zsqsiXF>}T0d3%9sRwy^#!%qe^9i3NJfdm%E`WNWVffi5Ngc(Z}hoVBwIE-gbOFC(Y zGVx3e(@=6AiYAJ^YFIyvQ@%GDzs34K@BP08LcPdu31TGDC%5)nh!92~ZX39L zE*?ca0WIov#Fs~=LB27`@G2vyKPENu+sQnj0cXgFxOZZDQ$4J7a>kjsMh3Uv2_Bi? zfr`AEv*1HNlkP0$6z3lwZobHRV`yZUqeOZh#V_%!WBxORabyJA$F+v!YL7oYBbx54+uA-coj5DkK~hB_j?g|ch*xUL-01G?a3H1%h-q_cnJ5ZtvnY_`V~`3hC;_!j+yuWq0anAVc7!yXK^I4IRbNR#8|0_ ztRrA~&F-V(VPhTg6VT#b;QoIE9RV((dRT-IFN{MC1GjSvfrS#=xkNwaG%y5({U&R0 zoANjlxkSK>{=~-?o0IDCz0>SevHHiq=z!ra(jkH@=8AN`?9_%IDd0;NX<`8<3L*h? zx9Fn|zIu_=HldK?SWzLT@|d{NR^V@R@yLe)qK1G<@UxgXG7&=u5pMP!LgH$5cwA58 z+jyJk6U8k4gKxM@ksPd0>G4t|bNvS8iB9Mz2b4vUPt@syNQzqF5c2)tJAJpu6Z?Ea%g>p<4@yH54rATE7-gH zY#RHq5?Gc>=u++iqr35jiQ@|*>jaMLqZPG_c6Rncd>mq=^+iJj=VPS>imFfB!?7(} zre2vHFCuDWNJbU7g{I_9QAE6fb96?VC3umT8q&s72ApK(2QYo;EeSLn8lQh`TO`EL5Tz=I_NYP^%nXc$-58jR+kuQ%K#) zLXUOZ8QBsgKP&N-9C;J`skP6a2A-XkUSpp?uR%5@uWQc6yd13o5s5FLJ!c@kvwM>M z_0yf`E-tVhtx{r-fC#}1&wuZi54;t;yR<){=^FVY^TZd?ZUONxKI}fRzsFe1C0(6yI6E}@#riA^`iITauM=?&xm zS6ulWku;Os1Doe9{sZQO0A$V?>Q0{7FNzDu{7T}|ZRgda5xYY_*m zcE$KG)(VzUaxxq#w1Z<^xhvVZ>r?#yCHW}?{1T4L=!)Sx*ShAg`(p87%JM6WoMNCu z5MTTik`3V{>?lJ8%zuam5ESB>gc!*aT6wT&==;?714(3B>4k##6b@aC-SB#2_3*H1 z2F6(Qxx2zm5^Wg{s!be=4g=ORAu^Ss+=4zy00e0#824X4$bP{n2_fZ*5*J|RP|O$$ z#_5LQ=tadQB@H5gKw15@>5KczU0Vd zor1XB>F`7OaIwS_XJ2N2b??>aAqq0h0Hr_^A`vtOQ0_#DUG!$hocx%}sfDS)l_!j0 zM0j)ry37!iuncCJg?f3VfeN|}ydxY5$)4eG2`ynYYK5Ibz5fC4{SPGZ+xd*HG6^~t ziq60TD+z2lO5PsgGp)IY)sL*!OHwbx_lyS-=?bkFxoNcU#k7E?WSGew+~&vsOI1IS zq#DHg7ptw!g9Bd{Ofupa7;izUt_S7r!oBunn?;!Bg9MqDf-$72!tGmV`UeXnYrHYe z6NMgLf_N>#IH^^cvbFNn=v112q}W4?gFE&%_BXgDN`rtKP*8I5zh2H zmk3nQ-Wa`sj|E-IVFL1dXrcuvfG?sh?7Cvvp#8&9V&(-a1xYanFQa6KSBU2<(;^#| z-R=A8KSi&!WiX{xYxMcw&~6I>fumg~*jt41wu7)Wmx)v$gzD<)gf7ch2qWvS;g@Uu zYACNsSpOS5c`wkHuZtpR_mn(|Z*?f)&HR0x47Q)mQFoPm7wP z3#9WGq32aF;k|r0ViZ@)-KSH=tQ#Y*mk27JhHSoU6}GCDmVeggPc0XVa3{E&B1XR~ zOZ2XHc&8LLhUiI6;+D%IdP*sthDMK-x_oeS8ps`?3>Wf!IQ^T#^y?@KlU?m2kysO~ zF9@jPgfvlqtXLNArg}1et}b3!zU~(tY93=O$=$v0VGr&|?4#`q^rf6?I)8XCFLYWC zDjCAwpePr z+UJ4MmXjE4j%s<1yr6lz{(qS7moZi)+4BCv9oV|hw#1~$1K0OSjGkxclPOFd+)I@< z!%f;B&(9tfwwI;)o)6&I>`w|TMGH&YsZj?lr)yCq<)y+>N@T;DK~GFkK14}F`1p=B zt%E{xR|xA7dh1Ijo^MR^wtXmnb?k8gRCAXjm};A}ugOna9D(KYc>f2RB(ilJg#yE@ zgaV+7tkf0z8U?LEtWhmpk@HXc#-s^?!zwcyK7Tl36=s9VpsaH_th8om1lmXoSW%d= z9cJ+9wppWBeXbZ#tEcpoMa|z#O>;*LS7nExv)=2EHz8ED7sZ#snPlN+gxyVv=Ty-~ z^Zdg^3J1t=iL`u;>QL*Z4o!D)A`S*|yg&B-^gJ52dOlG-<#>w?(0%tNv3|Mnq~<6^ zYhNc$`EaqYyX@1Lz`oig+sX_Z;v6%bA|Lxn12YDI(7wq;i5r9QzBQsguZYPGVxtGo zG?E2jG~(GtAmmDvI1ui3Sq3t$<+O&6unYJk%EdC@8xI;rlUdOXs(HaQ_;N`EGp`{^n~nrt*GHr5q5H5h&7) zLrI&%DGeK8th65FwfK*Q+_pu?Vde;jh7=F{#LN`Z-)N!^0LQbd>7)n{%f1+vc`~kT zP3^)RLls2IsnH#5dCnlXMqY5AD7g}qaD>)q^XzDmI!wX2bgO|(=eXG}qM=}RYRAZP zF#pCzgbL^1S8vbO(%1dsI*ExBLeP%kfoPvAMKgh@t$CLMpZO-zajUy~s z-pecD_K!LJYPp(ag@JorAbQB7OB5#kCpWKH>z|f-!y5T$GKH6UaY*-CY->%FI+?a+ zJ{CUAo~;N*?Xj zgV;dOTD1{Fqs$TRl47;?|AgVY41uj93zg&8gb~F^xt5lb2*?Ks6b6SC<{)OO?-D(U)li7r zmmXUgTGzy)z(yCuk|(1D1Vrd9}Sw#S@2qn~S~xp5}J@flu) zMIjfW3uK&cobNCPq$$@sYAHZe${^m-DFh+Es}>tJZ+m6pDL0{?0U4h3C}sU$xDy5k zcjg@j9Z1yNrHI{~UTmgz(TpPAY{4e?yKij57_8jB)1B#9Sn&;@kR#tQ&h9X;^m!2% zc(eDl8j9S_88KjnP$&U!TuZ9e0SLCRSq%iw%J3ggO2}tl3%i}e*z{0fXqR3@cpz_O zo)m*gKQya#9Q{MB@yAc<#D%HLzX)rJ_1}>yp@db`)oTg4+-?#X+uoLkg~BR*Nc8X* zpNH;GGF64juLsVzF1aL$pOA!pH38H=>!AHh2*?S-^LB%wrpdy`kwLTJ)kOxtK(olV zl@QQb5#zkJkRjB`#-$l0Ot#$Ld~_EpFV}hg@%Z(4Jbz8f6kT;H7f*3Goi3Cr6`yWu zMxg6l^mnL7$_Jh7+vSFA`8Qk?xh(n0;%7I_5zo5MW_lIFc1m(^y{E3#3nI$kDj}uB z_auN80cvEBp81?r*mLYet6@_$1R))4#+G@}L@ZFV#~2ilXv(h@)VrpILLMvcZLdT# zuDTT}Ccv^= zUSUmSD@FGT@Ca5kH%0G0_iJ2NF{$Ktd6KSaP~HSsEbYD5ALX>NMmzm8RGa@N$Igp3 z$)Fa)UVW8&7gu2dC_*vK_@b2c_>#u)v8CC~w~$B5j84{#*?9~Bx9vT8F0nmFO|Ww# z)k5mA3t8Ikqe|MT`6XF%7fqP)fIq#n2JI}WK8nG)_RA3pPlzu0dxK>T8W-woJsr`z z#oV0advm`DBDB-&4kHj7db*n>z+vMuj^!6I2Y+#!g1~*OM5wtvdE&Uq!&bE(D10B$ z^|mv3sK;7dk*9wzLpVC{0ybGbAhqG|s*JNY{%ECf1wP@(6S-uIISBUBcp=GiyUXonO@`p`tr~#B1 zTFB9e*#&>7>aLu@OB5{2gCsgDmytRJk#U^ti96M3KM$#W5|7{b0L>DoY*@}(DIq5JG;s=m#8+??d*eIB0TAk66BvAMe7V=>4n~($1id+vO=CTLq)mHT3&bd8SZ0Z45m}gp@a9lv;r&rRtUCFI`+LvzEgvm z^fw1H1BJ=tx{^&MGGvESnIT)cjiK1eFRTl-s75szF#BYN-N63b)Tz)9jO>qXy>KZ? zdxLv_);mT<+HOFKN}m^HEg;!Fego&PPQlQa0~$WujlwRwZpRQNZ;SLC6V@OIzDA^G zH4jYtxn6RJip}31kYetzx9ZZtM=i$iv+{va!aLYZf@~=>JEB*=Vl z&r~g6Ix(pe37pQmBq@PhGaBxSaF(UV2e>AmR?s<5>z7Dem~DyQxK8Ac8<_^9K!vowx5LQ`H=M#*iiYk_ z(3yF2zlF#~NtiB-4(9)P+DJ0JVD5A2zjh=yDmzGoVFMc{hDzo>Vv21CZN{$CrH=Br zYy2ykc3qypua`Y^d*r4p4%*e0fVyKEQXB=!NPh`q-ZhwBjvkF~TT|`J4oQu>YGPzH z{jtL-2zFy`tJ6G;gHxwIc(o+sfk;d?U5UT~LyN^(LAiIDKfK+tGSqzPl7#QTL@Hq) ziyKr*KFoim?=D54_Um3{UR$Xb%!7-XG$5_y_Pi5ER<5XrtPzFS&co5e8zJqVw`4f( zD~O)e5vY|KTxV5?*xsr%TG>Fz6RGi^##~-d~o& zdS_oxCSoI*_@f0{1;L~n0`~_eqCM0E)u#F%x=FYqU?0<0D&%usFUzW zz`+cHY(^Pt*sOJuQbTCE^YHR@n$`?QTtM!--6flX5-59YIgA%1-Ue|$`|z_X*^NCD zwUR;pI7EK2nVJRZfT1=i&xk#zf#F%04eb20Rebjw}P>O8c<{&F? zThhc0iHE1DOni(2nbJnJpcsiP8GX-irm0KAH7;Gc?F%fTMfK`H`y&;OoTO<+%r*kBEm|J6hX*C6z zISL<)nnJ7rdji?#W=}8CC(^0bVD#?l%keh`NoG9p$sqw&pM{o<=BbN*a`%=*Td9HG ztMOs!CUzWniL2u>1%BsTjG8p5DaC=0TPUOQwBbp1wQdHP)54v_DB1S+M*!jig^UbWL`?_X6|u55lD+1qpif53 z$*qqEs`N74+n9g}f2k`Ux-6k~eq~&ETw{sf`|*ev-{WQ)<@oQBfqO@%cmkoG;u{QX zXtOI0e{a0DLD0TZKLtn=d*(z`KOA|8h|3yR@+74Xlz_m$l5fmZtPCjVQ+_k zDyvPQ?nxY4{`eCa!n&H4HiD>x3&RWSJyV4LgGeaq58zTwi4F#1DTEy7;SwC_FFezc zjZO3$KcK-S@Sv9o-%XUMW6X8MU@9BB4sd8ye|!+Qe=MwdgCk+T89CAViOm|9rB@SQ zaY`J#1}}VW-?cBU04Xx4h-7^xev-wq9V1mw@j4XG<9G%4)hJbev^(Xi2bJ82fy3j` z%+mckKm0_{qX=rwb+6@akMa-r&T_Wyuv@wl3gww^_??WcOs;md2~$8DloZDPn>s@J$e~eN;_1y!!N8_UJ;|`EVIPBjw%i30L8yT z-PMW7q?yQ?Uog{cjK+g0qZ9iS6kZruc;cK4Z+ZNq%&~=;U6ZXTDO8@nO*zL~v^WkJ zn^mgG_K1G*-B8My8K|Vi5z&jXMa-&##xPEZ^fsI~rHgx8Cs9k{0m6?0%qcZ0TzS`8$s-ck zRw4))GHWn?l&12=N-b_um5Vn)dHB4Hqs#qEsza;iJuF|=IVMc;ZDOtIO(=@lOf!>8 z^V?OF4nS@FpRK5mMr^NWw${of6r#gS3NAQK8SKWQk;OV6@(_;eY?9EO#|Usf%oj)! z%sa}<4XJi#+RXgsI3E)<=UV3RCk`JFFR2N_0#GFgH}jY*>MA z_SsMcG7<0^jjwyprNX&k}_-T}+b=JT4|FTj{{GKdoIW z*3)d$DD8Qj^FEr+~WM4_P`!^k$WcTIv{bX=>S}PLy>TV|+LYaKAb+7T9_!aD{2Z#orTU4*t4%_M}Hp)+!R3CCLS7UXN>-tI!TPat_-Cd zxOnsCbD)&HPr^NjJ0fgV73UX*M=M}z-*rX zQ}uI30*B3TcBBz_gx`qzZiq2u7dkTXc~5BiCF zid}s=uY1%#dR);PveBOGvZ1^FnD{SgT zDdEW`@uEohbx@smH#Ea0kpxQ`Q9)Ty=R|V*Ca-0rtWf@l866>%p14vmogY9eeNuzu zN|JREd5m_M0d?a}-s+}ASL5ZNzQzK?))d5y+3bqBu~AN#kDml%RI$bf)~L=`qPJ66?cEGBPy#kZO@jnkBJEavB@KzlCEijPo8#?S`6Pu{Cmh{0RR}O^ zk0qma*qo5nS?cCvnEqxFLC?h&@k?Wb3swD;TdO7lm(-`IUli*Uah#a@or%H1gG({q zoPmC$R{~MUQ$TMdhf~&HkeI30w;zp?PpU%$Qy}qlQ~XxJq7BruJ5w}1VK{Cc(LXUR zK?9it7UGh---^d*rIW>@DC{D0x#NekPXDH$GStFEoWG69I*o5#uKrtmK)ATJ#0(Eo zcjbCFP6}_d2^eiX?k(h$Q$(BS1(qBas^K$nW0F4a-475>lEh9mYpp-Mv+HFA>H7Re zrCZpcE`cZCDvYVEupFI|7y{ zPej+hFuV6^{8oQdi_^_?n~_3tTo38rIf{?YZ{l@T&a2fzX^Z-bPm9z|Bk1LU{um~k z_4=BqiM0*O=+KxCvVRApc~3^^`1M9~TvpQY&und!CQLk%6I^MR6|X76F811LKe!{= z9dkWOP2+?bV7SxIuMm>nD@`}Y+CtfU*G#rM@xMog~ZU^y+&g5C|WLXV76k03N*Vtpq zP9m_nU*D636W={7J&aJ~xB!82;Boh7T=gd-6h|K_Z%kl*EVTYjL`c{oGF|KSZ(8{7 z-@PgyM(l{~ORaTa+Udq?3%zaii3y$c-K{t#1eInoT@}jKBJaMz9bhTsf?|`Z{K-Gr zjwBE~raaxl2xH*Xg;jp1wyk>@Mo9F@+cERVkN-mUs%WKlVYNCQe`?q=@pykbJHMa$ zX0yN&oM6y)P`g|RT^>A}uRPk-m{({z?YzzjL#6JvUSyBfs+!c?lW@wy$j+ll-`w}_ zpDcmfXtQwTq#1SBc-p$r@wge!GW#Y8HoX|g2(L393Xys2E;5F&fk5iqHd%(nXL|^+ zpSB&wEe3a(3V`P}sW#EGOAiw52_}b~A4eO{@6(`#a7~7cKkb{@FvH_o9>w!_Dy`HBugB7WE;P0#ZpThy;tSK!pp~a#<2s8^5%aghl>WkUMa$Q@`94~e@a($Ypl4qJ~+UnaVL}3P;Y0rlk=gog7d;diAky>kDKn)v#D&6 z`cn-yCCi~}1*l*9cN-|JI2;&y@b?<%lva*B?d!cJ_pIh5B{}vl|2FEQt1H-kKTfrI zb~LZ@h7(Uc>EBH8Bgi})6@RLVq@!b7HKTo49wmuT4d|Jx(Yoc7s>OvIMuFt?o@Y)> z?q!H0$+pBP2%!!6gOy*B#@(kbHcmwV;ZO<#T?N$5w)}gzEIP#a)T(;AUey7N3(74J z9~$FrS2?83G}^(@xyg(eJ}8{ladKq5rVTKTayU|^%cLW45aORk3+s@HuW&SunMjnX zHHE&4HM&j6LMxJT?n{m(2_y=Eg|$6jR7_H#zR#kMZY+e6zrXEodh3h@oo#}i- zj{bL64@b%QJjMtYMOWXLW3V~n3@7QgxY#HZn{?-+wcGr>{#%%?0>zg8-xZ3~j*+6{v*|T9rbox&3>!O-2A)X3vRM>i8UBr- zA5Mv)(=l$YK}5!qF5^OZM7*Mh)yvL0*3Z9;%(91C^|`%&8mV2 zndfx}lZzmyMH7*55$WAu~F^IAHFZ7|DqO_BM)-kd2)HTX?b zhaanhQSlr&hZQWkxD^o!U@9Y7R(10Y8}4qcc-1HE%-CHC=i_;>SqzrJSv#2*eVnB! zdtBdL?dyA6Qp_TbnO)nB+Y_DMq=*P0+6qb)J^cK_n{iDBw1h{NwW+L($*cEKJ4%o- zCHYoyB?*d*r6*MLIs?Wi^w`BTXWofo)X4j?(t<;`pL3~e?YG>;VXt!?QsCJjb`-p7 zLS)(mnfjxql9Yx&)00MV@aD<6H!WfRh!Ml>*b$sS7p`XQ)=jMHx{*9@TXpU40ki#% zV&b~5B}@~K>5ebm9w)J2y9D`uXAdUyinUQ^!`BPsN9H}l`uajCd<)Q9X^I2%;882O zU#VcUcne~6NU$bv`S)RNXw}_^T|^wogSjbZV%fhR$zBl{bqR_losvAGBDDpYn%kKN z>uyrGbms!Sj~eS^43-LUbyp7YVDq)r@9TPG?u<==Ep)xwZQgN+!S-5JTu0j~yqPlP zd#m)VE>q#`R*gh6)w&pXpD;|TpiW1Wmj@|}zvy_#-00!4r?q*J~>J_a<|PbK?DQdsMTbqb#kMXGrZ)E_tJixE!? zMiFpgbq(X9<0Ltpq+hu`WdA9e;tZW8=02y?29FU$+|n#CkIGyEo1aH5LhHR>4eX9I zOfo03hI|GgZAAkI+Qmiq3;)@NogW*lM}0Eq)C74e;{OBrad0)(8ecml!I~>rnL?X> z39S>MX_^XUC=@!}HR2?%{< z2<6EvgE%0r;Saad!}DEXI-?|73B_dwZUEpYgg;sbONRii`gwY0;*bnHvGt+Gzw4BZE3ae>QCBnYwq^o$k3QLKJ zFlug-FgZyZWl7Q!;a?%fQ{;^6E(fAgNnGM!ORN>Dj3HG)=%0F)jnmi(LFf- z1Z&ZxMHj7!XNJTm+rc(nI%H-M+E@8QI(xE)L>xq-*=e%dUwBT9 zX|TTP5B;7k#^U&x&zbdHbfWy>!SYSosPyvm!u)W=F9uVWy>U5RcXr%RF4G+c$N8Xe zt^K~kO>~OaqPjU4baI+kOgxwds@#H!uxVDcb?4-WToS#V@zq(vN?xT?G9s#SMMT4L|fgzp6{ed1~iL) zt8Z&M^}llHTcUc>BK(t!>5%n)1f?)#9F=K2OjCg7ofE_uQbj?O6w{V|Wc{)P*5{EB z1f2|IxJ}Y_mflau$$`6FX;Dy~N?`Rt6eIXUrb>(VyQ80YkWuOS_rQ7)-WGDrRC1$J znRBLpy=+zvr2ZzhC}Ju^enmg#C8jl zDjP>6P7kNgg(r^c`5ft%(!QJ?v#auurLLmWe3<|ao@<5N>^^a_gQkEtx+NJ>+0wjl zRWlr_a*kK0M`Qb&?T%TKMv^bBuAcN@bn3EF1U6+ALTPsQT4S0XQ)KXbV2hohp6@we z)e(tXSnt5mE>~Q}!n>PZhH-#tj(vGtCNP4Si>!W^EcB}LZ)J4Wzx|>@F zJARdfKDbX${uDZ6rWn}a!J2IIUzcyAx{!(T(dO+2gAR6bw%*}rq##^cM@N0bCRtJu zt?b_Uq#}+i@EW@Dar}rkRsR-$azj2{oGBB!qUxbyJ9G)nizATi z>L2gSpl2aZ>*!+h*;jODw;xAZo}b*>E5)61fG6+6|j(eCjSf_x*z-k`>OQbL^WMB;{& zCv9?9YLbH*gz#H#@lCVpgbv|ZpwFM*(Ajf~^UCc9S>(xa6sOrOVP)G5*R@@1FH@yX zB6mFjhrpb5iSg%9SHe>qYF+@c!+T+-!=c=KqlKj8Fo1JveM$^r`{lR>Z;I&l$5CG_vdDuyU6|78{q@ah}+m=-BcJD2xVQI5^c&HnUkH){aK z7-bX@E{715Nuo4NbkMkBDCW{$W+3X7x1ghNi9y-3m@f5Qj1%#9(!r$4K)prD7yV251*g3XzRRNs!9}qD$5rd{5qkd46Bdu;Hn?0e>`izFOV=BFR-%FD27~>|^6S zIXj)*J~cck+-5^PxZ_P+xG9Yp@)ebUYMIN~jmRDSsOd#Jlx=236$9MyDL(^7*l4~? z4<>u)R(JzvX$#O>nVSx}FnoYJnUOxK-WEGJ@0wmygC9^SEVuUA6df;FUS5PN$cP0Ow7(+U5B zFF&(#R0$;d{{XE(Qoom^yH}Q7^WIu!ge~{*Vav_(-ESR~uRzY_WaTHz&q0@STc~uQ z^qk_R@pIFr?%#+6wpP+_1m{}``*!p9cH@65W$|Wkz0>RWPUr8<$b72I|34CVbNZ=L z`qTKXV32){$D6~oHL`qb<21Pxx5Z1z#Wi(5C4ngkAKJ(R002M$NklZ>0p@zJN-a%PD6iEsCj&DG5wT zU`hg05}1;}lmy<15}0yU-ifU;#nY4orX(;Wfhh@0NnlC>X$ed@D``LXd@=8&eOu5XJOU>|P^Rmll zB$dHq;T$HLZhj3aN1J@ROR1ZoK*TUy;+Pn-zCa>V< z54?tS7=(4=q54>?h2b16oz-rcG6tVlUNV(zvgKnbIt;n=MLD}I1R-c0HrIb`@hd$8 zqcrd8giOjD}w{o$vN#|ucM>@%WVS9{AarpiGI{7%fmQUBy zT&lxhJEJS_@OimeyyWC%$u2uj*sRQLy8X43jl4WrSt`o))3B+$=FN8@et3#s6i^b&=q>en(VTn1oL<0Y zRL$TEnpe&Q^*U^pN%5=~)9HC{eY{GCEw5M8=*4vWdZ&R^m0f2Qls>*FTH;Y0CYZ~# zWSF;>o-t(1vHYrxt(UG;Ms*4BPxN$%UrXEGi@K>2Z_W1-_*F_%XWr>6l`>30Q!#Z( zD=%@PbZa#1TLSHy0eQ{xA|);o=DQC`gbe{(Q{na`<<%?G%#xSoMi@lJ(h$FvmS9y^ z`vkp(QXVl%9%4$AjjVWWcvKPblt3t<7Yy+Qfl3lB!kMEq!$0xZ?`{x9nkP}S|D4X*uk@-vS(?e9KHcu>C7QPGNq)OS|2*~y zueTW3zG1Y;@FAWFBmPNWUxZaTTOXzgukr}%%1VDFhEys-+$t|tz3V% zoTU(^tPQ`OeyKhzN;rd@XUn7HM~o&8 zx9~}CRW8Z;>E7yw)lKD@7k8OHrdnIx1S9sOmy&~Is{Wz&=6{c_V1-YzkiD>Q>Z_){ zOcT6mH{!Cq2rFsx*VtYmGk3zxOLMK0<<*oS4aq}?Bw;UIIi0QCf=StU$$`ZUU7gj2 z;z9Uq8S_STl`?vwIuIxI#^#Ea#YYfRSGAYur;Jb}5xrSmd_+te7>-mAwG+=)X2Lpk z{HsXdEqKpL@v8EL{02bkVGR^-)#-wz9N2U%#kHL!PQ?tevxTV80e%oEM28_MBV%;T zD!BQ6r#fYjCXKK94e&2a8u1`IG|NdD^<_vYc{HI;z&Gqtag}e3zEMwgesw4PnNKb9 zEQ6-v>=-n734TR7$iYkD9q0(h4S{1fn5kIb*m@XX5uIL#!dMg;U=|lFbo} zIzu8*f@n&{fggNiMj}v(NK&*}ZvpVD)N)&pDm8UA29kM_Mcl}SK;$D45Z~4qsjjMB zDmcd4+kqiKzC*lIr@uH{i#oXsmSjiy(+JRX6V^yt z1dIW--I^4ME1?e0#?xSvpj<;Kwx{7QY(8LR44vB87$JQ!TmvonrCiJl?#w-9tQv?e z+ebxk^^xD_?Pqs-4Nj_I#X329U4Fal$r2rFqI=i@~rz9 zbEO|V8GVv0R2T6e8S7VegSsl_btj!--Nlny*vKpprp)9ZkSFq+HCaLe5ILTfsN48CeZ7Y?wGSb9PoXQb14vEn<)>ZO0c9Ak>9a8KMa?}Hqv9Se` zpZ*bE9H?(g0g?aqqn1nQ77#!@UQoH`lJAOc|-Cn_?X0HoM~sa$3VmE%De84elcxQqggy*feA z>(o+i4F;)5IaQaPGC>0^+|oTWNXzJHprX=-?0~FS0GnAW4Twr>XcvFtEhK|4V<01; zG>j9Eh9O?7lhcYs!gT~6#dtDioDFM0SQn%D8e~MDMlUdiP^gS02~RGPaunk1G;~G~ zsHRaFkuS;1p+klHqKsz&K?bRn2t|?#&s;% zOG6hyNmBP3)Yb7dAbSRqjspED;#2)(I@E{t9S2ETkq|3q4FSgZ(ZEOs#+VvoK|p<4 zfvLv1lB{?aPK}#-B!VTprzxf^G~k#)M!$p$(4h**c=_SA9S&ootE4G{JN12L0;W0Fsq`}2F3^Z6t z4wOOSk@F_QnB=St$q_nXQG|k3EJLG4-r^jvH5ki*l5Se(Nohh%nVJDdGFIDYD3l>* zah4Hr8AIjCX^?K|FuF%ErZ~t-v@*6v_b3+xM0+~TF!HAq_K#SiMe=ru11jb=Xl*&Fq8tRj#zsb0> zk0CLNP@Fi_H`!j{a4E(U?03e=kdDfs(V6wtKqeigG{nDQS@c*33r{(+e}EH~Kx~lt zY6+fyW<;eaY4pt&D zKE`&Iz7dz~5A&rH6cE`~8R{wJ6Aag|4B@ET;RGEje%~w^Zbd9LO4*K>{-H{-grQ^Y z3>{CVNKfU=kCRSBeTa3EcBE+AOgmG*0+BHLqHGgolIZ88OU`5&c~E`T@2ozl4mzv` zkrbU;IhCR-CtUr=$k>!nS=B?17j2~+V%1*`g8B@(GveS`&by=(qa!A}8m2w76s;YS zJ|QXlM_po$+LXd49&#o*sco4odXljw3T{#nF%v}V_-TSbE{RNzvh6>z#Zr2;pK-Fu zWYX9|>I_pw%S1n=1;j1WDkn&Eh1=ARbtF4%E9we$**NT^B3ZE4kvGvIg)ptRCSx)2 zD_O?Kr^W!b7{?JC<+bu;=G5^Hmw>eMZ9b&=GTKrKUaKe2u*j&c8>*uc1<_uyg}F2o zyw+eDH=R(OYLp#MMqcG;eBxB#u#8wK;_)GrM5}S<;{jAuRN&x6bOeUC$iFZ%J`K1s z8gX?7YIcaTRn4YlP&DIWUnV`eIkX_0;}}CSPJK39(%4#KH%cJVrW1ltEnZqxf~s%? zGpEtnkl|vdi}O#;k~)Jrjn2VnE4l`P=-SB6Lqhd6WoU0qVAZ-Ho_;Zj4;@y^ZnccO zau6U#DH$kse9*};Q4TJps3Rpp98B3Mlbq!RN*G0m$q}Mq2t}}AOC5${rR=mxl+qb$ zXlO)PZ~)0fFIgrpl$l%rb?T&Z*2t?I6d4N{QIWLH0ZH>dz!{b?tTH5oOA!yH$9hs~ zGYPM*oZYfAvBjIP*I`?W*oz@M)VDY&{9L$ zaj+F9M4(?@lQ%l2apDiMA8D}CfFp!q_WNXl^Y~C0%Qw{Hz9&ZTk;7Y29$Lk8>AVJD z>5yoXy7a>62HTN*Yw*%wW9rTNC@LlW)u1Ke5>#xkZ}Ug==-`Kg5v(7o!W#CyMYA;c z_K-I@b{YujG^i+H>OA zkb@_6Q#{2;Uor^NL6poCDT|V}`bJPrnOa%VF50~Q;VSg>*AR~M!(*tcY(Q-}ai%s9 z2V0thsQS3kVag*G8e_kr?W26u&%`OM4$o2cAGM>>C@;##R7%(8kMd!~G&wU48F8Cs zGRcz-5~715O_0`VawYky51aO*jgHV^qse4h@=Y2M>aiTDIQ2NiL0`5?0#CA>CQ<4U zl6R2pt0=Q+L9t}mQ-qXB0@Rt2Ovzn*iN1A$WC!HHsa-73VZys;yjhkP3jULRsf@WJ zyq$#dUhNU4O;v-^xgH;>z=pv}v^E4$66_&MvTvNEupg`La@oyf%HZP7)bWm%09*C# zK4@0ed#Fohwn;^$S3$jP#Wgn%;ewxS#%Q3C9dVEVGz?VV2+u+E(|D9q$+%=fCne5- zEmc~9#og6dw!Q?Vr8GFG#!D~NVD%cRUWASaqnRAvskFn?LUumZF3cHflDI%a9;DF; z(&7i$Q9~TSgC#V8LFJ1o%kp1@g~G{Rgd;TcC7SJU=BgPN4bA;e#_-%ScFS-Xjf)Hh zJ$KRN;AgBBmk{DK?!WtI1jn4U9KZT&5VzbH!Letq#p0D^7#yp?UmnyF1EWFgRAJ6s z!if=5l~W`mOoKq{7b@Y5U9&+Bq)CQG5v;=#FP305&S**rWt6PL)i?FvoXZoq`VRqo z>Y|M}_XmCG+|0o(Qo;Ht+7aWRP)0f>Gz^+)5jPD-GBAiFafG5ANZ2qz&CDsQAn8Vu z{gii<{(y{x95Tv90|>iyGB`weanPg8QlSyzQ3zd)8m7!fxMVOwT9w43fpv(q7}YUO z5%U5yG=AiRvX;zbv;vfAn3gFTRy%Tl*Q`fNG~`H8t^fx*6OAM80NXAUtwNZCoZ5HG zXcXVOwiC}Sp`HZktS~Rc_R?~cX7a{9YH24*XD-@+2pvrgaFTC?4&s{4jkxizNequM zucVaqr-2Q3;NB<8aKe|@VdoQG#2z1f39}AgjC&s^%aI0hH^{+-gAi$z2I%a_NRziX z2a^!_if~X=J)+S{*0q*-q!-vinX%qJi~U6%1}l#Wycs zj%QyUB?ukd)ByFFahFgH!jwgT^49!QA46vqD4z=IUyN-uMwmK8DW9(j4S z;Ma8&Ss;WR76Z$Q^zQq&13C& z=(Q?FX{e)w>mP~Y`a9wnrM?BJj}gj5b)uGXFbQxd*5K944xA)UrF7`18yv7@u%hg9 zVLBXgWFxFwD8YyWoudHliey+4ky9e4hq5E=+MY7}{0{@HLkZ#8XA=W#OFCMNa0J7Q zM6m9mNIhO&*@TaLeLXJz)j0n8U==?1{Q=x^PYh$>Hr#yA2K3Ra1lczu>>m;8ubkHy z`;BJz+J_UQY$LKU(r4P8XtV^&);40IMp~vJ%;>A!^Z?}*kzCo&G;qsVG0`wN2&|i;S|LV!Lh^p)tWgF*b`14qITCW3 zC386gTADP`WYv+8>=64{f)0p!Lzwa`A)ZKxIzSza7|&vm_my~JaRqwg?6*;tH!i1I zgw^CvKsZC}<7~%ZL{1RTbW}|IhLZ^@nz272O%9k!*Wc(wDQ#Yqa+AFarYeX_Qwz4A zs$m>zMW-lBwz-@LO;pDzTiMAH(n*HdZfxUdTy>_b*meQg1vyZZQJ;Y8MLK|l{!N4XACF^n1eWO?~ zEsBb|a#V8=3s;PywxkV1LjkPcunD#Gt!RvLP>gNH7zfPpSUn=;b?DqMj1tbgTdEkX zp@R2pp(z_qqPDgH<>l<)<9%c=iJ{?ol$Fgzin8O~pf~u85+pDuTa15yVHn+KFdw+XZuH(D1M`eEFnlXs#N=^D9zl zY;R@gp+%E@Lniy&3GUM&IBV$}56ss^koVv$a*5nsNS5R^#$-%t9NT zkn+-UjE)WBtEV+#-vy%>8tlOLuOGopcMajZQ|s9k2ieg(uwer`I0wnLS`Glg_3Yqm zfY3~I_i}zRNLQ~SjLM2W)_NRUhNq*VtO7loHybCVzF|5|`zHDp12l5gtZO~iZ=^#( z6fLz;&W5*ej2gg5jC(yekfmrq*LM%1tTc>i)r^=WdPpZVoaOenU+XEtQO(VE|o&vvA~@ zkKog%EJDYeden#d(b5pc{Wt83mO43wT^yY1F_>s29Xe#A6>J0QVEq>6^ju{!+Z<~2CIL)8b@q2hViiqJo@w?LbG?nN{&NDk| z*wWL`xsiO*v8ruQ1ZWHEnMCg>ox<{27#O6pFgk|D+CfCi$}limj?EiK5h)9^j*LW% zcGG!Ip?9o|w5DM!HjduD5!BYSp|)&*`m+Jsx7OnP&ot6G>EqHByIXWFdiv|<@%{lph=!w=`<{Rfb5Iv=0?NG@149`JN&nD{B5F?qKtw)B) zYd>XNg~50&HgBN=MEll2z9Px>7*00groW9~yJ@AUsi{Uy1qax86kk8S4(EKL8R78& z{{DO`zWBpcxaZngXe#ZZEZeAK)VPrm)YUbkB09)AY^F0p*Duh9t{&P#_QB@54h)TN zX1}3axV+rU{#;%`XN`te&Q0$qCs1_A$^-p$tV%HwY{IIZI7-b53x9n=ZB83S?BZZy&6V6q#P>_=OMt7%i!r-mpy zx)ZSqws#oSCBukC*q0(5=-d>$Q~ z8$w%a1*3kYs4Pq2;~$=b3$I>|-7lSnitq+HNSr*d&uXuYS^A^KnSsglx~ZRcvIO2X zXNCQVYQTS*zR{FXGkBJFPR}~jf(M@-#Sw>Wj}i`U_dWU==AXb>E%mEmqROx!|lh zIADGltG4uWR+~a=Q;LHO@cQaaIB360eCecS)G?BC#~n1St;6`>LABV>TZzkl+s9=c z8Y~)s_Z<|)X~(tU$2UBU-~VJ0cA3jr_t1+JLK)hsIa3NXXxfNZ*QapNulliRV+bo( zZNh2qZ^222bZafxLd>pJ7ueNh5mJGB7~r3rM6w&TY) zZ@~+%agd;aX{w3h+)uV)ZgUxD*%91$7f{c^ac$QaUR|*TU;1bxK6G#+!>~d8?DkPQ zrJN11Om9yQt~jR*Gh175$3Nn@>c-WWF{6=7F@5+Z+h9&J2UI#oX5Pb@^QN)c`1$_~ z;#-$5PWPdEf3Xm4O*F(jo_={8H{G!mFD`kV#qYT$ywjGW;upPbKA)IvP3hevdxp-;uM*QQ^wWLsi zo4-E`CmuZ$uP#qv`T8h!*^#s5@x_d2w6ITT_L{&S*q3g;>7O{_T{BU~fodpTiK~9Y zXjwE&=YxaTU9 z!GUfgZu<)tR=YLX7-io|Ve_VS_|ey9VAr|8iWSt+KMdhJU#d5b_w5fvaQTg$m_CgW zh2%#3==?=^i1z5yC%5ARhgP5>_AvLDm7s|Z5j|D(jWly$t-`gx8|LI^3%2yf&{~(m zzn@Wu>GcCx(N&F$eyS*0DVEb&J@kMYoPFXP{PW2zxbP<%@X`0x;o&EG@eMj^rF6if zbn>f0>rqniD)!pxK$I~mGBQl3wx$JlKN`Zt*DS{DxigU*ScCIFza8eyh@)?`0l)a4 zKHT%@5N%KyW_Kj8rMCp%_*g9+;4ysdM+3O@oDMWsJcmSS2Yz{b9P?V^c>h6F*xcKS z%YL&C&#z<@C^mxk?9+x%P}f3?Qr!5*7#?_Xkdrnoe|F=d&&wBVAT_27toYjDVJjHuArd2(4R?Q<8t z_xToy9>2RggG*)C{rnuS~bF^Zr4 zz6*;Mwo-Yk@t;>eg-gCQ7x&%0D;i41@XXRAK7I8jJay%K4w?x({6Ygh`PC(O@YY#e zHmRp$@G`FY?ymUCDKoHTB!QF8S%clT1CBeihWDK9jF)krX+3`Tw;ptF2;heAZ_iFo zPoJU9!QKI^*-(mIW^$P%wu%bC88<_v0fu+OT%-*#?0oM%&)}=qEX4l1&%nwquA_hG zIlOzfX*4VW>^LokpMGaLnp&sh{%5wwPk-|w_P(GUAOGM?yu5rJzI|pJ+A242pA2Ul zjF@a1tm5o?4z>)G;JV)~r_;lo@MS^VbN?{bb+zF7OE|kH+&Mq$!9SmmW9OaPaK^VD z!7FzkjhRhb7zs(Bxw4-dhAZ&rJ6Ge&XP4m%r_EpxDS$70YdsC;Fg|k3G~9N34>t9d z2dt`5BKE&)W&GmgZR~5HHA35w-r7_}8!Cq_4b$ z=l{4HXVF|jo`4|2XMz9QO&pgvsXQ%i+y+?9CZwndD%^fDsL2ptFFmzMV6@V(eJ!F7x{aoLg4 zk`SYT+E5>)*SNlG0K4y+z?mnvu)hcK@0WDrk!J^R^5G4f$n@cBpQ**nwkB*EsKy6B z{XCA|XAlRzs}5&>EQ0-ZE5(ufrf6T1cy1{p6MQ;o(?C6=EW6?l4{pSQxd~1lqFgeH z;k!Q`#Z4Cebl1E=C)?2}d2-j=w$=#yOwrKu7IsTrOmUigl(bqYW>?#-SMQ{@oJ%=IS|U ztM6e15xD9XYjOA?`%}lu@cQ~7-v5?#q4GC7l~;NZt)aSRV7@a0o? z#CFrUbP{-#vs=zm#<|`(#x-v)d+fer4L*EmC6{4d*WOG#@%%WxcS;9#oEb-X@=3gV zmn3%IvjQvDvNHzDu;cU!9KKHp2fXJPk?F@5J~0CiKSkw^RdE@C`$!@TACD*Tz+;^_ z^Mod})$}mjyb_I7>;&9@lF%Bjd^2`FbqGm88{Kqh*Z(mGw_x8zWhe_girK9LIPI8v zhJ6{b4@MZeZo-BE;DJY0Vcq&6`gh!2!0=~z2?q-9!6@U*qBOFG>(!J69iQ+0bSb`k z>1Hf=-=oAk4extT4Q0wf;Q3**9Ps#yoAKxiee9s6_}ddb4Dp94t2SKo>({XOwSEpP zoavWF7|I?&)BGA7wQmKQ!b>r|ZZpmx+(XX>u$f`nho0!d$B&~7YdQ(vNyoAtPd~R2 zTe!_gtI{VOw*ZTFu0VO}Nz7>2F(yUicO&T|>Y=N_BEkpOnyo*`?7{{@@kV3jhP9V2*ct<~f+j&H!xq;>LP zQ7qW577sC+H#nyKM>4jhjI7n+sC`E8?!CFRN7MeflUq4Rjp2ZO8qEE8qMVT$DTg9H z0i1k9HICeC7#}`vHt$E#y_tDtLnd#zMn5nX!jqIU(1_PpjN;*E2IyEu@x+orhV3_F z*ZJC@4a{r-_TMFj+0@q{XU;l8QBDfR!*mF^l!peK@SYO9cb@^Aa(su$U$K%WIzyYf zHsaVL7T~1AtMJ}E265VZn()G!9*j^PgOuBhc1BCIFNcH37Vc5G^{!6TaM?`DThnID z!o3djw>1!X5_L4ZB^2fF1%~^IxZcu1v$!68}CYu|(#E*-Ax<`(=&BM#h`%RK?=S=$iK z{q!6>^5g*f8Yd83Ui;!Fr?XEDqAKtLj-xJb=o`aEE!%M^IYoJB>iyztoALd#Td>=9 z+&~+8flH)XMj7RDllF>ef1P%)aj;~R*KADIUK#Ga;>-xg99WIkvK3r%*hmMf8r@XG zaqg`u=MrR;A;-s`?8dV%^`fkn1MnCh9*=0R2JI4;!fF^fjWCkI?HsuDm+NreRh^i5 z_)~b~MMjH0Kc7n!o3U&amy+qsuUMF$(PjA69j06XxWq9tz zO=w^=wEAv(n8o!yQF zGY8o=om_Sc;qj-qM~rfN;E{E##}Hm$GQ$4HB_&SYjyt>s`|TJ-S@H?&GB1u#eW-(a ztO*DAkdaUA>1L-Dc3RCibceatU=jf*bzai#VOLlaHN&D{owe z`=9FPa%we~3Av|>QP48(^C{>4$Fh=5Y(p-AQ9f-grP$22ic?QgY*!PxRhu$z>iE}@ zz}psCF(ROoq8;AUVk#s}01d%-q7r|7ire+t1NhCoYYCP_O+yTKJ)UHQp_=!DXl&5z zlFKOcycK$kv-1S#x@Z~2Y>3h$n{;ZlAy>1W3L2thf-@)53-W1<05_BBi3=?~&6!rt zVT6&6I5$YrYW6W~IlYZaOXJ3Ur{rrKO?6dV|5cd)Dl55Ulj=u`dsITi)lkBH6$0?s-84Hx@D~6&vG>aN#JnYp+9fa4o)ZMm_F- zI)d+9)`b?f!G)ij#xOdU971}Eh~CF|FP|zY<9aVQiE;)q&KdgPC^y0}dXS3o=@~|e zqG9qy$A_1OzseG>#Z#W~q#PfKkpl~PZ>k=~Rp&I}-pAtj*pohAo`EzV>sfok74h*_4w#1dvITl=7gFLmv9!xCsnu(FM%fcL>Xf1 zxo5Eq&5cQhzk3;?o`&{j?z!Qav}&L>nt)MX90=AbTf{fay?y`z@g zu(8HD(3N$V?BOx?EEex!lh;MzA=u=j@?^dagPpI4bM0Z|zkJvHt`q!vLR%(bHFouxcYM zw~A19Lmcqcwb{nh89m;wjneeR(NMwdrrb*wWZxeiilU~Jxp!1Yi01Y@GL*nOt(>{;1oBxtB?Has-1z+)u%c;;S}AFv2}M z<&0?DddWPV=}h;BP}4zs$9j+PNf$jh&{$2I!byxa(#pv@Jf9D%Qg5bmsHNZuoFA)T!OEEs)==yUUBnjjIK~j z{ZUSlhIl?`Kq`7=LR}r{Cb;)0RmG(-J`TZ$05)(B$sm{iv{ZWKcelrkwlVHIjH9WU zDCMBD-NJ!Bwj1}%QFaC=CzMomN{JdlCqxd9c`pxXBxrZWxF@c&tHkI*W%&kN_@&t> zO?KkLM{zG>dkVMS*^M7w-G@ugpNU1YG!Afo87Ff=KCT>Lbd--*(MeWQk*4-bva{;( zdpTZ;t*BO0E(@FcU*J$rd|MCeK*+!4?s-1F(AgWs{f~6wmly1eX$|Z_JUTbk;{#uM zlrz!25h~?|D{eTIw;iAX4KchN3-IC=5A-Cztf1r*}up znZ7ffl5)KG`Y;DCDnB0&PH~sICIlti2U5?Cj!U?Kb*G)U5p|qS7du}Kjs91-zvbrt z8OEY>XK9RK2Nh46t7)e@BQqQTwRcA^bz58vKkRRX~HF_u-+TM4R`gGL*%0eA3vcQ^Nx882QOTNMROUpXZanbhw;e| z@JSOoY1(j_8r{r+v>$cc3v|+vr8w%)J@9W=cH!@jcJsOeOJ3THt}U}Lf6h+EsK2@_ z!3~cIKE9d6v_|d)>gJw|1+(eEjbin3&3!n8FvOeW$Po=&BcM%dGIJ4H>LmH2C1oTe z!N^!8-gnr6`0&}U;)nyeRL~-4WE`g+SIrqEpVVU8BxywBBmEL1jjWOD?f?g7^E3fXPMGV`Wz>Tq(tYwZ=~XE%;?l-$J2!IaM9~Gg zD{>OphTOEQV_>YA8#DRnHyzS_cWc0Z|8XOJf6oZYj-AQn$p&t+9z}b7koywUO;ULH zr7`a1nZvykT*i51EzbVvT$IysR67d0HV85xO&MsU(mPl*i{nxY9WS=Dp|3$j&$>uq zUS!0kw6TNHj}^RU^ns5DYdKMee2#Jskke)m_Wlig+v*}dbvkptwHKMwHd%X0DqE+CAUp}Z#IW*H@ z2-Bde4nb}bHvL6)p`5De@UB`}$-HuI3fEqzwYc`2={VxxN)94=heb2K$@bTiM@PM< zku~S0YB`(KcRBT8{zZ-R#l1*$N)p@vSwj5{f{_*GmC=dNQh*7`bI_7jxi`3$avz6Y zAgI0_<3Ly2#C->^_pzSssBfA_XQLjkEa}6HnPgl~%#r6ME7`W(KpmmO)VVQ=mpfD3 zD9wq$AltZ9gJO)EFKhAZN2q(pv~Xi4ohS0EcdzWZR~z2<@nv}byLaORXD2#Ube_m* zlKW?39I(UWhqDUeP+j=U3U$1k4+;#7RC2I<5swyZWE{9Uk z)Yo1#GzYPa%N@y*IgBjTqi7E#)k|{b);mS5{d~M)zQz!P^E<~r6 z(H%NjyUvT^#G|MS-1Mq;PI80(DBp3@!szN5$ChwubT54Bn=A0x3q9C-=jp`9>e3bm z%lWk3ke=YAF4G1F*r!WmZ>hz4Dk#CpR(07ZN{9Gp_ugf6rfJ{C^n@X+PA7U3CvFcd ziSglxYEA^_vi;hygv<8t-H#IvDqWauw`H9Clym8g8`0Nxaic8%1({bv8$Y8lh|@pF z&DKGN=n{-}()k}B>0`u-QO7HIdLC=kIJIt-d#W(cg^B=Cox{Z9g=0`8#ncpp72Os9TIA6ZT z*-V)0*ry&l2Zw)hDSmhPY=%lXZyk@}sQt$9DQ%cN_hp>*qZm#+goc@e^3t_jCqG!m zJISy(JF*_Nqb<`*#MZ6hlRFG~H`bKkv4_`k6EPn#<-U+>ZtKQIhFA4oikh+*J27Wj zf9EE#`DNH{CZE&U(#_?lIzHhsh7Rs``Q?QRan^aAxckQ0IOToQu*V7abMRk;9XjIN zD5)h6pksOq_j&YU;cT`Ijqj?}+^qL*I(D21EuxY6;q|@T(cg&Sz8F4#;q!R>H}m<7 zMmJ77eg;nbS{LQW9rdN9IQx66ap5`5s4C?Wz!;yx;!`BXec?c?cNjeL_+~no^>}?v z74CXS1N;b=lQ#1Soj8}wMzFAh8%qbC$G0!<#c>BzVdEw~Swy3L>_L3gnub|T8D^B_ zusvIG9$WU5bDzgSd$(~9%X<9dhDE5W7d38t5#mPAC}lr~OMPIyPCV;5>@Z^tKfk!08&0`V@2FOM_v)AVM_Dl< zw~Fodx1Z0#K8s4Y_h1^IDtLu^KsItiubiI%PNdUU$~E!cK1OKBLz0m=CQ~LFsJKrc z!AFl7H5=sf-0(Ch4b1#`qr0dqN#fgV?(7}u_GRIMZN8bRKOc~Dp zz|qrj=?$;pkW>GFj~_D=FD_k;_SO*hH7rCOpTgL69`*I=7x~A2!MzQ8?!pKpj{&o! zHiBahZN&xGa+&PRXRynHCOl6$oqBvb7S7}TfPqa|%5J#*Muqw5K+BETqGcowkaQkf zo5Hy2g%r-=K-0OFn|yB=!N1+h_!gH>1FS=7bO6U4#*Mx|Sb@Fv>cEyQV_c%MGSFmW zG#;a)K-tr2Yi&*8M_=l}e!G-omsxBx4i?WXAEZ2L`F-tBzAe7($ybfodwgYxK{yFTq zcO~Anumm?pU*mqLL7Z?{HQS<(duqnGZ>$7Ia(I@*Wv!mC3#bgv}J z`E=f$f1nP{Ek9VqNYM~pTQQE~4o+gHjw2U<* zmgaS1Z3x)WhZWk=wIdXf%3!`3R$tMNtDp}n0)eQQy=5)wK#?sbC2&SP^#U-Hmt$_8 zSb>X43;~K>Kv3Q|GK}ggqIMXu4WF$Wv;l(S&Luu#{;Y_NBMu>lqEQFzJI=oN3L-(I z;iN`_k#i%~SWXsB4lXjxv9;9&mR*`nTuCnsIBBOIU1h7c#cWd>>77LI&nAd3FULo= zLbk2h>O&wLBbFy<2SB`sDS-9i@`zn}>Uc7gcG*^fAAfZv0#6fG83Ki?Dsl-<&9G%y z7Mg+ErDtZ@;d4~~p@UUqT77NGnksR#u>-0sJYiCmO{j^vbjH=KS+>5JOfBL`zOyLH zCN@CegY9-p{aX2N!wv4m=r{&tT z`lJJYJm4lIJe;Tp*4vH5e0h@q%LV6Ffz1YUoG*oH}Q1gt)rHKT+* zr~p4hcFMwXyZMS*tXO0&k?|79EVljUjI&vd!)TdG8wcLgs)2bN?GU^~8=W<^7G_0W z5sc-%r6Xi3m_2#d0-Fwuk2|6Q;h3zkTm_P*Y|^9}gwl}J=l2m<7_)C4S7kGquP;;9 ztx7wS*p8}_K9}CPKX@^b_?g4@Dmvgzactgtw2cR$%`N6bg)26vA; zDAU0(Kry|bTy+R0h53FfugqXRF&J|$LS7UY$i)J~L4>WW43hRss&Br}4k8ftv!y*& zhPA4y(huAX0S$b>av~uhr>GovC2pzgQOhqa=G60S}5Q7BOCheq!#rDOTZi>pqZNZ!xXv?r2 zGB4XoGCOVh)G}hW(zxoP#9;;P+*50;x{NFz=3d7+TC|Ytoh@UWf9L^mG5a>L7A#Pz zS+#O=(mHyQ(7~Y1nLt{3fULu54G85V4G_#ewUNr$0i;^2P_j$}DP&h)<+n1%h5|csR;4Y&McCGzX*I-&OsT^FR6wi0G8^i(14xe8fgrnz ztiYMX#uXCtG@&vEjUL8ojq+8WO&zIzEH-}o{>3#my*^=eC8U*;qJR7$^<;O2Z0#1M zH3vC!VXU1feS=s~wD z%6Zx0ENN^Y>`GQotSoL#;$Q#$`oD#MBmgJxVR6%cf@T0iL-%l%-Tn9$`_|cKSz}|9 z<6W|r?vV}Ugwt!!d-vvl^N$bV;{S1{^eFs(Few$~X>HwZ6DCZwXYZO}C(fs)$yf`F zS%HPf?y4Xg=I~;rumsvENTnb)ng0R^0x^mx`SACK2>gp;QdZ$G^`KCUfIo(X1Z__h zA>$EhvY^EWNh5@6A`CHuC|Vd`3i7N}1h+@L6JdZrR0?aw@$))4n(O5AhGbBniA)xJ zn-n|1v@S{aoyF`EX+c1mGmbtU@wE=WqGG{XR&rNiYeW<_y1 z{uDwUR;M`6SC0`w@1PaAj8EBf;z|NU%O~+&i@D3wt2qQCMF?_(#0p?;CT>DOqAHP( z3<*3sxRZJa#2%#ZWy=#yG!BH6RLqf5yYR+tyNC4N`qCkYJEmiz=6%3LeT`HWJV4zW zU%G^y22#Ygq+*z5bdQE{>)`!9i5@eh=UV;<}WMg7vmHZUZX zJn$68?2oAc6?+ma;U0*h=Ei(uX#|xd7CVZ*3D!P>US)bV_92bHqH)vI$OvJwPLYXX z!huw={o-#U_Urq$Tg#jCVB|f-8-ZsDD!~%VljJ&I5VambAkFoDVh~iNHi4y3?uRgV z=O>Ub&boD;f)wuv5PLgM!3XyDexzW01;wO(g?Q+&Ve7dOq!ro zS9vwo0i3~C0^mc8n~HBIe5A~td@F+>=wAEX&npRDY#}z7lf+)C=$*=?D;^{erg|_| zD?jHqNlZoxr69m_eL-LX(Mo8~xHcFrV_tHL_JY@>zv4zoCVRu;|aIBfWdrnrh2%m*KJ+?LwfXJQ~8Z z+_0*a9Uwq92JVG{YXJI^#2qE4We|8I5fns|ghzhhkw*JYG2gJPDBz3&x_x98CZIQX~i9Bmr-juTr_NI3(B$JvZnpbN9qqx6Yj^`6gqWc|Dsw z@kPpcGF6(vpAeR~0C+aW9t0RC$oY;F7pM4{AnxyksoqN%D^!;K;MO*~^_ps{BVF0j zO3{TzaJK!zX-W6w0MiAmuhs5pSdx1{{3$uEL!+ zKXAM5PaSsntW2y$!&rw0;2Wwv#km4!M9)-QF+~ia&ZJ?rPLq+RF~I`%g>#qe#PY*= z$4##)zCq@gA|6X~NTcWk{fcW#GG^GWzpKwC*X5x65d#xn3oNj9>M?R@K^s9}{+A{6 zzhD115RmWy1Ub`kNpj`^u6kannBazvIX30^x9#;;U$!F`evSAtXb!x>6KBK)pdar3 z|1%L}uepKTEcB0DGmv*JiRFMWJ79nuUj#g@){O<~O zAz)L%b4vSjLKK-m3eH`=JZd9iUhyp)T%kXNo9}}7_frI2Y88GMt^DkX*iJS`0cQ{$ z=Bi4I3Z{$^Q<6$-bi_{#J;6NEjFE;ZB?$oYLzt<9#3AQ%2&;U*aUF6PEu%stR2`FN zVjfp9D4Cs8z{DTtM@(KG4@=srO7Q0o(kGkprRVbrB!JYc<}-M zqqR}gE1qOdirpE;ngbjq#w3A#0Ai(b>O%Thw3+Agg4ny>G*Ji>xCD^dS7nR9peY^& zH-^Xp-?6R0l6~SPzv4;|enlXYwux2nA@7sa+DL;}E=z=9*EoTFVo=IPP)trBjR2xo z6nvvxy3+3btlJ+|Oa_7;!jFQ2;|K?7m?}9UFug+SACMaeOJfvficsK7KuFm~HTO({ zBk3sk?cj_s53Y#e1Q^c=y^JUU(g$pWdkF}48Vy}|sT!i%v&7gIaF!>h5f(&5U_dG2 zM0fz$C8kW}S)EBWOiXTKF05T}<3C@t4OoGLCgnuwU_0DTIB+BdNENW*l?^4lzdH zUlIW|fS{2ivqimcf(Yj!aFZ{A0RnXrA&>EqLO4$Bl+Kd~7-+^3=Pa}kC#H01&IzvT zDDQ>a%ARx;awYgO&!iJ1$j+f2Nph^tHf&=rCB*Ta1S3PKarPwl2*~V%?GpNP8}YKtS*d2iY6Z z&j8O%fX~872{1`TZ*#rYoZ`HMVl8}*E0n@^z1@8XDY(Kl7MCQ(TAdKBGzK+!O*5_w zZ08Ibv`7LthYZ@O>%o1hGA2iNLTi{e&xrGE@eAR%+E=7v+jtg1#c@g*%+9hyW)c+77r4sWZWkGr^Zehh|34rAHtgM(svf$M?Fb7}!~z^j*Y5^2S1D*^2qj?7(hNg! z7%GQVhe#DQ6C8d8>7_X^I)rjD2swS!1rZ6;b+SUZ)Ix+JE`eZ3?Pkyx|=d+>TH|7NmBI?Pn(^~`lzH)A=*2*i1Ywm<^}An<zQRT%SOIgQ9~EG<$@2O910z_J(yal9t6JyuIRnaU^9Rq}4fC z^LB7xj_fD5yEzh>%^w;AykL@kENX1K1HU?GqvC=qa$RW-VmL4WHy?xsjp<;-+5@;5 z`PR)x1fQ`~o?Zce0brhBEUg`m!QB$~c$Sz0pMzzL$zFq(uCp+(lU5r;vrmZ#tH~sq z;TcEg&}XKJ09GpdBJc<|kf0a`+yS=ED#P4W&Rqae5T*9t?THQ%pGh-xV1mKOilGlR zQ^iF15i}J{h^c4yp2?b_H34S?9f!LToDjrR_FQ-BUI~d3LX-5ra!vBMKFi->Uq1+( zfob@$QmD_7#`Q{-!<1O7v}xCsS#ah#N@ErV`kA+DoQU?zReVKT0OsX{D*hF#Q`cr$ zB2+G2Ojq|g;aowIyn^7T@YFq5>jbA=1yX2-cYc`<*019hyad3Lcy{8mz6VUw_I63`Zcr!9Fs7UX5WQ{P8bqC?xs5_7i%Ibi3fYpT|X9O ztr<~`+lA+n`WRV)D6~2bz6RJ=M>_;2hg;Ae_FK(m^+=IBDHuo-z)QHwd3STse1sRg z;eXe>*3nMz1p0pN@nu$C#LZZ#)JRnLu7Gft6ajuqjY2BrGg)<+1F%r{>o_K2wYgAf zf4QSlychhrh^g}+jivRl-x4$PGDqy>L!jYUq8)8y&zyh_erlfTwZz@r&f48ydZ)e7 z4;Q$X{@(M_-?~P>_*+E6z29@qp8MSu{ImOdu60(pJ@@jt$2b1x>%DQkJG}8UydLwP z&ve7?9@G8(pHJBRPQU)w@BixyZ+!3j|N8WQ|JVOKzdcuabNgTCwCB6M`+M&SE7~Nt z>4G|k8)At30lYvGcEYP5sNW*_FkP6D>-B+vqZk(v2Z;(^?E}aG<8d|j>(KK(h^%P}By)hhf;;jgPtU91P&lKY7Q0zula5FbFY)8*yJghu1p6wWvh7qyU z!Q(Tob_Ywug)_oS#{$B}v&765G^@j`ne!osLPD1a0dsc5b2oGIhGGo;q3boW2!*B% zF_cyYu@OPhoLv(=4wtTVhs)ikXK*i?jUbKA%{_9S;5y@P&BdWEpJ|p70t9DW>zKS| z&x13A!}hiPIsBO5rP+({=}N(xD;<&J1Km%);*jy`NLvs+pR~o;2sDuz;C0R-@F(?Ucy4r)Bhj;qUDHm>!yuH;J`cHVG z`~Pztyb&s>iJbz~)!W0dKa8cbDJJ7)F2M1(=mx9v<|V~|H4B=6(#^)f(S0w<#l8BR zGZoO>H3QKRw{wEJ&Zq}bZ%=i$*+IdW6G9x8b2~yhpE(#aCkb(yuyBdd+$KjiO6j&A9yHb3HAECFp>e>>&b&-0Xs{;NXN02XAjijKcUl z*9k!yMv5)N0CVl9aL}U(&|HX^6cJ^@38x*IR>BnMsU+hbVB?=XHvAm+hbiX z9CN1wy5U_Z{Py6g`#ktE2f>tUMHltAH$T1iMt6-`8C#Zk_dPdy`^IoQM>JeECtdHs zPUp&-onY@qad^z005`Q%aJ$9d9?kZk;r5hiAe^{jFQ|z2@re z@qZE}?$N$$=7Od!;$J`^oYZ~5UqTA+bidZG?a+QD(e!numu=N+uac(_mO$rOFo+X+hD~i@CEE02c774e0%?rB0T0M7) z7`Sj!0|N(l#ywne_`^Mp;W87=M>NU_z3v8&nQQ-&2o#!!_gv^8*J+M;D*0Qm6ySL_ zG)G;_okfN5JlKgz3g7hGn`xqhIS zT&J~Ze+7B=f@kyAy?54c26Gm2(Qbz~JRdlE);I0D&NbH{faqMw%H%;p)<<>>DZ}A1 z_e!`^?HnJ%r!3|gSN95GgcTlLl90px%399+hlRNKkIwvN8(a9p*4s zopG(x6W(Z;D4ao<0lE&(4%f9ny?6ggY4UKu(R>N4z)>QfVB+YX_KHuuSqft`zL+{A z?1qVFJ<)f%SHEQ)!$J<;>9mP=$fYKl>AV6wL)or+w$6}Kl6IeU!7T+17ah=k3r{p# zcfNM-(|-yJtY72nNB8Jjt;69xGx7FJ`|TjabTzuh($u}Ny)yyM3%8h#@H#*|p`M|$ zpzj4o(KXfc@%BpRg1_9{nBHD~337Y!r7h(VTD*ITfa#ecg-FhN0tkhC5?TbHG%2hJ z0HRKs?kjf?aP7Dal~k?6z{^1zB;{T`z#Rs+1!9nF3=h=778=OyB{**9RPscKie}FAG`jmsJE6Jg8hs`ryBnf> z;~hZ3#lr>1{sE2VBH}GKl<-Tq!v78y8c|GHpJ_byQ0qZ@5HZz=5<=934N}8Cxkea2taut(?-)aKmfJ<+q=^+1l)QKZZ@pT( z+Yjxr_TQ~R;|u13oESg90f8rs=v-qI z1sFFM;i7KRz6-bHmf(CjtCd!(9|;+{%UhrJSo^Mh_4dKtzWX124y)$SdU(Nmr;LDe znm@5&`YpG)x5qjwaw&PjoCFdl3=6jI*}9GyaF6EUrmcCqzr@tIZa2)l?&5lmU=6(h zCbBB{5FmV5TSUi&L+<=ZP=|(TFSRRLnWKq<6OT%vj5=#L2%&fvDORd%E*jzX*-b?M zaIbrg#?^d09F^e2P5gk}%uln|dK`Z7+tDU)!(G8J+S>%1C*0H?>POq6{bkzRr+wo- z-e_OF`*eZeD=Ufq7M{aX*kiqO2iu)T!Hv)OPHuQ9iy69GyoxzwxX#0aIn->KOIv6rQHX_GlL7Mv`-)vf9Rko zm{W)vc8*981OyYY*#lgWdkHS?Yqo=lI0B>#99hH{tZ^WpLK6-UTgK*!Xap&CB_b^Z zlrSLzDfEmJry(~_5GE*g5F}PbOgc$QvE*#eB_m?!ZM-&6hhK=Gvdl9aKGAYt#V~u@S-3cy7KNME&iAF<_{;4OtJw%ynG=I4CYcQns=hbh%<% z6xivyI|FMmPmlOBlv}IzM&oz~B*of5e>bl7Lvu)DF>nWr`RzG~lJ>P@f~599*OA8J z-s=-0kM$KlB5WcST8GBd0hG|fJ&fTItKDO{H2^=|2Tm!TNr%~KKf8&71Scm{YG^mE zp00UmF1&=lSgr&|fldOBvW7xrhNu`=nrsQVH8o>t&u$d<*u`w^!KvLitZP(V(>o_j ziZQhk;ex(%u@`(Dz`rkIWCV?hF*%rXjjs1_g_*j2l!cG$_z_VNvvf-po&b0Kt;<*; zxXiPFopX(_r@D`6IUJFuF5J`%d}zV}GM(hsNK*%6&)C{i%~ixw!QO5xh^z>>TsOim z2{q~{9SRbkf%!j4(4=B}v}BRH|8m(ekT+BJnHY(nBG-;w89MvUMFu_x2&PYCg){|& zlbzzK;1VmQ@zM}foiW8PXlx0ABJduLa~2p=Sv`7|#`e}In6MJyqi53S-?s6yQ{%#Y3y#_-I$^c z5}pJn;Um}bJdbvIzx`w-`Uz4GlAyrF9_?Y`ac4!+`ZY0$rU*6sw`YA)X533eLf7z} zXsCoG(JAejKI5SXLeL3d8j&WhxI%DG0m&Lm_%Hlce327w++v^)d>&Bj4{(rsS=pC9 zWfejrRP)GV+7bdphXfDN7eP_e;Wu*@{1mekym2LX=xp)h@Qur*q)VC7x_LT38cF*h+Q^;4QC-ioymRc~ zhJ)7bIck3;5bI9SWXA*D+$F5K9p^KhQPDz%<~s)CFSiojv+sH&&(U?FBf5*z%lrgi zz4P{wKiqq62FBi;HO{}|r5)INFCZh$Mivwi4oQjf$HpmXtyD#q?ZJXWq_k$DiM7&< zYxx?#O&phD6YI1Cs#&3}=Cym1o#qQ<1w2_4B&~-D9F>rf(7|9sF-CBs4oW7C2tCb%&!sJMk#lqCIny#Oq4cC zaAMyGfyK-(hl-JY{Pa%XV6GvE1QdX=wYV6}vVickTpBNeoE*YDD$Qn5z^k=8MS;2y z&Hl1^3fT(~0Zms{7LN!6Pcdyi6S0&=hSI=wutid#RR~N3A;>dh=?Z>4%TRy;RAxOy zr(&g~dJ*@m5n?ILNo&_w3<|V4RKQCILxe#D+&hpG64?z$Y?znB7la>2WEql|+;%LK z|A7_oan>&0NsP@1dK?IWaqj*{KeOV1t&r8$|{!YCS-AZ9@s zVTx_?2pSkB*y=xUOMB~CEwm521sTB$9MhbW@ zw%S?<)i>D!!0 zKa5Ze*PU(lO(?*ebz=167Q|mp@Zp`5pcH3iloVX0r-xzu!W)tL(Nrb$4;p65fE1I^ z*J*j6XanM?8{>#+3k$@2*>88& z#jNSZG16bmoCBEYA zrN-w}I^1;&cle}>98Y9CXL*4}fJj=NuI6t}oc01(bA?t%A{T$dTScSC2zCy!jxd>x z@;t=&TlnMbSb7X|+uJW@yEk7tD(o;X$56=niIW3={|hmDcPWRH-HE|WyV^SJ!Q1EC z&c2i_x@xiA^Yd9YsU8p}h@((S8i0hZa_ISS(z}Ed+#dl7k+E?~`xFBW%NoLIbHWjT z_Yuu5(`!(*8>qbMBV#0r29hGkF&rqjCtpj!T!!t3r&qGM+nA)H=@sWe(J2vhQXkzu zP-|SZM0}VwdLY;oh^7&r8nac}J74lSwI^mZkzJD9;WTS!@t}r<0(ey`W)EtmFjb35 zrl|v>ssUbh4k>x^$-)gBalm2`Aiy^y|GPVw-eC=dv+08or*;4xkrz=LVT76p5KFOI zQ5a=Uu?Q+^mf7;=9DC@+9y(S_ivW%w+|8F)Ldbno?(DGrep$%D-bTtFu+=FQanv@s z!$d6#Y2h3c5x4|?_7MGzT0636CqQOWhzK) z0-lb)YR$-PRWK^KnvD zMVx|Unfa;f;8J&Cu+qvsYXk7n;fCocJI&MNM7&|v2q=!Z} z6I|;9UwVhbeg0w2+GF*)qPZl5R9Rmt{jaomfa5gIuHhnU=^UlV7I1;Mt3OaE++fSL z=Gs$l_1OoXuy4R^|Jmd0;;(1ALUU>LF%q1Zi{=>v#!B^drSqL-0*#wGKt9ru6&MV% zFb^``u)Il76Ryk6BC_R#IT2RQSxCwN?gV9D_^1ceK2U6@{&0!ibY+uG9be4n>@`d{ zO}2}OuoGrsXwn+h_;6!uHg!W{RFp+{2S+#W%(P=JTWL33T4dK>RLs3f`6j3yybxoN zn4BPEN`8G2R58jFm4KB#9z^&~QYcVFUKwU1R%dT44$+l&yRBZ+Yf~v4c-*0-6gpHh zzA;DnfAfn)BWv(c2UPbN1>XlwErtTNMuaWa35>>b4#__4{V$C%RTb$sH?PmtBwRvj4CWl<)QvpD!0>~GHwQm`;= z$Is8RQE1zkufl%)mre@771;3yP>Ute3ceu(Ll*^0%~jc3BxO6?N%6N27w*=+Tq~iU zR$*3;Md_9G;N!h^?xJjpOTw^d6M$Ib@LUQRgYY-CnHbNxUK}+-J<=X)4UT?tMj6}T zAou!EgwhmxOCcP%ePsxxtjDRl^0PlT+j+;;*}=1@?~`J0IDe`eBLbp!3O;CF%Fo`c z@Y_?bbh<{hq$^l0{mX8;tb(SXRQdgQ8^Q{;TBuc&@S}j~Y-kL)qNOA$wn(TLvbXiL~$|hti4(&gFK@mkDt2tHeZo`vUH^noYby(+1 zYXp)CHE_x^`=)XdQj9=w&Lw-ro`(={6in;6k0hL^N0cmHlsF)RDG1+jp#~E6-2;G` z#?+bO%sAyzf-(2>M=NR0N!yM>=oEp5I-^b?mxYRTlPk!N6j<;@=!i(CYTiNKySPT^ zg|fVy>r>~7eN+1m*JDd1E}&mhtd(UY0Z!G?C2Dx(FW_5z#?pZYzeVqe+E*z6C(wb{ zveYGMh2g|k=Pdvi$S{jpg>~=pSy>^%HKhyQelJLcV;>D|YmxD{GN6j^fp4Na{KJuQ zfAjBNd+lXmMK~;Mk=CkJ;cuq?No$ASUU;k7F8k(8x{T8HsvP|bgp5Fx^a^F@==(UL zDn3*|4-pY#1RY8|$4Ipd_(jB}nN>n4w7omnq#)NCQWl@~?lej9DW@MomL&G6pp$V6rNF7YSo>O|-!$$L#onD5@nQ0&@~k^G7H= zhx({Sa9KeR928sCTwwd2_LV*OtBH2?w}3fJqPcm1%C=G4*-zEYic*S})zF!mS}K&p z&!izF>Hgu33l_}TI?qmPO|zE3VI=BZ{3-sy#T~v ze#pA|V|M#rMyPWUx9^`>%{*4q3#EyAGa_OnOcpT;L`nI|~>)D9u-yYZk0WtA~kEbv6jj=7{Jy;hNrqMT|`;`7yfrW>R%E5}^(Y zbuuI{Sb7p2k=Uuk_t=SgL-iq5LPGwY@7oM?> zu?N_LDVCArx10XB)xLFX8RNmw!<9C~*%%ok4-AlJXMp=?m}2*%G?WriI1_7+5BLTUQ2OJI_Koi@v2)ItYTrAn$%521QDxzm?>U49cT{5L%+d-JI9AXv z29U6?8QdQX^>ent2^5DE?Ia>tPgD}IkAhO+NEH@X74=Kmk`1|b+LfQ%35SidOV1r| zpRH`SwHtdZm$cZ

msk+9&qtZ42yFDygQEU$V3axRnhKuy^cvly2>rRG5_#5C;#2 zM-f&bJK=1Y18Y!o%wV9D^6U}1f`d1}CY#j$02Oi7WHp&o;k{x6$qX9=??xybm`@YK zFv7$j?G~aW2;?IF1SlLBq`uG64Iz8<VE?TVk8(cf-L*chtM2`wuNArs&5hxG}&LC?y_?iQmwbCn3`13BdXb6!n_eHM zd7{zUqZhp79E1M_rMMD9+&6>}2*L(Q2TxLXPC9)!Sk06&saDIgfm={@vZ&S4(p6`F zc%;*A`SCS6LHdag#l^}Jjd@%et!k-cL;?Sl8-j=0q;n8-43E?HeV6c|@G0-e5Z?2m-mO3AFK|&v! zN(g`yR0-vBgHc?P`}_!%3bF7-C^jg?8_Qe(K9WwbM({hG(LhsF)d^$nkXV(Z=;*4j z+wb3DzqlGYl-_2uW>?uUmu|4xv&w8veF^(dL00x!))<*2{vEHqR&M_c%I#08Lt+%; zMk<&1q4&E59!Q18k}s0EEs2lz3phh)PTS&-P%)V&wQL` zTXyBzU;o)=uYSn&kvtn$Ut}LG_t~|#ZnY7ZUIPj};J z$R2+)U>kbMA#5G?&o_o34~4Fbd4xceC949MRg+ZeL_nc;(rb&ucGr_#_TszLD}`W8 zYAUxo?%!^UPJw`*lxg|FwJ?CFHIzZnXh_%sE&O0rE`%VHW@{Vl?IqOF$j!BCN}|g? zwXP-C{`}9Bz4Q@1aTe2iqq>luMOk!)joGgt6aivhzS`brv&ZMz^3_RPby3@YN-w3> z16EN}YtOv4%T7Er8(}fi-d~w#ccS^cPRHf3M3yyG<9fn$xNLI)bM8PZj?>=VfE9W& z&E}qb6&UuG*~9c|df^QU)Um$}^|=VCXpdHJuYF2Cp{IK7wU0-zhVflZvFl;7jxNB@ ze~^bTnr#m}+k(~xLC&Xt3jLA-$vk`ei;(@{sV@81=RVp6(C4MzM=z6XTiaG-zki~i zj=6)jwJk^~`eMt134exL?^l28x9Dhw6_)2w@|!tu;H5b^iwcJ`NpGl8|2TW=<1m$I zTkV#6TkO@($86e!5|~$(-TPFtHBP9p_dXxCpZs{F~rp8F5_@p)WR8*>4{1v7g<&!9HDvC5-Bx<7%;L!0aA;am-%)D1r+x*RHr> zxxMrOf`9IK+Aw6q|7)tDg?rr9a;QerC`ZvzYm0+2XHK_RJf*?1n4%vx`nGvg78_NN-=r_DINnbze6< zXyVpK_wirc*=C0wl4HGth4#anHd;`ISL<=7cE(C@R# z(lM(mMQFszwR}^yy|*HZlKT<+>(e{!{bha{PtqB>IM@F9cGRx^YbT0CwyoPTXcH&q zQeuAq*w@;7%X{px*SfJb1ZfSBPZLM86&o@+b9uI!K3zY$ZL{su5VXp=GR{SY-Syaj zz5MYIibn?57t<;vYAsm5o_#OdHtsC82mZ0uUj2wR9Yw{^5LsK(_UhuO6~PzFi)f)S zR%=hbnX(7|*~b~ALp`ky@(L^Lm`hgJtM|;I^}v{A`?t}jhu%(<=SMELjoTx(bdy0F zBlhSEJME({X{K39JB@r<(?*~hg?8@?Nt$N%+VXX2T8osR^kmxY529F7%70r2iXXk; z>gtN^?N7Vu>6K{{%QsnlO$lA(^K8fVUfYkRq@xssd+z;|-Tic@z5h9Gt2|m%&_Fea zC8)ip*8cM3us!ldx9#Z3wJkj~E$!&AiS>o{#>Z)Tj78yHSPockOK&lKm}p_pgy2jU z@U`t_cE_W=_Tu{kv@Y;jBlA%2ww9g}`^!H^?fG{Gp;ai%S=k6fIrj5AH(3YgW*NFh zQBln8aU3DiPiOm0ZK6*>+umPd|M9y`wEQserNTa4nPI=>883fIyMtk9$2gkT2HPlB z+5}xl**#CU+0xB)AB)%8iWU7fh3@hj*|+E49f1ZBvlW77rYh{QmquvkkZ)z^T|39h z?IGSj^4yRuU459JXnZ(~c}hjU25F(bV!N^VX`4_N zu{j7SH)582@zZ>J{H>rp{Yt+bHNTX8B{|mIQ)Q366|&zvK&N3$hxgx5Y+sv)l>!NR zES_zzz@(1df6%_ZVA!60r^5zEFoYq&$C9OJwkvJp)-Jo^oN;#M*J*P1-A%T=HHDu( zV29DT?TCHicHxQWRk(sAh5hcn#a3I|XFt89(JFHD?3WL=VUNhCl{@A8f6{EvzOlnD zzhnaaTOu?h%&|FVZ?XgShY(&+ZRG_)`;R-?>He!iYzRnC?6BA0#?*Rp1zJO$U3v|~ zYB|zzq}*0+_0uB*sXAP0?=1`4Wk2n(^G|BDAD^3pu!Y`@fB4q>e?FoqS}_PCb~QV+y*(v679Z3(+^~>0y#*pDu5=i3m}-^y694oNYh; z;{d&eCfQZzHQFODCGGtcG$e?Z+eJTIWk(%0!+v^ErJXdN+BTV(LD6{ccwn2o`hFX5 zt+H=_tJcoCeuusP1?$dkvbQ1D*WK1*=bu__-#Zr}VQ~^dk3|R#a1^cdJ2!txx7$&> z>iltb^uhu=`hu^lcc{oV?~2&BZymGmoITsFIy;{pC3IAdL}6mZcKsi^Z2Gh+yY7m8 zEE>(TyPg?B0HmMHp<{O6wFn&`;ji!SrFmq8SdA()sCZ6}>N-|l&Gn?3OS7))%My)3F2JpVQf(=rYF!J zZa%@z`PQ)rRqO1fcfdPexpj?Y+r!Vd*?CtjwFBlaq)$+z{qR?-?6`~Gwb^qIf%!Ms z&wszsK3|)}%2aNje$mevRS`t08)Mb$=%vZ$zjje8#cv1CPT3ou;$BT-(ZJQ*KT>4p z|8xb`u%zAa?Qt|*EwU?aZMD7_Zoes%soyt`2B0Y{!f}fB;jToBe)z>TcF4Y2?(8mJ z?}NUfakKw|)uJobSnIZ7yZx#L6q=xYu`bJwzqrM|fkJuJ#kIDL2B7ymrWOp9#3mHj z<-b@-hx0D`@dfpq`Eonzyp6VDC&CJ)@?ZW}H%(M=`$bCasW*r1nRol_vU98K%G1;z z&2L+F`mDC5&ZafuRvBG|TaVZVTu^bkzN?yX088>;AK66A)rehnNrg?BSOzZcz#0WZ z#yaE2o%;1d{C4eS6M=JyJ&gjCmmRi)=jPLoD8tS_js~<-v2qa8CE`1lEJhsO$98oG z?VS&s5vsGWM1}25_WGp{&Hm$(X?DhmEpl)7N~2_g)*r z^8Dwg)+3aa+YO9);qd`0%%Oi=*#P{gi2hD~`}U%wO(+||3eNe*Ev;gB(Rcw0!Fu~> zNr&A?umwofZ33lxTP2ge_VSUg-SyRE0LA&G0PWu5g_4I`$cIV$V zz`L+m;{_NQ&a;1gM)oJx6|bUtmc8>u+Pa}nsbGmc_I$5((GmP-S5BhGS)+aX7j4!Z z9cLe}h}ui33=Zhao)!^;keIj^2uxIPlNl!Isds# z=5$QGLD^+-@^Ca@Xlh`GkBk?h3q*g&E?iV>#~fN?=MW=s%O6+R%|F@)?TSYA1aKXE z@hW@ZwWwWn=`=g*7_0|5U1lR&Fb$U2mX5fs*p@>RvRo_AlV==X>Ne)2-WphcrcNue zb5AO?eJZ!pOwjCm=hZ?m6KDW5Y%8R3Te+cOrRXk-)qqwvSDZJ4cICM?zO0qQ9kz~6 zx)F1pGBBl=(`ERFFv|JU1`udM5Un@|h^BjoWY~m~O?DAY6cZfc^{omR%%C!*&n`c+ z!e)*evUyWR?ZG=5?6r4BIq;d5iRPb^5kdaQuoocu58hd82hE7u%(`ydcRW@r+yMS8 zuA_&J;FUu7l4(^wWd)kaJg{io{ zJ|8Q9*IjlzmX{pX+K3syL6#}@CxY+uug~_{t@OFM_{PmxmLhif>E#Gq!}jj7F+1t7 z3Ob%n#5T1M?}JO`(E0gRU7>iJgcYI{eft{~GpMJjCdWJAPL*VAks3;iH+k=1E0h7e^ zA8)ft8VH`VsMLON{yui?pLf`0H?Ok+v?K-C0Z8_OphjY@^t$!(TD#(8w4H0|@>&x^ z=$5cDY8QNCn*IEeQoG{x44XS0p`pizuol3D60x(sKAwXgLD)5$H@m=^>2f_ZJYvTl z?6*bxV~&jg%abx-M#ID?5qs1=YBL(-5`jS{5G>PFU&Z|wK!94aEr`IFu!ny+#eQ^l z1ZD+t@_O?fgrjIC7B!EU-1@y*yXGvOeF|%4ukZdzm0k6%Fzc*#@B7j2RdF^ej0xEa z>{`aT>ddg+@SRDva%G$C>_phbVy0IHO2zPKj=k~TE-Nmop^LQ|^U-MXlqp!6)?uC= zva?UpxG4n0fc@aiAQmn4bWvX@tbE|aDEI&Cy&aZa0Ry<8-2Q{P-+9eUTd}&!*0f^D zqW4t@^Y)PkH`-0#F0w1n2-z9O*4c_3baL(=w3$u)_SEmD+tMvD2>wz#?do>>=&O8d zz;bZLu`;ix>}vPBQ$EH-nQU4&;(AV1S*H_frZ1!UAq&}<+ z2#dosw5(zo^wom$xF2cX0nu)R_Y`iD^G~R>MTg~BQ|UH4_P{Lg!H<;&CXQx)@P4Iq zWtU=0v(jl5bk~Lt;4ZGO4qE}0m8T#ORnxdOt%7p6QvUEnw_X3hh@E#`v(1@RZ%59< z9g2(Z)%RQMgrjPR;R~>TId%vZ?bU0#h>I+?hd)GkpmX>5(q=nmo)JS;&zWYsoXZPW zWQOd_`hoMH#8uI})aW<2uKmdj=Kuge07*naRGG6M$9gzP@mg^jP>!Rc`3P=sI=N%5 zTDxt8bCJffpJxY5j94iRIc2pj#cDnYMXnI<0oo4pR!ofIN+2j%)pq6SRW@(pE(G4N zeY~vQ79Lb%m1P;gIc5tFt+m(Q=s@`@RK0anT|dw+jJrD&cXuo9?i6>2;_h;AD8=1f zio3(X-HW@s73UxyzxTc0UF-grwRct~JITzRJd@1xb=du9>vmx8wB<7D`bH^R=k0nZ zgM)l3$8Ro0Yj||fD zR4VY*?zeKi{tZnN0q;N*82J^NiHOUbQ?hkjDdwqmaIyG9nky59o_^B_7<4RPW-(vs z>j>pa+kLs;V_R%wR(a)-(|2J{)Im52sRpZmlU{=;>1k#btJaDXgCtO;eNFLor}Xxd zf$0jdO(g19UL7IxEK&fiXr|FvBvNqQU1_Xz4(j3K2uQOx7ONEP=4}bkC4qL?v;b6eE{ZW40>VmItlh?*lckT8Yvge-F~Yio-QxXM63|)%oo>_-nqFx zzFS-@S@(m<%i7;Ht0xl|h1t|i+`IK)S@b;>zKif{p80wf}PV!e1BFte3F*B{sYvSiGFAECaRR>cskQP z!{@%zpyOrsBQMF8YH@^C^`YcQ*}jsNdfuq7^3TVH4~NE?Ffb3up&20cpaq5uD@wrS zWho9Kq!HP90%9c7+ZMMd#TG_}z71b&d}>>+>%v|_1TT67p^`J13t*Nb3!!BtLv0pC zS-$?YlcdeFksN~aT7NZ3grkd=1}aytfI;8<-sMXs*4FEr-# z2N+uHMCYxPcM%f3zyYG6UiKa*XP6GUxzoV}marp!E*FkjiPgbXG>*G8`5T7D%r_=w=80ekZftt?5UiQmdbg< zW*~$Pg6kI(4`KoP3v^AlY)r1`1#(+BTJ*S>^fjxTDDH7g`!RH+C#0da0r`iLo*Qe; z!138d3hKxzcCY>y9#3PZys-s_M1@5s((>(Se&DCY7~L*M;|oV)bq^STtT{tFi_QGJs5l*>stIzI((L^T$wC2c1M3}uAslI)*A~OFic~RzBw>_IWljgpH z+ZK2rreum~sbAW|bINM~l`eHJQ+`@-{fBanuk|Z!n37DL7gx zW{!Y)&$jeNH#iJ^I>I4U^^LjrXqqgU@G<8Ot7t@1h)r32eI7ex0HLr-QE5z?c0uVW@GYm zZi~`Ve;3o{)&S}KuCWK%K>i-@OPrk?8?9{3pNhz#&6uvmE5o6$vGty0#$dE~SyQ4r z-!F~HTN~g-V;x$x%fWFyGg$%X{bVPIk{m0XZ0WEwbP`@l?3#cStYGCi%^Ox3X?y*c zrc@PE35ZBNWn)i^wH{(S=>_bvCyrWS2db3hA+y0CVE0@$wKZ~AC zk8K#Q^&kUp(Ig_iq~QveSn7pGyOiFb!)a zIcJxW2~n^W07q?<3?Uzb#XdviO@xR%dyF{J{ho_wUSwGV9*h(sJonH3ZFr8o6g4T8 zav-)o{1WVTmmGIikLl26x|I z*d$UCJgW$1MXKM11_?{$|HNH)ykawTWZc+-Q&dHt=NSIj}X;Q3yTCa*y-7so$7k)AD%iyrbs28M5-$(#y ztU;G0IRWRMID6p{e%??74;V>IlhnoK zJ(sD0{xtWY^x9P}{+<~!h&NfF#zA9T0UBa9_W1{6C#DMCU!-?`yu{~t=tmg@n)hhn z(DREJ1j{xqf*%SC2fJIQI&r}4KIs^|h;q-$?=&IiKSG@$J&sQdtE^Q)EDz=nJg&j= z=j8wU*dJN3YwAA#yuq%S=B^?b=Oa~*%A-}F!^VT1>#GjWvO7v91Z|3UKi;NMd2EO_ zY9Ey&@BNzy{%-#3SN(^gJ)2(Lr}+k=*T=#_4&Bn9-&#%$ zk<^q^4}H5op)c)exbr)EQ`c)K6!YKW zS$b!ZXyUl*@Yn16P@!%4;k&g-!OhdnAv%*}6H3UZRhEr(^ytMp`p4SaEWHp9M8i5D zjCo1Q$ZC|vM}wdNs@zDzLB^dHxcK~4y$zf}{6$MRwFJ#0hSnvGwt_giaJ);^s&bf= z15hO+dfK%TRD0B7ZImjxZ*3d@IJicjaE5$*@1GEvX#Oe+KplUdH4z-l^8ZX#^jMQ^ zym(qx9MZ@n54SIgO<156EQdhU0jzQ?o6qHNZxt2z);LI6#Re>)QSP{=lNhPC+pi_s zPb{OZiMMiVE14sTDMS&u?+}7UDzh$6AV&u77iAJayys_B2d5r8u&i4B!9K_p3$8&V zUOaWdfPp3~xLC5GMS+AY@RlR%YNN&G7gcTrC?0hiVh92Se#M|3lEbn*ygKCdNX0Q# zV|u`Ibu=eA6%R#3kEVq-HLM-ESi5N}yNUatCq`0&x!@q3%vT+E~Qm=te!s`UF7YE<@6;*IYg!S?S_k0A;8doYKjcrBt5z{ zjM>HAmr=&}#KWCaLICKwEgPcbwMyV5#*bYe<__x$cFT>iS4R5!v*m+Q;TnEM-9MbE zFNGN!TVo(b{^$)88TEu?-QwaL$^Bf$L$Zv_7ivI3(}egd;eGJJ92zF4x0xtQYRN&D z5lshpCMQC@WfX>b3Z*!^Tr;|ET7;UhKeKB()MFRDPdD(;EqXtAZ1&iehis{DOa<=C zL1en3+Z8XGq!I@iv)fukpDgmw=JD7blKzg_E27fvLO!45n5k-vOM7fIDuzi+XecrZ zGVTt#yr>vnu2sNMcu{5NkzT6oaH7P2dxL>Th=UcpC>3rDQzbognq0HaBXX>Pr^`T>d%d4{c0zrY zr|??`a|{Wem|{x6&WKURWTvZUl@I>jb~vxsO@k)J6Kg``?^Sots1m<`!{SS)$S{|m zk~up>ZsR?`H{Leu>oOHgZQh4v#O6on3`%#yw$QqQTqP%w;T&7N6#Son^{u437EuLE zs3RP6pY6Z~yOk?Hr=<6ff%74A-&wn%~^e#~2JXZ5Q!F?mtb3V9qcnUp^ z;!;-^TOZb0u72qq_CJ{855hSq?deT7%h!GeKUW2G-sV~g=E699zgQ;~Er6r$`avT7 zZeVJzW6fwJ7NbIt-UDlB?NsBHSu9Y~4G8MZ`rxx`eRr=U;wwuHI@AxP#Tl~26J-;& z{j0=R+PCIu*Wmlnvu5bS0?u(wmeRI~4$=>%fABe~R_eUnG7N>Uil>Jgu?kITkmX)w$YxL7ZFmkE54(Cz@N7>p75PQKHUa^ zPvx6~-aauwx4(LHh~rHBEB1$Ag;KEPdOdS1dJ=joYF&$+ zH2?;f4`tRa=|qZt+~0mNFdrPAC%JSmdjpoi3V(h9urVpY|EB7~!3;AECQGgqMF8dS z1sfglMB0jND7XtH&$(U{EPA{%t=r#r)Y)GX#U0j=z(7B{E3ePQG<9lGYo&a!Td0=Cj~6#RCGP@7#WEA4D;OYGOM$QQXRvJ#ddnN zcR$O9b9wTtmOf&D{C;=XO(>!d6%OfYN7z{vX7azPz@WeGMcV_Utp#m)kk+oR0`q2q zSX{@cBOd_A!*-jGk?!|t&JA$0S3^XhV=RJQb0~c!2Ae8&%3Vt#v)J{G&iEe}tt5LW zzxJ*u+5#+U?QOsHF=4j8QzN4xF4*w<(3n}w)Md#|!tKQ-?dWO|G|?D_8(xk!DDop- z&f&JMu{tx-G5$!RIan_vv2Qpw+_vWT<*?I}-$<}_a1>?lzYzsY@HEnm~K|0^Q=JSx;@SvM*PZ&=${83?NAhGlkp2(z0i zRn>c0L<@FL#Cq}rU5Z~)V*VhD#9G_V+X&woX;O8NbAA%K-OI0jrvF`8nZTQD0t#v$ z%T$kAzZf|lxv1%7qR0T#-4gtw*jTlgLnpZc5rVJ+mgK7o36mqn30P4!rek8-FPf33 zbe7h&`o!4_QYg3^!}Cw|Pa;E2j$K|o4&j1Ft&H znfa4;s-v3iTrlofbQA6>McLnrjV=cRjWjasrT1o-Ma0q!7(;QQ5HqX;f-52g9R5hH zF^RkWp>0LZ;&D-FMMrAS!%j0E&N7to6a6csS3gn1+dJ120F3<2Ahj?t-q4a`#_j(` zd+Kfqgp;ujfuG#g(RtTY$p0eV@V%(>9-6cVgKtFj>zfZERLz#A^uCMaBIk^mGrkHM z%dmE5wMAIDq%{1>BO5X1`4=H_^wZe8R`Bj8;w>(h9bP#BalPz=b&xf$8Pw~LGx}r} z*)P~p844x~C38|ySSQ8g!x#JAU9Lw|ltl&-bjHZK8;M=|loZ-_@Wd|^!L#rVZ} zDKN86|MO%&{#{Z|d{a@6!b|L9Dto^vxFgeS321QN`*uxV&s*9_-k7+}s#4zAlES4e zy?JHIz&QUm$w}@Ov`ThJ%R%PfD5&D;<|2~orYwBCqnHFzbrR6_VrI-eitL#^XRY5G z<=KRf)(@Nqp!9cL?W>x6KkDanTapUclOVpFsvp>(F%07$(S2OIt}mu{a7d&-K}txg z!8*{7qZ9m>IVDApFCFEV>RAI18IObf1COXsO;1ASP0r8Dz23>ea?%&wi}>bpziE^# zehGeRw)%tqBJi7l!;-fn^XQSS8N(!8*grn*i9V)@bnZ9Kxy@cg>Y)$h2ts#8n1cLslW1sB;{-(h+Ge#$Uphv`@fO$@`3{ULH?mOT&v*6UWe8+UTY!m;N4g4$3 ze6@l9-F$@QI5o#zljjF`}_uKcBX~;&x&NV{Z@Pq2GXv<3cD2%M&%>} zUs*QhYG&NL0>b7~%*YoT3}=#-7NLgIGmeY+RiTL)ROiyhd}IS!M7l5U3#RivnUUdF z(;hXFwt;_!{pyDO`I4h#9m`O2<{&g$D>MirORb`D%@Oa)Zg|jCUy;h3#-WvGThFn5 z2pr-zs;r!x84Td2#Spwi!*E%(K)qIQC&yGP(nJ=D!Tu9+OrPJBc?l}I-hFreEH3;q zay6hbg6t8=M2rM+{X<=cqHRSXase2GXGnLeWVAX4n`WvpIE5jepC51PU^y-)Z{8fE z$L1g|?_qWe2vDtKPaQXTE1n_?`NL~9O3%PhUeuH`i4>|ay|=SMyUM*IhMsDQS2Ov6 zA$O!SoC?;Zk!HOF#Yoi_rXf><&^nfEGX%xxnW(yARq`S`KXK=L>XuZJ^ea(ai<|D6YogjgN9#rk`&Z2 z3MrHNODhiYk(e8G$X_u=Lk*d_*JlJ!kU}9%29LKGx5XQ8sw2abREt zZ%G_Xl*NZC8s`C3IicYo5@9FGxi@fU8I^vLCw{~v>Z%ZmO z53wS7JJ)Y)_HoU3*>I`I&lm_HER#Bq#2A=JNlS?i2a~VF*96@c-si?|`Tl$Dy!-?`dedA} zQAW~HhWaLguG)_;8XnhdYnvZ+ZLi;}8P?-%44^Q$+M?&TNFGuc9 zX>igMNJ@-0TY~Whl4G;rXGRv!_9eQ$)mCuo(;9zR*&BOS!N}$7w_W74F+!YzSz=&U zWf(P>*HbHH7aJL{iH(rU(p@q!;&No@2J@p7M8y+;+c{qRE_t9J)nqPsqrZ=X9 z$Eyy>ihkDqx>UzzELS=Bd;U@fiDQRc6E0!vV^kpkJ(zx=2Zti$lV_-bUJoC`I{U*4 zQ;+SyEWHj#2=Ki*vbP7rezguX`I{VlQS$9?=zs}3RV+krtpRRs1U9_&R` z^>=Gb!$gtm15n-+J4nh&p|{rh(ma={Az8L;fjF5+kc>0ow(u}sN+BVr9l?*3>=!S< zvoL0b?UXi!KS7nDdn+NJ>7@b$tQC4F-Ei$Z_sfun4@905{$pi$$Sy<%adB);RrAzo zg+;v;8ufwn5_a5yDot3s)3=}LUDeQBE8yCt@`0vUVF6BpRC*d?9yh0)@-di9ta6Wu zGA25vsL0owiKPY&6<8gVHP}+%mkkk8!O!8GY~0+z&Hu_-iuw4PHiqOLV>aH%&`Q-sCCbB9>AUJWTt31V#q<0flR_uPu{pR{$O zv<%*BjP&HY_pm&Vvqr9W-yp|5U;&rD#ZC@wWL=yRwP+V! zFCzTgTa?uJV|ZK}a)GP_$&CkE+~U~$t}5UA235x?&3<}1+QrOplHm)+bZy_JahfvA zO$s5+u5+|aFT`Pg)%H?5p~Y(Hu5-3b3{bF-H!SVPe(dyIuSb^mOyTVDY}(A{AE18} zGDC98(bl)c2i-Q~Zke)(dB+N4cx!UF1?Ld>V0RKDKEu5BVsYW;Jt(iO1>^2af4xZl z^Ma{Cz56;2Rzs%opzU4ud%2`(kNiPFEdzK|lfb+>bz4h9fml3Tg%}~xEr!E+!aEa5 zARFUpu$k<|S3JBt*YOp3!Yn#q8*KaTYDA;0_E)hy=mfheNHC+1cH%;QaK&XK+JH=! z8U=e+FZMk=84tr+=YUP{xUop}pw4z-nm)K=p}?q*&f!h1p7>lt>48bHV~>wz)2pUS}9_zix`{*Jx)r66eN*!wzr>aM%g<+p3aY zVO6nPCGopmiH``tR+Fg97{08&4U&(7V!q8VdBRsb#5|BqaO z`TQ^%2+~n60=g^Hc#S3vY4M50SIme%5U%EJ#Ae&en3N=@r=Fly7_$V!Zxh5prK31O z4S&P1{^E=*dJO+^qpR=St*y8kLT4aJf-#|f7Iw`p@~>idaC|4)YCR_G{F~c7?9o}x z=c+OY)c{O5$S9aZugFdurPBXYt22d(f}PfErkYP%ed`8x)9d}(ea}`oe0gsx6o=e> z*xca?f-mT#L_f8m^MT!TnG(T4RS)5IAoG_niF`|4TEzM7XF*%W>XeE z2P8vpIvA@9Ibk$h20h>0y0Q2bEj7+wEM-0goVh#=ylDwnZo=x1tbsnl>3OF^O&rpb z=y{0jjth*FX{U49WBV}%JU^i(G-es3O2t*+6p)&2Rzzl)hLy_2mTd^bF6y7T`8tn1 zImbSV)g7tG>T#+SEjFzJHZ9EbiDIK2e#?s*RP!RxjmSYIv@SY%S5V=C#h^XgfTc6F zKXm0g8q5!|mE+qNpBi&&JY!(aevUC>dr2EgAuj?0hoQFyX*|owk(o`XC}b8!pC&MA zYEM%Pi0-sPaus}W&RoE%#_xZCzvTx-BudWN0(2>5j820F#IcQHYyIKgA=*{cWq|34 zpE@X02=yz$0Vt(GEA^Qm94(KM-X7WGP@z2j2|d-=w|)H1M%4*f9XTs}hg%fn!BVi< zc1>Fl=~VAa8TGkMBU2X1Urwo9;$I#^;yw2BNLlvo{?w6Ll*P}&Ui+!Wy&X*Z1f~OM z+@g$mPyUtO{u``?-Z}UKrX&9o;(fL^ zye5<@+)+7K4H(~_vPTXMgQqBLxCjxq+c_=}48)bdUb4SId1o{V?w1VmRG-o})kbUC zvPMFr$U$WqV2ONk)<;n-9lxK{qJUT|Dn2lt&(9q7zolu9Q+Rlzes>JBEd}z~BRL&v##}irGc+__}@Stlq6mLdr*8 zisoDGv;w&MtQvmZAF#0PHa7e;nXwLS^)ek;sV&u~hS=2ZL-0_|xC6#vwjVEs$}gq< z6>#X{9QW(f<3~eG86vm_-doO^tgKdLd3f*lg~7Ss!5E`HLv1R=C*I^C47-*h5CUMY zV{C)D-$A38%F(kW5IMT=_fQSMQ2L4Vatr808ZT&b{eE}gz~p*Ygf`9@Lp`}I7Xv&8 z3W>1-4t#n|$W~Pw?k!4jw00Pz5tIiOZno;E+B1Y-HCPNWi_lNqo>|m}>0Mjee(;{hwS`81O#f?k6x9Vi zf{qu1A1Y~Ha~~>!LRNwI)zr*nD){OBZs?v$W)!^WfOr}*Xtx_W&W3E< zmTL^P>LKihWrG{LAl|=>sJlc|i9vo2SF0O`m;hpE6gKN1+#Vb`4^|?sq^%eIw!BUy zhfxw_xMf_nZ)I%io(LQyqX}A2U(S-QHJnqZ4UhuwD2MT-3jWwS-39`#4#P|SBk>k0 zme5V1oZ6yI0kdv>TKViY2Oppn6?&&EaP!8ps|_d*5Z@uvF8U>~BXB^IlL9#$hPXd4 z{Ttjc4PD#Dx(MI0eB>RQc`Io|`u8{XD}9QWDPw_MH|2Q@!JC zS!29oM9ItWpQGFI!tLEcR%4BaXimRb@4C;O=}K7!Y*mV0H@neeG!46qQ2AJJo6uib z3nd+IEP0U;TOw!8)2z>p+>{dU(!E0!?sK+>amAAnmFGg09o_i3KLpLMCk zQat1^-$O!6`(HVSnp*h1FiF@SE-rC<>v<<9EE8=O51@#-p$q7lTPjGQ;xZ4z9cq`i zG44|&&s>!Q#A0NgpjoBPs%(P%nZ(E)27GY`e`4d}{=36?eY}P1VRW+d=j3tP4wIFa zK{iMqeGN_YoweTqr>pjg$B`EU!d|vmTXu>=83Ivk#82NUP@nv@IaCBV-i`khdFf3m zP+oPGHw$>;2OtsMV#|3rvH#I}#x95aFagI`3h*G`k}?Ad#E#=%5B0kQZ?8V2GQste z1vJe(z-DK9K{6g;t3V0QDSI8^sA|$KJV;_0x#-Crj2T)J1WwU!R2JBi?aVvA4I1^J zdL|#%Uq}!0UmolikyIu7=5v~N*0Ihq*;sxs&OH{-zGEDg5qdfB7V!O;TnUykh!Zx6 zQH%RrB^Rd?eSt@}02h`j9igmki=p~#{bwb!iVU9ER5E{a3t@8BTC-^t4!u?sDYDvy zn#K8V%OajM`CL79Kfryb%ZQIKAMXJ9+Ha2dy7(%}lz_^5Peyu18=c2Mi(@P-*gNeO zYW5!AdfquId>(-fJF^XfsneyE^1N2)W6a-ccsqz6OTWrvu5CZX1gTp@ICHD&AW3Kx zJ{nN~VhjHcju?zUreHr&@+J;q;a6BoPpP`2;6_Bm^E23)xu$wuVnoBGV5fi+q~J|j z>J8x0x`-z02r+zF`F&~Jo>95GiZ2Gy>7Y}9|325ti)mbdSyJ=Wq!F>rr4rNzR^jo9 z?HskK8OJ}5OItJmlA+2UQGVxFLPvMVCcpYw=CbwS=1<`X3@Oi!6v+|lC`^2va1C!` z-j;COqXFV<6TD|#?`O4TgZB>Dzh(dHzkcqPrR;fK2-V%E1ShGPVB#sV2!m&1zYf&n z(4*ehCEKYFwFq9;H$R)e^&hgxoZMpwUsrz1bs3o8wcq%7_C#|1ypHec1%m16cZ|(j zygDXRPVL`V_**^tYI8gP7!)8ZSldPSHkLBhrKsNg_jd-#I5ZAL*62^xoWPym zZRBW%bQ|)PKhU!wPuo9*`U@wbL7v9^uV*KaMYYgbyOEAngv0R;0XW|pksn1?bK7vGLZkgfxI(K9v0&>sU_Gp@h|xAqzltGkMQB&aF{WeT z$;rFirh6VcHv-<;vsD&?Ac``JcoN`ydSjRhq9-;UK~wKiswN%^B-*QV#!+@eUAou& zh`Z61jI;pJIuynnc8XFgY~r0eP$UnM*QmYGcB19ZI<4a8wh9zop=0#6zNOI@Kx`nD zU5d*89yaLdZ$|ls|Gi4Ln$)oV%-jY@9`P~OJLR$jeM2x*yzVWY_S$pBrOM&hF zi;etVXl*W%XB>e4XIlxoyds@=8iCJ-Xmi(4@iM?CZKDhs}nfgutK3 zZzES_oNv6@Lf37a0loVDUh8-*M3bR=_0}bQ^-(RMtH?@i;LOzPrI$Yt2+9!Q$6-~Z zb+=o7`Hv|VtOtSlVpU08es{2|`O))83ghP{$j3nsUuv#*{EV5=RnKT#)awGOL z;;Fvaw@gra_EPB+Kb z=!AhRy#!xoPN0GuUr*43{qR*4!6FIq2M;(vUKaRByde*~o)E7Kl88I4shPIZlX2+xFI_1E@eYG&3(TVdd- zCPOBRM5SF!Xt95oQlD*E5t02M40kPPN`~RTDdlrq$dz%k>JAN0qe_HVu|= z;CCFfIb6(y~UbYF{dzHJ&$|2N!l1C`qs{#+{jJvNo>>Ote(U+Fnko`<$ZgJ7G} zhw)xUZ@`YD!}3@r=bN%JRTI1LC(ss&>oNpj+>*0}^rh9zWzy45iV?XLTUw#JCh~FVr&opB!8=7Hrfi z5usde6!0VH;P3W*itdUtMoG=+#wuz{}X`347 zTR)iPz}7`pEYYDlTbXX45U7|>m;-uXS>lBCddbgIQ$`|b!r{aHTZs!aWuBp=6;Ytk}bIpRw8EPw(w(o!Pn#;JSiG zWMOGW!Q=@@srAQ%JmT2W?SHq=wa0Zs^YN2jYDq7;F1j2^H2;;D0@k@}UlIA@xIz!G zyp}~dsK=94H#S>!ae7%nhEnMh#M5aRS0DD=M@AS~sfiD%0H}7VCLljMr(t>L0t6LraxV{m+Lo|h_KIM5=^s)iF z)~mR9tUGcj8S=M(Uj^X=rOieJv|bxIw60qGJ&L3+q^f4giGT8Eyv)UMFg!J>#BsCI zZ5sX89pX44Sm%4w^OQ_bL3#C1{si(?qI7vAmhpt8Of}LRWvaC^VSm>H#2J_*G}Q6` z46w0~`o~5P7S6E?55@W#h*vbH*tXpu!9b@P=kVgU{P9nSE!LY|e@kG_tEq27n(+@sOFnx=LaRlRUn!*c0sj$iByps%{bZ!NCIf#SHng(JXWnZOqPf-7@rv z$`{*zXsN6mu_)G`t8f6)8IdoND@_ZX>0+#O*lk4`0G{mi_e!>H+`U%-@8tUYqcg4S zsJkc?P>u4$Ccec0{8jJ^s}isH+2#!tw%yr_F4WOi^Jb`I+NAv?<+GLk_n&Yo$OLkc zDTqTqkm!}E;l}JZB}E8Pu4!=m+meLFvxxAut`0fOKqtt15xg_VjQO9G1UHACeUyjo z#Y6*_-Ql(cQZk#$un0cne}y_+Q{Y+nKBB$O?IAD0b&W%-Y3AV#6{26wiC3D_L|(6f zY?SL&Qv(J-A%O1gOg^9d%5%%Teg)G)mw3N-{*Lf z_?qV9TK|u*i+4OYmv7fHZjU**QKS>M`R2SQ^_9FDJlldsQ)Z}h>Gk`(G6`*|IavRq z6tlQ5L&wE66@+#rOkh_r%+<%{F5`r6i%~NCBYSRF#oey(>6G;RQ{5A?k$&-yMy%h$ zKB>4xY6b30ZEb(IfSbUhHOr3yu)~ah6-fIePwVsB=+x^_t_iX*0tTV}4tz&cFH@R}`@ELs19e~TOUzK* zAMK;g-n>2(Uk(@UF)c=E_VpfyX&QJmRqQo#Z`U$$ue>HyJmhHz_*$d|h@T8b4vVR9duAs&7hA`Wn2cQ81xB1YM6{4ugoXB`xZ#I+5 zw#nJ-8$&qU=x?UMX|YN9|I;cfOutubDSd8>d6XV#RXMN_mZVGhGhj|DdCcoP*y&Ya zyXgpfV~PFbKOJEOgM@PRV!*gh=;$CoCiq+U=Vx;AmLYdoqXpL4F!EQ2lAa zWTEV?(!RwG<3R$0_p!yjJ+Ll?rKd)2E62DT0^Fv0xVE&Q3a;PAI+*S3k$Rp$kY6dY zNNXz5m-%nbFVKoifFRh+tYQ%nA(AeWl|S>Z`v z9rQyr*9^Bk-}c2=a@qVJ6KczLW4iBayH)4P^~JJvp)Y&5@!#|RYh%AU>;M$`a-EQ6 zm~j7Re*W+4P83KB3ybC527R^v*L=YseL07lL4REm{%_s?k#){mx1NISh;hgP@%F!~ zO2p4^JDqlaFXb0_9>6^JsONq}>+>kBcEclujK$1Ynhou)*-PNU`uqAstDY7l{>cye zAyG@a_8H*x;dkzyQD7`&q~P#A=u%WWz;GBS6b1;T$3vx4qI@zjdhyF8GV}yxo8nr*S=k!`KH;Ry6alZVn|o|Q;mv& zDq|eM-P7Z&5B6$#AWop>7?)91+6wnowzxo|0(93!z~8=cqy-7{%IT8)BsVYw#jW;d$zAZ|#Xb#8rfGSh2%!8|MF$PXe%{E>Fj5~Ozmabc; z_qPKlnyX!metzVPfz-}dvKHn7^sryXSzida3N<)NmFM~zt!zi*$<9ts;m6-<)O5cL z%$|;kDl})Mc~o2=_8sdXyj`n(N|L45?!w`!fUDICnLP@v}d_Z@5-T7qX96{cT6GXTQn6Y3T{O?;rW9$8jao8iF5qDfnnzj4KGaW5HoqGJ6nc3k|ryWl=$VYHAi z(k1m>?;D#we2_GDe5uFn7f`C({4-@bU>oE%av3w zf+J@?5{eTR*PU`O)JQ}{Yl;h}rz)JK9un6k`EgZYg)0Lf#GXxg$^u8cHn0D2T(rmY`xIxI(2SN!a$ct2MJl@}?F)$63y^0>Qjwrmct0nLq z0KmZDM=JS*LW-(b*dx9aNF7kq9;|h*nQ_j^z}(OX1Ok5;9=UcN7{4SJ#QeQ}vgL=R;Ll@IHvS_YCT{qfUNqEcAGpZOIP}yh&nUuFio?_BBPY7ng^7`xHnB zCf`|^!MH)vGSYiETF1H2HCKx3@gz^EsW-rFAHyK7R;_l>YO0J{tcqGzHLS(lMS22gv%_a~EO0mqC}#t;EaXgjv~z^s-r?Im=e z*eZBDZkNv(YW_LXdA&A#p5o!Y40_YDM)gSC<*5~t!%Nxb0 zy^W&k!%dYmp8|6{if9BJsBrm%W!AxPkgGAP2KH)!4-mrko<-cL`iSyTTs-Qw<+bMpX3xofAxI|5fot7TTY_hrOh`+{Ec}ipK!mRxmx5KF5#f(oq1!l;g#b*Nh{ZU5OuJ5wZbElOQYkq2LfJC8wZAQzJo=s~ zmNV(aKNSM{1C#Ii(h}5q9j*tV! z|4gItS|6Q)&628yh?x$RD$hQ`Uz5v;JAwDlhCPHt3Jtg0sZWN6KLZ?VzwN?ljPQ;dg~u7b2%oFtAYA5TDG(cce41(7I(=B%PHAZ0o!H z_;V^~VhjCj{EC1CZ6Qa*iQJX^Cn(_NuNw#A@vx!cE|HOgT}nX>3~vRhx@#+LZ#| z)T`@OF*VmxVL2J|;SQkp z{yJZIZOt2b*%NSH>%R3Refz2o%V2|-t*IiApJZ9XYQ!PX8PeZoa+GR5Y-oVl+*^s6 zYHboR`_FEOr%pR26~#H5u}&a#0+c?gh)Nm=Pk8;3|Lml)5&>Wn zo{6&<21}%@nAl3p9?gss%qSd4>+x&ND}vnVJh%*Nj9B0IUs2=YcTPX>iHAb`&d+@x zeWIuF`#6ouC0(e{rcwWnm_qo$An}aWxdKD+>u?`#cH5u+Hbl^eu^M2Bt9&7hG2@Bg zbvnZw8blkS^iiw(aTb*xml=OM&p`ad&-=flQvKWM_<%SI-1v8Ytx-AA;&tEC-+eGe zX}13DmPfP_!%n8X@Y=GiZkcJ@e+Odvg@8wIqc{S;bxw~HGe3HekC~nEnN0f~81smS z8J6raML_y8!z^EW4g*#k-4o#(cSHqUl|gYS1QLjNMl>D_qx;bxXoi0ESct|W&v17H zmrvl@^;)&QR+~Dju{`u@aAU7(s-UM*C+W$H6)q?qefh0sgd$x9H>Nyao~foXb;=~+ zy**vFtN6KPE|+9FrA$}Rm%mu5)bH*r=+3|N>O*g@(UegemF|Y-N$OajA}$?6fw-eU z-NkB;di&KkeZTk)pvY{B!y%oZy9 zOhWFMhs8AKOgnMyw6YQ*+<&c2xhVl_pbBJ#pH@iRmJ2bO11ZKi%#*l`s4~y+TskXYER+qGnG?!t z+P+}mEy4f-1O{d=y@FXYg(=%JFxuvTMhE(N4;0T3`+8-Y&~L`7`*({J(5Z3b|EAAE z^tKdy`{0IWe!ur(WsAn_+dt@_$DRwPCIqQzqFAc-i02+l zL$XG5m(^?e>XHtg)Qg_7hIGx!_g3rczgSJaMxA@sV2VKj@c0W8#VCL5;=rs!X}D+9 zNqMA0!N{qGydI++1P}*K9`XV?4XA|n&2y~6*`kvKA6SKKB}9PdEtqikcxK&^tJeQr z{j6@dUpis-5KSLbt6YLIU8h|`SV=-WiY+lE>NH-{BlYOKCVls+b-Mg&HYEy{lNuc~ zy-uJ1U{)s_(V}POtkiFA>ef3>ti)>L{^_PnLxa-BN;;3qAf&B)gjG`RE#7&?k0|92 z7nXl^T%Eh3UW?b1G<(02YSZ8hcg32{CVk|Ki}b((EL811TED5NLk~*m*x3zw?;D0_ z@-X1ekrw5z1h$MzFBO3c0uMYc>0oDG^Dj{V?@90jP{mS~l*Sq2oW3lvq}Tj4D6!{L34Vo&M}_nW7Pp8_ zX)?-ikuq7gL)}OwR3F}oU$s!qYd&l_#F{njiP*S9IOp4WZyZB6#mM?y8 zPl^BfO7g=0EK8$(qCf(d+sb3|_$!i5es1WZlKkthUpuX=L_KdXfINT`RT|hcj}2O& zjlT}dHd450G{_nn1H|3wDnb((&_SVat!50t?`WjcmF_T?76dp#F(AsMLpkI#q!!|t zp!^uIe&$shluY>roB6DD+q~txJKg|8@mDUl#vLBx(0Aa>mAY=XoA2QJ4y`gior2fE zUx2tGEYT_pK5!M~?+fTO^WeGLs!HqQ(A~eQC;HFEa)<_f8l;q4(&g_-TJ{ZVN2G z^*Cc}aElCK?4K>w_kU5)!dnkiYxQCjqxE5YH#gO(c}O)!I}D#>D(I^=Cn0$-O-J$8hSI(ULsukF-#u6RyW$+4;g z?izLwwrWyQfuO}Q($!mubqS%G>BNAwlCpr!B01BFb)=Gj&xj8t-2PIViiK4{vC1W8 z>A!!mNSFMyT?=kHRE^E^h(%(R7nUlE^fk*0de@tWu;E`(zr6Z+J@C*bJv?WSe)NS_ z4Xs!etUQ_YAUI-;x^o<(0bbG-y~-6=5?8~n)aKpUp>PS{?FE0(K&Fa7<3b0PoDRBR z1nt1xl%hXycXjt<>0`nql301L-lXl3px;v!^-O50!6}LYsLEt|mC9`d=iTbD1Wyd5 zz0gg<3TFlGO4D6d*)$Wz!ZP5n9K*wr8IIypLq8Ks1VL7$QFx2(0hR=N)}*q-)tyIK zhTo-tBb8yNx8AkD;jI*)BHEV>E*?uzyZKyCKmWF5G7F0)w*&Yh`6z(Twk|(+Ie-0f zQ+Ls|4Pe5fmj=i-fgr0b}oL3v^z_}k{j z=(2G3GX{EKIDY4xkzx{v3wM;_a)S2ex?PuXWt{p~M6{9eLE4}k(NE#MD|0J6vvGx}jeQz=^qWU}>jfsM{mXFf7r%hz_X#lu57?>#fv zqhbg`D5uq3E&Ac_+ca!QtENpHs#|V+u5hpARtp(x4vD|rw-R+i&}KmwM(^_dd7{aCjStPZ2=f&9ydKut$QEs z)!A>JszpoM_0wzD>MbXw^rj<+YF$@ax89xB{ZDkMt+PX;ht%n{hc{}*gbHO#i?pJB zKV5O{Cbcv-=zwX%^@l&r)#4R7O`A}ux4af%URPwh^(sBRpi@sTm&P>psAUZPoMfY( zU0JJ}@6Bo9f_5d+NsSy_r&EsQ7>Z#-_4D7@no^_pzok|G_L0#V*0>Qxt4im-dAvq6 z=THQCP@X*I+O(>3vVL-Nr|!9bqXq-VYmTnfF;mzP2SMM1JLldf)4KPmlol*Nxg*Dz zG4*=GQO%k%yoA*!tLyH{p{T6YtZ9w9^fwz(czX4YQ=8RX=f&u*V}ss*+87;kU_ob{ zc!bXV)HC|SZ5>*1PF}+&V*MyJ>hVQ&y8iAi&0nxlH8s^b>Yy5(ID3p5GE3E!9IQV- zTcum?>(tWa>(o+Pr-@@Kb?iY!jT~O1U*B5Pf~7gV_V}rKTu=Fm$~b1kP;3c3);A`U?IWGHkzp;qOG(TKQM%Zm%w=@&$Gz1W}GoA zCXZKE*ouI`k}*mUFbaZQMOIi`w%tWGb6In=sWZud;0d!12f*!4aTM$g^tn6U^+)17 zi6-lI6)<611#m!@-#K*>Q(~lKmAnvZ6Ss}$wm&jc3$a7IEU`ks zdw3*7kQ(K^xU%3f(71zgh@#}8`D<0GWFI`(@-r(RK`ji5pA{%yHg^(~2m(E;^s-&U zV3@3xht^Q8#eP7tv+RXe@Wa{~Wfzd~N*mL8;D=TmhvivI)ZV=i_%cX)9s{Ns(~kKK zZqLp_h37i$PG?fjtDiiZr;>qsD-{M73^q=F~qs#+)iN1T| zAU*c|@mjH>L+}6OpEYspV69%$uCM&)Y4)>Pr_*0MPK^zw$V#n3K{@k-ob6!#5=Lm3 z9(!_(zWJ%aYOdd?XBPG9qD$v%@rFVA@l}5z4KN)#QC-Z`zkSs_efg55Jh8Cys*b1h zZ{MlVEkB&36Al`oE)t{UhK>Mcr`kLS1q73LWxa z<8{#FT0J$lL2v%(Ld{!g98fOfNTs<(~uS4HCk7w)k`jdv~@xRQ`#XpfQ{P+a@>$}J5ndgc+ z{yh)#zFu!RVX$t$bB^Y%l5V|hf<}*N(A|%&(D$xftgnB6jW+ZeckZWU`q}MGy5U=+ zwBL}N9(WoB=-p579Jf_yrzf3l`uI5$^l$GSsith3o_Hpw3%|EszqpP*U5I;1XtR1w z-@2ULn2r$SH`BnWN0D6DF1Za0F^?h7jd7NxdG zL?(P&EG$4Vh1rR0|0!v#sN`)qNaJs<90f6spkE?k9u$DTf|e~h?60I9_&SPzaEE!Y zNARYAEe-q?nHK!eEiBW+#EI!HQVLL9c<+=!5i=8V@N!TMth#Ih7Z4VT8(RwUyOVrG z%m;da@npxaLFyEq6WoVMFVb#Evdzn_!kq&G?DlX>4LQIS__X<#{xB~#{(X5_W)ib& z5(efd{Umj#QBag%W@9{~goWknz!Lnlq5mj1FrQGwHvjy!_uI@g;Njo4WNyHq3LS3+ z*vs!DtClT&w(OZ<+5+ak<*ST=?O_0mI&ml|I93)HPz|%=3SDx=3Qe0dOjF0?G;@Nj zmeL>Y>d}TaTt6u*!ATuAyP2m6{nL9{)_%(&y7kAibim{mt(eoMXRv}ynV8r0mmQ}$ z3pQ!Y$XbNXY%Tl4R81Y$i?YK6qrOtlJkzbuoi|DgZa+-F`QCv_rfT)m>)W+-H8B>% zo>T&t1?i8kdQe~b$631NvLkfjVQD=zCy%1CQsc)C*L9a3s3m`#tZ#mLw!zdTKi;Gb z9fN|q;;?BEj9>ov7%jQuK>hN+romV0^qtFBYtCw7C@RxxsK%OtVv$1V=SmIw)$MCg zR7(25nPc?iZ>Q?{8%FB3%O--0v{tSw>4_Bu&X!4NL@NqMVI#O`W`yBl$~l0T<=B*F z{%GMnb9_;M{`Jv%{JKec%c(}YZylj){`W0<+Z*;r z*I2D*=dRO~aYbGKqt|Hu{2mQ&%IfYP9jWDi+#iLp3s(rI1_#{K>Ba|>y7YHd`oblT zYw@C$dhcls;JZ?txSp=L)r!F;edogC^^>o>M!)}`_vqwfCh3CjJg>QH2y|X&bwGO4 z$wT$Ie>g;UUj16#^NVSkF_9(fJqZ*>HcCZdd;Q5-J$2(O-S^9BI{By}n)`H44?VRW zYsU!v>~~h)D)p})oTT|TPtv_Vo~dJI4b^vkuvmY73O7zgEyvp!D-G&}=im9$!}a_v z6ZFydOyJp2J@RxL?xH#^UW|JTuKb=;hwGwG9-`-OJwf;XY&yr`6t!e&h5qT{CA@Fd zO+PtPKl;YOy5@gQ<2OppjjObwhyHUs6Y?U+RA}1Mi7z?kgO52@+9)rv!3cZ@yMy>Anb4sI!W^}^A_4pxeLX5HA*9ie*(K8D|3)AS0 zpT%jokA-jmD1IKJJIZgEEc6_7YURVTkpjNywp_Drzb|q@9CuJw)%I%whZQvwNgf|< zP#VZ%1;AQEvDD2G-7FP^aN9dF_>l9e%BEFaSw!JkuT33U{qC*`m`z%zV&WS!i2Cvn zJf%i6uNb!Yn|7gmgrG0l;(Z)~wTjtcP?Rv?hFMMy*nykE@P~7XFf)LpBT+;9QJ`?v zrYr65hWXHIw#qmL$<`yMvCb4JBwORia17|rfZGUfTi`i>J23B-Y5J9&sI0hK`pXCf z3@E)XKVQM!h?171t;TUF4-KIi7f@^+1!efQE+;Cj{CD5Lz(BhC+goJ%DMIh55KA{m zPdp2=Va3Zavs=}kZ@}nM)X7Iq&@ZlErGI!&qlWH}!h$=;!JxKUoN>YkjU7B6v;JTm zGQB~MKbue|C)cO5MKyVQ2(ARV zv#iYNBv!<2a$JYMH|eAAnyin!tyvA(Ie{PE^N0g~6DThAI%0a7+52NU`EV;M@Q**W zYt6sw1FQ6_8#cj%8i>I`F)HFR!UFp2g1r8C|9Y)$?*eY)zCxSK`-$fm zTobrHav-U}bv;;A7E^3e8?l6W6Tl&@jk@TPyUF9LUT42!rat%H1`WI_a1OTs{R?Y7`LA6|r=BaMS zOTSEYeH@m%mp;DsYGtJl9keG3c(to%Q*o%ia#@?^Eh7jCv*3jv9ijb4Z6K0<69a-+ z6q_Q!pdAeEK+out4um9N%(E8TSrY6JnkNuxdHYZVJw#fwkZ%Y%HChW-*6HsrdRXr| zeLVh0lokAa9;9BLZFfw3m0&(bbb{#flJ_xiP%ly7uG3u+-iL^O|Ma?i2Ptvy3k9RZ zKn#)Tz*;0QDkwcb=M^W(Y>gH!OX!UAo(;(#`t?{HFp4sJJ=m~N6bPiUg2D{U!av$- z8%;MmTu&};&~%qrYQU-qOiA|b5xmj;Jwh9JQsgGT_5T&J4_x@y}#xs@*1jCgu zv$`DvFGrP!x`^7hyUY>lZh)-{KBJ?X0KYZu!)QxhtyN2a2P;a&P|T1u`s(GYINYrP zteZ(C@JZJ&cxrXTv`YNl>jKd`9w*w5fjt#aAj3)p@ElByq3?`C27x!WLJ{)pD!9YK zo)PW~4;rJ=4BP$SzKIYVJ+sw=XVl~8(QcIV6EFqcG1TQe(a?$8+y5TM2#wgZU8G+Z zv<;$ebYky z&$TNVHzkc9;&GkPJ&zNAGG$00$!HmSPSq^eEf5fDw`~;)iZ4%6d=*ffO|N0~2Ah@? zu|6bI4z$D>vo5dNQMIaZ9)eB9!tm>hAS8 zDzck0Zr%hIjo$T~`PQS6; z5WG)rSTR>dbv3+V5@uFaNt>)V^}0SLoew^p)Y0#IhG!jEBAPY(z%gvVXuR3A4~&M5 z$(-9+gqIg`;5pZcQprTtsWKH>g^TIZe?JkcPEO~2;SO*FPLSb6l!JV6EK94I7PeIB zj{6?bja^K{XxgN4DIGbpmbP!gb(PmGmrmBjtZKUU<|VrJjwKp3qFL8pJW>-!c@k)= zw@>m0H+dA81U$MX;$w*dtA6;jQMWBCRarL3>sbOmv0YU=jr$)il&>Th+6Y1Usy9BE z6!v6PW%+=kC&P@em;GsGO_F~1y(#+6k5}rd8yAJoteFk^^*8p1AG;2sd{W)I@w&UU zzI`LKBAx!4dbKnX$n2F{MfktT!pzfn^p}6~iC%=8&3~fjn}2R`6LIPIQ7AP2@uJ^* zwX$N(&6gYQC1Po|?O!l2t8ZMkOtmPA?I87yM~@?D2{Tn(L~|GN5DFb@EQ2dihM#R9lpPY)@F6+0ZsbDeYIGT*sQM#xCwak zikD&@hK8Ru;UH4}WiT%m6MTq~Nf5V^K`AJ*Trb4{No3OCZ=BwH{sPJyr7xa$ATA+q zKhCNJ{Ov!z4hEEA`DG1}=^M-t=JQGEvae0mJC1KrW%pdlhK*OCEhS*i7W^>X1d3Ch zoe!g3K8cz)<4v>Tz=ILIih-C)GgF{1y+M=rR!GzC;EVzHVS^`>lQiX)Xh+dBKw2w~ zNiQcPa-X#a)aPDdRRGM={pR=SB!e+Se-Yo~_Js?R`dKhySWRN1fhZP#sk4WX2pEDj% zl>4Ly$_y(_;3w(qWP(qbT#(kwOb6ix&cM6uHin>iXPBAb*mvtO7ZiEE;W0Q}$VGMV z#`-45s7UjcFgfgLP(#x!t$IFzqSC8TgRJz_!v_+o4V!+Yv|>YrW>+4}?7T*gJ-vvW zJqXljB9pLxo{~Kdt1EH@c6tJk)N?BmI+B$>X%w6emb4VQS7>N+x88JIR@eSPy6yfp zl&Jlg`EyhYVE~=B-sV>`-r-L!k}}hdy`wD&0&*jz2p)|>(aBi9<-Qf9$>?+cc(DHE zUBgkVvO53b=XkbI)t;DAh^^QzhxIo~(WZ2EJba}Cr2%WO-RNL)%J?>dhHG`ty+d^A zPafBS`%TgZPMbj-O^@~)Q=z}iZP!0taD-leBzVm&p-h&6hC}DfzV>8_i8_J9X*SS1<%nh-IctpW zd*T57*UuK}##08_gDv}u%spqO=#Bi5fIS| z^^`{GPY)33{^%y{Hz7eZH^jw`h{qg^)(wIBs;(TOdCQyhBtE}RBCBSOt-#M*jmEkH z-)og_`wMX;FoJ^*n5ie9=+M*8mvq8mwHiGv#ZtNkJ@r&UPcErqkR&t#cj~N3V_C|_ z5=~w8A8gay1(-mGbYt}xr{R?-d{{oZ31WL5#`EODEcmQq;Ph($5n0XHuMYez zR8Mh~ZoNN+yKJq75W#=_y)52iN#-##vkY=mZJiF7nAQJ%X`-75B1QB8>EwXfR-ik&&{H(ODJqfIN9X7R!6S}Jyw95kXUDZBVcRiBSx(x|UosiX- z))9L2iREe#ldCnn*( zU!KZgo@OQjTBlD=YsQEg%*;#R+k>=xO|u@D)2`Kk3Ob%~*m^;cxlc_b^~Ltt|vm(7r8v^thR|1wY)_%zOLn7C{K} z4yprtqCEa=8@ao6wTxxY4-Fkz3r4%l$Gc7>2J58C{@Ep8d=gS-G1*XedNq)&6okE>xNe){zt`(OOAO;`Q) zDK%A((U69emQdE>`Rn!GGn>K1N zHG#vTes!~V}H(I4+z zqN}faTBjYyN+4Lw6u)5MvBH94X%9i5 z;V3yd23%3Uz1YYZka+X$UaqXr9L!lZSEdOLThp1;jd!`Dq_a*&8^j&*-Jd?D^FPq4 zX0%`Ln7ed!ozD8_r&*&vnB{xxb^l|F`x=|lS3fgeA9=?FU3Lrq1`5Q(SNVdT~B26g@0<+7ryaC zNO3Qb=);P`^i6`$F8tmco<)j}U;e+d_R|F)9;#6->os>tquzS{LptgB8ZBG7Qcpe= z*HNoKTsB@O5J0thZB}po;H{zDXMQt9&7-q=c-|m==4X(pS$L zq(Sv-v;tSy)YCl+>4)E`zWJ|H^x?No)^&eap?803G58y$tFN8I2aTR<)$Oyea2bDW zKE!(d)}~(el1S;$gVEOXD0>GKF-gJ?>&NKsryGcr2fq&f?YqVdZ`9S_90x;7pd{7l z!v9*MAO6a?iKzax*X^(0-85exIBOEh%23ReoqEIH8_tsE{B}G8zDpass`SZkuO>pi zOLt#3k|meT1oXA&UkDid)eR^n1Fj!`^&p*j@>o5;D5W#cTcPI`IxffpAi8e5{BSK@ z)1?o5_CXyyeUvW0@-aRfWH)OtrrhD!&D;;!IC=SdAH+ZktA%B{EfrQ=EM9_)$6esK zu@E5NN1xkjJ{Eqg2RSWoOX^DYR`HlRW@bslaVM78cO-?Pu-~BRI)V+y9(r~WlYnNu zwRO1WJmPVIQc-l=14;x|gbkal9Ir$0J2s+|rMwmTr2P)^OZP zE85z%;+dqnG41;>!8CCii|07M-5tb+p!{@qpaoD~Pge~wbKYK_eLWbXWjwc z+|Mo3=fC|3X`<`2lSbnL=Ww-U6Wu!c5c8acy7&i=h0oYAL-fgW z4%0V&a6cAya0(w9-O{6f`G1Z2+GVTs{!iZ-D!KKt{nUi9_xX9YNK*$vswG@RGxy8s zEB`V~=YQ=veda59UGeQvI_s2bEm%HC-@xttn?Lrq<@HCl>g=I(CS|N|}J>9gKqlc>H0b^#9Jkqc2^hKKHRPnzcXHmfm@+BATj&i;BpUdAO10 z_oX2y`snU@B>@`C^_VAbRA)O%2^Q#;8>CNM^h96&zl86% zpEgK5vAMKW3(WdBZAw`|`*1n;X~Z zlvzaeGs786@Yr3?b)u-;qfeYO14~YgMh)p;R$oL>c~+Ar4I!RpqM94B`tK_jGLx8# zMm|X&J*yesJ*Z72n{AX%|sSI1+9vu>Wvf>2aS>}HuO;X1+Z znZ*Yi;%@K>FJ9E4F+)na_43z+eI72j-C{CD%8TB1Fe{2e|5<;mO$2P!f;HT z&#-sG-`}lId~1PDKYl#gd5gaI52N(?i|^6<&YG?FojF*wrAL{8Y|;`;hxS<)q13-T z&%jxj(~qx-l%m=D*QmLXJthbq`{oaArtQ&XUq2QXRRg~Kc75QVZ`J?)a)Z8g{z%M| zohT|RG;{J`o&V7Z!B2bPxBg6?YBkwKhbcG3tYik}^m!`@NW^7iQQ{fsWOmAY;+YyO z9~I0ftM&6+HUvfGGk<@mK6qv;dmH5S{?9(Hvp@APdmbFBXXocpRF><^HynWCI~hmO zI{o?a#hNsl{RW0L=rjLtm@c~betr0T2kV{e!%)u>(rkJ?{GNVf78j|vfL6ZtISjyd zVb&h6n7o-S+Nfd|W{6pe{JMeew#AAAU68hbYaoMd_`;8j*XKTrm3R0?T!|}De;NbB zuYlgpds`z*Mh{}|jBWy57wP(+O;;Czks}5Z3xUfeOPs`cXSW~(M`>y+abI0ss)zmk z%ln#j;!(pnK(>k*b4mL{v&qs5-Fd|U%&vib_C$tG_V8aI?!iLN%C zdUT@>IADgxG_rcIxRF&$wk+d1%B|3`(}yu}8m{N&4^w+riLwitGB!nUDU+TY79#e% zs>BWSPsdScMaM z{Sh-YendM~p-vVpjChfO~yxYP1%mNvAdQ=dO)u-^8%vFu$!KrkT&P3$ML-$-y%SfJx)vP}J_2QcAD zIkX7gm(!%N3AHjI?4lnsY*J^1;_atS(*++oOmm*=#>!pP%!x(Sq?XfGX&6fF=RQ=c zx1M?clSo!(Voe`E3Typv0*!MZ2sifWCp7Bd*;6#435^7IkIzv$@_?*v`q9B^#oAoa zy;^U;I&$0p&A=i?=mK;+q`6nq#uU_2y_Fck7)2*7Gm0@dP?_6lL`^Z_1diUwI8ar$?Gmt8jl{^vN zy|{#Mg%GILYev=Gg|abSAA2joZ*0|Egv=j!&KkM&wo`}cO-GgR)3$3}+erQP=I3=g zO4GaESQS>sc(yU?fI)iqsZ|7$Ehko^24y9s+aBrAa%K+6WF71N^IFaR010+{T-}z{ zy$_lt4$?ViumRHajksg51UN{H0pFRc4fPIdg)wHlN(Yw=BgA{LBnY5|!-5&d(l}XB z|HvBs;}2f1)wsxhb9)C&YxHRYTZsI`?i;n8d1e&fBN!+%qz&&aBpxs~U9e zZJRV|WD~BjYAs*duBysP4Ij~}dmmn_Rh!0WKEc6;^Np_?t~VaZu9fVjxaRCxdjG%P z4fAK^K$>WO#!g2M=d!LQSQ(|kGea*k&i7hz126cu*L7v}r$4XacfAG;Zq#CyRQ3?K zHfvgw<}X}_C9VX6u=$wCo;mA*Yq8#NbfwNhDM^=}*Pv{r_8(&fjNJLYDHMS2zY72%cx+U^j;8(eay6bO+9q$Dqi4Vv7p1V`uB0QU)gw@_O7|jcnK)$G-Sv=(DD{lXA`27p zSPLc(CnjSgt^xuqa|k#G!7_jtU*yXz!#Yx_(QMcTV`OrG!iHjyV>j3Y8SrAdcqS6bDDR41;tPjU!%(Il)d#`wFg(+rh(|=$B6RjcI~gApHFdC941~XHTr9 zXLt&3T&88vU=)YeDRrO&PRax@*VDtUy7P6&_(mlrV3`W^5G}FJ(bn3k4jnO>m=RXl zSin1ozeG%4iJ;C-aL+nivC9*2LN{3Fu8pK9) zk=jfZ^)S@ zW={@qCcdD=!jweUdJU^;*RUB_eRywIPJ+Be=%m<3`#hu_z6^K(o^LN$h@PXO_C0sK zUU3`A2p;x5kM4H#?_RI0L`&70wF;&2cYpG1yg`3@a5cVNhIo4(jk!fP-r1(7*tUAe zpn5DLMo>Wq%wRT9&j9Rvisi@C)Pk0{e6E!k0w^JH0;Hp>mpG0t+!Jg&obeJ{28@G^ zrtDXRqS1wxzChXRaGtR>HA~@A1R;f8bOWXJ=+G$@#4xNtJDscU%oO4%Y<|PUNbE|w zuv&$#`7DW>$&;)WFYh73JFcU|E`}MH5Jc1Qw1(6$!x>Sph0OZBb#oF5a~_4Pea6P^Cr4f3(Ff5`sf9Zaqftt)fporAd@W05KPa3c*LW!mqXOf z1o%@2W=97WS2qE!2^Sj&%EOmGbG#mWc&UDS!}FZ!F&La+4rcb$(`|fqs-dn)Z$0k@ zSJ8J7lY_t35&fIm4X0SP38RI9lu(9RoA-;-+0?{r3RWH%mHS(myX_XF*I`W7$ne%< z`RHXZcD0o>bXbc%aly?|{_q!5IKAD4;&Je_f-d>=M4k7a%k`z_mgq~DNFRC6IDPuP zBk=8F3MOFAR{^@|?IKtgW|~Sj1;+2TMxT(R{AVA-fM<|QVS{isz1VV}M&l2H7A7C4 zG$Pe)w;Z8uD-67!GN5A=7s6pF1fc+{szeZY+l4$lr-W<3BP@lLuvkFAQ(}n4Fun8K ze5=F+rO4zXS>Y9YQFvpCRV*d8Sb*f2FTlSDzAvBBI@e3bCH46$#bh;IO z3)(7`VwDTUaj`W%brzY>1ZP@-xC)+|^MvgX*!JJE_LLE2*^Xl3_&UEgxJ4AEG|F8D zt9BtlAU?u2?aeMZnb}Gi%ZgCS;HA`^v=UWn0au>Hpriz-Xdla`y?Q8xGG@GbvSDtV z=*1dkMa_5u*9l@kN=1}HTr{DtGQbYcG%!VuE3nd$0*)kBTThV+{I()wKH%6O_e%!6 zgAicid2pM!OgK4mBSm>R#~+zTqj08E@FJ8j9|Mr0FG8@tD+3=Tw*FTMh0FL&tfM`^ zOnK=H$}D`+eM|HZ?|}>AOjbB12*lF2Nt8>A!vy=?m=^q-M1bS|1!gA10K_wT4Y-JW zq%iV8vtb3U@n!)-dsu23m1`miJU&oMM1v?M?rSS|-s-{%dWm4_f=~T)a)T4@cJ7Zy z!CrhhJPg7}HS5`h6}scz4VpBeMZdd;nKFw*Ow}WYTe;}cp9sV}>OgOS48j@swR8~U z0*1h<8wjX3j2LV2?j(eFi1uduakfMTLGNI#ym$PHMwTj=J=iEpY&=ZH9o4sED?|{s z&!!j;KyT3;mc25Nvh-;qRvcfe*U*`)KJnfyn>uXu=+U5tCLo4b`U2Ayv(<7BgsAHM zwV|^TMFp46P^<{<6@1LmX}mVMagZK^siBFDlQ#CKqiZB4#3{hM!PtR-36dJ@*?*;e z@%xQB@7;rS)YKU)=f+%|V#N=Ps>t!|Ub5$4Ker_ubcN!V#*&g?CSu0p{TeoPSLjoGk2KyJfOHHBld7+D8n+nMkrWXeEc5x@&b45%pGTVf)~)g8p$p?~3K?b;Y-i z)8Plu{`S>atJt4_f(EhYK^12B55290AhTxO^8eX84?sJMI_*EF-rie!a?=x%kV5YW zDkvb>78^~#g39XZXJtiEY`bD-ML}%Xu&jkoMfv*FFJW0dQfucuhUsy!a79(T)GtIb8oB+(hLgiF4GQ0!HR$# z<}I#6TIl9~rS#q!`p@&Exlo=vU8OnB?tk7sURazR0%>_>=DfMhepkFUc-c00!Uqq! zZ#hUucPPVcvUN@#Yp)hZ@!Gxyl&ovm0Jiatke_E1+D@JBXPC1C-J*BIEizqF zfH{N8X#R=%>AKxkzZbzj7IQT%3-qyLPrn~I-s9kQ0Wjj=cAGA4qeG|xB{{0EP>D}w~`@i;}k5J3PX`-6#>6b2g;qCFT#(1Nhr@;Yx zet{yhJ88+X9xYxo5(GRxSj?~{h7ji=dm%C_48uC&(pZV`;Caz3YN!Yr*i0=>&u$}+ zSRjpPV)}$gWN^cu*6Dzmqj1yQsQxzaML+ULA!p*f0NV zGqzsNY8VE~E~&1~85})Tst3(lH_4HDyA7 z5M5k*eYZY;b+@*@7OnmIJhJSx9$wOqb18rmW{HVlZU}`un7valjY6Dvjdmngz%t1b za2g#P(-Ui@zb?~aL?oSPsQbnDDWWNB4ic8H?$e^x9c(^Fpw!DC0JOQ(+7_6=JU9(kl+Qx69OK=}1*IUt_;CytRihl4Pf zG;aLMCLO`~7LD~ah|*TL%BbBu!^#TS;{m-V^_M%DtgA-qXE$%sy$jcobsQipucoRT z&!LL5HBe-}f1?hW-loQ;T5Ur4ZP}WBjhRuadlzL97r_>vT!#YSFt~OF-Ezlr9P712 z=xi7Df<9G=e<@MO)LQRH2!t3uK7TN!=++cymM*z>{* zId~Hm(}`#Qo^Q_?suucH_eEXF%k79A0+x3;;o1H_gWe7q(Ou7tHpb;+@+D zyiM9%uD>s%`uenvI=otcL{tAY7yiy!6|3*i>{%SBbuXuq#wGy*GAN%$gX@}KEkLvX zVN&I^@^>=;JZ{r=-FA;fGHr;tHt6*yj?%Y((W=X?UWEDgM7`<68hzkx&HD3O7VG>E z|Bdvt)9lO47X9ZtChG911s%A5Ui;5%(VuQzt)p&@K+5RR9i+8fpB*7GT@IGj%Z{nn z{CgH5wgRBwPsSpy180uIDc({v$^fjG?DNAJ+Q zX$$_DLM61H3JT9(=+m*>e5X>a~jNL;J0s5Vdx`q5$z*xM^tWT(IDO32GWNnMe?2m$DF|&q|iUMl?cD2@S(@(xQ zLMzsdhue`=ZFR4{a`FDEMQ&M{LQ#p)TT|7eD?c$gh-{nc(0WHkIeBbBzyH=84wLFf zxhsz!kvMs_qsvj$=RPu5ot%x8LOhT~jy$rlL0`M*AXV1dss_hsa1YLT%_yC8{Aipc zq3p!s6}Jo%GsoKWQoZNQPAq2Ta!v)xPAGeh80ph3izuw2y^d%C2l!C2UlTeT^^td; ztOXA)hMQ5)A#*uOeHvn=%qpy3M(B!5royp_tGaGIBAqTBH@jK4Tr(L@mlbeKGALdp zHE&8Bs~wJHNB3g?u}S^%tHZJH9}U-lR-u^I++3|0qu?-9W1`6KtD%EWRBdenyagq* zUcKtX5jyCA@fwSjLS=51&Of!4;|7{AnHxZ_L^|Op%!Ir1m{!&UX2{pqtEr$l&Y zr(5qpCx&-;{3z%L7bAnel&@ZV0CXzC%|am$$7x@S_fKf%#I4HouF;%{!#U7vCS@`SF!(2R zCRJ4fz=NKifIJS%;rU4cKUz=tUN*O(OLj1#!BdXl6~uX1G~qJ?Kgsit78}#XS5ZQi z4}M3BMzp?o@^=zmY9dODMM&o?zDIhCoPx;23qpwY;^I3!|`U61p)-e^&dDlFCM{NZ-^a8JT-K1$coVi%Pc)fPW0+@-~8|dNq!TO?mp`Z}w_{TPEupN{ZJ-fkjchu=x=v(M(DUI{&pw^E5>}*ez z!*D37&)pg4heU%lgmT@k!S^8~^6#k4 zG{7yU(_qDVBzd&1C$%Ru82THU-&($#Ov#+hjRu6va$k7NStJ#^G|MO5kWgT!y!&+&36D+&O8CFcO%pS zrfn>km1t`pyI&F=26jihxz!BBJf>h%N2N4nTq~3W;o}*$h&ftkOOM|23M+QOrO4ps zpMS4B#=8g`jTqI{BdCI&n@^XEN0D7UR@qs~6TyBS_wH0f( z92gYC3Mi9}WRZW>ac#88I~Dd(SOKIs^iczxp@go^YmY|}jYU0{B_C{|H1v8UW^F}p z0Z`P-3~WbdB(FEV6h&3)NMIRM&|)QrCw17=S`>uQFR^t(VU3Q)G18&a$;(cp0y99n z^{#0y>MbV`M=mSx1^I4%HI@)foHJr)SGF1e-~nQ0;KgzJSTylb1hy_w3MJW?4=+Ol z-}IjhBV_`XR#`w`RZXw1`JH9Fp2loKT7?!+x0)-K>($2$M^xyi;c_0KjsYDx3Fl8} z-sjj>#8JHKY{+RGba6UEloY$96xIMalx4@Zw(IPZ&>OH^91&kC$qvSr2AG%Hjeokh zM#s!<*Ab!p)Q)9R@IDF2JP@Oy7n)Z7cpg$9jBEFcF^-dG05I=$-rT5>4%o&lgSJk4;xfwY$8f@=JA;(vhsKqznPfxT-6mhVY+pMJ*6T$q zBrnxl`W})dq%VCqvHwctm45qvDQ_rn@b}Pbr*Rk>=j&bbg+zn#rL<2JH#D!WOX-Kk zm+EkvhxXrPmeTvWl*iY*hElr0G@&ZTmC6ngyZm+;yX1S!@6tV|a~N8G>E08?`M&>r zzx13-d5C&US^n)Xw2h_T{x(qYJC~-fW1-ISgTkj8{YyJ!9lIGR;v}0(o^7Q9x{9b;w(=7PrMG{mTId z)?P1K9#xAWRO>g&tBkbP?p6Ll?^8yCN0~`TQEnqYnnXSUB0D zD8c*WcTm>)&j4I-hUKA|#{pG>0~;xdwm_Jnc_D>>$7sbvi0xN3tim*r&mY+wFc^ zeptk`&DWN5TGJia!A%w=(0!4`#5b+s?ZidgpWfJL>knT8EXjZPa64#WyLw%6~{2!vzEI-N!;>F7Tp48Zv-Zm!6_*twx;7) z!tv@@w5EM1!Lmzmr`w0g45wUCJvX#fU^R}Sw6YB=mX9u z!Or0d2BB_0bcnnsV%ErY08g~R?~onDMJ)2@xmkRhH?3*A^@zMP;0e(V(}O&wdx-I~ z1<0C}2SH2U24db^mct8+0)9srJVX9MMERpkffq0Z!ps@<-}7V$PiJy;no|0~Y)|mD z?}HUDbzZmtDP}QQtPr|ppyDeQI4^*fr)Ir6vM@P5C|E%U+tFKW-~~HUkn-Xb*bl5; zh)ED%_>e@)eS^a!mONI%0E)cOS{W)bQKL*2FrN-=a1+S{qxynl0JqEKa^FjyeHhsM zI1dQ1(wkcn2W{rFTQqAzPLJL=4&_e-5>M?&e@xlGCO*N26Qe3{|MmfA`g; z^Jnuk_nTB+O5INq@@KjPHb{tdQ~}^%Wh|FtKxU3h5Q({2Ay4#W(>Bx1%2{ql06CNu zahVX45uak!H(i3+A>~-enf9XyGkz!iZFx6k0hAPB;_GTO`^&(rNj@I!s13He;1 zY0n+NT*1nN+)oACAmT!dWwF}I>n9+l$#O=M&9*sc5kM0jJEi9^Qx0H-I!k7Z1};|A z3;m(2K&_}L5#Zs5Ngn+9IG$gAm)sXWPg6DkJwI387YCPR=U1kClsiP1KL#lfH2=9a z_$-BRs`!xKrTBgRJG?7H`$3DtyS&f0yxhPt1@;032FC{TBLjX%ebH5zPg#ng+B7oM=vkl!a!`e(@> zfi*KuUvV<~eX2LJuAi3<*7A`O3 zUcbD=0b;^p7cF{WaR(sf5UCg-_Z0~`oxy&!v0buROC45kQhavTgm%$jpY6Vu+Wu^p`)rjrw0)*8zXO^5@$Az3Sp+MC4c#&A8!(t4{utUJ$35M^ z;usblmWsiJ!Z!TT;=-cFLSb$|s3D*LFE0KCC}1JA2*v=zL<@k1v|gmc!sDfyr1tjc z1vKooy}*Zs&H0F?oXA6|7h?OfHCMp(d7<-y=7pPtfSbS;p!{%HU|8f_YeWNtIo!86 zC<0#rXmA}CR1{MKfTF!#NWJ)Z8TLZzJ{U;&+>fXpr*L}84ec-q04;-QJ$Z(F{uzrqP6gX4#)u6>iSq~-nQwVom zuiNfE_{zE{(eLn0nWk}gR)C;Df9U5kNL;nkSW4$B)5PEKQ^;Zwm^nBGLr&;>J3)!_ z6iMU$1nA=5E{0zbO$_az4?hF=atHZye|$d*{5GBW5xpZM32k&LVqI;x=jTEG&_l;X z_jlA674}blMx{hGxDsk|(|pNapi{W|CyRZKliN-DtXZ@4zW2Q^h(1eVq35_+|Htb6 z^q(v|)%_>SyEpEY+Q%CeUMm0Q-DNyQJ~!M3|K>A*uju!VcXG%3=ggr1zIijlc#LA4 zV`msrUx+OI-kIXry(*34QmJmE!_az5{d3yVeP0fy3G<)xJQra|dv;s>(0=ST!E+qx z_PG!K8@w(xx%B-kweT5(m59`CRkR2&1VYn_R&>O~wa6h*)(rOj-Gz|)DLOTT3_ISn z!CJy^Uq@-8g2FuyCKkMKT_R-kV=%7Nn!!B27hc*(JQPX{zq!v#ZQa-qJjl6-ep-~X z;}H!;T6|Lq&g-6=Hr8h;@eO?Kd|^4{a%YaxrDH;DsLxynM|gtqh07gmM@ZyIE~5WY zS|=lvH~`_Mf0yn?1%@}~=#2eS{kxiiLRmlQFxE?B>5Fu2|TiUzq~ z(YSfiT1r{>iEoR4e8}7J#~%;ig)@e7_E4}=x_&ZF%hO5ooOafaFuYIEo3i`1l%Cnn z=e_%Mdp_1Y%tQd--hTO=^nBts&0yYK&FH{WLw1X3ZK!}|8wm~W=?5F1IC^C6iSzjV zj~txg$B50I%Zl90NB6wZLYs}Uxf1}8=l&@7|E;%C{+;7KDkbVe)Fzi7D9uC=xf7t3 zT)RIuKCr{I;ESBiZsZ8geeeb(jJRjmzW-aVo?fofd-U{D@BLJ?Z^xr`Tlukvn*5Bx zidz=3)BOrF>9R|8Na1-l#fi0Xjb;;TuEZ1czx{@|FsGqO`j!yOh>>O`2VQm&$xP-$ToPvT}x|d$Mv% zWt5)duD^GecWBv9C;bzZQHp=Mb(iYeHJ^VEy;mx)ly1j2i6RiSBWB12BZ5gZnXco= z0Ibpof!V|4>BYk4)g_MCkWBmhrGZ8Xq`0xWf|3LW?fK7urj&NqP8sm{yspw=nY-S9 z?tc$``lZ(RE%2hs>gWe)uBER-*j@U;TYLJ8b_6B%Fay&@{(Gi2=m?*j+H1YFz@XFrjY-Fb@AW8bloC z&}iC1{lOxVvZx~%F3=tehn>#gBjOrUow?mCED@*H&tb5?WR1A=^ufA#JHr31eLJ$h z&@cS@L+w2oo*`I?u(@AeKX%TG3|?H_;5#2gn1DZmOwMM-r9u1nuI$6f34XR)C(+2DNV#nWMM0~h_+ z%b%cSi*rE`yjzHO4I}_17Ow{v5avGct_3#-LDX$<0p}&4Y%lN*hBdaZGe?Ghh@OLM z2*8=$?0XfU@Nh2)a4|3mey$7kd>+(rYV*r4vB0gCn6+@9c~ zKUh#&?Cm0a=%dBX{0`!8Xy6l#%{6j@a(HGGFbRhafoE3QZgW3Fu)<5ki()g;09VYm z$4sNCqed5h#maT|i%||`3Y01EbI$vHH?&IiJ&XcX#X4 zpZ>JI@|CXy4$H3Pg~IlkkHHx_OxFI0w6?+u88|Io%%8(xeJ(&#;39Z&hu&!7@lMey zE9B$A67~qMmo5Ap%ReIw@>rZ=@u@i{R-UI2!w))RHxVe|bC9ZmPCkLC9{^)Rf&^Sa zyB^4q-(WnH=<@!r!J;UFJ(OFBZm{7#*a(1+07`gk!uJl>2c&Z^gMTIiMz}{-2nZm? zpp7mhd`dfQb(!VxuR(!l2v*!l*nWN&VF&u$dCiOAgg>2XdFg{p|dU1Wx?8 zFgVm77hjGjQ{eePf!)9L?itg#ecn{m>eh!EIXQd{PVa0rW7r zQ!D}~is{I;04|_eyx}@{@%OSG=Ia1w_>IOL&(NNtt#of!>h4oAhlgi=7vq4Pyn%yV z3>c_+NijI$+6p593_S5DAi;t7`*$|M-3yP6j}mzE-J#bzQ^NQ?UJ~ArfZ5%FVa+*B8jjoY{#8hUe9G|dUs&Utawt>a`AUI+HJ+ezMIVWf?$FBh zoSEdn5VD70v({Nz!X)hJ-s5U-1_K7`jQsl7zYgHT+>m9#v3(y^k`L8RfRu;8T(86|H`@TN))Khijkw*r=VsKOfDQ-{cdT-L?u;3Lu)MKuUIejeZSl#)`z#$J*ab25Ci~C$J5l8 z4PcADQh&jnHK!+lFN3uTZRu@PKMcwdwLU_H^Vgt*+hieG)T_`e=(4kJKfO}BOR+oK z8>KIQ?mY@TL$Kl(-#Z5$w_g&TZ8&#@m>6fVt&=f7h>m^($R+$t8iia^Zy+20&9&QxiBjCGN{}MT4hbuOG6- zcG1EvbV8Co&x?h+Q0Qa+8#2imERZ^Y3l8!UNQk6|mhP=5q7ifE)-08}7+D=OigI%hndt3w}o~=ut7LSn|x-Omqh4ndIpyhegraHg*OwX3Z`}`+@r}4=D1(P}eKeB|_{kRDk_V9-R%X#OWr!iy31SfBAed}9w z$|DsW>D}*sw8*=T-Lz{9~?L`rS3guu`fmLGv9tb{8=Qz z0`KST=eV{juIlQfYN`O7VUZ>YDZ}&iH#_)5dPF!((|3FjXMqDX3;C^Bh;cGIju)-e zny|GFr>Yly+%so*#VbdEivT%3h!mZsgsovX(LEM#=QsDw8Q^64A}5Z=lMVchg_hgF zQHXwWKI5zhQOIf*a3Dj{8%>IX^IhabcM8Lp1|-1QU&)}8!pY+U96(wyR*R6#oeSA~<^IbGdgB?0J&BQh%3 zl?C7`m{Y?cDRNS~VM{zU+^J|cWdY*M-AQuB*s{&-I_Kjnbjv^1Y0l)B?pjn-69-5x z_`@XBZQAwGuPxSovnT7EQ>QAi^=|UkvgC~6;L@$M2N9#64P2LFjyP8Bodwl#n!PzP zv1*jJ5nsh|>Y0U(<_f6*-8i8gV)AU-$Z5c7@fhP0y2c9K9GAkOwv5^E%mekxAA5@e z&k(GDKm*$FByOxk*t#ed)eQYzQn>Bw>r+chO8_eNTx*bH-4w_B&zuxn__#dl zsJI-9feduEZQG`W3m1lK1EI;2Cx`l6MkzSoz6)4rXVBO4lQwXGGXY5W>W_Q%>X+1O z?&N;;WzeqRBysEZ#o4jv;WqRY$LVi(#dYT*KnRQJq>&XmdQOGvYddw@!dm_H-i-P= z@qF62DjhbbL+zDYv<|V&kN=#|fzw84?S_o*T0Ec$Bdc`O-06C7L6`1WlGmu=H9BEl zQX?A+TD+=Bzx{hs2h1L&MJqOI;qrnGnpvac_Zz8{xe9^fjo6U`TVnu-O+8Jz>8_-f zJepH`6AS#jHtjdMN=r9X>WW`%(Zg%{b@g?5jcrfqw8NUolLU}eAr@kh=b+KeJstYp z-^for4b=%9x_^^qc2*#^>DJ#KtkNSZt2J%PX#L}^HR|Rx`FYdobo6w<01CR-|E*Fh zR~Ph}6I#_=wFb}tEfN1bx~dR>%I7aSTyHw1R$DhLQr|XsAe8@`TeAA{Pqyj}aA3N( z74_oTt!iqj)-Qk6uVW6L0$|*xTkqSTGfrv&EOCZ!L|Fij4qFgg_lX^xm%d%D>ALc%f0ivt1+=-!V3#sC|ME1_%ZW(XP_U znd~+JE)FH&V7(TnGpEJ+D(9SY4vR&T9)9>?tzElT&CShfZ*Skhd2zngt5=6IT%Pk; zk=ERnQZO)DwQ5xWHKlq=_x8na0}yjdd_p(86eUs}$|Q#?lU}D>x*4fPs}kmn6ng+E zFg=Vx6CL`=jRl>1(S2%ds8>f@QfpWgANc-s%?G5s=*@SKD6Y9P+H}$V>+~PzcIx7D zJG7-grFUO)JNGIzvb|bc`(oUHFOB zdhog+_Y1oDJCikcYO}ui!+Z6c z`2`($;54<3<-N)P`T!xQI1y618s1i~w|)38w0op({%Qw9brr2f49D2@ z0lS7PkyOF$EQdWwfoB=4?9$YLK6i;L|149WOo4r#0tN~_J%|CY=<&5d$@=x{14qQa z{bw~`OWOQt98&p2kG2%&kf+iK<28euF~AO za|5Twq9coo?02QJvr|^mJ@d>nL)w)qSB5(Gbwb(OaPT>TZpX^*2L0%Imak+^w?DWY z-H96A!~%TaMCq6Vs}b>R##v^C)^4uRe_wvD4xTesSAJ}g+MBxo9-EX*70GWf*Qi^r znXXCWD)jlQ>-6a>9@ZOQw7=@xY^YGFgJ!qsd!L@AP22K1nOeXRh_!`MV&h7oa=S_ovZbV6DI53=VWx*Rhx9rFJ^1}@MX#-)~i@!BvDO0 zm2hoR0F^F%;QUE?&lzL1WJO+wzV5I3=KuES!2KqwxyGB-3Vr5+VLIpZvAX#my?X5h zck7X*1syRxuS?%OQUCF_xH?CmJe%7FR|S_Ch-9XB*6WKO83%A$pzr^$^uaSH>&>sO z(&$kI{pW=8hW}w_klm8dfm5Zm40=_VVc#s4$;ph#FlN4t_hHV z$On$nev|99achNsefcysH&*N4KVH=3KkV20 #^>s*N5bLL2m98s_9{=j=YvsMSp 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�LYD*RYd)1$1wt{zs`VhsGx*i;jp*nM6s} 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 diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-cohere-chat-low-level-api.jpg b/spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-cohere-chat-low-level-api.jpg deleted file mode 100644 index 0e182f28adbb9674d0566543ea58393def2abe5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 452454 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%0DD-Z3xE8j0s3 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

YgfnY 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

    xhzGNLxHVJ7a@*u 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

    KfKYiz@(RG;HnizvKvqe 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 z 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 M$L5db^Vih>0K)DbFaQ7m diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-titan-chat-low-level-api.jpg b/spring-ai-docs/src/main/antora/modules/ROOT/images/bedrock/bedrock-titan-chat-low-level-api.jpg deleted file mode 100644 index 20a111086e5d1b185da91338bc268ed20084bdc6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 243012 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 z%0D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 response = chatModel.stream( new Prompt("Generate the names of 5 famous pirates.")); ---- - -=== Low-level AnthropicChatBedrockApi Client [[low-level-api]] - -The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java[AnthropicChatBedrockApi] provides is lightweight Java client on top of AWS Bedrock link:https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html[Anthropic Claude models]. - -Following class diagram illustrates the AnthropicChatBedrockApi interface and building blocks: - -image::bedrock/bedrock-anthropic-chat-api.png[AnthropicChatBedrockApi Class Diagram] - -Client supports the `anthropic.claude-instant-v1`, `anthropic.claude-v2` and `anthropic.claude-v2:1` models for both synchronous (e.g. `chatCompletion()`) and streaming (e.g. `chatCompletionStream()`) responses. - -Here is a simple snippet how to use the api programmatically: - -[source,java] ----- -AnthropicChatBedrockApi anthropicChatApi = new AnthropicChatBedrockApi( - AnthropicModel.CLAUDE_V2.id(), Region.EU_CENTRAL_1.id(), Duration.ofMillis(1000L)); - -AnthropicChatRequest request = AnthropicChatRequest - .builder(String.format(AnthropicChatBedrockApi.PROMPT_TEMPLATE, "Name 3 famous pirates")) - .withTemperature(0.8f) - .withMaxTokensToSample(300) - .withTopK(10) - .build(); - -// Sync request -AnthropicChatResponse response = anthropicChatApi.chatCompletion(request); - -// Streaming request -Flux responseStream = anthropicChatApi.chatCompletionStream(request); -List responses = responseStream.collectList().block(); ----- - -Follow the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic/api/AnthropicChatBedrockApi.java[AnthropicChatBedrockApi.java]'s JavaDoc for further information. diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc index 590dbf7908..8178481c09 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc @@ -86,7 +86,7 @@ The prefix `spring.ai.bedrock.anthropic3.chat` is the property prefix that confi | spring.ai.bedrock.anthropic3.chat.options.stopSequences | Configure up to four sequences that the generative recognizes. After a stop sequence, the generative stops generating further tokens. The returned text doesn't contain the stop sequence. | AWS Bedrock default |==== -Look at the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java[AnthropicChatModel] for other model IDs. +Look at the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java[Anthropic3ChatModel] for other model IDs. Supported values are: `anthropic.claude-instant-v1`, `anthropic.claude-v2` and `anthropic.claude-v2:1`. Model ID values can also be found in the https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html[AWS Bedrock documentation for base model IDs]. @@ -235,14 +235,12 @@ Next, create an https://github.com/spring-projects/spring-ai/blob/main/models/sp [source,java] ---- -Anthropic3ChatBedrockApi anthropicApi = new Anthropic3ChatBedrockApi( - AnthropicChatBedrockApi.AnthropicModel.CLAUDE_V3_SONNET.id(), +BedrockConverseApi converseApi = new BedrockConverseApi( EnvironmentVariableCredentialsProvider.create(), - Region.US_EAST_1.id(), - new ObjectMapper(), + Region.EU_CENTRAL_1.id(), Duration.ofMillis(1000L)); -BedrockAnthropic3ChatModel chatModel = new BedrockAnthropic3ChatModel(anthropicApi, +BedrockAnthropic3ChatModel chatModel = new BedrockAnthropic3ChatModel(converseApi, AnthropicChatOptions.builder() .withTemperature(0.6f) .withMaxTokens(100) @@ -258,33 +256,3 @@ ChatResponse response = chatModel.call( Flux response = chatModel.stream( new Prompt("Generate the names of 5 famous pirates.")); ---- - -=== Low-level Anthropic3ChatBedrockApi Client [[low-level-api]] - -The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java[Anthropic3ChatBedrockApi] provides is lightweight Java client on top of AWS Bedrock link:https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html[Anthropic Claude models]. - -Client supports the `anthropic.claude-3-opus-20240229-v1:0`,`anthropic.claude-3-sonnet-20240229-v1:0`,`anthropic.claude-3-haiku-20240307-v1:0` and the legacy `anthropic.claude-v2`, `anthropic.claude-v2:1` and `anthropic.claude-instant-v1` models for both synchronous (e.g. `chatCompletion()`) and streaming (e.g. `chatCompletionStream()`) responses. - -Here is a simple snippet how to use the api programmatically: - -[source,java] ----- -Anthropic3ChatBedrockApi anthropicChatApi = new Anthropic3ChatBedrockApi( - AnthropicModel.CLAUDE_V2.id(), Region.EU_CENTRAL_1.id(), Duration.ofMillis(1000L)); - -AnthropicChatRequest request = AnthropicChatRequest - .builder(String.format(Anthropic3ChatBedrockApi.PROMPT_TEMPLATE, "Name 3 famous pirates")) - .withTemperature(0.8f) - .withMaxTokensToSample(300) - .withTopK(10) - .build(); - -// Sync request -AnthropicChatResponse response = anthropicChatApi.chatCompletion(request); - -// Streaming request -Flux responseStream = anthropicChatApi.chatCompletionStream(request); -List responses = responseStream.collectList().block(); ----- - -Follow the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/api/Anthropic3ChatBedrockApi.java[Anthropic3ChatBedrockApi.java]'s JavaDoc for further information. diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-cohere.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-cohere.adoc index 85a66b36e9..e62751c113 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-cohere.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-cohere.adoc @@ -71,7 +71,7 @@ The prefix `spring.ai.bedrock.cohere.chat` is the property prefix that configure | Property | Description | Default | spring.ai.bedrock.cohere.chat.enabled | Enable or disable support for Cohere | false -| spring.ai.bedrock.cohere.chat.model | The model id to use. See the https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java#L326C14-L326C29[CohereChatModel] for the supported models. | `cohere.command-text-v14` +| spring.ai.bedrock.cohere.chat.model | The model id to use. See the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModel.java[CohereChatModel] for the supported models. | `cohere.command-text-v14` | spring.ai.bedrock.cohere.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | AWS Bedrock default | spring.ai.bedrock.cohere.chat.options.topP | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default | spring.ai.bedrock.cohere.chat.options.topK | Specify the number of token choices the model uses to generate the next token | AWS Bedrock default @@ -83,7 +83,7 @@ The prefix `spring.ai.bedrock.cohere.chat` is the property prefix that configure | spring.ai.bedrock.cohere.chat.options.truncate | Specifies how the API handles inputs longer than the maximum token length | AWS Bedrock default |==== -Look at the https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java#L326C14-L326C29[CohereChatModel] for other model IDs. +Look at the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModel.java[CohereChatModel] for other model IDs. Supported values are: `cohere.command-light-text-v14` and `cohere.command-text-v14`. Model ID values can also be found in the https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html[AWS Bedrock documentation for base model IDs]. @@ -187,13 +187,12 @@ Next, create an https://github.com/spring-projects/spring-ai/blob/main/models/sp [source,java] ---- -CohereChatBedrockApi api = new CohereChatBedrockApi(CohereChatModel.COHERE_COMMAND_V14.id(), - EnvironmentVariableCredentialsProvider.create(), - Region.US_EAST_1.id(), - new ObjectMapper(), - Duration.ofMillis(1000L)); +BedrockConverseApi converseApi = new BedrockConverseApi( + EnvironmentVariableCredentialsProvider.create(), + Region.EU_CENTRAL_1.id(), + Duration.ofMillis(1000L)); -BedrockCohereChatModel chatModel = new BedrockCohereChatModel(api, +BedrockCohereChatModel chatModel = new BedrockCohereChatModel(converseApi, BedrockCohereChatOptions.builder() .withTemperature(0.6f) .withTopK(10) @@ -208,58 +207,3 @@ ChatResponse response = chatModel.call( Flux response = chatModel.stream( new Prompt("Generate the names of 5 famous pirates.")); ---- - -== Low-level CohereChatBedrockApi Client [[low-level-api]] - -The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereChatBedrockApi.java[CohereChatBedrockApi] provides is lightweight Java client on top of AWS Bedrock https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command.html[Cohere Command models]. - -Following class diagram illustrates the CohereChatBedrockApi interface and building blocks: - -image::bedrock/bedrock-cohere-chat-low-level-api.jpg[align="center", width="800px"] - -The CohereChatBedrockApi supports the `cohere.command-light-text-v14` and `cohere.command-text-v14` models for both synchronous (e.g. `chatCompletion()`) and streaming (e.g. `chatCompletionStream()`) requests. - -Here is a simple snippet how to use the api programmatically: - -[source,java] ----- -CohereChatBedrockApi cohereChatApi = new CohereChatBedrockApi( - CohereChatModel.COHERE_COMMAND_V14.id(), - Region.US_EAST_1.id(), - Duration.ofMillis(1000L)); - -var request = CohereChatRequest - .builder("What is the capital of Bulgaria and what is the size? What it the national anthem?") - .withStream(false) - .withTemperature(0.5f) - .withTopP(0.8f) - .withTopK(15) - .withMaxTokens(100) - .withStopSequences(List.of("END")) - .withReturnLikelihoods(CohereChatRequest.ReturnLikelihoods.ALL) - .withNumGenerations(3) - .withLogitBias(null) - .withTruncate(Truncate.NONE) - .build(); - -CohereChatResponse response = cohereChatApi.chatCompletion(request); - -var request = CohereChatRequest - .builder("What is the capital of Bulgaria and what is the size? What it the national anthem?") - .withStream(true) - .withTemperature(0.5f) - .withTopP(0.8f) - .withTopK(15) - .withMaxTokens(100) - .withStopSequences(List.of("END")) - .withReturnLikelihoods(CohereChatRequest.ReturnLikelihoods.ALL) - .withNumGenerations(3) - .withLogitBias(null) - .withTruncate(Truncate.NONE) - .build(); - -Flux responseStream = cohereChatApi.chatCompletionStream(request); -List responses = responseStream.collectList().block(); ----- - - diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-jurassic2.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-jurassic2.adoc index 55e813cbdb..ca0c112e40 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-jurassic2.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-jurassic2.adoc @@ -84,7 +84,7 @@ The prefix `spring.ai.bedrock.jurassic2.chat` is the property prefix that config | spring.ai.bedrock.jurassic2.chat.options.countPenalty | Penalty object for count | AWS Bedrock default |==== -Look at https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java#L164[Ai21Jurassic2ChatBedrockApi#Ai21Jurassic2ChatModel] for other model IDs. The other value supported is `ai21.j2-ultra-v1`. +Look at https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatModel.java[Ai21Jurassic2ChatModel] for other model IDs. The other value supported is `ai21.j2-ultra-v1`. Model ID values can also be found in the https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html[AWS Bedrock documentation for base model IDs]. TIP: All properties prefixed with `spring.ai.bedrock.jurassic2.chat.options` can be overridden at runtime by adding a request specific <> to the `Prompt` call. @@ -182,13 +182,12 @@ Next, create an https://github.com/spring-projects/spring-ai/blob/main/models/sp [source,java] ---- -Ai21Jurassic2ChatBedrockApi api = new Ai21Jurassic2ChatBedrockApi(Ai21Jurassic2ChatModel.AI21_J2_MID_V1.id(), +BedrockConverseApi converseApi = new BedrockConverseApi( EnvironmentVariableCredentialsProvider.create(), - Region.US_EAST_1.id(), - new ObjectMapper(), + Region.EU_CENTRAL_1.id(), Duration.ofMillis(1000L)); -BedrockAi21Jurassic2ChatModel chatModel = new BedrockAi21Jurassic2ChatModel(api, +BedrockAi21Jurassic2ChatModel chatModel = new BedrockAi21Jurassic2ChatModel(converseApi, BedrockAi21Jurassic2ChatOptions.builder() .withNumResults(1) .withMaxTokens(100) @@ -204,34 +203,4 @@ BedrockAi21Jurassic2ChatModel chatModel = new BedrockAi21Jurassic2ChatModel(api, ChatResponse response = chatModel.call( new Prompt("Generate the names of 5 famous pirates.")); - ----- - -== Low-level Client [[low-level-api]] - -https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java[Ai21Jurassic2ChatBedrockApi] provides a lightweight Java client on top of AWS Bedrock https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jurassic2.html[Jurassic-2 and Jurassic-2 Chat models]. - -The `Ai21Jurassic2ChatBedrockApi` supports the `ai21.j2-mid-v1` and `ai21.j2-ultra-v1` models and only support synchronous ( `chatCompletion()`). - -Here is a simple snippet on how to use the API programmatically: - - -[source,java] ----- -Ai21Jurassic2ChatBedrockApi jurassic2ChatApi = new Ai21Jurassic2ChatBedrockApi( - Ai21Jurassic2ChatModel.AI21_J2_MID_V1.id(), - Region.US_EAST_1.id(), - Duration.ofMillis(1000L)); - -Ai21Jurassic2ChatRequest request = Ai21Jurassic2ChatRequest.builder("Hello, my name is") - .withTemperature(0.9f) - .withTopP(0.9f) - .withMaxTokens(20) - .build(); - -Ai21Jurassic2ChatResponse response = jurassic2ChatApi.chatCompletion(request); - - ---- - -Follow the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java[Ai21Jurassic2ChatBedrockApi.java]'s JavaDoc for further information. diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-llama.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-llama.adoc index a7c526f453..257dabd3e5 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-llama.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-llama.adoc @@ -82,7 +82,7 @@ The prefix `spring.ai.bedrock.llama.chat` is the property prefix that configures | spring.ai.bedrock.llama.chat.options.max-gen-len | Specify the maximum number of tokens to use in the generated response. The model truncates the response once the generated text exceeds maxGenLen. | AWS Bedrock default |==== -Look at https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java#L164[LlamaChatBedrockApi#LlamaChatModel] for other model IDs. The other value supported is `meta.llama2-13b-chat-v1`. +Look at https://github.com/spring-projects/spring-ai/blob/4ba9a3cd689b9fd3a3805f540debe398a079c6ef/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModel.java[LlamaChatModel] for other model IDs. The other value supported is `meta.llama2-13b-chat-v1`. Model ID values can also be found in the https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html[AWS Bedrock documentation for base model IDs]. TIP: All properties prefixed with `spring.ai.bedrock.llama.chat.options` can be overridden at runtime by adding a request specific <> to the `Prompt` call. @@ -185,13 +185,12 @@ Next, create an https://github.com/spring-projects/spring-ai/blob/main/models/sp [source,java] ---- -LlamaChatBedrockApi api = new LlamaChatBedrockApi(LlamaChatModel.LLAMA2_70B_CHAT_V1.id(), - EnvironmentVariableCredentialsProvider.create(), - Region.US_EAST_1.id(), - new ObjectMapper(), - Duration.ofMillis(1000L)); +BedrockConverseApi converseApi = new BedrockConverseApi( + EnvironmentVariableCredentialsProvider.create(), + Region.EU_CENTRAL_1.id(), + Duration.ofMillis(1000L)); -BedrockLlamaChatModel chatModel = new BedrockLlamaChatModel(api, +BedrockLlamaChatModel chatModel = new BedrockLlamaChatModel(converseApi, BedrockLlamaChatOptions.builder() .withTemperature(0.5f) .withMaxGenLen(100) @@ -204,39 +203,3 @@ ChatResponse response = chatModel.call( Flux response = chatModel.stream( new Prompt("Generate the names of 5 famous pirates.")); ---- - -== Low-level LlamaChatBedrockApi Client [[low-level-api]] - -https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java[LlamaChatBedrockApi] provides is lightweight Java client on top of AWS Bedrock https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html[Meta Llama 2 and Llama 2 Chat models]. - -Following class diagram illustrates the LlamaChatBedrockApi interface and building blocks: - -image::bedrock/bedrock-llama-chat-api.jpg[LlamaChatBedrockApi Class Diagram] - -The LlamaChatBedrockApi supports the `meta.llama3-8b-instruct-v1:0`,`meta.llama3-70b-instruct-v1:0`,`meta.llama2-13b-chat-v1` and `meta.llama2-70b-chat-v1` models for both synchronous (e.g. `chatCompletion()`) and streaming (e.g. `chatCompletionStream()`) responses. - -Here is a simple snippet how to use the api programmatically: - -[source,java] ----- -LlamaChatBedrockApi llamaChatApi = new LlamaChatBedrockApi( - LlamaChatModel.LLAMA3_70B_INSTRUCT_V1.id(), - Region.US_EAST_1.id(), - Duration.ofMillis(1000L)); - -LlamaChatRequest request = LlamaChatRequest.builder("Hello, my name is") - .withTemperature(0.9f) - .withTopP(0.9f) - .withMaxGenLen(20) - .build(); - -LlamaChatResponse response = llamaChatApi.chatCompletion(request); - -// Streaming response -Flux responseStream = llamaChatApi.chatCompletionStream(request); -List responses = responseStream.collectList().block(); ----- - -Follow the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/llama/api/LlamaChatBedrockApi.java[LlamaChatBedrockApi.java]'s JavaDoc for further information. - - diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-titan.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-titan.adoc index 86a59c4c7a..8aa0f1b931 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-titan.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-titan.adoc @@ -72,14 +72,14 @@ The prefix `spring.ai.bedrock.titan.chat` is the property prefix that configures | Property | Description | Default | spring.ai.bedrock.titan.chat.enable | Enable Bedrock Titan chat model. Disabled by default | false -| spring.ai.bedrock.titan.chat.model | The model id to use. See the link:https://github.com/spring-projects/spring-ai/blob/4839a6175cd1ec89498b97d3efb6647022c3c7cb/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java#L220[TitanChatBedrockApi#TitanChatModel] for the supported models. | `amazon.titan-text-express-v1` +| spring.ai.bedrock.titan.chat.model | The model id to use. See the link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModel.java[TitanChatModel] for the supported models. | `amazon.titan-text-express-v1` | spring.ai.bedrock.titan.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | AWS Bedrock default | spring.ai.bedrock.titan.chat.options.topP | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default | spring.ai.bedrock.titan.chat.options.stopSequences | Configure up to four sequences that the generative recognizes. After a stop sequence, the generative stops generating further tokens. The returned text doesn't contain the stop sequence. | AWS Bedrock default | spring.ai.bedrock.titan.chat.options.maxTokenCount | Specify the maximum number of tokens to use in the generated response. Note that the models may stop before reaching this maximum. This parameter only specifies the absolute maximum number of tokens to generate. We recommend a limit of 4,000 tokens for optimal performance. | AWS Bedrock default |==== -Look at the https://github.com/spring-projects/spring-ai/blob/4839a6175cd1ec89498b97d3efb6647022c3c7cb/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java#L220[TitanChatBedrockApi#TitanChatModel] for other model IDs. +Look at the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanChatModel.java[TitanChatModel] for other model IDs. Supported values are: `amazon.titan-text-lite-v1`, `amazon.titan-text-express-v1` and `amazon.titan-text-premier-v1:0`. Model ID values can also be found in the https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html[AWS Bedrock documentation for base model IDs]. @@ -183,14 +183,12 @@ Next, create an https://github.com/spring-projects/spring-ai/blob/main/models/sp [source,java] ---- -TitanChatBedrockApi titanApi = new TitanChatBedrockApi( - TitanChatModel.TITAN_TEXT_EXPRESS_V1.id(), - EnvironmentVariableCredentialsProvider.create(), - Region.US_EAST_1.id(), - new ObjectMapper(), +BedrockConverseApi converseApi = new BedrockConverseApi( + EnvironmentVariableCredentialsProvider.create(), + Region.EU_CENTRAL_1.id(), Duration.ofMillis(1000L)); -BedrockTitanChatModel chatModel = new BedrockTitanChatModel(titanApi, +BedrockTitanChatModel chatModel = new BedrockTitanChatModel(converseApi, BedrockTitanChatOptions.builder() .withTemperature(0.6f) .withTopP(0.8f) @@ -204,36 +202,3 @@ ChatResponse response = chatModel.call( Flux response = chatModel.stream( new Prompt("Generate the names of 5 famous pirates.")); ---- - -== Low-level TitanChatBedrockApi Client [[low-level-api]] - -The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java[TitanChatBedrockApi] provides is lightweight Java client on top of AWS Bedrock link:https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html[Bedrock Titan models]. - -Following class diagram illustrates the TitanChatBedrockApi interface and building blocks: - -image::bedrock/bedrock-titan-chat-low-level-api.jpg[width=800,align="center"] - -Client supports the `amazon.titan-text-lite-v1` and `amazon.titan-text-express-v1` models for both synchronous (e.g. `chatCompletion()`) and streaming (e.g. `chatCompletionStream()`) responses. - -Here is a simple snippet how to use the api programmatically: - -[source,java] ----- -TitanChatBedrockApi titanBedrockApi = new TitanChatBedrockApi(TitanChatCompletionModel.TITAN_TEXT_EXPRESS_V1.id(), - Region.EU_CENTRAL_1.id(), Duration.ofMillis(1000L)); - -TitanChatRequest titanChatRequest = TitanChatRequest.builder("Give me the names of 3 famous pirates?") - .withTemperature(0.5f) - .withTopP(0.9f) - .withMaxTokenCount(100) - .withStopSequences(List.of("|")) - .build(); - -TitanChatResponse response = titanBedrockApi.chatCompletion(titanChatRequest); - -Flux response = titanBedrockApi.chatCompletionStream(titanChatRequest); - -List results = response.collectList().block(); ----- - -Follow the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanChatBedrockApi.java[TitanChatBedrockApi]'s JavaDoc for further information. diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfiguration.java index 9a7112c8ef..2dfb4e3c80 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic/BedrockAnthropicChatAutoConfiguration.java @@ -15,23 +15,19 @@ */ package org.springframework.ai.autoconfigure.bedrock.anthropic; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.bedrock.anthropic.BedrockAnthropicChatModel; -import org.springframework.ai.bedrock.anthropic.api.AnthropicChatBedrockApi; import org.springframework.ai.bedrock.api.BedrockConverseApi; 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.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; /** * {@link AutoConfiguration Auto-configuration} for Bedrock Anthropic Chat Client. @@ -49,16 +45,6 @@ @Import(BedrockAwsConnectionConfiguration.class) public class BedrockAnthropicChatAutoConfiguration { - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public AnthropicChatBedrockApi anthropicApi(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockAnthropicChatProperties properties, - BedrockAwsConnectionProperties awsProperties) { - return new AnthropicChatBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - new ObjectMapper(), awsProperties.getTimeout()); - } - @Bean @ConditionalOnBean(BedrockConverseApi.class) public BedrockAnthropicChatModel anthropicChatModel(BedrockConverseApi converseApi, diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java index 8728f8db0a..f25dee674f 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/anthropic3/BedrockAnthropic3ChatAutoConfiguration.java @@ -15,15 +15,12 @@ */ package org.springframework.ai.autoconfigure.bedrock.anthropic3; -import com.fasterxml.jackson.databind.ObjectMapper; - import java.util.List; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.bedrock.anthropic3.BedrockAnthropic3ChatModel; -import org.springframework.ai.bedrock.anthropic3.api.Anthropic3ChatBedrockApi; import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.model.function.FunctionCallback; import org.springframework.ai.model.function.FunctionCallbackContext; @@ -39,7 +36,6 @@ import org.springframework.util.CollectionUtils; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; /** * {@link AutoConfiguration Auto-configuration} for Bedrock Anthropic3 Chat Client. @@ -57,16 +53,6 @@ @Import(BedrockAwsConnectionConfiguration.class) public class BedrockAnthropic3ChatAutoConfiguration { - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public Anthropic3ChatBedrockApi anthropic3Api(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockAnthropic3ChatProperties properties, - BedrockAwsConnectionProperties awsProperties) { - return new Anthropic3ChatBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - new ObjectMapper(), awsProperties.getTimeout()); - } - @Bean @ConditionalOnBean(BedrockConverseApi.class) public BedrockAnthropic3ChatModel anthropic3ChatModel(BedrockConverseApi converseApi, diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfiguration.java index cc15a767ae..aa609227d4 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereChatAutoConfiguration.java @@ -15,23 +15,19 @@ */ package org.springframework.ai.autoconfigure.bedrock.cohere; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.bedrock.cohere.BedrockCohereChatModel; -import org.springframework.ai.bedrock.cohere.api.CohereChatBedrockApi; 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.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; /** * {@link AutoConfiguration Auto-configuration} for Bedrock Cohere Chat Client. @@ -49,16 +45,6 @@ @Import(BedrockAwsConnectionConfiguration.class) public class BedrockCohereChatAutoConfiguration { - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public CohereChatBedrockApi cohereChatApi(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockCohereChatProperties properties, - BedrockAwsConnectionProperties awsProperties) { - return new CohereChatBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - new ObjectMapper(), awsProperties.getTimeout()); - } - @Bean @ConditionalOnBean(BedrockConverseApi.class) public BedrockCohereChatModel cohereChatModel(BedrockConverseApi converseApi, diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatAutoConfiguration.java index 4da85c90df..6cb50096a6 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/jurrasic2/BedrockAi21Jurassic2ChatAutoConfiguration.java @@ -16,23 +16,19 @@ package org.springframework.ai.autoconfigure.bedrock.jurrasic2; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatModel; -import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi; 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.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; /** * {@link AutoConfiguration Auto-configuration} for Bedrock Jurassic2 Chat Client. @@ -51,16 +47,6 @@ @Import(BedrockAwsConnectionConfiguration.class) public class BedrockAi21Jurassic2ChatAutoConfiguration { - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public Ai21Jurassic2ChatBedrockApi ai21Jurassic2ChatBedrockApi(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockAi21Jurassic2ChatProperties properties, - BedrockAwsConnectionProperties awsProperties) { - return new Ai21Jurassic2ChatBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - new ObjectMapper(), awsProperties.getTimeout()); - } - @Bean @ConditionalOnBean(BedrockConverseApi.class) public BedrockAi21Jurassic2ChatModel jurassic2ChatModel(BedrockConverseApi converseApi, diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfiguration.java index ad5c1d3c3d..5f2719b03a 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/llama/BedrockLlamaChatAutoConfiguration.java @@ -15,21 +15,16 @@ */ package org.springframework.ai.autoconfigure.bedrock.llama; -import com.fasterxml.jackson.databind.ObjectMapper; - import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.bedrock.llama.BedrockLlamaChatModel; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; -import org.springframework.ai.bedrock.llama.api.LlamaChatBedrockApi; 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.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -51,15 +46,6 @@ @Import(BedrockAwsConnectionConfiguration.class) public class BedrockLlamaChatAutoConfiguration { - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public LlamaChatBedrockApi llamaApi(AwsCredentialsProvider credentialsProvider, AwsRegionProvider regionProvider, - BedrockLlamaChatProperties properties, BedrockAwsConnectionProperties awsProperties) { - return new LlamaChatBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - new ObjectMapper(), awsProperties.getTimeout()); - } - @Bean @ConditionalOnBean(BedrockConverseApi.class) public BedrockLlamaChatModel llamaChatModel(BedrockConverseApi converseApi, BedrockLlamaChatProperties properties) { diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfiguration.java index 746ae52435..39c0730c19 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanChatAutoConfiguration.java @@ -15,23 +15,19 @@ */ package org.springframework.ai.autoconfigure.bedrock.titan; -import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; import org.springframework.ai.bedrock.api.BedrockConverseApi; import org.springframework.ai.bedrock.titan.BedrockTitanChatModel; -import org.springframework.ai.bedrock.titan.api.TitanChatBedrockApi; 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.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; /** * {@link AutoConfiguration Auto-configuration} for Bedrock Titan Chat Client. @@ -49,16 +45,6 @@ @Import(BedrockAwsConnectionConfiguration.class) public class BedrockTitanChatAutoConfiguration { - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public TitanChatBedrockApi titanChatBedrockApi(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockTitanChatProperties properties, - BedrockAwsConnectionProperties awsProperties) { - return new TitanChatBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - new ObjectMapper(), awsProperties.getTimeout()); - } - @Bean @ConditionalOnBean(BedrockConverseApi.class) public BedrockTitanChatModel titanChatModel(BedrockConverseApi converseApi, BedrockTitanChatProperties properties) { From 470d00e76f814ef57d902da62ccc465c7fb20e70 Mon Sep 17 00:00:00 2001 From: wmz7year Date: Sat, 8 Jun 2024 18:20:23 +0800 Subject: [PATCH 5/9] Amazon Bedrock converse API module supports custom BedrockRuntimeClient and BedrockRuntimeAsyncClient. --- .../ai/bedrock/api/AbstractBedrockApi.java | 49 ++++++++------- .../ai/bedrock/api/BedrockConverseApi.java | 56 ++++++++--------- .../cohere/api/CohereEmbeddingBedrockApi.java | 15 +++++ .../titan/api/TitanEmbeddingBedrockApi.java | 16 +++++ .../BedrockAwsConnectionConfiguration.java | 29 +++++++++ .../BedrockConverseApiAutoConfiguration.java | 12 ++-- ...drockCohereEmbeddingAutoConfiguration.java | 15 +++-- ...edrockTitanEmbeddingAutoConfiguration.java | 15 +++-- .../BedrockAwsConnectionConfigurationIT.java | 60 +++++++++++++++++++ ...BedrockConverseApiAutoConfigurationIT.java | 2 - 10 files changed, 190 insertions(+), 79 deletions(-) diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/AbstractBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/AbstractBedrockApi.java index 24a383adac..823c3f1bd0 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/AbstractBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/AbstractBedrockApi.java @@ -70,7 +70,6 @@ public abstract class AbstractBedrockApi { private final String modelId; private final ObjectMapper objectMapper; - private final Region region; private final BedrockRuntimeClient client; private final BedrockRuntimeAsyncClient clientStreaming; @@ -136,29 +135,36 @@ public AbstractBedrockApi(String modelId, AwsCredentialsProvider credentialsProv */ public AbstractBedrockApi(String modelId, AwsCredentialsProvider credentialsProvider, Region region, ObjectMapper objectMapper, Duration timeout) { + this(modelId, BedrockRuntimeClient.builder() + .region(region) + .credentialsProvider(credentialsProvider) + .overrideConfiguration(c -> c.apiCallTimeout(timeout)) + .build(), BedrockRuntimeAsyncClient.builder() + .region(region) + .credentialsProvider(credentialsProvider) + .overrideConfiguration(c -> c.apiCallTimeout(timeout)) + .build(), objectMapper); + } + /** + * Create a new AbstractBedrockApi instance using the provided AWS Bedrock clients, region and object mapper. + * + * @param modelId The model id to use. + * @param bedrockRuntimeClient The AWS BedrockRuntimeClient instance. + * @param bedrockRuntimeAsyncClient The AWS BedrockRuntimeAsyncClient instance. + * @param objectMapper The object mapper to use for JSON serialization and deserialization. + */ + public AbstractBedrockApi(String modelId, BedrockRuntimeClient bedrockRuntimeClient, BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient, + ObjectMapper objectMapper) { Assert.hasText(modelId, "Model id must not be empty"); - Assert.notNull(credentialsProvider, "Credentials provider must not be null"); - Assert.notNull(region, "Region must not be empty"); + Assert.notNull(bedrockRuntimeClient, "bedrockRuntimeClient must not be null"); + Assert.notNull(bedrockRuntimeAsyncClient, "bedrockRuntimeAsyncClient must not be null"); Assert.notNull(objectMapper, "Object mapper must not be null"); - Assert.notNull(timeout, "Timeout must not be null"); this.modelId = modelId; + this.client = bedrockRuntimeClient; + this.clientStreaming = bedrockRuntimeAsyncClient; this.objectMapper = objectMapper; - this.region = region; - - - this.client = BedrockRuntimeClient.builder() - .region(this.region) - .credentialsProvider(credentialsProvider) - .overrideConfiguration(c -> c.apiCallTimeout(timeout)) - .build(); - - this.clientStreaming = BedrockRuntimeAsyncClient.builder() - .region(this.region) - .credentialsProvider(credentialsProvider) - .overrideConfiguration(c -> c.apiCallTimeout(timeout)) - .build(); } /** @@ -168,13 +174,6 @@ public String getModelId() { return this.modelId; } - /** - * @return The AWS region. - */ - public Region getRegion() { - return this.region; - } - /** * Encapsulates the metrics about the model invocation. * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-claude.html diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java index e5a4be2f58..dc1e5404e9 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/api/BedrockConverseApi.java @@ -69,11 +69,9 @@ public class BedrockConverseApi { private static final Logger logger = LoggerFactory.getLogger(BedrockConverseApi.class); - private final Region region; + private final BedrockRuntimeClient bedrockRuntimeClient; - private final BedrockRuntimeClient client; - - private final BedrockRuntimeAsyncClient clientStreaming; + private final BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient; private final RetryTemplate retryTemplate; @@ -155,32 +153,34 @@ public BedrockConverseApi(AwsCredentialsProvider credentialsProvider, Region reg */ public BedrockConverseApi(AwsCredentialsProvider credentialsProvider, Region region, Duration timeout, RetryTemplate retryTemplate) { - Assert.notNull(credentialsProvider, "Credentials provider must not be null"); - Assert.notNull(region, "Region must not be empty"); - Assert.notNull(timeout, "Timeout must not be null"); - Assert.notNull(retryTemplate, "RetryTemplate must not be null"); - - this.region = region; - this.retryTemplate = retryTemplate; - - this.client = BedrockRuntimeClient.builder() - .region(this.region) - .credentialsProvider(credentialsProvider) - .overrideConfiguration(c -> c.apiCallTimeout(timeout)) - .build(); - - this.clientStreaming = BedrockRuntimeAsyncClient.builder() - .region(this.region) - .credentialsProvider(credentialsProvider) - .overrideConfiguration(c -> c.apiCallTimeout(timeout)) - .build(); + this(BedrockRuntimeClient.builder() + .region(region) + .credentialsProvider(credentialsProvider) + .overrideConfiguration(c -> c.apiCallTimeout(timeout)) + .build(), BedrockRuntimeAsyncClient.builder() + .region(region) + .credentialsProvider(credentialsProvider) + .overrideConfiguration(c -> c.apiCallTimeout(timeout)) + .build(), retryTemplate); } /** - * @return The AWS region. + * Create a new BedrockConverseApi instance using the provided AWS Bedrock clients and the RetryTemplate. + * + * @param bedrockRuntimeClient The AWS BedrockRuntimeClient instance. + * @param bedrockRuntimeAsyncClient The AWS BedrockRuntimeAsyncClient instance. + * @param retryTemplate The retry template used to retry the Amazon Bedrock Converse + * API calls. */ - public Region getRegion() { - return this.region; + public BedrockConverseApi(BedrockRuntimeClient bedrockRuntimeClient, BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient, + RetryTemplate retryTemplate) { + Assert.notNull(bedrockRuntimeClient, "bedrockRuntimeClient must not be null"); + Assert.notNull(bedrockRuntimeAsyncClient, "bedrockRuntimeAsyncClient must not be null"); + Assert.notNull(retryTemplate, "RetryTemplate must not be null"); + + this.bedrockRuntimeClient = bedrockRuntimeClient; + this.bedrockRuntimeAsyncClient = bedrockRuntimeAsyncClient; + this.retryTemplate = retryTemplate; } /** @@ -215,7 +215,7 @@ public ConverseResponse converse(ConverseRequest converseRequest) { Assert.notNull(converseRequest, "'converseRequest' must not be null"); return this.retryTemplate.execute(ctx -> { - return client.converse(converseRequest); + return bedrockRuntimeClient.converse(converseRequest); }); } @@ -280,7 +280,7 @@ public Flux converseStream(ConverseStreamRequest converseS }) .build(); - clientStreaming.converseStream(converseStreamRequest, responseHandler); + bedrockRuntimeAsyncClient.converseStream(converseStreamRequest, responseHandler); return eventSink.asFlux(); }); diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereEmbeddingBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereEmbeddingBedrockApi.java index 1ae4242275..a4022f8761 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereEmbeddingBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/api/CohereEmbeddingBedrockApi.java @@ -25,6 +25,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; import org.springframework.ai.bedrock.api.AbstractBedrockApi; import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingRequest; @@ -109,6 +111,19 @@ public CohereEmbeddingBedrockApi(String modelId, AwsCredentialsProvider credenti super(modelId, credentialsProvider, region, objectMapper, timeout); } + /** + * Create a new CohereEmbeddingBedrockApi instance using the provided AWS Bedrock clients, region and object mapper. + * + * @param modelId The model id to use. + * @param bedrockRuntimeClient The AWS BedrockRuntimeClient instance. + * @param bedrockRuntimeAsyncClient The AWS BedrockRuntimeAsyncClient instance. + * @param objectMapper The object mapper to use for JSON serialization and deserialization. + */ + public CohereEmbeddingBedrockApi(String model, BedrockRuntimeClient bedrockRuntimeClient, + BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient, ObjectMapper objectMapper) { + super(model, bedrockRuntimeClient, bedrockRuntimeAsyncClient, objectMapper); + } + /** * The Cohere Embed model request. * diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanEmbeddingBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanEmbeddingBedrockApi.java index 016ec4306b..3c4c135a7c 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanEmbeddingBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanEmbeddingBedrockApi.java @@ -24,6 +24,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; import org.springframework.ai.bedrock.api.AbstractBedrockApi; import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi.TitanEmbeddingRequest; @@ -81,6 +83,20 @@ public TitanEmbeddingBedrockApi(String modelId, AwsCredentialsProvider credentia super(modelId, credentialsProvider, region, objectMapper, timeout); } + /** + * Create a new TitanEmbeddingBedrockApi instance. + * + * @param modelId The model id to use. + * @param bedrockRuntimeClient The AWS BedrockRuntimeClient instance. + * @param bedrockRuntimeAsyncClient The AWS BedrockRuntimeAsyncClient instance. + * @param objectMapper The object mapper to use for JSON serialization and deserialization. + */ + public TitanEmbeddingBedrockApi(String model, BedrockRuntimeClient bedrockRuntimeClient, + BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient, ObjectMapper objectMapper) { + super(model, bedrockRuntimeClient, bedrockRuntimeAsyncClient, objectMapper); + } + + /** * Titan Embedding request parameters. * diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/BedrockAwsConnectionConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/BedrockAwsConnectionConfiguration.java index 46ee804056..33dd645776 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/BedrockAwsConnectionConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/BedrockAwsConnectionConfiguration.java @@ -22,7 +22,10 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.AwsRegionProvider; import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; @@ -60,6 +63,32 @@ public AwsRegionProvider regionProvider(BedrockAwsConnectionProperties propertie return DefaultAwsRegionProviderChain.builder().build(); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) + public BedrockRuntimeClient bedrockRuntimeClient(AwsCredentialsProvider credentialsProvider, + AwsRegionProvider regionProvider, BedrockAwsConnectionProperties properties) { + + return BedrockRuntimeClient.builder() + .region(regionProvider.getRegion()) + .credentialsProvider(credentialsProvider) + .overrideConfiguration(c -> c.apiCallTimeout(properties.getTimeout())) + .build(); + } + + @Bean + @ConditionalOnMissingBean + @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) + public BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient(AwsCredentialsProvider credentialsProvider, + AwsRegionProvider regionProvider, BedrockAwsConnectionProperties properties) { + + return BedrockRuntimeAsyncClient.builder() + .region(regionProvider.getRegion()) + .credentialsProvider(credentialsProvider) + .overrideConfiguration(c -> c.apiCallTimeout(properties.getTimeout())) + .build(); + } + /** * @author Wei Jiang */ diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfiguration.java index 8c10040c3a..b287c670d8 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfiguration.java @@ -28,8 +28,6 @@ import org.springframework.context.annotation.Import; import org.springframework.retry.support.RetryTemplate; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; @@ -47,12 +45,10 @@ public class BedrockConverseApiAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public BedrockConverseApi bedrockConverseApi(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockAwsConnectionProperties awsProperties, - RetryTemplate retryTemplate) { - return new BedrockConverseApi(credentialsProvider, regionProvider.getRegion(), awsProperties.getTimeout(), - retryTemplate); + @ConditionalOnBean({ BedrockRuntimeClient.class, BedrockRuntimeAsyncClient.class }) + public BedrockConverseApi bedrockConverseApi(BedrockRuntimeClient bedrockRuntimeClient, + BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient, RetryTemplate retryTemplate) { + return new BedrockConverseApi(bedrockRuntimeClient, bedrockRuntimeAsyncClient, retryTemplate); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfiguration.java index 82b6292de4..229d390741 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfiguration.java @@ -16,8 +16,8 @@ package org.springframework.ai.autoconfigure.bedrock.cohere; import com.fasterxml.jackson.databind.ObjectMapper; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; @@ -48,12 +48,11 @@ public class BedrockCohereEmbeddingAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public CohereEmbeddingBedrockApi cohereEmbeddingApi(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockCohereEmbeddingProperties properties, - BedrockAwsConnectionProperties awsProperties) { - return new CohereEmbeddingBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - new ObjectMapper(), awsProperties.getTimeout()); + @ConditionalOnBean({ BedrockRuntimeClient.class, BedrockRuntimeAsyncClient.class }) + public CohereEmbeddingBedrockApi cohereEmbeddingApi(BedrockCohereEmbeddingProperties properties, + BedrockRuntimeClient bedrockRuntimeClient, BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient) { + return new CohereEmbeddingBedrockApi(properties.getModel(), bedrockRuntimeClient, bedrockRuntimeAsyncClient, + new ObjectMapper()); } @Bean diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfiguration.java index b019dc1c6b..bbaeb6b4c0 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfiguration.java @@ -16,8 +16,8 @@ package org.springframework.ai.autoconfigure.bedrock.titan; import com.fasterxml.jackson.databind.ObjectMapper; -import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; -import software.amazon.awssdk.regions.providers.AwsRegionProvider; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; @@ -48,12 +48,11 @@ public class BedrockTitanEmbeddingAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnBean({ AwsCredentialsProvider.class, AwsRegionProvider.class }) - public TitanEmbeddingBedrockApi titanEmbeddingBedrockApi(AwsCredentialsProvider credentialsProvider, - AwsRegionProvider regionProvider, BedrockTitanEmbeddingProperties properties, - BedrockAwsConnectionProperties awsProperties) { - return new TitanEmbeddingBedrockApi(properties.getModel(), credentialsProvider, regionProvider.getRegion(), - new ObjectMapper(), awsProperties.getTimeout()); + @ConditionalOnBean({ BedrockRuntimeClient.class, BedrockRuntimeAsyncClient.class }) + public TitanEmbeddingBedrockApi titanEmbeddingBedrockApi(BedrockTitanEmbeddingProperties properties, + BedrockRuntimeClient bedrockRuntimeClient, BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient) { + return new TitanEmbeddingBedrockApi(properties.getModel(), bedrockRuntimeClient, bedrockRuntimeAsyncClient, + new ObjectMapper()); } @Bean diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/BedrockAwsConnectionConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/BedrockAwsConnectionConfigurationIT.java index bea58ce80e..801b832d69 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/BedrockAwsConnectionConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/BedrockAwsConnectionConfigurationIT.java @@ -31,6 +31,8 @@ import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.regions.providers.AwsRegionProvider; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeAsyncClient; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; /** * @author Wei Jiang @@ -87,6 +89,39 @@ public void autoConfigureWithCustomAWSCredentialAndRegionProvider() { }); } + @Test + public void autoConfigureBedrockClients() { + new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id()) + .withConfiguration(AutoConfigurations.of(TestAutoConfiguration.class)) + .run((context) -> { + var bedrockRuntimeClient = context.getBean(BedrockRuntimeClient.class); + var bedrockRuntimeAsyncClient = context.getBean(BedrockRuntimeAsyncClient.class); + + assertThat(bedrockRuntimeClient).isNotNull(); + assertThat(bedrockRuntimeAsyncClient).isNotNull(); + }); + } + + @Test + public void autoConfigureWithCustomBedrockClients() { + new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id()) + .withConfiguration(AutoConfigurations.of(TestAutoConfiguration.class, + CustomBedrockRuntimeClientAutoConfiguration.class)) + .run((context) -> { + var bedrockRuntimeClient = context.getBean(BedrockRuntimeClient.class); + var bedrockRuntimeAsyncClient = context.getBean(BedrockRuntimeAsyncClient.class); + + assertThat(bedrockRuntimeClient).isNotNull(); + assertThat(bedrockRuntimeAsyncClient).isNotNull(); + }); + } + @EnableConfigurationProperties({ BedrockAwsConnectionProperties.class }) @Import(BedrockAwsConnectionConfiguration.class) static class TestAutoConfiguration { @@ -136,4 +171,29 @@ public Region getRegion() { } + @AutoConfiguration + static class CustomBedrockRuntimeClientAutoConfiguration { + + @Bean + public BedrockRuntimeClient bedrockRuntimeClient(AwsCredentialsProvider credentialsProvider, + AwsRegionProvider regionProvider) { + + return BedrockRuntimeClient.builder() + .region(regionProvider.getRegion()) + .credentialsProvider(credentialsProvider) + .build(); + } + + @Bean + public BedrockRuntimeAsyncClient bedrockRuntimeAsyncClient(AwsCredentialsProvider credentialsProvider, + AwsRegionProvider regionProvider) { + + return BedrockRuntimeAsyncClient.builder() + .region(regionProvider.getRegion()) + .credentialsProvider(credentialsProvider) + .build(); + } + + } + } diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfigurationIT.java index 4142d4a173..1d4c42c77d 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/api/BedrockConverseApiAutoConfigurationIT.java @@ -46,8 +46,6 @@ public void autoConfigureBedrockConverseApi() { var bedrockConverseApi = context.getBean(BedrockConverseApi.class); assertThat(bedrockConverseApi).isNotNull(); - - assertThat(bedrockConverseApi.getRegion()).isEqualTo(Region.US_EAST_1); }); } From 216df35a0cb21a497fbb6158e7fa78e4dde884d7 Mon Sep 17 00:00:00 2001 From: wmz7year Date: Sat, 8 Jun 2024 20:10:36 +0800 Subject: [PATCH 6/9] AWS Bedrock embedding models add exponential backoff support. --- .../cohere/BedrockCohereEmbeddingModel.java | 34 ++++++++++++---- .../titan/BedrockTitanEmbeddingModel.java | 39 +++++++++++++------ .../titan/api/TitanEmbeddingBedrockApi.java | 2 +- ...drockCohereEmbeddingAutoConfiguration.java | 8 ++-- ...edrockTitanEmbeddingAutoConfiguration.java | 9 +++-- ...ockCohereEmbeddingAutoConfigurationIT.java | 16 +++++--- ...rockTitanEmbeddingAutoConfigurationIT.java | 16 +++++--- 7 files changed, 88 insertions(+), 36 deletions(-) diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereEmbeddingModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereEmbeddingModel.java index 25e3f35b43..b074cc9c4b 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereEmbeddingModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereEmbeddingModel.java @@ -28,6 +28,8 @@ import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.embedding.EmbeddingResponse; import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; /** @@ -44,6 +46,11 @@ public class BedrockCohereEmbeddingModel extends AbstractEmbeddingModel { private final BedrockCohereEmbeddingOptions defaultOptions; + /** + * The retry template used to retry the Bedrock API calls. + */ + private final RetryTemplate retryTemplate; + // private CohereEmbeddingRequest.InputType inputType = // CohereEmbeddingRequest.InputType.SEARCH_DOCUMENT; @@ -60,10 +67,18 @@ public BedrockCohereEmbeddingModel(CohereEmbeddingBedrockApi cohereEmbeddingBedr public BedrockCohereEmbeddingModel(CohereEmbeddingBedrockApi cohereEmbeddingBedrockApi, BedrockCohereEmbeddingOptions options) { + this(cohereEmbeddingBedrockApi, options, RetryUtils.DEFAULT_RETRY_TEMPLATE); + } + + public BedrockCohereEmbeddingModel(CohereEmbeddingBedrockApi cohereEmbeddingBedrockApi, + BedrockCohereEmbeddingOptions options, RetryTemplate retryTemplate) { Assert.notNull(cohereEmbeddingBedrockApi, "CohereEmbeddingBedrockApi must not be null"); - Assert.notNull(options, "BedrockCohereEmbeddingOptions must not be null"); + Assert.notNull(options, "DefaultOptions must not be null"); + Assert.notNull(retryTemplate, "RetryTemplate must not be null"); + this.embeddingApi = cohereEmbeddingBedrockApi; this.defaultOptions = options; + this.retryTemplate = retryTemplate; } // /** @@ -104,13 +119,16 @@ public EmbeddingResponse call(EmbeddingRequest request) { var apiRequest = new CohereEmbeddingRequest(request.getInstructions(), optionsToUse.getInputType(), optionsToUse.getTruncate()); - CohereEmbeddingResponse apiResponse = this.embeddingApi.embedding(apiRequest); - var indexCounter = new AtomicInteger(0); - List embeddings = apiResponse.embeddings() - .stream() - .map(e -> new Embedding(e, indexCounter.getAndIncrement())) - .toList(); - return new EmbeddingResponse(embeddings); + + return this.retryTemplate.execute(ctx -> { + CohereEmbeddingResponse apiResponse = this.embeddingApi.embedding(apiRequest); + var indexCounter = new AtomicInteger(0); + List embeddings = apiResponse.embeddings() + .stream() + .map(e -> new Embedding(e, indexCounter.getAndIncrement())) + .toList(); + return new EmbeddingResponse(embeddings); + }); } /** diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingModel.java index e3089eec5d..979e4c82b6 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/BedrockTitanEmbeddingModel.java @@ -31,6 +31,8 @@ import org.springframework.ai.embedding.EmbeddingOptions; import org.springframework.ai.embedding.EmbeddingRequest; import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.ai.retry.RetryUtils; +import org.springframework.retry.support.RetryTemplate; import org.springframework.util.Assert; /** @@ -50,6 +52,11 @@ public class BedrockTitanEmbeddingModel extends AbstractEmbeddingModel { private final TitanEmbeddingBedrockApi embeddingApi; + /** + * The retry template used to retry the Bedrock API calls. + */ + private final RetryTemplate retryTemplate; + public enum InputType { TEXT, IMAGE @@ -62,7 +69,15 @@ public enum InputType { private InputType inputType = InputType.TEXT; public BedrockTitanEmbeddingModel(TitanEmbeddingBedrockApi titanEmbeddingBedrockApi) { + this(titanEmbeddingBedrockApi, RetryUtils.DEFAULT_RETRY_TEMPLATE); + } + + public BedrockTitanEmbeddingModel(TitanEmbeddingBedrockApi titanEmbeddingBedrockApi, RetryTemplate retryTemplate) { + Assert.notNull(titanEmbeddingBedrockApi, "TitanEmbeddingBedrockApi must not be null"); + Assert.notNull(retryTemplate, "RetryTemplate must not be null"); + this.embeddingApi = titanEmbeddingBedrockApi; + this.retryTemplate = retryTemplate; } /** @@ -87,17 +102,19 @@ public EmbeddingResponse call(EmbeddingRequest request) { "Titan Embedding does not support batch embedding. Will make multiple API calls to embed(Document)"); } - List> embeddingList = new ArrayList<>(); - for (String inputContent : request.getInstructions()) { - var apiRequest = createTitanEmbeddingRequest(inputContent, request.getOptions()); - TitanEmbeddingResponse response = this.embeddingApi.embedding(apiRequest); - embeddingList.add(response.embedding()); - } - var indexCounter = new AtomicInteger(0); - List embeddings = embeddingList.stream() - .map(e -> new Embedding(e, indexCounter.getAndIncrement())) - .toList(); - return new EmbeddingResponse(embeddings); + return this.retryTemplate.execute(ctx -> { + List> embeddingList = new ArrayList<>(); + for (String inputContent : request.getInstructions()) { + var apiRequest = createTitanEmbeddingRequest(inputContent, request.getOptions()); + TitanEmbeddingResponse response = this.embeddingApi.embedding(apiRequest); + embeddingList.add(response.embedding()); + } + var indexCounter = new AtomicInteger(0); + List embeddings = embeddingList.stream() + .map(e -> new Embedding(e, indexCounter.getAndIncrement())) + .toList(); + return new EmbeddingResponse(embeddings); + }); } private TitanEmbeddingRequest createTitanEmbeddingRequest(String inputContent, EmbeddingOptions requestOptions) { diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanEmbeddingBedrockApi.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanEmbeddingBedrockApi.java index 3c4c135a7c..9a5d180957 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanEmbeddingBedrockApi.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/titan/api/TitanEmbeddingBedrockApi.java @@ -173,7 +173,7 @@ public enum TitanEmbeddingModel { /** * amazon.titan-embed-text-v2 */ - TITAN_EMBED_TEXT_V2("amazon.titan-embed-text-v2:0");; + TITAN_EMBED_TEXT_V2("amazon.titan-embed-text-v2:0"); private final String id; diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfiguration.java index 229d390741..fff11b99e8 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingModel; import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -31,6 +32,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.retry.support.RetryTemplate; /** * {@link AutoConfiguration Auto-configuration} for Bedrock Cohere Embedding Client. @@ -39,7 +41,7 @@ * @author Wei Jiang * @since 0.8.0 */ -@AutoConfiguration +@AutoConfiguration(after = SpringAiRetryAutoConfiguration.class) @ConditionalOnClass(CohereEmbeddingBedrockApi.class) @EnableConfigurationProperties({ BedrockCohereEmbeddingProperties.class, BedrockAwsConnectionProperties.class }) @ConditionalOnProperty(prefix = BedrockCohereEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") @@ -59,9 +61,9 @@ public CohereEmbeddingBedrockApi cohereEmbeddingApi(BedrockCohereEmbeddingProper @ConditionalOnMissingBean @ConditionalOnBean(CohereEmbeddingBedrockApi.class) public BedrockCohereEmbeddingModel cohereEmbeddingModel(CohereEmbeddingBedrockApi cohereEmbeddingApi, - BedrockCohereEmbeddingProperties properties) { + BedrockCohereEmbeddingProperties properties, RetryTemplate retryTemplate) { - return new BedrockCohereEmbeddingModel(cohereEmbeddingApi, properties.getOptions()); + return new BedrockCohereEmbeddingModel(cohereEmbeddingApi, properties.getOptions(), retryTemplate); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfiguration.java index bbaeb6b4c0..9e4c75f1d2 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfiguration.java +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfiguration.java @@ -21,6 +21,7 @@ import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel; import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi; import org.springframework.boot.autoconfigure.AutoConfiguration; @@ -31,6 +32,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.retry.support.RetryTemplate; /** * {@link AutoConfiguration Auto-configuration} for Bedrock Titan Embedding Client. @@ -39,7 +41,7 @@ * @author Wei Jiang * @since 0.8.0 */ -@AutoConfiguration +@AutoConfiguration(after = SpringAiRetryAutoConfiguration.class) @ConditionalOnClass(TitanEmbeddingBedrockApi.class) @EnableConfigurationProperties({ BedrockTitanEmbeddingProperties.class, BedrockAwsConnectionProperties.class }) @ConditionalOnProperty(prefix = BedrockTitanEmbeddingProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") @@ -59,9 +61,10 @@ public TitanEmbeddingBedrockApi titanEmbeddingBedrockApi(BedrockTitanEmbeddingPr @ConditionalOnMissingBean @ConditionalOnBean(TitanEmbeddingBedrockApi.class) public BedrockTitanEmbeddingModel titanEmbeddingModel(TitanEmbeddingBedrockApi titanEmbeddingApi, - BedrockTitanEmbeddingProperties properties) { + BedrockTitanEmbeddingProperties properties, RetryTemplate retryTemplate) { - return new BedrockTitanEmbeddingModel(titanEmbeddingApi).withInputType(properties.getInputType()); + return new BedrockTitanEmbeddingModel(titanEmbeddingApi, retryTemplate) + .withInputType(properties.getInputType()); } } diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java index 14d3889551..6c0a9f8a10 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereEmbeddingAutoConfigurationIT.java @@ -18,6 +18,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.bedrock.cohere.BedrockCohereEmbeddingModel; import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingModel; import org.springframework.ai.bedrock.cohere.api.CohereEmbeddingBedrockApi.CohereEmbeddingRequest; @@ -47,7 +48,8 @@ public class BedrockCohereEmbeddingAutoConfigurationIT { "spring.ai.bedrock.cohere.embedding.model=" + CohereEmbeddingModel.COHERE_EMBED_MULTILINGUAL_V1.id(), "spring.ai.bedrock.cohere.embedding.options.inputType=SEARCH_DOCUMENT", "spring.ai.bedrock.cohere.embedding.options.truncate=NONE") - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockCohereEmbeddingAutoConfiguration.class)); @Test public void singleEmbedding() { @@ -91,7 +93,8 @@ public void propertiesTest() { "spring.ai.bedrock.cohere.embedding.model=MODEL_XYZ", "spring.ai.bedrock.cohere.embedding.options.inputType=CLASSIFICATION", "spring.ai.bedrock.cohere.embedding.options.truncate=START") - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockCohereEmbeddingAutoConfiguration.class)) .run(context -> { var properties = context.getBean(BedrockCohereEmbeddingProperties.class); var awsProperties = context.getBean(BedrockAwsConnectionProperties.class); @@ -113,7 +116,8 @@ public void embeddingDisabled() { // It is disabled by default new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockCohereEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isEmpty(); @@ -121,7 +125,8 @@ public void embeddingDisabled() { // Explicitly enable the embedding auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.cohere.embedding.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockCohereEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isNotEmpty(); @@ -129,7 +134,8 @@ public void embeddingDisabled() { // Explicitly disable the embedding auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.cohere.embedding.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockCohereEmbeddingAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockCohereEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockCohereEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockCohereEmbeddingModel.class)).isEmpty(); diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfigurationIT.java index 5a5a2ad4c1..e709a3d093 100644 --- a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfigurationIT.java +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/titan/BedrockTitanEmbeddingAutoConfigurationIT.java @@ -23,6 +23,7 @@ import software.amazon.awssdk.regions.Region; import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel; import org.springframework.ai.bedrock.titan.BedrockTitanEmbeddingModel.InputType; import org.springframework.ai.bedrock.titan.api.TitanEmbeddingBedrockApi.TitanEmbeddingModel; @@ -47,7 +48,8 @@ public class BedrockTitanEmbeddingAutoConfigurationIT { "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), "spring.ai.bedrock.titan.embedding.model=" + TitanEmbeddingModel.TITAN_EMBED_IMAGE_V1.id()) - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)); + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockTitanEmbeddingAutoConfiguration.class)); @Test public void singleTextEmbedding() { @@ -87,7 +89,8 @@ public void propertiesTest() { "spring.ai.bedrock.aws.access-key=ACCESS_KEY", "spring.ai.bedrock.aws.secret-key=SECRET_KEY", "spring.ai.bedrock.aws.region=" + Region.EU_CENTRAL_1.id(), "spring.ai.bedrock.titan.embedding.model=MODEL_XYZ", "spring.ai.bedrock.titan.embedding.inputType=TEXT") - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockTitanEmbeddingAutoConfiguration.class)) .run(context -> { var properties = context.getBean(BedrockTitanEmbeddingProperties.class); var awsProperties = context.getBean(BedrockAwsConnectionProperties.class); @@ -108,7 +111,8 @@ public void embeddingDisabled() { // It is disabled by default new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockTitanEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isEmpty(); @@ -116,7 +120,8 @@ public void embeddingDisabled() { // Explicitly enable the embedding auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.titan.embedding.enabled=true") - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockTitanEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isNotEmpty(); assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isNotEmpty(); @@ -124,7 +129,8 @@ public void embeddingDisabled() { // Explicitly disable the embedding auto-configuration. new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.titan.embedding.enabled=false") - .withConfiguration(AutoConfigurations.of(BedrockTitanEmbeddingAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockTitanEmbeddingAutoConfiguration.class)) .run(context -> { assertThat(context.getBeansOfType(BedrockTitanEmbeddingProperties.class)).isEmpty(); assertThat(context.getBeansOfType(BedrockTitanEmbeddingModel.class)).isEmpty(); From 2c8968d56dde72b3f179a6e08851c9e2fcbeebfa Mon Sep 17 00:00:00 2001 From: wmz7year Date: Sun, 2 Jun 2024 11:21:42 +0800 Subject: [PATCH 7/9] Add Amazon Bedrock Mistral model support. --- README.md | 2 +- models/spring-ai-bedrock/README.md | 1 + .../mistral/BedrockMistralChatModel.java | 297 ++++++++++++++++++ .../mistral/BedrockMistralChatOptions.java | 256 +++++++++++++++ .../mistral/BedrockMistralChatModelIT.java | 246 +++++++++++++++ .../src/main/antora/modules/ROOT/nav.adoc | 2 + .../modules/ROOT/pages/api/bedrock.adoc | 1 + .../api/chat/bedrock/bedrock-mistral.adoc | 206 ++++++++++++ .../bedrock-mistral-chat-functions.adoc | 187 +++++++++++ .../modules/ROOT/pages/api/chatmodel.adoc | 1 + .../modules/ROOT/pages/api/functions.adoc | 3 +- .../api/structured-output-converter.adoc | 3 +- .../modules/ROOT/pages/getting-started.adoc | 1 + .../BedrockMistralChatAutoConfiguration.java | 76 +++++ .../mistral/BedrockMistralChatProperties.java | 72 +++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + ...BedrockMistralChatAutoConfigurationIT.java | 160 ++++++++++ .../tool/FunctionCallWithFunctionBeanIT.java | 106 +++++++ .../FunctionCallWithPromptFunctionIT.java | 85 +++++ .../mistral/tool/MockWeatherService.java | 91 ++++++ 20 files changed, 1794 insertions(+), 3 deletions(-) create mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModel.java create mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatOptions.java create mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModelIT.java create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-mistral.adoc create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-mistral-chat-functions.adoc create mode 100644 spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatAutoConfiguration.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatProperties.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatAutoConfigurationIT.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/FunctionCallWithFunctionBeanIT.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/FunctionCallWithPromptFunctionIT.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/MockWeatherService.java diff --git a/README.md b/README.md index fe4aa92acd..33fff49f2a 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ You can find more details in the [Reference Documentation](https://docs.spring.i Spring AI supports many AI models. For an overview see here. Specific models currently supported are * OpenAI * Azure OpenAI -* Amazon Bedrock (Anthropic, Llama, Cohere, Titan, Jurassic2) +* Amazon Bedrock (Anthropic, Llama, Cohere, Titan, Jurassic2, Mistral) * HuggingFace * Google VertexAI (PaLM2, Gemini) * Mistral AI diff --git a/models/spring-ai-bedrock/README.md b/models/spring-ai-bedrock/README.md index 19e48518a6..782af7a853 100644 --- a/models/spring-ai-bedrock/README.md +++ b/models/spring-ai-bedrock/README.md @@ -8,4 +8,5 @@ - [Titan Chat Documentation](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/chat/bedrock/bedrock-titan.html) - [Titan Embedding Documentation](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/embeddings/bedrock-titan-embedding.html) - [Jurassic2 Chat Documentation](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/chat/bedrock/bedrock-jurassic2.html) +- [Mistral Chat Documentation](https://docs.spring.io/spring-ai/reference/1.0-SNAPSHOT/api/chat/bedrock/bedrock-mistral.html) diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModel.java new file mode 100644 index 0000000000..61460bbd38 --- /dev/null +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModel.java @@ -0,0 +1,297 @@ +/* + * Copyright 2023 - 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.bedrock.mistral; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.ai.bedrock.BedrockConverseChatGenerationMetadata; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.api.BedrockConverseApiUtils; +import org.springframework.ai.bedrock.api.BedrockConverseApi.BedrockConverseRequest; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.model.StreamingChatModel; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.ModelDescription; +import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.model.function.AbstractFunctionCallSupport; +import org.springframework.ai.model.function.FunctionCallbackContext; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +import reactor.core.publisher.Flux; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole; +import software.amazon.awssdk.services.bedrockruntime.model.Message; +import software.amazon.awssdk.services.bedrockruntime.model.StopReason; +import software.amazon.awssdk.services.bedrockruntime.model.Tool; +import software.amazon.awssdk.services.bedrockruntime.model.ToolConfiguration; +import software.amazon.awssdk.services.bedrockruntime.model.ToolInputSchema; +import software.amazon.awssdk.services.bedrockruntime.model.ToolResultBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ToolResultContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ToolResultStatus; +import software.amazon.awssdk.services.bedrockruntime.model.ToolSpecification; +import software.amazon.awssdk.services.bedrockruntime.model.ToolUseBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock.Type; + +/** + * Java {@link ChatModel} and {@link StreamingChatModel} for the Bedrock Mistral chat + * generative model. + * + * @author Wei Jiang + * @since 1.0.0 + */ +public class BedrockMistralChatModel extends AbstractFunctionCallSupport + implements ChatModel, StreamingChatModel { + + private final String modelId; + + private final BedrockConverseApi converseApi; + + private final BedrockMistralChatOptions defaultOptions; + + public BedrockMistralChatModel(BedrockConverseApi converseApi) { + this(converseApi, BedrockMistralChatOptions.builder().build()); + } + + public BedrockMistralChatModel(BedrockConverseApi converseApi, BedrockMistralChatOptions options) { + this(MistralChatModel.MISTRAL_LARGE.id(), converseApi, options); + } + + public BedrockMistralChatModel(String modelId, BedrockConverseApi converseApi, BedrockMistralChatOptions options) { + this(modelId, converseApi, options, null); + } + + public BedrockMistralChatModel(String modelId, BedrockConverseApi converseApi, BedrockMistralChatOptions options, + FunctionCallbackContext functionCallbackContext) { + super(functionCallbackContext); + + Assert.notNull(modelId, "modelId must not be null."); + Assert.notNull(converseApi, "BedrockConverseApi must not be null."); + Assert.notNull(options, "BedrockMistralChatOptions must not be null."); + + this.modelId = modelId; + this.converseApi = converseApi; + this.defaultOptions = options; + } + + @Override + public ChatResponse call(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); + + var request = createBedrockConverseRequest(prompt); + + return this.callWithFunctionSupport(request); + } + + @Override + public Flux stream(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); + + var request = createBedrockConverseRequest(prompt); + + return converseApi.converseStream(request); + } + + private BedrockConverseRequest createBedrockConverseRequest(Prompt prompt) { + var request = BedrockConverseApiUtils.createBedrockConverseRequest(modelId, prompt, defaultOptions); + + ToolConfiguration toolConfiguration = createToolConfiguration(prompt); + + return BedrockConverseRequest.from(request).withToolConfiguration(toolConfiguration).build(); + } + + private ToolConfiguration createToolConfiguration(Prompt prompt) { + Set functionsForThisRequest = new HashSet<>(); + + if (this.defaultOptions != null) { + Set promptEnabledFunctions = this.handleFunctionCallbackConfigurations(this.defaultOptions, + !IS_RUNTIME_CALL); + functionsForThisRequest.addAll(promptEnabledFunctions); + } + + if (prompt.getOptions() != null) { + if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { + BedrockMistralChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, + ChatOptions.class, BedrockMistralChatOptions.class); + + Set defaultEnabledFunctions = this.handleFunctionCallbackConfigurations(updatedRuntimeOptions, + IS_RUNTIME_CALL); + functionsForThisRequest.addAll(defaultEnabledFunctions); + } + else { + throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " + + prompt.getOptions().getClass().getSimpleName()); + } + } + + if (!CollectionUtils.isEmpty(functionsForThisRequest)) { + return ToolConfiguration.builder().tools(getFunctionTools(functionsForThisRequest)).build(); + } + + return null; + } + + private List getFunctionTools(Set functionNames) { + return this.resolveFunctionCallbacks(functionNames).stream().map(functionCallback -> { + var description = functionCallback.getDescription(); + var name = functionCallback.getName(); + String inputSchema = functionCallback.getInputTypeSchema(); + + return Tool.builder() + .toolSpec(ToolSpecification.builder() + .name(name) + .description(description) + .inputSchema(ToolInputSchema.builder() + .json(BedrockConverseApiUtils.convertObjectToDocument(ModelOptionsUtils.jsonToMap(inputSchema))) + .build()) + .build()) + .build(); + }).toList(); + } + + @Override + public ChatOptions getDefaultOptions() { + return defaultOptions; + } + + @Override + protected BedrockConverseRequest doCreateToolResponseRequest(BedrockConverseRequest previousRequest, + Message responseMessage, List conversationHistory) { + List toolToUseList = responseMessage.content() + .stream() + .filter(content -> content.type() == Type.TOOL_USE) + .map(content -> content.toolUse()) + .toList(); + + List toolResults = new ArrayList<>(); + + for (ToolUseBlock toolToUse : toolToUseList) { + var functionCallId = toolToUse.toolUseId(); + var functionName = toolToUse.name(); + var functionArguments = toolToUse.input().unwrap(); + + if (!this.functionCallbackRegister.containsKey(functionName)) { + throw new IllegalStateException("No function callback found for function name: " + functionName); + } + + String functionResponse = this.functionCallbackRegister.get(functionName) + .call(ModelOptionsUtils.toJsonString(functionArguments)); + + toolResults.add(ToolResultBlock.builder() + .toolUseId(functionCallId) + .status(ToolResultStatus.SUCCESS) + .content(ToolResultContentBlock.builder().text(functionResponse).build()) + .build()); + } + + // Add the function response to the conversation. + Message toolResultMessage = Message.builder() + .content(toolResults.stream().map(toolResult -> ContentBlock.fromToolResult(toolResult)).toList()) + .role(ConversationRole.USER) + .build(); + conversationHistory.add(toolResultMessage); + + // Recursively call chatCompletionWithTools until the model doesn't call a + // functions anymore. + return BedrockConverseRequest.from(previousRequest).withMessages(conversationHistory).build(); + } + + @Override + protected List doGetUserMessages(BedrockConverseRequest request) { + return request.messages(); + } + + @Override + protected Message doGetToolResponseMessage(ChatResponse response) { + Generation result = response.getResult(); + + var metadata = (BedrockConverseChatGenerationMetadata) result.getMetadata(); + + return metadata.getMessage(); + } + + @Override + protected ChatResponse doChatCompletion(BedrockConverseRequest request) { + return converseApi.converse(request); + } + + @Override + protected Flux doChatCompletionStream(BedrockConverseRequest request) { + throw new UnsupportedOperationException("Streaming function calling is not supported."); + } + + @Override + protected boolean isToolFunctionCall(ChatResponse response) { + Generation result = response.getResult(); + if (result == null) { + return false; + } + + return StopReason.fromValue(result.getMetadata().getFinishReason()) == StopReason.TOOL_USE; + } + + /** + * Mistral models version. + */ + public enum MistralChatModel implements ModelDescription { + + /** + * mistral.mistral-7b-instruct-v0:2 + */ + MISTRAL_7B_INSTRUCT("mistral.mistral-7b-instruct-v0:2"), + + /** + * mistral.mixtral-8x7b-instruct-v0:1 + */ + MISTRAL_8X7B_INSTRUCT("mistral.mixtral-8x7b-instruct-v0:1"), + + /** + * mistral.mistral-large-2402-v1:0 + */ + MISTRAL_LARGE("mistral.mistral-large-2402-v1:0"), + + /** + * mistral.mistral-small-2402-v1:0 + */ + MISTRAL_SMALL("mistral.mistral-small-2402-v1:0"); + + private final String id; + + /** + * @return The model id. + */ + public String id() { + return id; + } + + MistralChatModel(String value) { + this.id = value; + } + + @Override + public String getModelName() { + return this.id; + } + + } + +} diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatOptions.java new file mode 100644 index 0000000000..d2d7f9f78f --- /dev/null +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatOptions.java @@ -0,0 +1,256 @@ +/* + * Copyright 2023 - 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.bedrock.mistral; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.util.Assert; + +/** + * Java {@link ChatOptions} for the Bedrock Mistral chat generative model chat options. + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral-text-completion.html + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral-chat-completion.html + * + * @author Wei Jiang + * @since 1.0.0 + */ +@JsonInclude(Include.NON_NULL) +public class BedrockMistralChatOptions implements ChatOptions, FunctionCallingOptions { + + /** + * The temperature value controls the randomness of the generated text. Use a lower + * value to decrease randomness in the response. + */ + private @JsonProperty("temperature") Float temperature; + + /** + * (optional) The maximum cumulative probability of tokens to consider when sampling. + * The generative uses combined Top-k and nucleus sampling. Nucleus sampling considers + * the smallest set of tokens whose probability sum is at least topP. + */ + private @JsonProperty("top_p") Float topP; + + /** + * (optional) Specify the number of token choices the generative uses to generate the + * next token. + */ + private @JsonProperty("top_k") Integer topK; + + /** + * (optional) Specify the maximum number of tokens to use in the generated response. + */ + private @JsonProperty("max_tokens") Integer maxTokens; + + /** + * (optional) Configure up to four sequences that the generative recognizes. After a + * stop sequence, the generative stops generating further tokens. The returned text + * doesn't contain the stop sequence. + */ + private @JsonProperty("stop") List stopSequences; + + /** + * (optional) Specifies how functions are called. If set to none the model won't call + * a function and will generate a message instead. If set to auto the model can choose + * to either generate a message or call a function. If set to any the model is forced + * to call a function. + */ + private @JsonProperty("tool_choice") String toolChoice; + + /** + * Tool Function Callbacks to register with the ChatModel. For Prompt Options the + * functionCallbacks are automatically enabled for the duration of the prompt + * execution. For Default Options the functionCallbacks are registered but disabled by + * default. Use the enableFunctions to set the functions from the registry to be used + * by the ChatModel chat completion requests. + */ + @NestedConfigurationProperty + @JsonIgnore + private List functionCallbacks = new ArrayList<>(); + + /** + * List of functions, identified by their names, to configure for function calling in + * the chat completion requests. Functions with those names must exist in the + * functionCallbacks registry. The {@link #functionCallbacks} from the PromptOptions + * are automatically enabled for the duration of the prompt execution. + * + * Note that function enabled with the default options are enabled for all chat + * completion requests. This could impact the token count and the billing. If the + * functions is set in a prompt options, then the enabled functions are only active + * for the duration of this prompt execution. + */ + @NestedConfigurationProperty + @JsonIgnore + private Set functions = new HashSet<>(); + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final BedrockMistralChatOptions options = new BedrockMistralChatOptions(); + + public Builder withTemperature(Float temperature) { + this.options.setTemperature(temperature); + return this; + } + + public Builder withTopP(Float topP) { + this.options.setTopP(topP); + return this; + } + + public Builder withTopK(Integer topK) { + this.options.setTopK(topK); + return this; + } + + public Builder withMaxTokens(Integer maxTokens) { + this.options.setMaxTokens(maxTokens); + return this; + } + + public Builder withStopSequences(List stopSequences) { + this.options.setStopSequences(stopSequences); + return this; + } + + public Builder withToolChoice(String toolChoice) { + this.options.toolChoice = toolChoice; + return this; + } + + public Builder withFunctionCallbacks(List functionCallbacks) { + this.options.functionCallbacks = functionCallbacks; + return this; + } + + public Builder withFunctions(Set functionNames) { + Assert.notNull(functionNames, "Function names must not be null"); + this.options.functions = functionNames; + return this; + } + + public Builder withFunction(String functionName) { + Assert.hasText(functionName, "Function name must not be empty"); + this.options.functions.add(functionName); + return this; + } + + public BedrockMistralChatOptions build() { + return this.options; + } + + } + + public void setTemperature(Float temperature) { + this.temperature = temperature; + } + + @Override + public Float getTemperature() { + return this.temperature; + } + + public void setTopP(Float topP) { + this.topP = topP; + } + + @Override + public Float getTopP() { + return this.topP; + } + + public void setTopK(Integer topK) { + this.topK = topK; + } + + @Override + public Integer getTopK() { + return this.topK; + } + + public Integer getMaxTokens() { + return maxTokens; + } + + public void setMaxTokens(Integer maxTokens) { + this.maxTokens = maxTokens; + } + + public List getStopSequences() { + return stopSequences; + } + + public void setStopSequences(List stopSequences) { + this.stopSequences = stopSequences; + } + + public String getToolChoice() { + return toolChoice; + } + + public void setToolChoice(String toolChoice) { + this.toolChoice = toolChoice; + } + + @Override + public List getFunctionCallbacks() { + return this.functionCallbacks; + } + + @Override + public void setFunctionCallbacks(List functionCallbacks) { + Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null"); + this.functionCallbacks = functionCallbacks; + } + + @Override + public Set getFunctions() { + return this.functions; + } + + @Override + public void setFunctions(Set functions) { + Assert.notNull(functions, "Function must not be null"); + this.functions = functions; + } + + public static BedrockMistralChatOptions fromOptions(BedrockMistralChatOptions fromOptions) { + return builder().withTemperature(fromOptions.getTemperature()) + .withTopP(fromOptions.getTopP()) + .withTopK(fromOptions.getTopK()) + .withMaxTokens(fromOptions.getMaxTokens()) + .withStopSequences(fromOptions.getStopSequences()) + .withToolChoice(fromOptions.toolChoice) + .withFunctionCallbacks(fromOptions.getFunctionCallbacks()) + .withFunctions(fromOptions.getFunctions()) + .build(); + } + +} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModelIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModelIT.java new file mode 100644 index 0000000000..c1a6e3a834 --- /dev/null +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModelIT.java @@ -0,0 +1,246 @@ +/* + * Copyright 2023 - 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.bedrock.mistral; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.bedrock.MockWeatherService; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.chat.prompt.SystemPromptTemplate; +import org.springframework.ai.converter.BeanOutputConverter; +import org.springframework.ai.converter.ListOutputConverter; +import org.springframework.ai.converter.MapOutputConverter; +import org.springframework.ai.model.function.FunctionCallbackWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.io.Resource; + +import reactor.core.publisher.Flux; +import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; +import software.amazon.awssdk.regions.Region; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@SpringBootTest +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +public class BedrockMistralChatModelIT { + + private static final Logger logger = LoggerFactory.getLogger(BedrockMistralChatModelIT.class); + + @Autowired + private BedrockMistralChatModel chatModel; + + @Value("classpath:/prompts/system-message.st") + private Resource systemResource; + + @Test + void multipleStreamAttempts() { + + Flux joke1Stream = chatModel.stream(new Prompt(new UserMessage("Tell me a joke?"))); + Flux joke2Stream = chatModel.stream(new Prompt(new UserMessage("Tell me a toy joke?"))); + + String joke1 = joke1Stream.collectList() + .block() + .stream() + .map(ChatResponse::getResults) + .flatMap(List::stream) + .map(Generation::getOutput) + .map(AssistantMessage::getContent) + .collect(Collectors.joining()); + String joke2 = joke2Stream.collectList() + .block() + .stream() + .map(ChatResponse::getResults) + .flatMap(List::stream) + .map(Generation::getOutput) + .map(AssistantMessage::getContent) + .collect(Collectors.joining()); + + assertThat(joke1).isNotBlank(); + assertThat(joke2).isNotBlank(); + } + + @Test + void roleTest() { + String request = "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."; + String name = "Bob"; + String voice = "pirate"; + UserMessage userMessage = new UserMessage(request); + SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource); + Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice)); + Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); + ChatResponse response = chatModel.call(prompt); + assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard"); + } + + @Test + void listOutputConverter() { + DefaultConversionService conversionService = new DefaultConversionService(); + ListOutputConverter outputConverter = new ListOutputConverter(conversionService); + + String format = outputConverter.getFormat(); + String template = """ + List five {subject} + {format} + """; + PromptTemplate promptTemplate = new PromptTemplate(template, + Map.of("subject", "ice cream flavors.", "format", format)); + Prompt prompt = new Prompt(promptTemplate.createMessage()); + Generation generation = this.chatModel.call(prompt).getResult(); + + List list = outputConverter.convert(generation.getOutput().getContent()); + assertThat(list).hasSize(5); + } + + @Test + void mapOutputConverter() { + MapOutputConverter outputConverter = new MapOutputConverter(); + + String format = outputConverter.getFormat(); + String template = """ + Remove Markdown code blocks from the output. + Provide me a List of {subject} + {format} + """; + PromptTemplate promptTemplate = new PromptTemplate(template, + Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", format)); + Prompt prompt = new Prompt(promptTemplate.createMessage()); + Generation generation = chatModel.call(prompt).getResult(); + + Map result = outputConverter.convert(generation.getOutput().getContent()); + assertThat(result.get("numbers")).isEqualTo(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); + + } + + record ActorsFilmsRecord(String actor, List movies) { + } + + @Test + void beanOutputConverterRecords() { + + BeanOutputConverter outputConverter = new BeanOutputConverter<>(ActorsFilmsRecord.class); + + String format = outputConverter.getFormat(); + String template = """ + Generate the filmography of 5 movies for Tom Hanks. + {format} + Remove Markdown code blocks from the output. + """; + PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format)); + Prompt prompt = new Prompt(promptTemplate.createMessage()); + Generation generation = chatModel.call(prompt).getResult(); + + ActorsFilmsRecord actorsFilms = outputConverter.convert(generation.getOutput().getContent()); + assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks"); + assertThat(actorsFilms.movies()).hasSize(5); + } + + @Test + void beanStreamOutputConverterRecords() { + + BeanOutputConverter outputConverter = new BeanOutputConverter<>(ActorsFilmsRecord.class); + + String format = outputConverter.getFormat(); + String template = """ + Generate the filmography of 5 movies for Tom Hanks. + {format} + Remove Markdown code blocks from the output. + """; + PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format)); + Prompt prompt = new Prompt(promptTemplate.createMessage()); + + String generationTextFromStream = chatModel.stream(prompt) + .collectList() + .block() + .stream() + .map(ChatResponse::getResults) + .flatMap(List::stream) + .map(Generation::getOutput) + .map(AssistantMessage::getContent) + .collect(Collectors.joining()); + + ActorsFilmsRecord actorsFilms = outputConverter.convert(generationTextFromStream); + System.out.println(actorsFilms); + assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks"); + assertThat(actorsFilms.movies()).hasSize(5); + } + + @Test + void functionCallTest() { + UserMessage userMessage = new UserMessage( + "What's the weather like in San Francisco, Tokyo and Paris? Return the result in Celsius."); + + List messages = new ArrayList<>(List.of(userMessage)); + + var promptOptions = BedrockMistralChatOptions.builder() + .withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService()) + .withName("getCurrentWeather") + .withDescription("Get the weather in location. Return temperature in 36°F or 36°C format.") + .build())) + .build(); + + ChatResponse response = chatModel.call(new Prompt(messages, promptOptions)); + + logger.info("Response: {}", response); + + Generation generation = response.getResult(); + assertThat(generation.getOutput().getContent()).containsAnyOf("30.0", "30"); + assertThat(generation.getOutput().getContent()).containsAnyOf("10.0", "10"); + assertThat(generation.getOutput().getContent()).containsAnyOf("15.0", "15"); + } + + @SpringBootConfiguration + public static class TestConfiguration { + + @Bean + public BedrockConverseApi converseApi() { + return new BedrockConverseApi(EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), + Duration.ofMinutes(2)); + } + + @Bean + public BedrockMistralChatModel mistralChatModel(BedrockConverseApi converseApi) { + return new BedrockMistralChatModel(converseApi); + } + + } + +} diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc index 9475388b43..e73f932563 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc @@ -17,6 +17,8 @@ **** xref:api/chat/bedrock/bedrock-cohere.adoc[Cohere] **** xref:api/chat/bedrock/bedrock-titan.adoc[Titan] **** xref:api/chat/bedrock/bedrock-jurassic2.adoc[Jurassic2] +**** xref:api/chat/bedrock/bedrock-mistral.adoc[Mistral] +***** xref:api/chat/functions/bedrock/bedrock-mistral-chat-functions.adoc[Function Calling] *** xref:api/chat/huggingface.adoc[HuggingFace] *** xref:api/chat/google-vertexai.adoc[Google VertexAI] **** xref:api/chat/vertexai-palm2-chat.adoc[VertexAI PaLM2 ] diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc index 8671dea707..fd4b15b31e 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc @@ -106,3 +106,4 @@ For more information, refer to the documentation below for each supported model. * xref:api/chat/bedrock/bedrock-titan.adoc[Spring AI Bedrock Titan Chat]: `spring.ai.bedrock.titan.chat.enabled=true` * xref:api/embeddings/bedrock-titan-embedding.adoc[Spring AI Bedrock Titan Embeddings]: `spring.ai.bedrock.titan.embedding.enabled=true` * xref:api/chat/bedrock/bedrock-jurassic2.adoc[Spring AI Bedrock Ai21 Jurassic2 Chat]: `spring.ai.bedrock.jurassic2.chat.enabled=true` +* xref:api/chat/bedrock/bedrock-mistral.adoc[Spring AI Bedrock Mistral Chat]: `spring.ai.bedrock.mistral.chat.enabled=true` diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-mistral.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-mistral.adoc new file mode 100644 index 0000000000..95fcc60539 --- /dev/null +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-mistral.adoc @@ -0,0 +1,206 @@ += Mistral Chat + +Provides Bedrock Mistral chat model. +Integrate generative AI capabilities into essential apps and workflows that improve business outcomes. + +The https://aws.amazon.com/bedrock/mistral/[AWS Bedrock Mistral Model Page] and https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html[Amazon Bedrock User Guide] contains detailed information on how to use the AWS hosted model. + +== Prerequisites + +Refer to the xref:api/bedrock.adoc[Spring AI documentation on Amazon Bedrock] for setting up API access. + +=== Add Repositories and BOM + +Spring AI artifacts are published in Spring Milestone and Snapshot repositories. Refer to the xref:getting-started.adoc#repositories[Repositories] section to add these repositories to your build system. + +To help with dependency management, Spring AI provides a BOM (bill of materials) to ensure that a consistent version of Spring AI is used throughout the entire project. Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build system. + + +== Auto-configuration + +Add the `spring-ai-bedrock-ai-spring-boot-starter` dependency to your project's Maven `pom.xml` file: + +[source,xml] +---- + + org.springframework.ai + spring-ai-bedrock-ai-spring-boot-starter + +---- + +or to your Gradle `build.gradle` build file. + +[source,gradle] +---- +dependencies { + implementation 'org.springframework.ai:spring-ai-bedrock-ai-spring-boot-starter' +} +---- + +TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file. + +=== Enable Mistral Chat Support + +By default the Mistral model is disabled. +To enable it set the `spring.ai.bedrock.mistral.chat.enabled` property to `true`. +Exporting environment variable is one way to set this configuration property: + +[source,shell] +---- +export SPRING_AI_BEDROCK_MISTRAL_CHAT_ENABLED=true +---- + +=== Chat Properties + +The prefix `spring.ai.bedrock.aws` is the property prefix to configure the connection to AWS Bedrock. + +[cols="3,3,3"] +|==== +| Property | Description | Default + +| spring.ai.bedrock.aws.region | AWS region to use. | us-east-1 +| spring.ai.bedrock.aws.timeout | AWS timeout to use. | 5m +| spring.ai.bedrock.aws.access-key | AWS access key. | - +| spring.ai.bedrock.aws.secret-key | AWS secret key. | - +|==== + +The prefix `spring.ai.bedrock.mistral.chat` is the property prefix that configures the chat model implementation for Mistral. + +[cols="2,5,1"] +|==== +| Property | Description | Default + +| spring.ai.bedrock.mistral.chat.enabled | Enable or disable support for Mistral | false +| spring.ai.bedrock.mistral.chat.model | The model id to use. See the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModel.java[MistralChatModel] for the supported models. | mistral.mistral-large-2402-v1:0 +| spring.ai.bedrock.mistral.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | AWS Bedrock default +| spring.ai.bedrock.mistral.chat.options.topP | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default +| spring.ai.bedrock.mistral.chat.options.topK | Specify the number of token choices the model uses to generate the next token | AWS Bedrock default +| spring.ai.bedrock.mistral.chat.options.maxTokens | Specify the maximum number of tokens to use in the generated response. | AWS Bedrock default +| spring.ai.bedrock.mistral.chat.options.stopSequences | Configure up to four sequences that the model recognizes. | AWS Bedrock default +| spring.ai.bedrock.mistral.chat.options.stopSequences | Specifies how functions are called. If set to none the model won't call a function and will generate a message instead. If set to auto the model can choose to either generate a message or call a function. If set to any the model is forced to call a function. | AWS Bedrock default +|==== + +Look at the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModel.java[MistralChatModel] for other model IDs. +Supported values are: `mistral.mistral-7b-instruct-v0:2`, `mistral.mixtral-8x7b-instruct-v0:1`, `mistral.mistral-large-2402-v1:0` and `mistral.mistral-small-2402-v1:0`. +Model ID values can also be found in the https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html[AWS Bedrock documentation for base model IDs]. + +TIP: All properties prefixed with `spring.ai.bedrock.mistral.chat.options` can be overridden at runtime by adding a request specific <> to the `Prompt` call. + +== Runtime Options [[chat-options]] + +The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatOptions.java[BedrockMistralChatOptions.java] provides model configurations, such as temperature, topK, topP, etc. + +On start-up, the default options can be configured with the `BedrockMistralChatModel(api, options)` constructor or the `spring.ai.bedrock.mistral.chat.options.*` properties. + +At run-time you can override the default options by adding new, request specific, options to the `Prompt` call. +For example to override the default temperature for a specific request: + +[source,java] +---- +ChatResponse response = chatModel.call( + new Prompt( + "Generate the names of 5 famous pirates.", + BedrockMistralChatOptions.builder() + .withTemperature(0.4) + .build() + )); +---- + +TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatOptions.java[BedrockMistralChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/prompt/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()]. + +== Sample Controller + +https://start.spring.io/[Create] a new Spring Boot project and add the `spring-ai-bedrock-ai-spring-boot-starter` to your pom (or gradle) dependencies. + +Add a `application.properties` file, under the `src/main/resources` directory, to enable and configure the Mistral chat model: + +[source] +---- +spring.ai.bedrock.aws.region=eu-central-1 +spring.ai.bedrock.aws.timeout=1000ms +spring.ai.bedrock.aws.access-key=${AWS_ACCESS_KEY_ID} +spring.ai.bedrock.aws.secret-key=${AWS_SECRET_ACCESS_KEY} + +spring.ai.bedrock.mistral.chat.enabled=true +spring.ai.bedrock.mistral.chat.options.temperature=0.8 +---- + +TIP: replace the `regions`, `access-key` and `secret-key` with your AWS credentials. + +This will create a `BedrockMistralChatModel` implementation that you can inject into your class. +Here is an example of a simple `@Controller` class that uses the chat model for text generations. + +[source,java] +---- +@RestController +public class ChatController { + + private final BedrockMistralChatModel chatModel; + + @Autowired + public ChatController(BedrockMistralChatModel chatModel) { + this.chatModel = chatModel; + } + + @GetMapping("/ai/generate") + public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { + return Map.of("generation", chatModel.call(message)); + } + + @GetMapping("/ai/generateStream") + public Flux generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { + Prompt prompt = new Prompt(new UserMessage(message)); + return chatModel.stream(prompt); + } +} +---- + +== Manual Configuration + +The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModel.java[BedrockMistralChatModel] implements the `ChatModel` and `StreamingChatModel` and uses the <> to connect to the Bedrock Mistral service. + +Add the `spring-ai-bedrock` dependency to your project's Maven `pom.xml` file: + +[source,xml] +---- + + org.springframework.ai + spring-ai-bedrock + +---- + +or to your Gradle `build.gradle` build file. + +[source,gradle] +---- +dependencies { + implementation 'org.springframework.ai:spring-ai-bedrock' +} +---- + +TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file. + +Next, create an https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModel.java[BedrockMistralChatModel] and use it for text generations: + +[source,java] +---- +BedrockConverseApi converseApi = new BedrockConverseApi( + EnvironmentVariableCredentialsProvider.create(), + Region.EU_CENTRAL_1.id(), + Duration.ofMillis(1000L)); + +BedrockMistralChatModel chatModel = new BedrockMistralChatModel(converseApi, + BedrockMistralChatOptions.builder() + .withTemperature(0.6f) + .withTopK(10) + .withTopP(0.5f) + .withMaxTokens(678) + .build() + +ChatResponse response = chatModel.call( + new Prompt("Generate the names of 5 famous pirates.")); + +// Or with streaming responses +Flux response = chatModel.stream( + new Prompt("Generate the names of 5 famous pirates.")); +---- diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-mistral-chat-functions.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-mistral-chat-functions.adoc new file mode 100644 index 0000000000..3e76c632c7 --- /dev/null +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-mistral-chat-functions.adoc @@ -0,0 +1,187 @@ += Bedrock Mistral Function Calling + +You can register custom Java functions with the `BedrockMistralChatModel` and have the Bedrock Mistral models intelligently choose to output a JSON object containing arguments to call one or many of the registered functions. +This allows you to connect the LLM capabilities with external tools and APIs. + +The Bedrock Mistral API does not call the function directly; instead, the model generates JSON that you can use to call the function in your code and return the result back to the model to complete the conversation. + +Spring AI provides flexible and user-friendly ways to register and call custom functions. +In general, the custom functions need to provide a function `name`, `description`, and the function call `signature` (as JSON schema) to let the model know what arguments the function expects. +The `description` helps the model to understand when to call the function. + +As a developer, you need to implement a function that takes the function call arguments sent from the AI model, and respond with the result back to the model. +Your function can in turn invoke other 3rd party services to provide the results. + +Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`. + +Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code. +The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions. + +== How it works + +Suppose we want the AI model to respond with information that it does not have, for example the current temperature at a given location. + +We can provide the AI model with metadata about our own functions that it can use to retrieve that information as it processes your prompt. + +For example, if during the processing of a prompt, the AI Model determines that it needs additional information about the temperature in a given location, it will start a server side generated request/response interaction. The AI Model invokes a client side function. +The AI Model provides method invocation details as JSON and it is the responsibility of the client to execute that function and return the response. + +Spring AI greatly simplifies the code you need to write to support function invocation. +It brokers the function invocation conversation for you. +You can simply provide your function definition as a `@Bean` and then provide the bean name of the function in your prompt options. +You can also reference multiple function bean names in your prompt. + +== Quick Start + +Let's create a chatbot that answer questions by calling our own function. +To support the response of the chatbot, we will register our own function that takes a location and returns the current weather in that location. + +When the response to the prompt to the model needs to answer a question such as `"What’s the weather like in Boston?"` the AI model will invoke the client providing the location value as an argument to be passed to the function. This RPC-like data is passed as JSON. + +Our function can some SaaS based weather service API and returns the weather response back to the model to complete the conversation. +In this example we will use a simple implementation named `MockWeatherService` that hard codes the temperature for various locations. + +The following `MockWeatherService.java` represents the weather service API: + +[source,java] +---- +public class MockWeatherService implements Function { + + public enum Unit { C, F } + public record Request(String location, Unit unit) {} + public record Response(double temp, Unit unit) {} + + public Response apply(Request request) { + return new Response(30.0, Unit.C); + } +} +---- + +=== Registering Functions as Beans + +With the link:../bedrock/bedrock-mistral.html#_auto_configuration[BedrockMistralChatModel Auto-Configuration] you have multiple ways to register custom functions as beans in the Spring context. + +We start with describing the most POJO friendly options. + +==== Plain Java Functions + +In this approach you define `@Beans` in your application context as you would any other Spring managed object. + +Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` wrapper that adds the logic for it being invoked via the AI model. +The name of the `@Bean` is passed as a `ChatOption`. + + +[source,java] +---- +@Configuration +static class Config { + + @Bean + @Description("Get the weather in location") // function description + public Function weatherFunction1() { + return new MockWeatherService(); + } + ... +} +---- + +The `@Description` annotation is optional and provides a function description (2) that helps the model understand when to call the function. +It is an important property to set to help the AI model determine what client side function to invoke. + +Another option to provide the description of the function is to use the `@JsonClassDescription` annotation on the `MockWeatherService.Request` to provide the function description: + +[source,java] +---- + +@Configuration +static class Config { + + @Bean + public Function currentWeather3() { // (1) bean name as function name. + return new MockWeatherService(); + } + ... +} + +@JsonClassDescription("Get the weather in location") // (2) function description +public record Request(String location, Unit unit) {} +---- + +It is a best practice to annotate the request object with information such that the generated JSON schema of that function is as descriptive as possible to help the AI model pick the correct function to invoke. + +The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/mistral/tool/FunctionCallWithFunctionBeanIT.java.java[FunctionCallWithFunctionBeanIT.java] demonstrates this approach. + + +==== FunctionCallback Wrapper + +Another way to register a function is to create a `FunctionCallbackWrapper` wrapper like this: + +[source,java] +---- +@Configuration +static class Config { + + @Bean + public FunctionCallback weatherFunctionInfo() { + + return new FunctionCallbackWrapper<>("CurrentWeather", // (1) function name + "Get the weather in location", // (2) function description + (response) -> "" + response.temp() + response.unit(), // (3) Response Converter + new MockWeatherService()); // function code + } + ... +} +---- + +It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `BedrockMistralChatModel`. +It also provides a description (2) and an optional response converter (3) to convert the response into a text as expected by the model. + +NOTE: By default, the response converter does a JSON serialization of the Response object. + +NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class. + +=== Specifying functions in Chat Options + +To let the model know and call your `CurrentWeather` function you need to enable it in your prompt requests: + +[source,java] +---- +BedrockMistralChatModel chatModel = ... + +UserMessage userMessage = new UserMessage("What's the weather like in Paris?"); + +ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), + BedrockMistralChatOptions.builder().withFunction("CurrentWeather").build())); // (1) Enable the function + +logger.info("Response: {}", response); +---- + +// NOTE: You can can have multiple functions registered in your `ChatModel` but only those enabled in the prompt request will be considered for the function calling. + +Above user question will trigger 3 calls to `CurrentWeather` function (one for each city) and produce the final response. + +=== Register/Call Functions with Prompt Options + +In addition to the auto-configuration you can register callback functions, dynamically, with your Prompt requests: + +[source,java] +---- +BedrockMistralChatModel chatModel = ... + +UserMessage userMessage = new UserMessage("What's the weather like in Paris?"); + +var promptOptions = BedrockMistralChatOptions.builder() + .withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>( + "CurrentWeather", // name + "Get the weather in location", // function description + new MockWeatherService()))) // function code + .build(); + +ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); +---- + +NOTE: The in-prompt registered functions are enabled by default for the duration of this request. + +This approach allows to dynamically chose different functions to be called based on the user input. + +The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/mistral/tool/FunctionCallWithPromptFunctionIT.java[FunctionCallWithPromptFunctionIT.java] integration test provides a complete example of how to register a function with the `BedrockAnthropic3ChatModel` and use it in a prompt request. \ No newline at end of file diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatmodel.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatmodel.adoc index 9fdce3b424..8918e34e0e 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatmodel.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chatmodel.adoc @@ -202,6 +202,7 @@ image::spring-ai-chat-completions-clients.jpg[align="center", width="800px"] ** xref:api/chat/bedrock/bedrock-titan.adoc[Titan Chat Completion] ** xref:api/chat/bedrock/bedrock-anthropic.adoc[Anthropic Chat Completion] ** xref:api/chat/bedrock/bedrock-jurassic2.adoc[Jurassic2 Chat Completion] +** xref:api/chat/bedrock/bedrock-mistral.adoc[Mistral Chat Completion] * xref:api/chat/mistralai-chat.adoc[Mistral AI Chat Completion] (streaming & function-calling support) * xref:api/chat/anthropic-chat.adoc[Anthropic Chat Completion] (streaming) diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc index c704e83220..6a2bba63a6 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc @@ -14,4 +14,5 @@ Spring AI currently supports Function invocation for the following AI Models * Anthropic Claude: Refer to the xref:api/chat/functions/anthropic-chat-functions.adoc[Anthropic Claude function invocation docs]. * MiniMax : Refer to the xref:api/chat/functions/minimax-chat-functions.adoc[MiniMax function invocation docs]. * ZhiPu AI : Refer to the xref:api/chat/functions/zhipuai-chat-functions.adoc[ZhiPu AI function invocation docs]. -* Amazon Bedrock Anthropic 3 : Refer to the xref:api/chat/functions/bedrock/bedrock-anthropic3-chat-functions.adoc[Amazon Bedrock Anthropic3 function invocation docs]. \ No newline at end of file +* Amazon Bedrock Anthropic 3 : Refer to the xref:api/chat/functions/bedrock/bedrock-anthropic3-chat-functions.adoc[Amazon Bedrock Anthropic3 function invocation docs]. +* Amazon Bedrock Mistral : Refer to the xref:api/chat/functions/bedrock/bedrock-mistral-chat-functions.adoc[Amazon Bedrock Mistral function invocation docs]. \ No newline at end of file diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/structured-output-converter.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/structured-output-converter.adoc index 6346d4e5c1..8d9d9f3b48 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/structured-output-converter.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/structured-output-converter.adoc @@ -210,7 +210,8 @@ The following AI Models have been tested to support List, Map and Bean structure | xref:api/chat/bedrock/bedrock-anthropic.adoc[Bedrock Anthropic 2] | link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic/BedrockAnthropicChatModelIT.java[BedrockAnthropicChatModelIT.java] | xref:api/chat/bedrock/bedrock-anthropic3.adoc[Bedrock Anthropic 3] | link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModelIT.java[BedrockAnthropic3ChatModelIT.java] | xref:api/chat/bedrock/bedrock-cohere.adoc[Bedrock Cohere] | link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereChatModelIT.java[BedrockCohereChatModelIT.java] -| xref:api/chat/bedrock/bedrock-llama.adoc[Bedrock Llama] | link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModelIT.java[BedrockLlamaChatModelIT.java.java] +| xref:api/chat/bedrock/bedrock-llama.adoc[Bedrock Llama] | link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/llama/BedrockLlamaChatModelIT.java[BedrockLlamaChatModelIT.java] +| xref:api/chat/bedrock/bedrock-mistral.adoc[Bedrock Mistral] | link:https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/mistral/BedrockMistralChatModelIT.java[BedrockMistralChatModelIT.java] |==== == Build-in JSON mode diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc index 224df22be4..b4378566d8 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc @@ -153,6 +153,7 @@ Each of the following sections in the documentation shows which dependencies you *** xref:api/chat/bedrock/bedrock-titan.adoc[Titan Chat Completion] *** xref:api/chat/bedrock/bedrock-anthropic.adoc[Anthropic Chat Completion] *** xref:api/chat/bedrock/bedrock-jurassic2.adoc[Jurassic2 Chat Completion] +*** xref:api/chat/bedrock/bedrock-mistral.adoc[Mistral Chat Completion] ** xref:api/chat/mistralai-chat.adoc[MistralAI Chat Completion] (streaming and function-calling support) === Image Generation Models diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatAutoConfiguration.java new file mode 100644 index 0000000000..cafda0096b --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatAutoConfiguration.java @@ -0,0 +1,76 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.mistral; + +import java.util.List; + +import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; +import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatModel; +import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.model.function.FunctionCallbackContext; +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.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.util.CollectionUtils; + +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; + +/** + * {@link AutoConfiguration Auto-configuration} for Bedrock Mistral Chat Client. + * + * Leverages the Spring Cloud AWS to resolve the {@link AwsCredentialsProvider}. + * + * @author Wei Jiang + * @since 1.0.0 + */ +@AutoConfiguration(after = BedrockConverseApiAutoConfiguration.class) +@ConditionalOnClass(BedrockConverseApi.class) +@EnableConfigurationProperties({ BedrockMistralChatProperties.class, BedrockAwsConnectionProperties.class }) +@ConditionalOnProperty(prefix = BedrockMistralChatProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true") +@Import(BedrockAwsConnectionConfiguration.class) +public class BedrockMistralChatAutoConfiguration { + + @Bean + @ConditionalOnBean(BedrockConverseApi.class) + public BedrockMistralChatModel mistralChatModel(BedrockConverseApi converseApi, + BedrockMistralChatProperties properties, FunctionCallbackContext functionCallbackContext, + List toolFunctionCallbacks) { + if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) { + properties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks); + } + + return new BedrockMistralChatModel(properties.getModel(), converseApi, properties.getOptions(), + functionCallbackContext); + } + + @Bean + @ConditionalOnMissingBean + public FunctionCallbackContext springAiFunctionManager(ApplicationContext context) { + FunctionCallbackContext manager = new FunctionCallbackContext(); + manager.setApplicationContext(context); + return manager; + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatProperties.java new file mode 100644 index 0000000000..ecd88b8cd2 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatProperties.java @@ -0,0 +1,72 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.mistral; + +import org.springframework.ai.bedrock.mistral.BedrockMistralChatOptions; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatModel.MistralChatModel; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * Configuration properties for Bedrock Mistral. + * + * @author Wei Jiang + * @since 1.0.0 + */ +@ConfigurationProperties(BedrockMistralChatProperties.CONFIG_PREFIX) +public class BedrockMistralChatProperties { + + public static final String CONFIG_PREFIX = "spring.ai.bedrock.mistral.chat"; + + /** + * Enable Bedrock Mistral Chat Client. False by default. + */ + private boolean enabled = false; + + /** + * Bedrock Mistral Chat generative name. Defaults to + * 'mistral.mistral-large-2402-v1:0'. + */ + private String model = MistralChatModel.MISTRAL_LARGE.id(); + + @NestedConfigurationProperty + private BedrockMistralChatOptions options = BedrockMistralChatOptions.builder().build(); + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getModel() { + return this.model; + } + + public void setModel(String model) { + this.model = model; + } + + public BedrockMistralChatOptions getOptions() { + return this.options; + } + + public void setOptions(BedrockMistralChatOptions options) { + this.options = options; + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index ddbfd533d8..4d2ebca22b 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -14,6 +14,7 @@ org.springframework.ai.autoconfigure.bedrock.anthropic.BedrockAnthropicChatAutoC org.springframework.ai.autoconfigure.bedrock.anthropic3.BedrockAnthropic3ChatAutoConfiguration org.springframework.ai.autoconfigure.bedrock.titan.BedrockTitanChatAutoConfiguration org.springframework.ai.autoconfigure.bedrock.titan.BedrockTitanEmbeddingAutoConfiguration +org.springframework.ai.autoconfigure.bedrock.mistral.BedrockMistralChatAutoConfiguration org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration org.springframework.ai.autoconfigure.mistralai.MistralAiAutoConfiguration org.springframework.ai.autoconfigure.vectorstore.pgvector.PgVectorStoreAutoConfiguration diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatAutoConfigurationIT.java new file mode 100644 index 0000000000..7d0b59842b --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/BedrockMistralChatAutoConfigurationIT.java @@ -0,0 +1,160 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.mistral; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.messages.AssistantMessage; +import reactor.core.publisher.Flux; +import software.amazon.awssdk.regions.Region; + +import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatModel; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatModel.MistralChatModel; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.SystemPromptTemplate; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +public class BedrockMistralChatAutoConfigurationIT { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.mistral.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), + "spring.ai.bedrock.mistral.chat.model=" + MistralChatModel.MISTRAL_SMALL.id(), + "spring.ai.bedrock.mistral.chat.options.temperature=0.5") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockMistralChatAutoConfiguration.class)); + + private final Message systemMessage = new SystemPromptTemplate(""" + You are a helpful AI assistant. Your name is {name}. + You are an AI assistant that helps people find information. + Your name is {name} + You should reply to the user's request with your name and also in the style of a {voice}. + """).createMessage(Map.of("name", "Bob", "voice", "pirate")); + + private final UserMessage userMessage = new UserMessage( + "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."); + + @Test + public void chatCompletion() { + contextRunner.run(context -> { + BedrockMistralChatModel mistralChatModel = context.getBean(BedrockMistralChatModel.class); + ChatResponse response = mistralChatModel.call(new Prompt(List.of(userMessage, systemMessage))); + assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard"); + }); + } + + @Test + public void chatCompletionStreaming() { + contextRunner.run(context -> { + + BedrockMistralChatModel mistralChatModel = context.getBean(BedrockMistralChatModel.class); + + Flux response = mistralChatModel.stream(new Prompt(List.of(userMessage, systemMessage))); + + List responses = response.collectList().block(); + assertThat(responses.size()).isGreaterThan(2); + + String stitchedResponseContent = responses.stream() + .map(ChatResponse::getResults) + .flatMap(List::stream) + .map(Generation::getOutput) + .map(AssistantMessage::getContent) + .collect(Collectors.joining()); + + assertThat(stitchedResponseContent).contains("Blackbeard"); + }); + } + + @Test + public void propertiesTest() { + + new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.mistral.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=ACCESS_KEY", "spring.ai.bedrock.aws.secret-key=SECRET_KEY", + "spring.ai.bedrock.mistral.chat.model=MODEL_XYZ", + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), + "spring.ai.bedrock.mistral.chat.options.temperature=0.55") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockMistralChatAutoConfiguration.class)) + .run(context -> { + var mistralChatProperties = context.getBean(BedrockMistralChatProperties.class); + var awsProperties = context.getBean(BedrockAwsConnectionProperties.class); + + assertThat(mistralChatProperties.isEnabled()).isTrue(); + assertThat(awsProperties.getRegion()).isEqualTo(Region.US_EAST_1.id()); + + assertThat(mistralChatProperties.getOptions().getTemperature()).isEqualTo(0.55f); + assertThat(mistralChatProperties.getModel()).isEqualTo("MODEL_XYZ"); + + assertThat(awsProperties.getAccessKey()).isEqualTo("ACCESS_KEY"); + assertThat(awsProperties.getSecretKey()).isEqualTo("SECRET_KEY"); + }); + } + + @Test + public void chatCompletionDisabled() { + + // It is disabled by default + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockMistralChatAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(BedrockMistralChatProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(BedrockMistralChatModel.class)).isEmpty(); + }); + + // Explicitly enable the chat auto-configuration. + new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.mistral.chat.enabled=true") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockMistralChatAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(BedrockMistralChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(BedrockMistralChatModel.class)).isNotEmpty(); + }); + + // Explicitly disable the chat auto-configuration. + new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.mistral.chat.enabled=false") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockMistralChatAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(BedrockMistralChatProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(BedrockMistralChatModel.class)).isEmpty(); + }); + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/FunctionCallWithFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/FunctionCallWithFunctionBeanIT.java new file mode 100644 index 0000000000..084985d374 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/FunctionCallWithFunctionBeanIT.java @@ -0,0 +1,106 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.mistral.tool; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.bedrock.mistral.BedrockMistralChatAutoConfiguration; +import org.springframework.ai.autoconfigure.bedrock.mistral.tool.MockWeatherService.Request; +import org.springframework.ai.autoconfigure.bedrock.mistral.tool.MockWeatherService.Response; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatModel; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatModel.MistralChatModel; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatOptions; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Description; + +import software.amazon.awssdk.regions.Region; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +class FunctionCallWithFunctionBeanIT { + + private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionBeanIT.class); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.mistral.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), + "spring.ai.bedrock.mistral.chat.model=" + MistralChatModel.MISTRAL_LARGE.id(), + "spring.ai.bedrock.mistral.chat.options.temperature=0.5") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockMistralChatAutoConfiguration.class)) + .withUserConfiguration(Config.class); + + @Test + void functionCallTest() { + + contextRunner.run(context -> { + + BedrockMistralChatModel chatModel = context.getBean(BedrockMistralChatModel.class); + + var userMessage = new UserMessage( + "What's the weather like in San Francisco, Tokyo and Paris? Return the result in Celsius."); + + ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), + BedrockMistralChatOptions.builder().withFunction("weatherFunction").build())); + + logger.info("Response: {}", response); + + assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15"); + + }); + } + + @Configuration + static class Config { + + @Bean + @Description("Get the weather in location. Return temperature in 36°F or 36°C format.") + public Function weatherFunction() { + return new MockWeatherService(); + } + + // Relies on the Request's JsonClassDescription annotation to provide the + // function description. + @Bean + public Function weatherFunction3() { + MockWeatherService weatherService = new MockWeatherService(); + return (weatherService::apply); + } + + } + +} \ No newline at end of file diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/FunctionCallWithPromptFunctionIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/FunctionCallWithPromptFunctionIT.java new file mode 100644 index 0000000000..0e0bfcfcbb --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/FunctionCallWithPromptFunctionIT.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.mistral.tool; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.bedrock.mistral.BedrockMistralChatAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatModel; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatModel.MistralChatModel; +import org.springframework.ai.bedrock.mistral.BedrockMistralChatOptions; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.function.FunctionCallbackWrapper; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import software.amazon.awssdk.regions.Region; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +public class FunctionCallWithPromptFunctionIT { + + private final Logger logger = LoggerFactory.getLogger(FunctionCallWithPromptFunctionIT.class); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.mistral.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), + "spring.ai.bedrock.mistral.chat.model=" + MistralChatModel.MISTRAL_LARGE.id(), + "spring.ai.bedrock.mistral.chat.options.temperature=0.5") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockMistralChatAutoConfiguration.class)); + + @Test + void functionCallTest() { + contextRunner.run(context -> { + + BedrockMistralChatModel chatModel = context.getBean(BedrockMistralChatModel.class); + + UserMessage userMessage = new UserMessage( + "What's the weather like in San Francisco, Tokyo and Paris? Return the result in Celsius."); + + var promptOptions = BedrockMistralChatOptions.builder() + .withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService()) + .withName("CurrentWeatherService") + .withDescription("Get the weather in location. Return temperature in 36°F or 36°C format.") + .build())) + .build(); + + ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); + + logger.info("Response: {}", response); + + assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15"); + }); + } + +} \ No newline at end of file diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/MockWeatherService.java new file mode 100644 index 0000000000..0199dc589c --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/mistral/tool/MockWeatherService.java @@ -0,0 +1,91 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.mistral.tool; + +import java.util.function.Function; + +import com.fasterxml.jackson.annotation.JsonClassDescription; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; + +/** + * Mock 3rd party weather service. + * + * @author Wei Jiang + */ +public class MockWeatherService implements Function { + + /** + * Weather Function request. + */ + @JsonInclude(Include.NON_NULL) + @JsonClassDescription("Get the weather in location. Return temperature in 36°F or 36°C format.") + public record Request(@JsonProperty(required = true, + value = "location") @JsonPropertyDescription("The city and state e.g. San Francisco, CA") String location, + @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { + } + + /** + * Temperature units. + */ + public enum Unit { + + /** + * Celsius. + */ + C("metric"), + /** + * Fahrenheit. + */ + F("imperial"); + + /** + * Human readable unit name. + */ + public final String unitName; + + private Unit(String text) { + this.unitName = text; + } + + } + + /** + * Weather Function response. + */ + public record Response(double temperature, double feels_like, double temp_min, double temp_max, int pressure, + int humidity, Unit unit) { + } + + @Override + public Response apply(Request request) { + double temperature = 0; + if (request.location().contains("Paris")) { + temperature = 15; + } + else if (request.location().contains("Tokyo")) { + temperature = 10; + } + else if (request.location().contains("San Francisco")) { + temperature = 30; + } + + return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); + } + +} \ No newline at end of file From 0fffb5aeeb3bc15ae5081c0ac4731c77326e14a4 Mon Sep 17 00:00:00 2001 From: wmz7year Date: Wed, 1 May 2024 09:19:12 +0800 Subject: [PATCH 8/9] Add Bedrock Cohere Command R model support. --- .../BedrockCohereCommandRChatModel.java | 283 +++++++++++++ .../BedrockCohereCommandRChatOptions.java | 388 ++++++++++++++++++ .../BedrockCohereCommandRChatModelIT.java | 246 +++++++++++ .../ai/bedrock/cohere/MockWeatherService.java | 90 ++++ .../src/main/antora/modules/ROOT/nav.adoc | 2 + .../modules/ROOT/pages/api/bedrock.adoc | 1 + .../chat/bedrock/bedrock-coherecommandr.adoc | 213 ++++++++++ ...bedrock-coherecommandr-chat-functions.adoc | 187 +++++++++ .../modules/ROOT/pages/api/functions.adoc | 3 +- .../modules/ROOT/pages/getting-started.adoc | 1 + ...ckCohereCommandRChatAutoConfiguration.java | 77 ++++ .../BedrockCohereCommandRChatProperties.java | 72 ++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + ...CohereCommandRChatAutoConfigurationIT.java | 190 +++++++++ .../tool/FunctionCallWithFunctionBeanIT.java | 106 +++++ .../FunctionCallWithPromptFunctionIT.java | 85 ++++ .../cohere/tool/MockWeatherService.java | 92 +++++ 17 files changed, 2036 insertions(+), 1 deletion(-) create mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModel.java create mode 100644 models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatOptions.java create mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModelIT.java create mode 100644 models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/MockWeatherService.java create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-coherecommandr.adoc create mode 100644 spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-coherecommandr-chat-functions.adoc create mode 100644 spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatAutoConfiguration.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatProperties.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatAutoConfigurationIT.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/FunctionCallWithFunctionBeanIT.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/FunctionCallWithPromptFunctionIT.java create mode 100644 spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/MockWeatherService.java diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModel.java new file mode 100644 index 0000000000..6395e09c92 --- /dev/null +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModel.java @@ -0,0 +1,283 @@ +/* + * Copyright 2023 - 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.bedrock.cohere; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.springframework.ai.bedrock.BedrockConverseChatGenerationMetadata; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.api.BedrockConverseApiUtils; +import org.springframework.ai.bedrock.api.BedrockConverseApi.BedrockConverseRequest; +import org.springframework.ai.chat.model.ChatModel; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.model.StreamingChatModel; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.ModelOptionsUtils; +import org.springframework.ai.model.function.AbstractFunctionCallSupport; +import org.springframework.ai.model.function.FunctionCallbackContext; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +import reactor.core.publisher.Flux; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ConversationRole; +import software.amazon.awssdk.services.bedrockruntime.model.Message; +import software.amazon.awssdk.services.bedrockruntime.model.StopReason; +import software.amazon.awssdk.services.bedrockruntime.model.Tool; +import software.amazon.awssdk.services.bedrockruntime.model.ToolConfiguration; +import software.amazon.awssdk.services.bedrockruntime.model.ToolInputSchema; +import software.amazon.awssdk.services.bedrockruntime.model.ToolResultBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ToolResultContentBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ToolResultStatus; +import software.amazon.awssdk.services.bedrockruntime.model.ToolSpecification; +import software.amazon.awssdk.services.bedrockruntime.model.ToolUseBlock; +import software.amazon.awssdk.services.bedrockruntime.model.ContentBlock.Type; + +/** + * Java {@link ChatModel} and {@link StreamingChatModel} for the Bedrock Command R chat + * generative model. + * + * @author Wei Jiang + * @since 1.0.0 + */ +public class BedrockCohereCommandRChatModel + extends AbstractFunctionCallSupport + implements ChatModel, StreamingChatModel { + + private final String modelId; + + private final BedrockConverseApi converseApi; + + private final BedrockCohereCommandRChatOptions defaultOptions; + + public BedrockCohereCommandRChatModel(BedrockConverseApi converseApi) { + this(converseApi, BedrockCohereCommandRChatOptions.builder().build()); + } + + public BedrockCohereCommandRChatModel(BedrockConverseApi converseApi, BedrockCohereCommandRChatOptions options) { + this(CohereCommandRChatModel.COHERE_COMMAND_R_PLUS_V1.id(), converseApi, options); + } + + public BedrockCohereCommandRChatModel(String modelId, BedrockConverseApi converseApi, + BedrockCohereCommandRChatOptions options) { + this(modelId, converseApi, options, null); + } + + public BedrockCohereCommandRChatModel(String modelId, BedrockConverseApi converseApi, + BedrockCohereCommandRChatOptions options, FunctionCallbackContext functionCallbackContext) { + super(functionCallbackContext); + + Assert.notNull(modelId, "modelId must not be null."); + Assert.notNull(converseApi, "BedrockConverseApi must not be null."); + Assert.notNull(options, "BedrockCohereCommandRChatOptions must not be null."); + + this.modelId = modelId; + this.converseApi = converseApi; + this.defaultOptions = options; + } + + @Override + public ChatResponse call(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); + + var request = createBedrockConverseRequest(prompt); + + return this.callWithFunctionSupport(request); + } + + @Override + public Flux stream(Prompt prompt) { + Assert.notNull(prompt, "Prompt must not be null."); + + var request = createBedrockConverseRequest(prompt); + + return converseApi.converseStream(request); + } + + private BedrockConverseRequest createBedrockConverseRequest(Prompt prompt) { + var request = BedrockConverseApiUtils.createBedrockConverseRequest(modelId, prompt, defaultOptions); + + ToolConfiguration toolConfiguration = createToolConfiguration(prompt); + + return BedrockConverseRequest.from(request).withToolConfiguration(toolConfiguration).build(); + } + + private ToolConfiguration createToolConfiguration(Prompt prompt) { + Set functionsForThisRequest = new HashSet<>(); + + if (this.defaultOptions != null) { + Set promptEnabledFunctions = this.handleFunctionCallbackConfigurations(this.defaultOptions, + !IS_RUNTIME_CALL); + functionsForThisRequest.addAll(promptEnabledFunctions); + } + + if (prompt.getOptions() != null) { + if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { + BedrockCohereCommandRChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, + ChatOptions.class, BedrockCohereCommandRChatOptions.class); + + Set defaultEnabledFunctions = this.handleFunctionCallbackConfigurations(updatedRuntimeOptions, + IS_RUNTIME_CALL); + functionsForThisRequest.addAll(defaultEnabledFunctions); + } + else { + throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " + + prompt.getOptions().getClass().getSimpleName()); + } + } + + if (!CollectionUtils.isEmpty(functionsForThisRequest)) { + return ToolConfiguration.builder().tools(getFunctionTools(functionsForThisRequest)).build(); + } + + return null; + } + + private List getFunctionTools(Set functionNames) { + return this.resolveFunctionCallbacks(functionNames).stream().map(functionCallback -> { + var description = functionCallback.getDescription(); + var name = functionCallback.getName(); + String inputSchema = functionCallback.getInputTypeSchema(); + + return Tool.builder() + .toolSpec(ToolSpecification.builder() + .name(name) + .description(description) + .inputSchema(ToolInputSchema.builder() + .json(BedrockConverseApiUtils.convertObjectToDocument(ModelOptionsUtils.jsonToMap(inputSchema))) + .build()) + .build()) + .build(); + }).toList(); + } + + @Override + public ChatOptions getDefaultOptions() { + return BedrockCohereCommandRChatOptions.fromOptions(defaultOptions); + } + + @Override + protected BedrockConverseRequest doCreateToolResponseRequest(BedrockConverseRequest previousRequest, + Message responseMessage, List conversationHistory) { + List toolToUseList = responseMessage.content() + .stream() + .filter(content -> content.type() == Type.TOOL_USE) + .map(content -> content.toolUse()) + .toList(); + + List toolResults = new ArrayList<>(); + + for (ToolUseBlock toolToUse : toolToUseList) { + var functionCallId = toolToUse.toolUseId(); + var functionName = toolToUse.name(); + var functionArguments = toolToUse.input().unwrap(); + + if (!this.functionCallbackRegister.containsKey(functionName)) { + throw new IllegalStateException("No function callback found for function name: " + functionName); + } + + String functionResponse = this.functionCallbackRegister.get(functionName) + .call(ModelOptionsUtils.toJsonString(functionArguments)); + + toolResults.add(ToolResultBlock.builder() + .toolUseId(functionCallId) + .status(ToolResultStatus.SUCCESS) + .content(ToolResultContentBlock.builder().text(functionResponse).build()) + .build()); + } + + // Add the function response to the conversation. + Message toolResultMessage = Message.builder() + .content(toolResults.stream().map(toolResult -> ContentBlock.fromToolResult(toolResult)).toList()) + .role(ConversationRole.USER) + .build(); + conversationHistory.add(toolResultMessage); + + // Recursively call chatCompletionWithTools until the model doesn't call a + // functions anymore. + return BedrockConverseRequest.from(previousRequest).withMessages(conversationHistory).build(); + } + + @Override + protected List doGetUserMessages(BedrockConverseRequest request) { + return request.messages(); + } + + @Override + protected Message doGetToolResponseMessage(ChatResponse response) { + Generation result = response.getResult(); + + var metadata = (BedrockConverseChatGenerationMetadata) result.getMetadata(); + + return metadata.getMessage(); + } + + @Override + protected ChatResponse doChatCompletion(BedrockConverseRequest request) { + return converseApi.converse(request); + } + + @Override + protected Flux doChatCompletionStream(BedrockConverseRequest request) { + throw new UnsupportedOperationException("Streaming function calling is not supported."); + } + + @Override + protected boolean isToolFunctionCall(ChatResponse response) { + Generation result = response.getResult(); + if (result == null) { + return false; + } + + return StopReason.fromValue(result.getMetadata().getFinishReason()) == StopReason.TOOL_USE; + } + + /** + * Cohere command R models version. + */ + public enum CohereCommandRChatModel { + + /** + * cohere.command-r-v1:0 + */ + COHERE_COMMAND_R_V1("cohere.command-r-v1:0"), + + /** + * cohere.command-r-plus-v1:0 + */ + COHERE_COMMAND_R_PLUS_V1("cohere.command-r-plus-v1:0"); + + private final String id; + + /** + * @return The model id. + */ + public String id() { + return id; + } + + CohereCommandRChatModel(String value) { + this.id = value; + } + + } + +} diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatOptions.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatOptions.java new file mode 100644 index 0000000000..9aa18849ea --- /dev/null +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatOptions.java @@ -0,0 +1,388 @@ +/* + * Copyright 2023 - 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.bedrock.cohere; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonInclude.Include; + +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.model.function.FunctionCallingOptions; +import org.springframework.boot.context.properties.NestedConfigurationProperty; +import org.springframework.util.Assert; + +/** + * Java {@link ChatOptions} for the Bedrock Cohere Command R chat generative model chat + * options. + * https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html + * + * @author Wei Jiang + * @since 1.0.0 + */ +@JsonInclude(Include.NON_NULL) +public class BedrockCohereCommandRChatOptions implements ChatOptions, FunctionCallingOptions { + + // @formatter:off + /** + * (optional) When enabled, it will only generate potential search queries without performing + * searches or providing a response. + */ + @JsonProperty("search_queries_only") Boolean searchQueriesOnly; + /** + * (optional) Overrides the default preamble for search query generation. + */ + @JsonProperty("preamble") String preamble; + /** + * (optional) Specify the maximum number of tokens to use in the generated response. + */ + @JsonProperty("max_tokens") Integer maxTokens; + /** + * (optional) Use a lower value to decrease randomness in the response. + */ + @JsonProperty("temperature") Float temperature; + /** + * Top P. Use a lower value to ignore less probable options. Set to 0 or 1.0 to disable. + */ + @JsonProperty("p") Float topP; + /** + * Top K. Specify the number of token choices the model uses to generate the next token. + */ + @JsonProperty("k") Integer topK; + /** + * (optional) Dictates how the prompt is constructed. + */ + @JsonProperty("prompt_truncation") PromptTruncation promptTruncation; + /** + * (optional) Used to reduce repetitiveness of generated tokens. + */ + @JsonProperty("frequency_penalty") Float frequencyPenalty; + /** + * (optional) Used to reduce repetitiveness of generated tokens. + */ + @JsonProperty("presence_penalty") Float presencePenalty; + /** + * (optional) Specify the best effort to sample tokens deterministically. + */ + @JsonProperty("seed") Integer seed; + /** + * (optional) Specify true to return the full prompt that was sent to the model. + */ + @JsonProperty("return_prompt") Boolean returnPrompt; + /** + * (optional) A list of stop sequences. + */ + @JsonProperty("stop_sequences") List stopSequences; + /** + * (optional) Specify true, to send the user’s message to the model without any preprocessing. + */ + @JsonProperty("raw_prompting") Boolean rawPrompting; + + /** + * Tool Function Callbacks to register with the ChatModel. For Prompt Options the + * functionCallbacks are automatically enabled for the duration of the prompt + * execution. For Default Options the functionCallbacks are registered but disabled by + * default. Use the enableFunctions to set the functions from the registry to be used + * by the ChatModel chat completion requests. + */ + @NestedConfigurationProperty + @JsonIgnore + private List functionCallbacks = new ArrayList<>(); + + /** + * List of functions, identified by their names, to configure for function calling in + * the chat completion requests. Functions with those names must exist in the + * functionCallbacks registry. The {@link #functionCallbacks} from the PromptOptions + * are automatically enabled for the duration of the prompt execution. + * + * Note that function enabled with the default options are enabled for all chat + * completion requests. This could impact the token count and the billing. If the + * functions is set in a prompt options, then the enabled functions are only active + * for the duration of this prompt execution. + */ + @NestedConfigurationProperty + @JsonIgnore + private Set functions = new HashSet<>(); + // @formatter:on + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final BedrockCohereCommandRChatOptions options = new BedrockCohereCommandRChatOptions(); + + public Builder withSearchQueriesOnly(Boolean searchQueriesOnly) { + options.setSearchQueriesOnly(searchQueriesOnly); + return this; + } + + public Builder withPreamble(String preamble) { + options.setPreamble(preamble); + return this; + } + + public Builder withMaxTokens(Integer maxTokens) { + options.setMaxTokens(maxTokens); + return this; + } + + public Builder withTemperature(Float temperature) { + options.setTemperature(temperature); + return this; + } + + public Builder withTopP(Float topP) { + options.setTopP(topP); + return this; + } + + public Builder withTopK(Integer topK) { + options.setTopK(topK); + return this; + } + + public Builder withPromptTruncation(PromptTruncation promptTruncation) { + options.setPromptTruncation(promptTruncation); + return this; + } + + public Builder withFrequencyPenalty(Float frequencyPenalty) { + options.setFrequencyPenalty(frequencyPenalty); + return this; + } + + public Builder withPresencePenalty(Float presencePenalty) { + options.setPresencePenalty(presencePenalty); + return this; + } + + public Builder withSeed(Integer seed) { + options.setSeed(seed); + return this; + } + + public Builder withReturnPrompt(Boolean returnPrompt) { + options.setReturnPrompt(returnPrompt); + return this; + } + + public Builder withStopSequences(List stopSequences) { + options.setStopSequences(stopSequences); + return this; + } + + public Builder withRawPrompting(Boolean rawPrompting) { + options.setRawPrompting(rawPrompting); + return this; + } + + public Builder withFunctionCallbacks(List functionCallbacks) { + this.options.functionCallbacks = functionCallbacks; + return this; + } + + public Builder withFunctions(Set functionNames) { + Assert.notNull(functionNames, "Function names must not be null"); + this.options.functions = functionNames; + return this; + } + + public Builder withFunction(String functionName) { + Assert.hasText(functionName, "Function name must not be empty"); + this.options.functions.add(functionName); + return this; + } + + public BedrockCohereCommandRChatOptions build() { + return this.options; + } + + } + + public Boolean getSearchQueriesOnly() { + return searchQueriesOnly; + } + + public void setSearchQueriesOnly(Boolean searchQueriesOnly) { + this.searchQueriesOnly = searchQueriesOnly; + } + + public String getPreamble() { + return preamble; + } + + public void setPreamble(String preamble) { + this.preamble = preamble; + } + + public Integer getMaxTokens() { + return maxTokens; + } + + public void setMaxTokens(Integer maxTokens) { + this.maxTokens = maxTokens; + } + + @Override + public Float getTemperature() { + return temperature; + } + + public void setTemperature(Float temperature) { + this.temperature = temperature; + } + + @Override + public Float getTopP() { + return topP; + } + + public void setTopP(Float topP) { + this.topP = topP; + } + + @Override + public Integer getTopK() { + return topK; + } + + public void setTopK(Integer topK) { + this.topK = topK; + } + + public PromptTruncation getPromptTruncation() { + return promptTruncation; + } + + public void setPromptTruncation(PromptTruncation promptTruncation) { + this.promptTruncation = promptTruncation; + } + + public Float getFrequencyPenalty() { + return frequencyPenalty; + } + + public void setFrequencyPenalty(Float frequencyPenalty) { + this.frequencyPenalty = frequencyPenalty; + } + + public Float getPresencePenalty() { + return presencePenalty; + } + + public void setPresencePenalty(Float presencePenalty) { + this.presencePenalty = presencePenalty; + } + + public Integer getSeed() { + return seed; + } + + public void setSeed(Integer seed) { + this.seed = seed; + } + + public Boolean getReturnPrompt() { + return returnPrompt; + } + + public void setReturnPrompt(Boolean returnPrompt) { + this.returnPrompt = returnPrompt; + } + + public List getStopSequences() { + return stopSequences; + } + + public void setStopSequences(List stopSequences) { + this.stopSequences = stopSequences; + } + + public Boolean getRawPrompting() { + return rawPrompting; + } + + public void setRawPrompting(Boolean rawPrompting) { + this.rawPrompting = rawPrompting; + } + + @Override + public List getFunctionCallbacks() { + return this.functionCallbacks; + } + + @Override + public void setFunctionCallbacks(List functionCallbacks) { + Assert.notNull(functionCallbacks, "FunctionCallbacks must not be null"); + this.functionCallbacks = functionCallbacks; + } + + @Override + public Set getFunctions() { + return this.functions; + } + + @Override + public void setFunctions(Set functions) { + Assert.notNull(functions, "Function must not be null"); + this.functions = functions; + } + + /** + * Specifies how the prompt is constructed. + */ + public enum PromptTruncation { + + /** + * Some elements from chat_history and documents will be dropped to construct a + * prompt that fits within the model's context length limit. + */ + AUTO_PRESERVE_ORDER, + /** + * (Default) No elements will be dropped. + */ + OFF + + } + + public static BedrockCohereCommandRChatOptions fromOptions(BedrockCohereCommandRChatOptions fromOptions) { + return builder().withSearchQueriesOnly(fromOptions.getSearchQueriesOnly()) + .withPreamble(fromOptions.getPreamble()) + .withMaxTokens(fromOptions.getMaxTokens()) + .withTemperature(fromOptions.getTemperature()) + .withTopP(fromOptions.getTopP()) + .withTopK(fromOptions.getTopK()) + .withPromptTruncation(fromOptions.getPromptTruncation()) + .withFrequencyPenalty(fromOptions.getFrequencyPenalty()) + .withPresencePenalty(fromOptions.getPresencePenalty()) + .withSeed(fromOptions.getSeed()) + .withReturnPrompt(fromOptions.getReturnPrompt()) + .withStopSequences(fromOptions.getStopSequences()) + .withRawPrompting(fromOptions.getRawPrompting()) + .withFunctionCallbacks(fromOptions.getFunctionCallbacks()) + .withFunctions(fromOptions.getFunctions()) + .build(); + } + +} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModelIT.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModelIT.java new file mode 100644 index 0000000000..03627a49d3 --- /dev/null +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModelIT.java @@ -0,0 +1,246 @@ +/* + * Copyright 2023 - 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.bedrock.cohere; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import reactor.core.publisher.Flux; + +import org.springframework.ai.chat.messages.AssistantMessage; +import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider; +import software.amazon.awssdk.regions.Region; + +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.PromptTemplate; +import org.springframework.ai.chat.prompt.SystemPromptTemplate; +import org.springframework.ai.converter.BeanOutputConverter; +import org.springframework.ai.converter.ListOutputConverter; +import org.springframework.ai.converter.MapOutputConverter; +import org.springframework.ai.model.function.FunctionCallbackWrapper; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Bean; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.core.io.Resource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Wei Jiang + */ +@SpringBootTest +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +class BedrockCohereCommandRChatModelIT { + + private static final Logger logger = LoggerFactory.getLogger(BedrockCohereCommandRChatModelIT.class); + + @Autowired + private BedrockCohereCommandRChatModel chatModel; + + @Value("classpath:/prompts/system-message.st") + private Resource systemResource; + + @Test + void multipleStreamAttempts() { + + Flux joke1Stream = chatModel.stream(new Prompt(new UserMessage("Tell me a joke?"))); + Flux joke2Stream = chatModel.stream(new Prompt(new UserMessage("Tell me a toy joke?"))); + + String joke1 = joke1Stream.collectList() + .block() + .stream() + .map(ChatResponse::getResults) + .flatMap(List::stream) + .map(Generation::getOutput) + .map(AssistantMessage::getContent) + .collect(Collectors.joining()); + String joke2 = joke2Stream.collectList() + .block() + .stream() + .map(ChatResponse::getResults) + .flatMap(List::stream) + .map(Generation::getOutput) + .map(AssistantMessage::getContent) + .collect(Collectors.joining()); + + assertThat(joke1).isNotBlank(); + assertThat(joke2).isNotBlank(); + } + + @Test + void roleTest() { + String request = "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."; + String name = "Bob"; + String voice = "pirate"; + UserMessage userMessage = new UserMessage(request); + SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource); + Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", name, "voice", voice)); + Prompt prompt = new Prompt(List.of(userMessage, systemMessage)); + ChatResponse response = chatModel.call(prompt); + assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard"); + } + + @Test + void listOutputConverter() { + DefaultConversionService conversionService = new DefaultConversionService(); + ListOutputConverter outputConverter = new ListOutputConverter(conversionService); + + String format = outputConverter.getFormat(); + String template = """ + List five {subject} + {format} + """; + PromptTemplate promptTemplate = new PromptTemplate(template, + Map.of("subject", "ice cream flavors.", "format", format)); + Prompt prompt = new Prompt(promptTemplate.createMessage()); + Generation generation = this.chatModel.call(prompt).getResult(); + + List list = outputConverter.convert(generation.getOutput().getContent()); + assertThat(list).hasSize(5); + } + + @Test + void mapOutputConverter() { + MapOutputConverter outputConverter = new MapOutputConverter(); + + String format = outputConverter.getFormat(); + String template = """ + Remove Markdown code blocks from the output. + Provide me a List of {subject} + {format} + """; + PromptTemplate promptTemplate = new PromptTemplate(template, + Map.of("subject", "an array of numbers from 1 to 9 under they key name 'numbers'", "format", format)); + Prompt prompt = new Prompt(promptTemplate.createMessage()); + Generation generation = chatModel.call(prompt).getResult(); + + Map result = outputConverter.convert(generation.getOutput().getContent()); + assertThat(result.get("numbers")).isEqualTo(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9)); + + } + + record ActorsFilmsRecord(String actor, List movies) { + } + + @Test + void beanOutputConverterRecords() { + + BeanOutputConverter outputConverter = new BeanOutputConverter<>(ActorsFilmsRecord.class); + + String format = outputConverter.getFormat(); + String template = """ + Generate the filmography of 5 movies for Tom Hanks. + {format} + Remove Markdown code blocks from the output. + """; + PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format)); + Prompt prompt = new Prompt(promptTemplate.createMessage()); + Generation generation = chatModel.call(prompt).getResult(); + + ActorsFilmsRecord actorsFilms = outputConverter.convert(generation.getOutput().getContent()); + assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks"); + assertThat(actorsFilms.movies()).hasSize(5); + } + + @Test + void beanStreamOutputConverterRecords() { + + BeanOutputConverter outputConverter = new BeanOutputConverter<>(ActorsFilmsRecord.class); + + String format = outputConverter.getFormat(); + String template = """ + Generate the filmography of 5 movies for Tom Hanks. + {format} + Remove Markdown code blocks from the output. + """; + PromptTemplate promptTemplate = new PromptTemplate(template, Map.of("format", format)); + Prompt prompt = new Prompt(promptTemplate.createMessage()); + + String generationTextFromStream = chatModel.stream(prompt) + .collectList() + .block() + .stream() + .map(ChatResponse::getResults) + .flatMap(List::stream) + .map(Generation::getOutput) + .map(AssistantMessage::getContent) + .collect(Collectors.joining()); + + ActorsFilmsRecord actorsFilms = outputConverter.convert(generationTextFromStream); + System.out.println(actorsFilms); + assertThat(actorsFilms.actor()).isEqualTo("Tom Hanks"); + assertThat(actorsFilms.movies()).hasSize(5); + } + + @Test + void functionCallTest() { + UserMessage userMessage = new UserMessage( + "What's the weather like in San Francisco, Tokyo and Paris? Return the result in Celsius."); + + List messages = new ArrayList<>(List.of(userMessage)); + + var promptOptions = BedrockCohereCommandRChatOptions.builder() + .withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService()) + .withName("getCurrentWeather") + .withDescription("Get the weather in location. Return temperature in 36°F or 36°C format.") + .build())) + .build(); + + ChatResponse response = chatModel.call(new Prompt(messages, promptOptions)); + + logger.info("Response: {}", response); + + Generation generation = response.getResult(); + assertThat(generation.getOutput().getContent()).containsAnyOf("30.0", "30"); + assertThat(generation.getOutput().getContent()).containsAnyOf("10.0", "10"); + assertThat(generation.getOutput().getContent()).containsAnyOf("15.0", "15"); + } + + @SpringBootConfiguration + public static class TestConfiguration { + + @Bean + public BedrockConverseApi converseApi() { + return new BedrockConverseApi(EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), + Duration.ofMinutes(2)); + } + + @Bean + public BedrockCohereCommandRChatModel cohereCommandRChatModel(BedrockConverseApi converseApi) { + return new BedrockCohereCommandRChatModel(converseApi); + } + + } + +} diff --git a/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/MockWeatherService.java b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/MockWeatherService.java new file mode 100644 index 0000000000..a9a4382059 --- /dev/null +++ b/models/spring-ai-bedrock/src/test/java/org/springframework/ai/bedrock/cohere/MockWeatherService.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 - 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.bedrock.cohere; + +import java.util.function.Function; + +import com.fasterxml.jackson.annotation.JsonClassDescription; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; + +/** + * @author Wei Jiang + */ +public class MockWeatherService implements Function { + + /** + * Weather Function request. + */ + @JsonInclude(Include.NON_NULL) + @JsonClassDescription("Weather API request") + public record Request( + @JsonProperty(required = true, + value = "location") @JsonPropertyDescription("The city, example: San Francisco") Object location, + @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { + } + + /** + * Temperature units. + */ + public enum Unit { + + /** + * Celsius. + */ + C("metric"), + /** + * Fahrenheit. + */ + F("imperial"); + + /** + * Human readable unit name. + */ + public final String unitName; + + private Unit(String text) { + this.unitName = text; + } + + } + + /** + * Weather Function response. + */ + public record Response(double temp, Unit unit) { + } + + @Override + public Response apply(Request request) { + + double temperature = 0; + if (request.location().toString().contains("Paris")) { + temperature = 15; + } + else if (request.location().toString().contains("Tokyo")) { + temperature = 10; + } + else if (request.location().toString().contains("San Francisco")) { + temperature = 30; + } + + return new Response(temperature, Unit.C); + } + +} \ No newline at end of file diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc index e73f932563..b9f042f3ee 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/nav.adoc @@ -15,6 +15,8 @@ **** xref:api/chat/bedrock/bedrock-anthropic.adoc[Anthropic2] **** xref:api/chat/bedrock/bedrock-llama.adoc[Llama] **** xref:api/chat/bedrock/bedrock-cohere.adoc[Cohere] +**** xref:api/chat/bedrock/bedrock-coherecommandr.adoc[CohereCommandR] +***** xref:api/chat/functions/bedrock/bedrock-coherecommandr-chat-functions.adoc[Function Calling] **** xref:api/chat/bedrock/bedrock-titan.adoc[Titan] **** xref:api/chat/bedrock/bedrock-jurassic2.adoc[Jurassic2] **** xref:api/chat/bedrock/bedrock-mistral.adoc[Mistral] diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc index fd4b15b31e..3fd71cc74a 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/bedrock.adoc @@ -102,6 +102,7 @@ For more information, refer to the documentation below for each supported model. * xref:api/chat/bedrock/bedrock-anthropic3.adoc[Spring AI Bedrock Anthropic 3 Chat]: `spring.ai.bedrock.anthropic.chat.enabled=true` * xref:api/chat/bedrock/bedrock-llama.adoc[Spring AI Bedrock Llama Chat]: `spring.ai.bedrock.llama.chat.enabled=true` * xref:api/chat/bedrock/bedrock-cohere.adoc[Spring AI Bedrock Cohere Chat]: `spring.ai.bedrock.cohere.chat.enabled=true` +* xref:api/chat/bedrock/bedrock-coherecommandr.adoc[Spring AI Bedrock Cohere Command R Chat]: `spring.ai.bedrock.coherecommandr.chat.enabled=true` * xref:api/embeddings/bedrock-cohere-embedding.adoc[Spring AI Bedrock Cohere Embeddings]: `spring.ai.bedrock.cohere.embedding.enabled=true` * xref:api/chat/bedrock/bedrock-titan.adoc[Spring AI Bedrock Titan Chat]: `spring.ai.bedrock.titan.chat.enabled=true` * xref:api/embeddings/bedrock-titan-embedding.adoc[Spring AI Bedrock Titan Embeddings]: `spring.ai.bedrock.titan.embedding.enabled=true` diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-coherecommandr.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-coherecommandr.adoc new file mode 100644 index 0000000000..a14c8cd1b6 --- /dev/null +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-coherecommandr.adoc @@ -0,0 +1,213 @@ += Cohere Command R Chat + +Provides Bedrock Cohere Command R Chat model. +Integrate generative AI capabilities into essential apps and workflows that improve business outcomes. + +The https://aws.amazon.com/bedrock/cohere-command-embed/[AWS Bedrock Cohere Model Page] and https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html[Amazon Bedrock User Guide] contains detailed information on how to use the AWS hosted model. + +== Prerequisites + +Refer to the xref:api/bedrock.adoc[Spring AI documentation on Amazon Bedrock] for setting up API access. + +=== Add Repositories and BOM + +Spring AI artifacts are published in Spring Milestone and Snapshot repositories. Refer to the xref:getting-started.adoc#repositories[Repositories] section to add these repositories to your build system. + +To help with dependency management, Spring AI provides a BOM (bill of materials) to ensure that a consistent version of Spring AI is used throughout the entire project. Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build system. + + +== Auto-configuration + +Add the `spring-ai-bedrock-ai-spring-boot-starter` dependency to your project's Maven `pom.xml` file: + +[source,xml] +---- + + org.springframework.ai + spring-ai-bedrock-ai-spring-boot-starter + +---- + +or to your Gradle `build.gradle` build file. + +[source,gradle] +---- +dependencies { + implementation 'org.springframework.ai:spring-ai-bedrock-ai-spring-boot-starter' +} +---- + +TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file. + +=== Enable Cohere Command R Chat Support + +By default the Cohere Command R model is disabled. +To enable it set the `spring.ai.bedrock.coherecommandr.chat.enabled` property to `true`. +Exporting environment variable is one way to set this configuration property: + +[source,shell] +---- +export SPRING_AI_BEDROCK_COHERECOMMANDR_CHAT_ENABLED=true +---- + +=== Chat Properties + +The prefix `spring.ai.bedrock.aws` is the property prefix to configure the connection to AWS Bedrock. + +[cols="3,3,3"] +|==== +| Property | Description | Default + +| spring.ai.bedrock.aws.region | AWS region to use. | us-east-1 +| spring.ai.bedrock.aws.timeout | AWS timeout to use. | 5m +| spring.ai.bedrock.aws.access-key | AWS access key. | - +| spring.ai.bedrock.aws.secret-key | AWS secret key. | - +|==== + +The prefix `spring.ai.bedrock.coherecommandr.chat` is the property prefix that configures the chat model implementation for Cohere Command R. + +[cols="2,5,1"] +|==== +| Property | Description | Default + +| spring.ai.bedrock.coherecommandr.chat.enabled | Enable or disable support for Cohere Command R | false +| spring.ai.bedrock.coherecommandr.chat.model | The model id to use. See the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModel.java[CohereCommandRChatModel] for the supported models. | cohere.command-r-plus-v1:0 +| spring.ai.bedrock.coherecommandr.chat.options.searchQueriesOnly | When enabled, it will only generate potential search queries without performing searches or providing a response. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.preamble | Overrides the default preamble for search query generation. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.maxToken | Specify the maximum number of tokens to use in the generated response. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.temperature | Controls the randomness of the output. Values can range over [0.0,1.0] | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.topP | The maximum cumulative probability of tokens to consider when sampling. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.topK | Specify the number of token choices the model uses to generate the next token | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.promptTruncation | Dictates how the prompt is constructed. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.frequencyPenalty | Used to reduce repetitiveness of generated tokens. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.presencePenalty | Used to reduce repetitiveness of generated tokens. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.seed | Specify the best effort to sample tokens deterministically. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.returnPrompt | Specify true to return the full prompt that was sent to the model. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.stopSequences | Configure up to four sequences that the model recognizes. | AWS Bedrock default +| spring.ai.bedrock.coherecommandr.chat.options.rawPrompting | Specify true, to send the user’s message to the model without any preprocessing. | AWS Bedrock default +|==== + +Look at the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModel.java[CohereCommandRChatModel] for other model IDs. +Supported values are: `cohere.command-r-plus-v1:0` and `cohere.command-r-v1:0`. +Model ID values can also be found in the https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html[AWS Bedrock documentation for base model IDs]. + +TIP: All properties prefixed with `spring.ai.bedrock.coherecommandr.chat.options` can be overridden at runtime by adding a request specific <> to the `Prompt` call. + +== Runtime Options [[chat-options]] + +The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatOptions.java[BedrockCohereCommandRChatOptions.java] provides model configurations, such as temperature, topK, topP, etc. + +On start-up, the default options can be configured with the `BedrockCohereCommandRChatModel(api, options)` constructor or the `spring.ai.bedrock.coherecommandr.chat.options.*` properties. + +At run-time you can override the default options by adding new, request specific, options to the `Prompt` call. +For example to override the default temperature for a specific request: + +[source,java] +---- +ChatResponse response = chatModel.call( + new Prompt( + "Generate the names of 5 famous pirates.", + BedrockCohereCommandRChatOptions.builder() + .withTemperature(0.4) + .build() + )); +---- + +TIP: In addition to the model specific https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatOptions.java[BedrockCohereCommandRChatOptions] you can use a portable https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/ChatOptions.java[ChatOptions] instance, created with the https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/chat/ChatOptionsBuilder.java[ChatOptionsBuilder#builder()]. + +== Sample Controller + +https://start.spring.io/[Create] a new Spring Boot project and add the `spring-ai-bedrock-ai-spring-boot-starter` to your pom (or gradle) dependencies. + +Add a `application.properties` file, under the `src/main/resources` directory, to enable and configure the Cohere Command R chat model: + +[source] +---- +spring.ai.bedrock.aws.region=eu-central-1 +spring.ai.bedrock.aws.timeout=1000ms +spring.ai.bedrock.aws.access-key=${AWS_ACCESS_KEY_ID} +spring.ai.bedrock.aws.secret-key=${AWS_SECRET_ACCESS_KEY} + +spring.ai.bedrock.coherecommandr.chat.enabled=true +spring.ai.bedrock.coherecommandr.chat.options.temperature=0.8 +---- + +TIP: replace the `regions`, `access-key` and `secret-key` with your AWS credentials. + +This will create a `BedrockCohereCommandRChatModel` implementation that you can inject into your class. +Here is an example of a simple `@Controller` class that uses the chat model for text generations. + +[source,java] +---- +@RestController +public class ChatController { + + private final BedrockCohereCommandRChatModel chatModel; + + @Autowired + public ChatController(BedrockCohereCommandRChatModel chatModel) { + this.chatModel = chatModel; + } + + @GetMapping("/ai/generate") + public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { + return Map.of("generation", chatModel.call(message)); + } + + @GetMapping("/ai/generateStream") + public Flux generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) { + Prompt prompt = new Prompt(new UserMessage(message)); + return chatModel.stream(prompt); + } +} +---- + +== Manual Configuration + +The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModel.java[BedrockCohereCommandRChatModel] implements the `ChatModel` and `StreamingChatModel` and uses the <> to connect to the Bedrock Cohere Command R service. + +Add the `spring-ai-bedrock` dependency to your project's Maven `pom.xml` file: + +[source,xml] +---- + + org.springframework.ai + spring-ai-bedrock + +---- + +or to your Gradle `build.gradle` build file. + +[source,gradle] +---- +dependencies { + implementation 'org.springframework.ai:spring-ai-bedrock' +} +---- + +TIP: Refer to the xref:getting-started.adoc#dependency-management[Dependency Management] section to add the Spring AI BOM to your build file. + +Next, create an https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/cohere/BedrockCohereCommandRChatModel.java[BedrockCohereCommandRChatModel] and use it for text generations: + +[source,java] +---- +BedrockConverseApi converseApi = new BedrockConverseApi( + EnvironmentVariableCredentialsProvider.create(), + Region.EU_CENTRAL_1.id(), + Duration.ofMillis(1000L)); + +BedrockCohereCommandRChatModel chatModel = new BedrockCohereCommandRChatModel(converseApi, + BedrockCohereCommandRChatOptions.builder() + .withTemperature(0.6f) + .withTopK(10) + .withTopP(0.5f) + .withMaxTokens(678) + .build() + +ChatResponse response = chatModel.call( + new Prompt("Generate the names of 5 famous pirates.")); + +// Or with streaming responses +Flux response = chatModel.stream( + new Prompt("Generate the names of 5 famous pirates.")); +---- diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-coherecommandr-chat-functions.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-coherecommandr-chat-functions.adoc new file mode 100644 index 0000000000..abf1410794 --- /dev/null +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/functions/bedrock/bedrock-coherecommandr-chat-functions.adoc @@ -0,0 +1,187 @@ += Bedrock Cohere Command R Function Calling + +You can register custom Java functions with the `BedrockCohereCommandRChatModel` and have the Bedrock Cohere Command R models intelligently choose to output a JSON object containing arguments to call one or many of the registered functions. +This allows you to connect the LLM capabilities with external tools and APIs. + +The Bedrock Cohere Command R API does not call the function directly; instead, the model generates JSON that you can use to call the function in your code and return the result back to the model to complete the conversation. + +Spring AI provides flexible and user-friendly ways to register and call custom functions. +In general, the custom functions need to provide a function `name`, `description`, and the function call `signature` (as JSON schema) to let the model know what arguments the function expects. +The `description` helps the model to understand when to call the function. + +As a developer, you need to implement a function that takes the function call arguments sent from the AI model, and respond with the result back to the model. +Your function can in turn invoke other 3rd party services to provide the results. + +Spring AI makes this as easy as defining a `@Bean` definition that returns a `java.util.Function` and supplying the bean name as an option when invoking the `ChatModel`. + +Under the hood, Spring wraps your POJO (the function) with the appropriate adapter code that enables interaction with the AI Model, saving you from writing tedious boilerplate code. +The basis of the underlying infrastructure is the link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallback.java[FunctionCallback.java] interface and the companion link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-core/src/main/java/org/springframework/ai/model/function/FunctionCallbackWrapper.java[FunctionCallbackWrapper.java] utility class to simplify the implementation and registration of Java callback functions. + +== How it works + +Suppose we want the AI model to respond with information that it does not have, for example the current temperature at a given location. + +We can provide the AI model with metadata about our own functions that it can use to retrieve that information as it processes your prompt. + +For example, if during the processing of a prompt, the AI Model determines that it needs additional information about the temperature in a given location, it will start a server side generated request/response interaction. The AI Model invokes a client side function. +The AI Model provides method invocation details as JSON and it is the responsibility of the client to execute that function and return the response. + +Spring AI greatly simplifies the code you need to write to support function invocation. +It brokers the function invocation conversation for you. +You can simply provide your function definition as a `@Bean` and then provide the bean name of the function in your prompt options. +You can also reference multiple function bean names in your prompt. + +== Quick Start + +Let's create a chatbot that answer questions by calling our own function. +To support the response of the chatbot, we will register our own function that takes a location and returns the current weather in that location. + +When the response to the prompt to the model needs to answer a question such as `"What’s the weather like in Boston?"` the AI model will invoke the client providing the location value as an argument to be passed to the function. This RPC-like data is passed as JSON. + +Our function can some SaaS based weather service API and returns the weather response back to the model to complete the conversation. +In this example we will use a simple implementation named `MockWeatherService` that hard codes the temperature for various locations. + +The following `MockWeatherService.java` represents the weather service API: + +[source,java] +---- +public class MockWeatherService implements Function { + + public enum Unit { C, F } + public record Request(String location, Unit unit) {} + public record Response(double temp, Unit unit) {} + + public Response apply(Request request) { + return new Response(30.0, Unit.C); + } +} +---- + +=== Registering Functions as Beans + +With the link:../bedrock/bedrock-coherecommandr.html#_auto_configuration[BedrockCohereCommandRChatModel Auto-Configuration] you have multiple ways to register custom functions as beans in the Spring context. + +We start with describing the most POJO friendly options. + +==== Plain Java Functions + +In this approach you define `@Beans` in your application context as you would any other Spring managed object. + +Internally, Spring AI `ChatModel` will create an instance of a `FunctionCallbackWrapper` wrapper that adds the logic for it being invoked via the AI model. +The name of the `@Bean` is passed as a `ChatOption`. + + +[source,java] +---- +@Configuration +static class Config { + + @Bean + @Description("Get the weather in location") // function description + public Function weatherFunction1() { + return new MockWeatherService(); + } + ... +} +---- + +The `@Description` annotation is optional and provides a function description (2) that helps the model understand when to call the function. +It is an important property to set to help the AI model determine what client side function to invoke. + +Another option to provide the description of the function is to use the `@JsonClassDescription` annotation on the `MockWeatherService.Request` to provide the function description: + +[source,java] +---- + +@Configuration +static class Config { + + @Bean + public Function currentWeather3() { // (1) bean name as function name. + return new MockWeatherService(); + } + ... +} + +@JsonClassDescription("Get the weather in location") // (2) function description +public record Request(String location, Unit unit) {} +---- + +It is a best practice to annotate the request object with information such that the generated JSON schema of that function is as descriptive as possible to help the AI model pick the correct function to invoke. + +The link:https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/coherecommandr/tool/FunctionCallWithFunctionBeanIT.java.java[FunctionCallWithFunctionBeanIT.java] demonstrates this approach. + + +==== FunctionCallback Wrapper + +Another way to register a function is to create a `FunctionCallbackWrapper` wrapper like this: + +[source,java] +---- +@Configuration +static class Config { + + @Bean + public FunctionCallback weatherFunctionInfo() { + + return new FunctionCallbackWrapper<>("CurrentWeather", // (1) function name + "Get the weather in location", // (2) function description + (response) -> "" + response.temp() + response.unit(), // (3) Response Converter + new MockWeatherService()); // function code + } + ... +} +---- + +It wraps the 3rd party `MockWeatherService` function and registers it as a `CurrentWeather` function with the `BedrockCohereCommandRChatModel`. +It also provides a description (2) and an optional response converter (3) to convert the response into a text as expected by the model. + +NOTE: By default, the response converter does a JSON serialization of the Response object. + +NOTE: The `FunctionCallbackWrapper` internally resolves the function call signature based on the `MockWeatherService.Request` class. + +=== Specifying functions in Chat Options + +To let the model know and call your `CurrentWeather` function you need to enable it in your prompt requests: + +[source,java] +---- +BedrockCohereCommandRChatModel chatModel = ... + +UserMessage userMessage = new UserMessage("What's the weather like in Paris?"); + +ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), + BedrockCohereCommandRChatOptions.builder().withFunction("CurrentWeather").build())); // (1) Enable the function + +logger.info("Response: {}", response); +---- + +// NOTE: You can can have multiple functions registered in your `ChatModel` but only those enabled in the prompt request will be considered for the function calling. + +Above user question will trigger 3 calls to `CurrentWeather` function (one for each city) and produce the final response. + +=== Register/Call Functions with Prompt Options + +In addition to the auto-configuration you can register callback functions, dynamically, with your Prompt requests: + +[source,java] +---- +BedrockCohereCommandRChatModel chatModel = ... + +UserMessage userMessage = new UserMessage("What's the weather like in Paris?"); + +var promptOptions = BedrockCohereCommandRChatOptions.builder() + .withFunctionCallbacks(List.of(new FunctionCallbackWrapper<>( + "CurrentWeather", // name + "Get the weather in location", // function description + new MockWeatherService()))) // function code + .build(); + +ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); +---- + +NOTE: The in-prompt registered functions are enabled by default for the duration of this request. + +This approach allows to dynamically chose different functions to be called based on the user input. + +The https://github.com/spring-projects/spring-ai/blob/main/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/FunctionCallWithPromptFunctionIT.java[FunctionCallWithPromptFunctionIT.java] integration test provides a complete example of how to register a function with the `BedrockAnthropic3ChatModel` and use it in a prompt request. \ No newline at end of file diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc index 6a2bba63a6..c975723d70 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/functions.adoc @@ -15,4 +15,5 @@ Spring AI currently supports Function invocation for the following AI Models * MiniMax : Refer to the xref:api/chat/functions/minimax-chat-functions.adoc[MiniMax function invocation docs]. * ZhiPu AI : Refer to the xref:api/chat/functions/zhipuai-chat-functions.adoc[ZhiPu AI function invocation docs]. * Amazon Bedrock Anthropic 3 : Refer to the xref:api/chat/functions/bedrock/bedrock-anthropic3-chat-functions.adoc[Amazon Bedrock Anthropic3 function invocation docs]. -* Amazon Bedrock Mistral : Refer to the xref:api/chat/functions/bedrock/bedrock-mistral-chat-functions.adoc[Amazon Bedrock Mistral function invocation docs]. \ No newline at end of file +* Amazon Bedrock Mistral : Refer to the xref:api/chat/functions/bedrock/bedrock-mistral-chat-functions.adoc[Amazon Bedrock Mistral function invocation docs]. +* Amazon Bedrock Cohere Command R : Refer to the xref:api/chat/functions/bedrock/bedrock-coherecommandr-chat-functions.adoc[Amazon Bedrock Cohere Command R function invocation docs]. \ No newline at end of file diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc index b4378566d8..5908be9a96 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/getting-started.adoc @@ -149,6 +149,7 @@ Each of the following sections in the documentation shows which dependencies you ** xref:api/chat/vertexai-gemini-chat.adoc[Google Vertex AI Gemini Chat Completion] (streaming, multi-modality & function-calling support) ** xref:api/bedrock.adoc[Amazon Bedrock] *** xref:api/chat/bedrock/bedrock-cohere.adoc[Cohere Chat Completion] +*** xref:api/chat/bedrock/bedrock-coherecommandr.adoc[Cohere Command R Chat Completion] *** xref:api/chat/bedrock/bedrock-llama.adoc[Llama Chat Completion] *** xref:api/chat/bedrock/bedrock-titan.adoc[Titan Chat Completion] *** xref:api/chat/bedrock/bedrock-anthropic.adoc[Anthropic Chat Completion] diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatAutoConfiguration.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatAutoConfiguration.java new file mode 100644 index 0000000000..48e4de13f2 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatAutoConfiguration.java @@ -0,0 +1,77 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.cohere; + +import java.util.List; + +import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionConfiguration; +import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.bedrock.api.BedrockConverseApi; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatModel; +import org.springframework.ai.model.function.FunctionCallback; +import org.springframework.ai.model.function.FunctionCallbackContext; +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.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.util.CollectionUtils; + +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; + +/** + * {@link AutoConfiguration Auto-configuration} for Bedrock Cohere Command R Chat Client. + * + * Leverages the Spring Cloud AWS to resolve the {@link AwsCredentialsProvider}. + * + * @author Wei Jiang + * @since 1.0.0 + */ +@AutoConfiguration(after = BedrockConverseApiAutoConfiguration.class) +@ConditionalOnClass(BedrockConverseApi.class) +@EnableConfigurationProperties({ BedrockCohereCommandRChatProperties.class, BedrockAwsConnectionProperties.class }) +@ConditionalOnProperty(prefix = BedrockCohereCommandRChatProperties.CONFIG_PREFIX, name = "enabled", + havingValue = "true") +@Import(BedrockAwsConnectionConfiguration.class) +public class BedrockCohereCommandRChatAutoConfiguration { + + @Bean + @ConditionalOnBean(BedrockConverseApi.class) + public BedrockCohereCommandRChatModel cohereCommandRChatModel(BedrockConverseApi converseApi, + BedrockCohereCommandRChatProperties properties, FunctionCallbackContext functionCallbackContext, + List toolFunctionCallbacks) { + if (!CollectionUtils.isEmpty(toolFunctionCallbacks)) { + properties.getOptions().getFunctionCallbacks().addAll(toolFunctionCallbacks); + } + + return new BedrockCohereCommandRChatModel(properties.getModel(), converseApi, properties.getOptions(), + functionCallbackContext); + } + + @Bean + @ConditionalOnMissingBean + public FunctionCallbackContext springAiFunctionManager(ApplicationContext context) { + FunctionCallbackContext manager = new FunctionCallbackContext(); + manager.setApplicationContext(context); + return manager; + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatProperties.java b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatProperties.java new file mode 100644 index 0000000000..96ef609f2e --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatProperties.java @@ -0,0 +1,72 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.cohere; + +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatModel.CohereCommandRChatModel; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatOptions; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.NestedConfigurationProperty; + +/** + * Bedrock Cohere Command R Chat autoconfiguration properties. + * + * @author Wei Jiang + * @since 1.0.0 + */ +@ConfigurationProperties(BedrockCohereCommandRChatProperties.CONFIG_PREFIX) +public class BedrockCohereCommandRChatProperties { + + public static final String CONFIG_PREFIX = "spring.ai.bedrock.coherecommandr.chat"; + + /** + * Enable Bedrock Cohere Command R Chat Client. False by default. + */ + private boolean enabled = false; + + /** + * Bedrock Cohere Command R Chat generative name. Defaults to + * 'cohere.command-r-plus-v1:0'. + */ + private String model = CohereCommandRChatModel.COHERE_COMMAND_R_PLUS_V1.id(); + + @NestedConfigurationProperty + private BedrockCohereCommandRChatOptions options = BedrockCohereCommandRChatOptions.builder().build(); + + public boolean isEnabled() { + return this.enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public String getModel() { + return this.model; + } + + public void setModel(String model) { + this.model = model; + } + + public BedrockCohereCommandRChatOptions getOptions() { + return this.options; + } + + public void setOptions(BedrockCohereCommandRChatOptions options) { + this.options = options; + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 4d2ebca22b..4383802b7c 100644 --- a/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-ai-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -9,6 +9,7 @@ org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfigura org.springframework.ai.autoconfigure.bedrock.jurrasic2.BedrockAi21Jurassic2ChatAutoConfiguration org.springframework.ai.autoconfigure.bedrock.llama.BedrockLlamaChatAutoConfiguration org.springframework.ai.autoconfigure.bedrock.cohere.BedrockCohereChatAutoConfiguration +org.springframework.ai.autoconfigure.bedrock.cohere.BedrockCohereCommandRChatAutoConfiguration org.springframework.ai.autoconfigure.bedrock.cohere.BedrockCohereEmbeddingAutoConfiguration org.springframework.ai.autoconfigure.bedrock.anthropic.BedrockAnthropicChatAutoConfiguration org.springframework.ai.autoconfigure.bedrock.anthropic3.BedrockAnthropic3ChatAutoConfiguration diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatAutoConfigurationIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatAutoConfigurationIT.java new file mode 100644 index 0000000000..d6fd1309d8 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/BedrockCohereCommandRChatAutoConfigurationIT.java @@ -0,0 +1,190 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.cohere; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.messages.AssistantMessage; +import reactor.core.publisher.Flux; +import software.amazon.awssdk.regions.Region; + +import org.springframework.ai.autoconfigure.bedrock.BedrockAwsConnectionProperties; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatModel; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatModel.CohereCommandRChatModel; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatOptions.PromptTruncation; +import org.springframework.ai.chat.model.Generation; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.chat.prompt.SystemPromptTemplate; +import org.springframework.ai.chat.messages.Message; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +public class BedrockCohereCommandRChatAutoConfigurationIT { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.coherecommandr.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), + "spring.ai.bedrock.coherecommandr.chat.model=" + CohereCommandRChatModel.COHERE_COMMAND_R_PLUS_V1.id(), + "spring.ai.bedrock.coherecommandr.chat.options.temperature=0.5", + "spring.ai.bedrock.coherecommandr.chat.options.maxTokens=500") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereCommandRChatAutoConfiguration.class)); + + private final Message systemMessage = new SystemPromptTemplate(""" + You are a helpful AI assistant. Your name is {name}. + You are an AI assistant that helps people find information. + Your name is {name} + You should reply to the user's request with your name and also in the style of a {voice}. + """).createMessage(Map.of("name", "Bob", "voice", "pirate")); + + private final UserMessage userMessage = new UserMessage( + "Tell me about 3 famous pirates from the Golden Age of Piracy and why they did."); + + @Test + public void chatCompletion() { + contextRunner.run(context -> { + BedrockCohereCommandRChatModel cohereCommandRChatModel = context + .getBean(BedrockCohereCommandRChatModel.class); + ChatResponse response = cohereCommandRChatModel.call(new Prompt(List.of(userMessage, systemMessage))); + assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard"); + }); + } + + @Test + public void chatCompletionStreaming() { + contextRunner.run(context -> { + + BedrockCohereCommandRChatModel cohereCommandRChatModel = context + .getBean(BedrockCohereCommandRChatModel.class); + + Flux response = cohereCommandRChatModel + .stream(new Prompt(List.of(userMessage, systemMessage))); + + List responses = response.collectList().block(); + assertThat(responses.size()).isGreaterThan(2); + + String stitchedResponseContent = responses.stream() + .map(ChatResponse::getResults) + .flatMap(List::stream) + .map(Generation::getOutput) + .map(AssistantMessage::getContent) + .collect(Collectors.joining()); + + assertThat(stitchedResponseContent).contains("Blackbeard"); + }); + } + + @Test + public void propertiesTest() { + + new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.coherecommandr.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=ACCESS_KEY", "spring.ai.bedrock.aws.secret-key=SECRET_KEY", + "spring.ai.bedrock.coherecommandr.chat.model=MODEL_XYZ", + "spring.ai.bedrock.aws.region=" + Region.EU_CENTRAL_1.id(), + "spring.ai.bedrock.coherecommandr.chat.options.searchQueriesOnly=true", + "spring.ai.bedrock.coherecommandr.chat.options.preamble=preamble", + "spring.ai.bedrock.coherecommandr.chat.options.maxTokens=123", + "spring.ai.bedrock.coherecommandr.chat.options.temperature=0.55", + "spring.ai.bedrock.coherecommandr.chat.options.topP=0.55", + "spring.ai.bedrock.coherecommandr.chat.options.topK=10", + "spring.ai.bedrock.coherecommandr.chat.options.promptTruncation=AUTO_PRESERVE_ORDER", + "spring.ai.bedrock.coherecommandr.chat.options.frequencyPenalty=0.55", + "spring.ai.bedrock.coherecommandr.chat.options.presencePenalty=0.66", + "spring.ai.bedrock.coherecommandr.chat.options.seed=555555", + "spring.ai.bedrock.coherecommandr.chat.options.returnPrompt=true", + "spring.ai.bedrock.coherecommandr.chat.options.stopSequences=END1,END2", + "spring.ai.bedrock.coherecommandr.chat.options.rawPrompting=true") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereCommandRChatAutoConfiguration.class)) + .run(context -> { + var chatProperties = context.getBean(BedrockCohereCommandRChatProperties.class); + var aswProperties = context.getBean(BedrockAwsConnectionProperties.class); + + assertThat(chatProperties.isEnabled()).isTrue(); + assertThat(aswProperties.getRegion()).isEqualTo(Region.EU_CENTRAL_1.id()); + assertThat(chatProperties.getModel()).isEqualTo("MODEL_XYZ"); + + assertThat(chatProperties.getOptions().getSearchQueriesOnly()).isTrue(); + assertThat(chatProperties.getOptions().getPreamble()).isEqualTo("preamble"); + assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123); + assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55f); + assertThat(chatProperties.getOptions().getTopP()).isEqualTo(0.55f); + assertThat(chatProperties.getOptions().getTopK()).isEqualTo(10); + assertThat(chatProperties.getOptions().getPromptTruncation()) + .isEqualTo(PromptTruncation.AUTO_PRESERVE_ORDER); + assertThat(chatProperties.getOptions().getFrequencyPenalty()).isEqualTo(0.55f); + assertThat(chatProperties.getOptions().getPresencePenalty()).isEqualTo(0.66f); + assertThat(chatProperties.getOptions().getSeed()).isEqualTo(555555); + assertThat(chatProperties.getOptions().getReturnPrompt()).isTrue(); + assertThat(chatProperties.getOptions().getStopSequences()).isEqualTo(List.of("END1", "END2")); + assertThat(chatProperties.getOptions().getRawPrompting()).isTrue(); + + assertThat(aswProperties.getAccessKey()).isEqualTo("ACCESS_KEY"); + assertThat(aswProperties.getSecretKey()).isEqualTo("SECRET_KEY"); + }); + } + + @Test + public void chatCompletionDisabled() { + + // It is disabled by default + new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereCommandRChatAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(BedrockCohereCommandRChatProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(BedrockCohereCommandRChatModel.class)).isEmpty(); + }); + + // Explicitly enable the chat auto-configuration. + new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.coherecommandr.chat.enabled=true") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereCommandRChatAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(BedrockCohereCommandRChatProperties.class)).isNotEmpty(); + assertThat(context.getBeansOfType(BedrockCohereCommandRChatModel.class)).isNotEmpty(); + }); + + // Explicitly disable the chat auto-configuration. + new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.coherecommandr.chat.enabled=false") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereCommandRChatAutoConfiguration.class)) + .run(context -> { + assertThat(context.getBeansOfType(BedrockCohereCommandRChatProperties.class)).isEmpty(); + assertThat(context.getBeansOfType(BedrockCohereCommandRChatModel.class)).isEmpty(); + }); + } + +} diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/FunctionCallWithFunctionBeanIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/FunctionCallWithFunctionBeanIT.java new file mode 100644 index 0000000000..cbcf63b444 --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/FunctionCallWithFunctionBeanIT.java @@ -0,0 +1,106 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.cohere.tool; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.bedrock.cohere.BedrockCohereCommandRChatAutoConfiguration; +import org.springframework.ai.autoconfigure.bedrock.cohere.tool.MockWeatherService.Request; +import org.springframework.ai.autoconfigure.bedrock.cohere.tool.MockWeatherService.Response; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatModel; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatModel.CohereCommandRChatModel; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatOptions; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Description; + +import software.amazon.awssdk.regions.Region; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +class FunctionCallWithFunctionBeanIT { + + private final Logger logger = LoggerFactory.getLogger(FunctionCallWithFunctionBeanIT.class); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.coherecommandr.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), + "spring.ai.bedrock.coherecommandr.chat.model=" + CohereCommandRChatModel.COHERE_COMMAND_R_PLUS_V1.id(), + "spring.ai.bedrock.coherecommandr.chat.options.temperature=0.5") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereCommandRChatAutoConfiguration.class)) + .withUserConfiguration(Config.class); + + @Test + void functionCallTest() { + + contextRunner.run(context -> { + + BedrockCohereCommandRChatModel chatModel = context.getBean(BedrockCohereCommandRChatModel.class); + + var userMessage = new UserMessage( + "What's the weather like in San Francisco, Tokyo and Paris? Return the result in Celsius."); + + ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), + BedrockCohereCommandRChatOptions.builder().withFunction("weatherFunction").build())); + + logger.info("Response: {}", response); + + assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15"); + + }); + } + + @Configuration + static class Config { + + @Bean + @Description("Get the weather in location. Return temperature in 36°F or 36°C format.") + public Function weatherFunction() { + return new MockWeatherService(); + } + + // Relies on the Request's JsonClassDescription annotation to provide the + // function description. + @Bean + public Function weatherFunction3() { + MockWeatherService weatherService = new MockWeatherService(); + return (weatherService::apply); + } + + } + +} \ No newline at end of file diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/FunctionCallWithPromptFunctionIT.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/FunctionCallWithPromptFunctionIT.java new file mode 100644 index 0000000000..8c38a1713a --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/FunctionCallWithPromptFunctionIT.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.cohere.tool; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ai.autoconfigure.bedrock.api.BedrockConverseApiAutoConfiguration; +import org.springframework.ai.autoconfigure.bedrock.cohere.BedrockCohereCommandRChatAutoConfiguration; +import org.springframework.ai.autoconfigure.retry.SpringAiRetryAutoConfiguration; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatModel; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatOptions; +import org.springframework.ai.bedrock.cohere.BedrockCohereCommandRChatModel.CohereCommandRChatModel; +import org.springframework.ai.chat.messages.UserMessage; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.ai.chat.prompt.Prompt; +import org.springframework.ai.model.function.FunctionCallbackWrapper; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; + +import software.amazon.awssdk.regions.Region; + +/** + * @author Wei Jiang + * @since 1.0.0 + */ +@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*") +@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*") +public class FunctionCallWithPromptFunctionIT { + + private final Logger logger = LoggerFactory.getLogger(FunctionCallWithPromptFunctionIT.class); + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withPropertyValues("spring.ai.bedrock.coherecommandr.chat.enabled=true", + "spring.ai.bedrock.aws.access-key=" + System.getenv("AWS_ACCESS_KEY_ID"), + "spring.ai.bedrock.aws.secret-key=" + System.getenv("AWS_SECRET_ACCESS_KEY"), + "spring.ai.bedrock.aws.region=" + Region.US_EAST_1.id(), + "spring.ai.bedrock.coherecommandr.chat.model=" + CohereCommandRChatModel.COHERE_COMMAND_R_PLUS_V1.id(), + "spring.ai.bedrock.coherecommandr.chat.options.temperature=0.5") + .withConfiguration(AutoConfigurations.of(SpringAiRetryAutoConfiguration.class, + BedrockConverseApiAutoConfiguration.class, BedrockCohereCommandRChatAutoConfiguration.class)); + + @Test + void functionCallTest() { + contextRunner.run(context -> { + + BedrockCohereCommandRChatModel chatModel = context.getBean(BedrockCohereCommandRChatModel.class); + + UserMessage userMessage = new UserMessage( + "What's the weather like in San Francisco, Tokyo and Paris? Return the result in Celsius."); + + var promptOptions = BedrockCohereCommandRChatOptions.builder() + .withFunctionCallbacks(List.of(FunctionCallbackWrapper.builder(new MockWeatherService()) + .withName("CurrentWeatherService") + .withDescription("Get the weather in location. Return temperature in 36°F or 36°C format.") + .build())) + .build(); + + ChatResponse response = chatModel.call(new Prompt(List.of(userMessage), promptOptions)); + + logger.info("Response: {}", response); + + assertThat(response.getResult().getOutput().getContent()).contains("30", "10", "15"); + }); + } + +} \ No newline at end of file diff --git a/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/MockWeatherService.java b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/MockWeatherService.java new file mode 100644 index 0000000000..12972e3bdb --- /dev/null +++ b/spring-ai-spring-boot-autoconfigure/src/test/java/org/springframework/ai/autoconfigure/bedrock/cohere/tool/MockWeatherService.java @@ -0,0 +1,92 @@ +/* + * Copyright 2023 - 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.autoconfigure.bedrock.cohere.tool; + +import java.util.function.Function; + +import com.fasterxml.jackson.annotation.JsonClassDescription; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; + +/** + * Mock 3rd party weather service. + * + * @author Wei Jiang + */ +public class MockWeatherService implements Function { + + /** + * Weather Function request. + */ + @JsonInclude(Include.NON_NULL) + @JsonClassDescription("Weather API request") + public record Request( + @JsonProperty(required = true, + value = "location") @JsonPropertyDescription("The city, example: San Francisco") Object location, + @JsonProperty(required = true, value = "unit") @JsonPropertyDescription("Temperature unit") Unit unit) { + } + + /** + * Temperature units. + */ + public enum Unit { + + /** + * Celsius. + */ + C("metric"), + /** + * Fahrenheit. + */ + F("imperial"); + + /** + * Human readable unit name. + */ + public final String unitName; + + private Unit(String text) { + this.unitName = text; + } + + } + + /** + * Weather Function response. + */ + public record Response(double temperature, double feels_like, double temp_min, double temp_max, int pressure, + int humidity, Unit unit) { + } + + @Override + public Response apply(Request request) { + double temperature = 0; + if (request.location().toString().contains("Paris")) { + temperature = 15; + } + else if (request.location().toString().contains("Tokyo")) { + temperature = 10; + } + else if (request.location().toString().contains("San Francisco")) { + temperature = 30; + } + + return new Response(temperature, 15, 20, 2, 53, 45, Unit.C); + } + +} \ No newline at end of file From 1935e2735c8ddc5a9491a054e910f829973a3841 Mon Sep 17 00:00:00 2001 From: maxjiang153 Date: Fri, 21 Jun 2024 08:09:34 +0800 Subject: [PATCH 9/9] Add Amazon Bedrock Anthropic Claude 3.5 Sonnet model support. --- .../ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java | 6 +++++- .../ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java index f8ad352059..70b8dbdcac 100644 --- a/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java +++ b/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java @@ -263,7 +263,11 @@ public enum Anthropic3ChatModel implements ModelDescription { /** * anthropic.claude-3-opus-20240229-v1:0 */ - CLAUDE_V3_OPUS("anthropic.claude-3-opus-20240229-v1:0"); + CLAUDE_V3_OPUS("anthropic.claude-3-opus-20240229-v1:0"), + /** + * anthropic.claude-3-5-sonnet-20240620-v1:0 + */ + CLAUDE_V3_5_SONNET("anthropic.claude-3-5-sonnet-20240620-v1:0"); private final String id; diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc index a96b04f27d..98af263183 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/chat/bedrock/bedrock-anthropic3.adoc @@ -87,7 +87,7 @@ The prefix `spring.ai.bedrock.anthropic3.chat` is the property prefix that confi |==== Look at the https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/anthropic3/BedrockAnthropic3ChatModel.java[Anthropic3ChatModel] for other model IDs. -Supported values are: `anthropic.claude-instant-v1`, `anthropic.claude-v2` and `anthropic.claude-v2:1`. +Supported values are: `anthropic.claude-3-5-sonnet-20240620-v1:0`, `anthropic.claude-3-opus-20240229-v1:0`, `anthropic.claude-3-sonnet-20240229-v1:0` and `anthropic.claude-3-haiku-20240307-v1:0`. Model ID values can also be found in the https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids-arns.html[AWS Bedrock documentation for base model IDs]. TIP: All properties prefixed with `spring.ai.bedrock.anthropic3.chat.options` can be overridden at runtime by adding a request specific <> to the `Prompt` call.