diff --git a/application/pom.xml b/application/pom.xml index e92aeba533..6a71074171 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -405,6 +405,10 @@ dev.langchain4j langchain4j-anthropic + + dev.langchain4j + langchain4j-bedrock + diff --git a/application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java b/application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java index aa4aed988d..404f9eac91 100644 --- a/application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java @@ -21,10 +21,13 @@ import com.google.cloud.vertexai.Transport; import com.google.cloud.vertexai.VertexAI; import com.google.cloud.vertexai.api.GenerationConfig; import com.google.cloud.vertexai.generativeai.GenerativeModel; +import dev.langchain4j.model.bedrock.BedrockChatModel; import dev.langchain4j.model.chat.ChatModel; +import dev.langchain4j.model.chat.request.ChatRequestParameters; import dev.langchain4j.model.vertexai.gemini.VertexAiGeminiChatModel; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.ai.model.chat.AmazonBedrockChatModel; import org.thingsboard.server.common.data.ai.model.chat.AnthropicChatModel; import org.thingsboard.server.common.data.ai.model.chat.AzureOpenAiChatModel; import org.thingsboard.server.common.data.ai.model.chat.GoogleAiGeminiChatModel; @@ -32,7 +35,12 @@ import org.thingsboard.server.common.data.ai.model.chat.GoogleVertexAiGeminiChat import org.thingsboard.server.common.data.ai.model.chat.Langchain4jChatModelConfigurer; import org.thingsboard.server.common.data.ai.model.chat.MistralAiChatModel; import org.thingsboard.server.common.data.ai.model.chat.OpenAiChatModel; +import org.thingsboard.server.common.data.ai.provider.AmazonBedrockProviderConfig; import org.thingsboard.server.common.data.ai.provider.GoogleVertexAiGeminiProviderConfig; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.bedrockruntime.BedrockRuntimeClient; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -138,6 +146,33 @@ class Langchain4jChatModelConfigurerImpl implements Langchain4jChatModelConfigur .build(); } + @Override + public ChatModel configureChatModel(AmazonBedrockChatModel chatModel) { + AmazonBedrockProviderConfig providerConfig = chatModel.providerConfig(); + AmazonBedrockChatModel.Config modelConfig = chatModel.modelConfig(); + + var credentialsProvider = StaticCredentialsProvider.create( + AwsBasicCredentials.create(providerConfig.accessKeyId(), providerConfig.secretAccessKey()) + ); + + var bedrockClient = BedrockRuntimeClient.builder() + .region(Region.of(providerConfig.region())) + .credentialsProvider(credentialsProvider) + .build(); + + var defaultChatRequestParams = ChatRequestParameters.builder() + .temperature(modelConfig.temperature()) + .build(); + + return BedrockChatModel.builder() + .client(bedrockClient) + .modelId(modelConfig.modelId()) + .defaultRequestParameters(defaultChatRequestParams) + .timeout(toDuration(modelConfig.timeoutSeconds())) + .maxRetries(modelConfig.maxRetries()) + .build(); + } + private static Duration toDuration(Integer timeoutSeconds) { return timeoutSeconds != null ? Duration.ofSeconds(timeoutSeconds) : null; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModelTypeIdResolver.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModelTypeIdResolver.java index 0f3634e1f4..3c7766c4ea 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModelTypeIdResolver.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModelTypeIdResolver.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.DatabindContext; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase; +import org.thingsboard.server.common.data.ai.model.chat.AmazonBedrockChatModel; import org.thingsboard.server.common.data.ai.model.chat.AnthropicChatModel; import org.thingsboard.server.common.data.ai.model.chat.GoogleAiGeminiChatModel; import org.thingsboard.server.common.data.ai.model.chat.GoogleVertexAiGeminiChatModel; @@ -86,6 +87,9 @@ public final class AiModelTypeIdResolver extends TypeIdResolverBase { map.put("ANTHROPIC::claude-3-5-haiku-latest", AnthropicChatModel.class); map.put("ANTHROPIC::claude-3-opus-latest", AnthropicChatModel.class); + // Amazon Bedrock models + map.put("AMAZON_BEDROCK::amazon.nova-lite-v1:0", AmazonBedrockChatModel.class); + typeIdToModelClass = Collections.unmodifiableMap(map); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModel.java index 2043efd860..ac0f2226f9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModel.java @@ -22,7 +22,8 @@ import org.thingsboard.server.common.data.ai.model.AiModelType; public sealed interface AiChatModel> extends AiModel permits OpenAiChatModel, AzureOpenAiChatModel, GoogleAiGeminiChatModel, - GoogleVertexAiGeminiChatModel, MistralAiChatModel, AnthropicChatModel { + GoogleVertexAiGeminiChatModel, MistralAiChatModel, AnthropicChatModel, + AmazonBedrockChatModel { ChatModel configure(Langchain4jChatModelConfigurer configurer); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModelConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModelConfig.java index d346b22731..e0d7232fa6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModelConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModelConfig.java @@ -20,7 +20,8 @@ import org.thingsboard.server.common.data.ai.model.AiModelConfig; public sealed interface AiChatModelConfig> extends AiModelConfig permits OpenAiChatModel.Config, AzureOpenAiChatModel.Config, GoogleAiGeminiChatModel.Config, - GoogleVertexAiGeminiChatModel.Config, MistralAiChatModel.Config, AnthropicChatModel.Config { + GoogleVertexAiGeminiChatModel.Config, MistralAiChatModel.Config, AnthropicChatModel.Config, + AmazonBedrockChatModel.Config { Double temperature(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java new file mode 100644 index 0000000000..e3cbaf426c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2025 The Thingsboard 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 + * + * http://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.thingsboard.server.common.data.ai.model.chat; + +import dev.langchain4j.model.chat.ChatModel; +import org.thingsboard.server.common.data.ai.provider.AmazonBedrockProviderConfig; + +public record AmazonBedrockChatModel( + AmazonBedrockProviderConfig providerConfig, + Config modelConfig +) implements AiChatModel { + + public record Config( + String modelId, + Double temperature, + Integer timeoutSeconds, + Integer maxRetries + ) implements AiChatModelConfig { + + @Override + public AmazonBedrockChatModel.Config withTemperature(Double temperature) { + return new Config(modelId, temperature, timeoutSeconds, maxRetries); + } + + @Override + public AmazonBedrockChatModel.Config withTimeoutSeconds(Integer timeoutSeconds) { + return new Config(modelId, temperature, timeoutSeconds, maxRetries); + } + + @Override + public AmazonBedrockChatModel.Config withMaxRetries(Integer maxRetries) { + return new Config(modelId, temperature, timeoutSeconds, maxRetries); + } + + } + + @Override + public ChatModel configure(Langchain4jChatModelConfigurer configurer) { + return configurer.configureChatModel(this); + } + + @Override + public AmazonBedrockChatModel withModelConfig(AmazonBedrockChatModel.Config config) { + return new AmazonBedrockChatModel(providerConfig, config); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/Langchain4jChatModelConfigurer.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/Langchain4jChatModelConfigurer.java index 3cb75419f0..d9d4c1dbb6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/Langchain4jChatModelConfigurer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/Langchain4jChatModelConfigurer.java @@ -31,4 +31,6 @@ public interface Langchain4jChatModelConfigurer { ChatModel configureChatModel(AnthropicChatModel chatModel); + ChatModel configureChatModel(AmazonBedrockChatModel chatModel); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProvider.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProvider.java index 78709fd52c..55b3cbe7f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProvider.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProvider.java @@ -22,6 +22,7 @@ public enum AiProvider { GOOGLE_AI_GEMINI, GOOGLE_VERTEX_AI_GEMINI, MISTRAL_AI, - ANTHROPIC + ANTHROPIC, + AMAZON_BEDROCK } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProviderConfig.java index e13f3fac7b..890ea197a0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProviderConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AiProviderConfig.java @@ -29,12 +29,14 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonSubTypes.Type(value = GoogleAiGeminiProviderConfig.class, name = "GOOGLE_AI_GEMINI"), @JsonSubTypes.Type(value = GoogleVertexAiGeminiProviderConfig.class, name = "GOOGLE_VERTEX_AI_GEMINI"), @JsonSubTypes.Type(value = MistralAiProviderConfig.class, name = "MISTRAL_AI"), - @JsonSubTypes.Type(value = AnthropicProviderConfig.class, name = "ANTHROPIC") + @JsonSubTypes.Type(value = AnthropicProviderConfig.class, name = "ANTHROPIC"), + @JsonSubTypes.Type(value = AmazonBedrockProviderConfig.class, name = "AMAZON_BEDROCK") }) public sealed interface AiProviderConfig permits OpenAiProviderConfig, AzureOpenAiProviderConfig, GoogleAiGeminiProviderConfig, - GoogleVertexAiGeminiProviderConfig, MistralAiProviderConfig, AnthropicProviderConfig { + GoogleVertexAiGeminiProviderConfig, MistralAiProviderConfig, AnthropicProviderConfig, + AmazonBedrockProviderConfig { AiProvider provider(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AmazonBedrockProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AmazonBedrockProviderConfig.java new file mode 100644 index 0000000000..7ecb0bcdb4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AmazonBedrockProviderConfig.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2025 The Thingsboard 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 + * + * http://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.thingsboard.server.common.data.ai.provider; + +public record AmazonBedrockProviderConfig(String region, String accessKeyId, String secretAccessKey) implements AiProviderConfig { + + @Override + public AiProvider provider() { + return AiProvider.AMAZON_BEDROCK; + } + +}