Implemented Bedrock Jurassic 2 ChatClient
- Implemented Bedrock Jurassic ChatClient - added documentation reference - implement auto-configuration and boot starter - Disable the BedrockAi21Jurassic2ChatClientIT.emojiPenaltyWhenTrueByDefaultApplyPenaltyTest() test as it fails when run in combination with the other tests.
This commit is contained in:
@@ -6,5 +6,6 @@
|
||||
- [Llama2 Chat Documentation](https://docs.spring.io/spring-ai/reference/api/chat/bedrock/bedrock-llama2.html)
|
||||
- [Titan Chat Documentation](https://docs.spring.io/spring-ai/reference/api/chat/bedrock/bedrock-titan.html)
|
||||
- [Titan Embedding Documentation](https://docs.spring.io/spring-ai/reference/api/embeddings/bedrock-titan-embedding.html)
|
||||
- [Jurassic2 Chat Documentation](https://docs.spring.io/spring-ai/reference/api/chat/bedrock/bedrock-jurassic2.html)
|
||||
|
||||
NOTE: There is not yet an implementation for Jurassic, but you can use the lower level client [Ai21Jurassic2ChatBedrockApi.java](https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/api/Ai21Jurassic2ChatBedrockApi.java) in the meantime. See [Issue 343](https://github.com/spring-projects/spring-ai/issues/343)
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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.chat.ChatClient;
|
||||
import org.springframework.ai.chat.ChatResponse;
|
||||
import org.springframework.ai.chat.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.util.Assert;
|
||||
|
||||
/**
|
||||
* Java {@link ChatClient} for the Bedrock Jurassic2 chat generative model.
|
||||
*
|
||||
* @author Ahmed Yousri
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class BedrockAi21Jurassic2ChatClient implements ChatClient {
|
||||
|
||||
private final Ai21Jurassic2ChatBedrockApi chatApi;
|
||||
|
||||
private final BedrockAi21Jurassic2ChatOptions defaultOptions;
|
||||
|
||||
public BedrockAi21Jurassic2ChatClient(Ai21Jurassic2ChatBedrockApi chatApi,
|
||||
BedrockAi21Jurassic2ChatOptions options) {
|
||||
Assert.notNull(chatApi, "Ai21Jurassic2ChatBedrockApi must not be null");
|
||||
Assert.notNull(options, "BedrockAi21Jurassic2ChatOptions must not be null");
|
||||
|
||||
this.chatApi = chatApi;
|
||||
this.defaultOptions = options;
|
||||
}
|
||||
|
||||
public BedrockAi21Jurassic2ChatClient(Ai21Jurassic2ChatBedrockApi chatApi) {
|
||||
this(chatApi,
|
||||
BedrockAi21Jurassic2ChatOptions.builder()
|
||||
.withTemperature(0.8f)
|
||||
.withTopP(0.9f)
|
||||
.withMaxTokens(100)
|
||||
.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());
|
||||
}
|
||||
|
||||
private Ai21Jurassic2ChatRequest createRequest(Prompt prompt) {
|
||||
|
||||
final String promptValue = MessageToPromptConverter.create().toPrompt(prompt.getInstructions());
|
||||
|
||||
Ai21Jurassic2ChatRequest request = Ai21Jurassic2ChatRequest.builder(promptValue).build();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
if (this.defaultOptions != null) {
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, Ai21Jurassic2ChatRequest.class);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
public static Builder builder(Ai21Jurassic2ChatBedrockApi chatApi) {
|
||||
return new Builder(chatApi);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final Ai21Jurassic2ChatBedrockApi chatApi;
|
||||
|
||||
private BedrockAi21Jurassic2ChatOptions options;
|
||||
|
||||
public Builder(Ai21Jurassic2ChatBedrockApi chatApi) {
|
||||
this.chatApi = chatApi;
|
||||
}
|
||||
|
||||
public Builder withOptions(BedrockAi21Jurassic2ChatOptions options) {
|
||||
this.options = options;
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockAi21Jurassic2ChatClient build() {
|
||||
return new BedrockAi21Jurassic2ChatClient(chatApi,
|
||||
options != null ? options : BedrockAi21Jurassic2ChatOptions.builder().build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.ai.chat.prompt.ChatOptions;
|
||||
|
||||
/**
|
||||
* Request body for the /complete endpoint of the Jurassic-2 API.
|
||||
*
|
||||
* @author Ahmed Yousri
|
||||
* @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.
|
||||
*/
|
||||
@JsonProperty("numResults")
|
||||
private Integer numResults;
|
||||
|
||||
/**
|
||||
* The maximum number of tokens to generate per result.
|
||||
*/
|
||||
@JsonProperty("maxTokens")
|
||||
private Integer maxTokens;
|
||||
|
||||
/**
|
||||
* The minimum number of tokens to generate per result.
|
||||
*/
|
||||
@JsonProperty("minTokens")
|
||||
private Integer minTokens;
|
||||
|
||||
/**
|
||||
* Modifies the distribution from which tokens are sampled.
|
||||
*/
|
||||
@JsonProperty("temperature")
|
||||
private Float temperature;
|
||||
|
||||
/**
|
||||
* Sample tokens from the corresponding top percentile of probability mass.
|
||||
*/
|
||||
@JsonProperty("topP")
|
||||
private Float topP;
|
||||
|
||||
/**
|
||||
* Return the top-K (topKReturn) alternative tokens.
|
||||
*/
|
||||
@JsonProperty("topKReturn")
|
||||
private Integer topK;
|
||||
|
||||
/**
|
||||
* Stops decoding if any of the strings is generated.
|
||||
*/
|
||||
@JsonProperty("stopSequences")
|
||||
private String[] stopSequences;
|
||||
|
||||
/**
|
||||
* Penalty object for frequency.
|
||||
*/
|
||||
@JsonProperty("frequencyPenalty")
|
||||
private Penalty frequencyPenalty;
|
||||
|
||||
/**
|
||||
* Penalty object for presence.
|
||||
*/
|
||||
@JsonProperty("presencePenalty")
|
||||
private Penalty presencePenalty;
|
||||
|
||||
/**
|
||||
* Penalty object for count.
|
||||
*/
|
||||
@JsonProperty("countPenalty")
|
||||
private Penalty countPenalty;
|
||||
|
||||
// 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.
|
||||
*/
|
||||
public Integer getNumResults() {
|
||||
return numResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number of completions to sample and return.
|
||||
* @param numResults The number of results.
|
||||
*/
|
||||
public void setNumResults(Integer numResults) {
|
||||
this.numResults = numResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum number of tokens to generate per result.
|
||||
* @return The maximum number of tokens.
|
||||
*/
|
||||
public Integer getMaxTokens() {
|
||||
return maxTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum number of tokens to generate per result.
|
||||
* @param maxTokens The maximum number of tokens.
|
||||
*/
|
||||
public void setMaxTokens(Integer maxTokens) {
|
||||
this.maxTokens = maxTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum number of tokens to generate per result.
|
||||
* @return The minimum number of tokens.
|
||||
*/
|
||||
public Integer getMinTokens() {
|
||||
return minTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum number of tokens to generate per result.
|
||||
* @param minTokens The minimum number of tokens.
|
||||
*/
|
||||
public void setMinTokens(Integer minTokens) {
|
||||
this.minTokens = minTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the temperature for modifying the token sampling distribution.
|
||||
* @return The temperature.
|
||||
*/
|
||||
public Float getTemperature() {
|
||||
return temperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the temperature for modifying the token sampling distribution.
|
||||
* @param temperature The temperature.
|
||||
*/
|
||||
public void setTemperature(Float temperature) {
|
||||
this.temperature = temperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the topP parameter for sampling tokens from the top percentile of probability
|
||||
* mass.
|
||||
* @return The topP parameter.
|
||||
*/
|
||||
public Float getTopP() {
|
||||
return topP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the topP parameter for sampling tokens from the top percentile of probability
|
||||
* mass.
|
||||
* @param topP The topP parameter.
|
||||
*/
|
||||
public void setTopP(Float topP) {
|
||||
this.topP = topP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the top-K (topKReturn) alternative tokens to return.
|
||||
* @return The top-K parameter. (topKReturn)
|
||||
*/
|
||||
@Override
|
||||
public Integer getTopK() {
|
||||
return topK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the top-K (topKReturn) alternative tokens to return.
|
||||
* @param topK The top-K parameter (topKReturn).
|
||||
*/
|
||||
public void setTopK(Integer topK) {
|
||||
this.topK = topK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the stop sequences for stopping decoding if any of the strings is generated.
|
||||
* @return The stop sequences.
|
||||
*/
|
||||
public String[] getStopSequences() {
|
||||
return stopSequences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the stop sequences for stopping decoding if any of the strings is generated.
|
||||
* @param stopSequences The stop sequences.
|
||||
*/
|
||||
public void setStopSequences(String[] stopSequences) {
|
||||
this.stopSequences = stopSequences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the frequency penalty object.
|
||||
* @return The frequency penalty object.
|
||||
*/
|
||||
public Penalty getFrequencyPenalty() {
|
||||
return frequencyPenalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the frequency penalty object.
|
||||
* @param frequencyPenalty The frequency penalty object.
|
||||
*/
|
||||
public void setFrequencyPenalty(Penalty frequencyPenalty) {
|
||||
this.frequencyPenalty = frequencyPenalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the presence penalty object.
|
||||
* @return The presence penalty object.
|
||||
*/
|
||||
public Penalty getPresencePenalty() {
|
||||
return presencePenalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the presence penalty object.
|
||||
* @param presencePenalty The presence penalty object.
|
||||
*/
|
||||
public void setPresencePenalty(Penalty presencePenalty) {
|
||||
this.presencePenalty = presencePenalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the count penalty object.
|
||||
* @return The count penalty object.
|
||||
*/
|
||||
public Penalty getCountPenalty() {
|
||||
return countPenalty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the count penalty object.
|
||||
* @param countPenalty The count penalty object.
|
||||
*/
|
||||
public void setCountPenalty(Penalty countPenalty) {
|
||||
this.countPenalty = countPenalty;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public Builder withMaxTokens(Integer maxTokens) {
|
||||
request.setMaxTokens(maxTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMinTokens(Integer minTokens) {
|
||||
request.setMinTokens(minTokens);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTemperature(Float temperature) {
|
||||
request.setTemperature(temperature);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopP(Float topP) {
|
||||
request.setTopP(topP);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withStopSequences(String[] stopSequences) {
|
||||
request.setStopSequences(stopSequences);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTopK(Integer topKReturn) {
|
||||
request.setTopK(topKReturn);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFrequencyPenalty(BedrockAi21Jurassic2ChatOptions.Penalty frequencyPenalty) {
|
||||
request.setFrequencyPenalty(frequencyPenalty);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPresencePenalty(BedrockAi21Jurassic2ChatOptions.Penalty presencePenalty) {
|
||||
request.setPresencePenalty(presencePenalty);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withCountPenalty(BedrockAi21Jurassic2ChatOptions.Penalty countPenalty) {
|
||||
request.setCountPenalty(countPenalty);
|
||||
return this;
|
||||
}
|
||||
|
||||
public BedrockAi21Jurassic2ChatOptions build() {
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Penalty object for frequency, presence, and count penalties.
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public record Penalty(@JsonProperty("scale") Float scale, @JsonProperty("applyToNumbers") Boolean applyToNumbers,
|
||||
@JsonProperty("applyToPunctuations") Boolean applyToPunctuations,
|
||||
@JsonProperty("applyToStopwords") Boolean applyToStopwords,
|
||||
@JsonProperty("applyToWhitespaces") Boolean applyToWhitespaces,
|
||||
@JsonProperty("applyToEmojis") Boolean applyToEmojis) {
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private Float scale;
|
||||
|
||||
// can't keep it null due to modelOptionsUtils#mapToClass convert null to
|
||||
// false
|
||||
private Boolean applyToNumbers = true;
|
||||
|
||||
private Boolean applyToPunctuations = true;
|
||||
|
||||
private Boolean applyToStopwords = true;
|
||||
|
||||
private Boolean applyToWhitespaces = true;
|
||||
|
||||
private Boolean applyToEmojis = true;
|
||||
|
||||
public Builder scale(Float scale) {
|
||||
this.scale = scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder applyToNumbers(Boolean applyToNumbers) {
|
||||
this.applyToNumbers = applyToNumbers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder applyToPunctuations(Boolean applyToPunctuations) {
|
||||
this.applyToPunctuations = applyToPunctuations;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder applyToStopwords(Boolean applyToStopwords) {
|
||||
this.applyToStopwords = applyToStopwords;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder applyToWhitespaces(Boolean applyToWhitespaces) {
|
||||
this.applyToWhitespaces = applyToWhitespaces;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder applyToEmojis(Boolean applyToEmojis) {
|
||||
this.applyToEmojis = applyToEmojis;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Penalty build() {
|
||||
return new Penalty(scale, applyToNumbers, applyToPunctuations, applyToStopwords, applyToWhitespaces,
|
||||
applyToEmojis);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,10 +22,12 @@ 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 software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
|
||||
|
||||
/**
|
||||
* Java client for the Bedrock Jurassic2 chat model.
|
||||
@@ -48,6 +50,21 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* AI21 Jurassic2 chat request parameters.
|
||||
*
|
||||
@@ -129,6 +146,74 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
@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<String> 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<String> 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -273,5 +358,7 @@ public class Ai21Jurassic2ChatBedrockApi extends
|
||||
public Ai21Jurassic2ChatResponse chatCompletion(Ai21Jurassic2ChatRequest request) {
|
||||
return this.internalInvocation(request, Ai21Jurassic2ChatResponse.class);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
// @formatter:on
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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 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 org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatClient;
|
||||
import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatOptions;
|
||||
import org.springframework.ai.chat.ChatResponse;
|
||||
import org.springframework.ai.chat.Generation;
|
||||
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.PromptTemplate;
|
||||
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
|
||||
import org.springframework.ai.parser.MapOutputParser;
|
||||
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.io.Resource;
|
||||
import software.amazon.awssdk.auth.credentials.EnvironmentVariableCredentialsProvider;
|
||||
import software.amazon.awssdk.regions.Region;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest
|
||||
@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*")
|
||||
@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*")
|
||||
class BedrockAi21Jurassic2ChatClientIT {
|
||||
|
||||
@Autowired
|
||||
private BedrockAi21Jurassic2ChatClient client;
|
||||
|
||||
@Value("classpath:/prompts/system-message.st")
|
||||
private Resource systemResource;
|
||||
|
||||
@Test
|
||||
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));
|
||||
|
||||
ChatResponse response = client.call(prompt);
|
||||
|
||||
assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmojiPenaltyFalse() {
|
||||
BedrockAi21Jurassic2ChatOptions.Penalty penalty = new BedrockAi21Jurassic2ChatOptions.Penalty.Builder()
|
||||
.applyToEmojis(false)
|
||||
.build();
|
||||
BedrockAi21Jurassic2ChatOptions options = new BedrockAi21Jurassic2ChatOptions.Builder()
|
||||
.withPresencePenalty(penalty)
|
||||
.build();
|
||||
|
||||
UserMessage userMessage = new UserMessage("Can you express happiness using an emoji like 😄 ?");
|
||||
Prompt prompt = new Prompt(List.of(userMessage), options);
|
||||
|
||||
ChatResponse response = client.call(prompt);
|
||||
|
||||
assertThat(response.getResult().getOutput().getContent()).matches(content -> content.contains("😄"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled("This test is failing when run in combination with the other tests")
|
||||
void emojiPenaltyWhenTrueByDefaultApplyPenaltyTest() {
|
||||
// applyToEmojis is by default true
|
||||
BedrockAi21Jurassic2ChatOptions.Penalty penalty = new BedrockAi21Jurassic2ChatOptions.Penalty.Builder().build();
|
||||
BedrockAi21Jurassic2ChatOptions options = new BedrockAi21Jurassic2ChatOptions.Builder()
|
||||
.withPresencePenalty(penalty)
|
||||
.build();
|
||||
|
||||
UserMessage userMessage = new UserMessage("Can you express happiness using an emoji like 😄?");
|
||||
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemResource);
|
||||
Message systemMessage = systemPromptTemplate.createMessage(Map.of("name", "Bob", "voice", "pirate"));
|
||||
|
||||
Prompt prompt = new Prompt(List.of(userMessage, systemMessage), options);
|
||||
|
||||
ChatResponse response = client.call(prompt);
|
||||
|
||||
assertThat(response.getResult().getOutput().getContent()).doesNotContain("😄");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapOutputParser() {
|
||||
MapOutputParser outputParser = new MapOutputParser();
|
||||
|
||||
String format = outputParser.getFormat();
|
||||
String template = """
|
||||
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 = client.call(prompt).getResult();
|
||||
|
||||
Map<String, Object> result = outputParser.parse(generation.getOutput().getContent());
|
||||
assertThat(result.get("numbers")).isEqualTo(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
|
||||
|
||||
}
|
||||
|
||||
@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));
|
||||
|
||||
ChatResponse response = client.call(prompt);
|
||||
|
||||
assertThat(response.getResult().getOutput().getContent()).contains("AI");
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BedrockAi21Jurassic2ChatClient bedrockAi21Jurassic2ChatClient(
|
||||
Ai21Jurassic2ChatBedrockApi jurassic2ChatBedrockApi) {
|
||||
return new BedrockAi21Jurassic2ChatClient(jurassic2ChatBedrockApi,
|
||||
BedrockAi21Jurassic2ChatOptions.builder()
|
||||
.withTemperature(0.5f)
|
||||
.withMaxTokens(100)
|
||||
.withTopP(0.9f)
|
||||
.build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
**** xref:api/chat/bedrock/bedrock-llama2.adoc[Llama2]
|
||||
**** 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/huggingface.adoc[HuggingFace]
|
||||
*** xref:api/chat/google-vertexai.adoc[Google VertexAI]
|
||||
**** xref:api/chat/vertexai-palm2-chat.adoc[VertexAI PaLM2 ]
|
||||
|
||||
@@ -72,6 +72,7 @@ Here are the supported `<model>` and `<chat|embedding>` combinations:
|
||||
| Model | Chat | Chat Streaming | Embedding
|
||||
|
||||
| llama2 | Yes | Yes | No
|
||||
| jurassic2 | Yes | No | No
|
||||
| cohere | Yes | Yes | Yes
|
||||
| anthropic | Yes | Yes | No
|
||||
| jurassic2 (WIP) | Yes | No | No
|
||||
@@ -90,5 +91,7 @@ For more information, refer to the documentation below for each supported model.
|
||||
* 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`
|
||||
* 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-jurassic2-chat.adoc[(WIP)Spring AI Bedrock Jurassic Chat]: `spring.ai.bedrock.jurassic2.chat.enabled=true`
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
= Jurassic-2 Chat
|
||||
|
||||
https://aws.amazon.com/bedrock/jurassic/[AI21 Labs Jurassic on Amazon Bedrock
|
||||
] Jurassic is AI21 Labs’ family of reliable FMs for the enterprise, powering sophisticated language generation tasks – such as question answering, text generation, search, and summarization – across thousands of live applications.
|
||||
|
||||
|
||||
== 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]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bedrock-ai-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
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 Jurassic-2
|
||||
|
||||
By default the Bedrock Jurassic-2 model is disabled.
|
||||
To enable it set the `spring.ai.bedrock.jurassic2.chat.enabled` property to `true`.
|
||||
Exporting environment variable is one way to set this configuration property:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
export SPRING_AI_BEDROCK_JURASSIC2_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.access-key | AWS access key. | -
|
||||
| spring.ai.bedrock.aws.secret-key | AWS secret key. | -
|
||||
|====
|
||||
|
||||
|
||||
The prefix `spring.ai.bedrock.jurassic2.chat` is the property prefix that configures the chat client implementation for Jurassic-2.
|
||||
|
||||
[cols="2,5,1"]
|
||||
|====
|
||||
| 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
|
||||
|====
|
||||
|
||||
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`.
|
||||
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 <<chat-options>> 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/jurassic2/BedrockAi21Jurassic2ChatOptions.java[BedrockAi21Jurassic2ChatOptions.java] provides model configurations, such as temperature, topP, maxTokens, etc.
|
||||
|
||||
On start-up, the default options can be configured with the `BedrockAi21Jurassic2ChatClient(api, options)` constructor or the `spring.ai.bedrock.jurassic2.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 = chatClient.call(
|
||||
new Prompt(
|
||||
"Generate the names of 5 famous pirates.",
|
||||
BedrockAi21Jurassic2ChatOptions.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/jurassic2/BedrockAi21Jurassic2ChatOptions.java[BedrockAi21Jurassic2ChatOptions] 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 Jurassic-2 Chat client:
|
||||
|
||||
[source]
|
||||
----
|
||||
spring.ai.bedrock.aws.region=eu-central-1
|
||||
spring.ai.bedrock.aws.access-key=${AWS_ACCESS_KEY_ID}
|
||||
spring.ai.bedrock.aws.secret-key=${AWS_SECRET_ACCESS_KEY}
|
||||
|
||||
spring.ai.bedrock.jurassic2.chat.enabled=true
|
||||
spring.ai.bedrock.jurassic2.chat.options.temperature=0.8
|
||||
----
|
||||
|
||||
TIP: replace the `regions`, `access-key` and `secret-key` with your AWS credentials.
|
||||
|
||||
This will create a `BedrockAi21Jurassic2ChatClient` implementation that you can inject into your class.
|
||||
Here is an example of a simple `@Controller` class that uses the chat client for text generations.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@RestController
|
||||
public class ChatController {
|
||||
|
||||
private final BedrockAi21Jurassic2ChatClient chatClient;
|
||||
|
||||
@Autowired
|
||||
public ChatController(BedrockAi21Jurassic2ChatClient chatClient) {
|
||||
this.chatClient = chatClient;
|
||||
}
|
||||
|
||||
@GetMapping("/ai/generate")
|
||||
public Map generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
|
||||
return Map.of("generation", chatClient.call(message));
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
== Manual Configuration
|
||||
|
||||
The https://github.com/spring-projects/spring-ai/blob/main/models/spring-ai-bedrock/src/main/java/org/springframework/ai/bedrock/jurassic2/BedrockAi21Jurassic2ChatClient.java[BedrockAi21Jurassic2ChatClient] implements the `ChatClient` uses the <<low-level-api>> to connect to the Bedrock Jurassic-2 service.
|
||||
|
||||
Add the `spring-ai-bedrock` dependency to your project's Maven `pom.xml` file:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-bedrock</artifactId>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
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/jurassic2/BedrockAi21Jurassic2ChatClient.java[BedrockAi21Jurassic2ChatClient] and use it for text generations:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Ai21Jurassic2ChatBedrockApi api = new Ai21Jurassic2ChatBedrockApi(Ai21Jurassic2ChatModel.AI21_J2_MID_V1.id(),
|
||||
EnvironmentVariableCredentialsProvider.create(), Region.US_EAST_1.id(), new ObjectMapper());
|
||||
|
||||
BedrockAi21Jurassic2ChatClient chatClient = new BedrockAi21Jurassic2ChatClient(api,
|
||||
BedrockAi21Jurassic2ChatOptions.builder()
|
||||
.withTemperature(0.5f)
|
||||
.withMaxTokens(100)
|
||||
.withTopP(0.9f).build());
|
||||
|
||||
ChatResponse response = chatClient.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());
|
||||
|
||||
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.
|
||||
@@ -186,6 +186,7 @@ image::spring-ai-chat-completions-clients.jpg[align="center", width="800px"]
|
||||
** xref:api/chat/bedrock/bedrock-llama2.adoc[Llama2 Chat Completion]
|
||||
** 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/mistralai-chat.adoc[Mistral AI Chat Completion] (streaming & function-calling support)
|
||||
|
||||
== Chat Model API
|
||||
|
||||
@@ -150,8 +150,8 @@ Each of the following sections in the documentation shows which dependencies you
|
||||
*** xref:api/chat/bedrock/bedrock-llama2.adoc[Llama2 Chat Completion]
|
||||
*** 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/mistralai-chat.adoc[MistralAI Chat Completion] (streaming and function-calling support)
|
||||
// ** xref:api/chat/bedrock/bedrock-jurassic.adoc[Jurassic2 Chat Completion] (WIP, no streaming support)
|
||||
|
||||
=== Image Generation Models
|
||||
* xref:api/imageclient.adoc[]
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.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.bedrock.jurassic2.BedrockAi21Jurassic2ChatClient;
|
||||
import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
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;
|
||||
|
||||
/**
|
||||
* {@link AutoConfiguration Auto-configuration} for Bedrock Jurassic2 Chat Client.
|
||||
*
|
||||
* @author Ahmed Yousri
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@ConditionalOnClass(Ai21Jurassic2ChatBedrockApi.class)
|
||||
@EnableConfigurationProperties({ BedrockAi21Jurassic2ChatProperties.class, BedrockAwsConnectionProperties.class })
|
||||
@ConditionalOnProperty(prefix = BedrockAi21Jurassic2ChatProperties.CONFIG_PREFIX, name = "enabled",
|
||||
havingValue = "true")
|
||||
@Import(BedrockAwsConnectionConfiguration.class)
|
||||
public class BedrockAi21Jurassic2ChatAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Ai21Jurassic2ChatBedrockApi ai21Jurassic2ChatBedrockApi(AwsCredentialsProvider credentialsProvider,
|
||||
BedrockAi21Jurassic2ChatProperties properties, BedrockAwsConnectionProperties awsProperties) {
|
||||
return new Ai21Jurassic2ChatBedrockApi(properties.getModel(), credentialsProvider, awsProperties.getRegion(),
|
||||
new ObjectMapper());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public BedrockAi21Jurassic2ChatClient jurassic2ChatClient(Ai21Jurassic2ChatBedrockApi ai21Jurassic2ChatBedrockApi,
|
||||
BedrockAi21Jurassic2ChatProperties properties) {
|
||||
|
||||
return BedrockAi21Jurassic2ChatClient.builder(ai21Jurassic2ChatBedrockApi)
|
||||
.withOptions(properties.getOptions())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.jurrasic2;
|
||||
|
||||
import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatOptions;
|
||||
import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi.Ai21Jurassic2ChatModel;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
|
||||
/**
|
||||
* Configuration properties for Bedrock Ai21Jurassic2.
|
||||
*
|
||||
* @author Ahmed Yousri
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@ConfigurationProperties(BedrockAi21Jurassic2ChatProperties.CONFIG_PREFIX)
|
||||
public class BedrockAi21Jurassic2ChatProperties {
|
||||
|
||||
public static final String CONFIG_PREFIX = "spring.ai.bedrock.jurassic2.chat";
|
||||
|
||||
/**
|
||||
* Enable Bedrock Ai21Jurassic2 chat client. Disabled by default.
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* The generative id to use. See the {@link Ai21Jurassic2ChatModel} for the supported
|
||||
* models.
|
||||
*/
|
||||
private String model = Ai21Jurassic2ChatModel.AI21_J2_MID_V1.id();
|
||||
|
||||
@NestedConfigurationProperty
|
||||
private BedrockAi21Jurassic2ChatOptions options = BedrockAi21Jurassic2ChatOptions.builder()
|
||||
.withTemperature(0.7f)
|
||||
.withMaxTokens(500)
|
||||
.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 BedrockAi21Jurassic2ChatOptions getOptions() {
|
||||
return this.options;
|
||||
}
|
||||
|
||||
public void setOptions(BedrockAi21Jurassic2ChatOptions options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ org.springframework.ai.autoconfigure.transformers.TransformersEmbeddingClientAut
|
||||
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.jurrasic2.BedrockAi21Jurassic2ChatAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.bedrock.llama2.BedrockLlama2ChatAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.bedrock.cohere.BedrockCohereChatAutoConfiguration
|
||||
org.springframework.ai.autoconfigure.bedrock.cohere.BedrockCohereEmbeddingAutoConfiguration
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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.jurassic2;
|
||||
|
||||
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.jurrasic2.BedrockAi21Jurassic2ChatAutoConfiguration;
|
||||
import org.springframework.ai.autoconfigure.bedrock.jurrasic2.BedrockAi21Jurassic2ChatProperties;
|
||||
import org.springframework.ai.bedrock.jurassic2.BedrockAi21Jurassic2ChatClient;
|
||||
import org.springframework.ai.bedrock.jurassic2.api.Ai21Jurassic2ChatBedrockApi;
|
||||
import org.springframework.ai.chat.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
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@EnabledIfEnvironmentVariable(named = "AWS_ACCESS_KEY_ID", matches = ".*")
|
||||
@EnabledIfEnvironmentVariable(named = "AWS_SECRET_ACCESS_KEY", matches = ".*")
|
||||
public class BedrockAi21Jurassic2ChatAutoConfigurationIT {
|
||||
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.bedrock.jurassic2.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.jurassic2.chat.model="
|
||||
+ Ai21Jurassic2ChatBedrockApi.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"));
|
||||
|
||||
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 -> {
|
||||
BedrockAi21Jurassic2ChatClient ai21Jurassic2ChatClient = context
|
||||
.getBean(BedrockAi21Jurassic2ChatClient.class);
|
||||
ChatResponse response = ai21Jurassic2ChatClient.call(new Prompt(List.of(userMessage, systemMessage)));
|
||||
assertThat(response.getResult().getOutput().getContent()).contains("Blackbeard");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void propertiesTest() {
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withPropertyValues("spring.ai.bedrock.jurassic2.chat.enabled=true",
|
||||
"spring.ai.bedrock.aws.access-key=ACCESS_KEY", "spring.ai.bedrock.aws.secret-key=SECRET_KEY",
|
||||
"spring.ai.bedrock.jurassic2.chat.model=MODEL_XYZ",
|
||||
"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))
|
||||
.run(context -> {
|
||||
var chatProperties = context.getBean(BedrockAi21Jurassic2ChatProperties.class);
|
||||
var awsProperties = context.getBean(BedrockAwsConnectionProperties.class);
|
||||
|
||||
assertThat(chatProperties.isEnabled()).isTrue();
|
||||
assertThat(awsProperties.getRegion()).isEqualTo(Region.US_EAST_1.id());
|
||||
|
||||
assertThat(chatProperties.getOptions().getTemperature()).isEqualTo(0.55f);
|
||||
assertThat(chatProperties.getOptions().getMaxTokens()).isEqualTo(123);
|
||||
assertThat(chatProperties.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(BedrockAi21Jurassic2ChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatClient.class)).isEmpty();
|
||||
});
|
||||
|
||||
// Explicitly enable the chat auto-configuration.
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.jurassic2.chat.enabled=true")
|
||||
.withConfiguration(AutoConfigurations.of(BedrockAi21Jurassic2ChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatProperties.class)).isNotEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatClient.class)).isNotEmpty();
|
||||
});
|
||||
|
||||
// Explicitly disable the chat auto-configuration.
|
||||
new ApplicationContextRunner().withPropertyValues("spring.ai.bedrock.jurassic2.chat.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(BedrockAi21Jurassic2ChatAutoConfiguration.class))
|
||||
.run(context -> {
|
||||
assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatProperties.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(BedrockAi21Jurassic2ChatClient.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user