AI rule node: add Anthropic support
This commit is contained in:
parent
d5c885dcde
commit
0cc5980156
@ -383,11 +383,11 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-azure-open-ai</artifactId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
<artifactId>langchain4j-azure-open-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
@ -401,6 +401,10 @@
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-mistral-ai</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-anthropic</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -25,6 +25,7 @@ import dev.langchain4j.model.chat.ChatModel;
|
||||
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.AnthropicChatModel;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.AzureOpenAiChatModel;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.GoogleAiGeminiChatModel;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.GoogleVertexAiGeminiChatModel;
|
||||
@ -125,6 +126,18 @@ class Langchain4jChatModelConfigurerImpl implements Langchain4jChatModelConfigur
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModel configureChatModel(AnthropicChatModel chatModel) {
|
||||
AnthropicChatModel.Config modelConfig = chatModel.modelConfig();
|
||||
return dev.langchain4j.model.anthropic.AnthropicChatModel.builder()
|
||||
.apiKey(chatModel.providerConfig().apiKey())
|
||||
.modelName(modelConfig.modelId())
|
||||
.temperature(modelConfig.temperature())
|
||||
.timeout(toDuration(modelConfig.timeoutSeconds()))
|
||||
.maxRetries(modelConfig.maxRetries())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Duration toDuration(Integer timeoutSeconds) {
|
||||
return timeoutSeconds != null ? Duration.ofSeconds(timeoutSeconds) : null;
|
||||
}
|
||||
|
||||
@ -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.AnthropicChatModel;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.GoogleAiGeminiChatModel;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.GoogleVertexAiGeminiChatModel;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.MistralAiChatModel;
|
||||
@ -77,6 +78,14 @@ public final class AiModelTypeIdResolver extends TypeIdResolverBase {
|
||||
map.put("MISTRAL_AI::ministral-3b-latest", MistralAiChatModel.class);
|
||||
map.put("MISTRAL_AI::open-mistral-nemo", MistralAiChatModel.class);
|
||||
|
||||
// Anthropic models
|
||||
map.put("ANTHROPIC::claude-opus-4-0", AnthropicChatModel.class);
|
||||
map.put("ANTHROPIC::claude-sonnet-4-0", AnthropicChatModel.class);
|
||||
map.put("ANTHROPIC::claude-3-7-sonnet-latest", AnthropicChatModel.class);
|
||||
map.put("ANTHROPIC::claude-3-5-sonnet-latest", AnthropicChatModel.class);
|
||||
map.put("ANTHROPIC::claude-3-5-haiku-latest", AnthropicChatModel.class);
|
||||
map.put("ANTHROPIC::claude-3-opus-latest", AnthropicChatModel.class);
|
||||
|
||||
typeIdToModelClass = Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,9 @@ import org.thingsboard.server.common.data.ai.model.AiModel;
|
||||
import org.thingsboard.server.common.data.ai.model.AiModelType;
|
||||
|
||||
public sealed interface AiChatModel<C extends AiChatModelConfig<C>> extends AiModel<C>
|
||||
permits OpenAiChatModel, AzureOpenAiChatModel, GoogleAiGeminiChatModel, GoogleVertexAiGeminiChatModel, MistralAiChatModel {
|
||||
permits
|
||||
OpenAiChatModel, AzureOpenAiChatModel, GoogleAiGeminiChatModel,
|
||||
GoogleVertexAiGeminiChatModel, MistralAiChatModel, AnthropicChatModel {
|
||||
|
||||
ChatModel configure(Langchain4jChatModelConfigurer configurer);
|
||||
|
||||
|
||||
@ -18,7 +18,9 @@ package org.thingsboard.server.common.data.ai.model.chat;
|
||||
import org.thingsboard.server.common.data.ai.model.AiModelConfig;
|
||||
|
||||
public sealed interface AiChatModelConfig<C extends AiChatModelConfig<C>> extends AiModelConfig<C>
|
||||
permits OpenAiChatModel.Config, AzureOpenAiChatModel.Config, GoogleAiGeminiChatModel.Config, GoogleVertexAiGeminiChatModel.Config, MistralAiChatModel.Config {
|
||||
permits
|
||||
OpenAiChatModel.Config, AzureOpenAiChatModel.Config, GoogleAiGeminiChatModel.Config,
|
||||
GoogleVertexAiGeminiChatModel.Config, MistralAiChatModel.Config, AnthropicChatModel.Config {
|
||||
|
||||
Double temperature();
|
||||
|
||||
|
||||
@ -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.AnthropicProviderConfig;
|
||||
|
||||
public record AnthropicChatModel(
|
||||
AnthropicProviderConfig providerConfig,
|
||||
Config modelConfig
|
||||
) implements AiChatModel<AnthropicChatModel.Config> {
|
||||
|
||||
public record Config(
|
||||
String modelId,
|
||||
Double temperature,
|
||||
Integer timeoutSeconds,
|
||||
Integer maxRetries
|
||||
) implements AiChatModelConfig<AnthropicChatModel.Config> {
|
||||
|
||||
@Override
|
||||
public AnthropicChatModel.Config withTemperature(Double temperature) {
|
||||
return new Config(modelId, temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnthropicChatModel.Config withTimeoutSeconds(Integer timeoutSeconds) {
|
||||
return new Config(modelId, temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnthropicChatModel.Config withMaxRetries(Integer maxRetries) {
|
||||
return new Config(modelId, temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModel configure(Langchain4jChatModelConfigurer configurer) {
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnthropicChatModel withModelConfig(AnthropicChatModel.Config config) {
|
||||
return new AnthropicChatModel(providerConfig, config);
|
||||
}
|
||||
|
||||
}
|
||||
@ -29,4 +29,6 @@ public interface Langchain4jChatModelConfigurer {
|
||||
|
||||
ChatModel configureChatModel(MistralAiChatModel chatModel);
|
||||
|
||||
ChatModel configureChatModel(AnthropicChatModel chatModel);
|
||||
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ public enum AiProvider {
|
||||
AZURE_OPENAI,
|
||||
GOOGLE_AI_GEMINI,
|
||||
GOOGLE_VERTEX_AI_GEMINI,
|
||||
MISTRAL_AI
|
||||
MISTRAL_AI,
|
||||
ANTHROPIC
|
||||
|
||||
}
|
||||
|
||||
@ -28,10 +28,13 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
@JsonSubTypes.Type(value = AzureOpenAiProviderConfig.class, name = "AZURE_OPENAI"),
|
||||
@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 = MistralAiProviderConfig.class, name = "MISTRAL_AI"),
|
||||
@JsonSubTypes.Type(value = AnthropicProviderConfig.class, name = "ANTHROPIC")
|
||||
})
|
||||
public sealed interface AiProviderConfig
|
||||
permits OpenAiProviderConfig, AzureOpenAiProviderConfig, GoogleAiGeminiProviderConfig, GoogleVertexAiGeminiProviderConfig, MistralAiProviderConfig {
|
||||
permits
|
||||
OpenAiProviderConfig, AzureOpenAiProviderConfig, GoogleAiGeminiProviderConfig,
|
||||
GoogleVertexAiGeminiProviderConfig, MistralAiProviderConfig, AnthropicProviderConfig {
|
||||
|
||||
AiProvider provider();
|
||||
|
||||
|
||||
@ -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 AnthropicProviderConfig(String apiKey) implements AiProviderConfig {
|
||||
|
||||
@Override
|
||||
public AiProvider provider() {
|
||||
return AiProvider.ANTHROPIC;
|
||||
}
|
||||
|
||||
}
|
||||
@ -16,7 +16,6 @@
|
||||
package org.thingsboard.rule.engine.ai;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.util.concurrent.FluentFuture;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import dev.langchain4j.data.message.SystemMessage;
|
||||
@ -25,7 +24,6 @@ import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.chat.request.ChatRequest;
|
||||
import dev.langchain4j.model.chat.request.ResponseFormat;
|
||||
import dev.langchain4j.model.chat.request.ResponseFormatType;
|
||||
import dev.langchain4j.model.chat.request.json.JsonSchema;
|
||||
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
@ -81,10 +79,13 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
|
||||
throw new TbNodeException(e, true);
|
||||
}
|
||||
|
||||
// LC4j AnthropicChatModel rejects requests with non-null ResponseFormat even if ResponseFormatType is TEXT
|
||||
if (config.getResponseFormatType() == ResponseFormatType.JSON) {
|
||||
responseFormat = ResponseFormat.builder()
|
||||
.type(config.getResponseFormatType())
|
||||
.jsonSchema(getJsonSchema(config.getResponseFormatType(), config.getJsonSchema()))
|
||||
.jsonSchema(config.getJsonSchema() != null ? Langchain4jJsonSchemaAdapter.fromJsonNode(config.getJsonSchema()) : null)
|
||||
.build();
|
||||
}
|
||||
|
||||
systemPrompt = config.getSystemPrompt();
|
||||
userPrompt = config.getUserPrompt();
|
||||
@ -101,13 +102,6 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonSchema getJsonSchema(ResponseFormatType responseFormatType, ObjectNode jsonSchema) {
|
||||
if (responseFormatType == ResponseFormatType.TEXT) {
|
||||
return null;
|
||||
}
|
||||
return responseFormatType == ResponseFormatType.JSON && jsonSchema != null ? Langchain4jJsonSchemaAdapter.fromJsonNode(jsonSchema) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMsg(TbContext ctx, TbMsg msg) {
|
||||
var ackedMsg = ackIfNeeded(ctx, msg);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user