Browse Source

AI rule node: fail node init if JSON mode is configured and the model does not support it

pull/13371/head
Dmytro Skarzhynets 1 year ago
parent
commit
1ce1a1b89c
No known key found for this signature in database GPG Key ID: 2B51652F224037DF
  1. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModelConfig.java
  2. 5
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModelConfig.java
  3. 5
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModelConfig.java
  4. 5
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModelConfig.java
  5. 5
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModelConfig.java
  6. 5
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModelConfig.java
  7. 5
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModelConfig.java
  8. 5
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModelConfig.java
  9. 5
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModelConfig.java
  10. 30
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java

2
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModelConfig.java

@ -42,4 +42,6 @@ public sealed interface AiChatModelConfig<C extends AiChatModelConfig<C>> extend
C withMaxRetries(Integer maxRetries);
boolean supportsJsonMode();
}

5
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModelConfig.java

@ -48,4 +48,9 @@ public record AmazonBedrockChatModelConfig(
return configurer.configureChatModel(this);
}
@Override
public boolean supportsJsonMode() {
return false;
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModelConfig.java

@ -49,4 +49,9 @@ public record AnthropicChatModelConfig(
return configurer.configureChatModel(this);
}
@Override
public boolean supportsJsonMode() {
return false;
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModelConfig.java

@ -50,4 +50,9 @@ public record AzureOpenAiChatModelConfig(
return configurer.configureChatModel(this);
}
@Override
public boolean supportsJsonMode() {
return true;
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModelConfig.java

@ -50,4 +50,9 @@ public record GitHubModelsChatModelConfig(
return configurer.configureChatModel(this);
}
@Override
public boolean supportsJsonMode() {
return false;
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModelConfig.java

@ -51,4 +51,9 @@ public record GoogleAiGeminiChatModelConfig(
return configurer.configureChatModel(this);
}
@Override
public boolean supportsJsonMode() {
return true;
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModelConfig.java

@ -51,4 +51,9 @@ public record GoogleVertexAiGeminiChatModelConfig(
return configurer.configureChatModel(this);
}
@Override
public boolean supportsJsonMode() {
return true;
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModelConfig.java

@ -50,4 +50,9 @@ public record MistralAiChatModelConfig(
return configurer.configureChatModel(this);
}
@Override
public boolean supportsJsonMode() {
return true;
}
}

5
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModelConfig.java

@ -50,4 +50,9 @@ public record OpenAiChatModelConfig(
return configurer.configureChatModel(this);
}
@Override
public boolean supportsJsonMode() {
return true;
}
}

30
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java

@ -48,6 +48,7 @@ import java.util.NoSuchElementException;
import java.util.Optional;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
import static org.thingsboard.rule.engine.ai.TbResponseFormat.TbResponseFormatType;
import static org.thingsboard.server.dao.service.ConstraintValidator.validateFields;
@RuleNode(
@ -91,8 +92,21 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
throw new TbNodeException(e, true);
}
// LangChain4j AnthropicChatModel rejects requests with non-null ResponseFormat even if ResponseFormatType is TEXT
if (config.getResponseFormat().type() == TbResponseFormat.TbResponseFormatType.JSON) {
Optional<AiModel> modelOpt = ctx.getAiModelService().findAiModelByTenantIdAndId(ctx.getTenantId(), modelId);
if (modelOpt.isEmpty()) {
throw new TbNodeException("[" + ctx.getTenantId() + "] AI model with ID: [" + modelId + "] was not found", true);
}
AiModel model = modelOpt.get();
AiModelType modelType = model.getConfiguration().modelType();
if (modelType != AiModelType.CHAT) {
throw new TbNodeException("[" + ctx.getTenantId() + "] AI model with ID: [" + modelId + "] must be of type CHAT, but was " + modelType, true);
}
AiChatModelConfig<?> chatModelConfig = (AiChatModelConfig<?>) model.getConfiguration();
if (isJsonModeConfigured(config)) {
if (!chatModelConfig.supportsJsonMode()) {
throw new TbNodeException("[" + ctx.getTenantId() + "] AI model with ID: [" + modelId + "] does not support '" + config.getResponseFormat().type() + "' response format", true);
}
// LangChain4j AnthropicChatModel rejects requests with non-null ResponseFormat even if ResponseFormatType is TEXT
responseFormat = config.getResponseFormat().toLangChainResponseFormat();
}
@ -101,15 +115,11 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
timeoutSeconds = config.getTimeoutSeconds();
modelId = config.getModelId();
super.forceAck = config.isForceAck() || super.forceAck; // force ack if node config says so, or if env variable (super.forceAck) says so
}
Optional<AiModel> model = ctx.getAiModelService().findAiModelByTenantIdAndId(ctx.getTenantId(), modelId);
if (model.isEmpty()) {
throw new TbNodeException("[" + ctx.getTenantId() + "] AI model with ID: [" + modelId + "] was not found", true);
}
AiModelType modelType = model.get().getConfiguration().modelType();
if (modelType != AiModelType.CHAT) {
throw new TbNodeException("[" + ctx.getTenantId() + "] AI model with ID: [" + modelId + "] must be of type CHAT, but was " + modelType, true);
}
private static boolean isJsonModeConfigured(TbAiNodeConfiguration config) {
var responseFormatType = config.getResponseFormat().type();
return responseFormatType == TbResponseFormatType.JSON || responseFormatType == TbResponseFormatType.JSON_SCHEMA;
}
@Override

Loading…
Cancel
Save