AI rule node: fail node init if JSON mode is configured and the model does not support it
This commit is contained in:
parent
28ce6ca8c8
commit
1ce1a1b89c
@ -42,4 +42,6 @@ public sealed interface AiChatModelConfig<C extends AiChatModelConfig<C>> extend
|
||||
|
||||
C withMaxRetries(Integer maxRetries);
|
||||
|
||||
boolean supportsJsonMode();
|
||||
|
||||
}
|
||||
|
||||
@ -48,4 +48,9 @@ public record AmazonBedrockChatModelConfig(
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsJsonMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -49,4 +49,9 @@ public record AnthropicChatModelConfig(
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsJsonMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -50,4 +50,9 @@ public record AzureOpenAiChatModelConfig(
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsJsonMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -50,4 +50,9 @@ public record GitHubModelsChatModelConfig(
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsJsonMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -51,4 +51,9 @@ public record GoogleAiGeminiChatModelConfig(
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsJsonMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -51,4 +51,9 @@ public record GoogleVertexAiGeminiChatModelConfig(
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsJsonMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -50,4 +50,9 @@ public record MistralAiChatModelConfig(
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsJsonMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -50,4 +50,9 @@ public record OpenAiChatModelConfig(
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsJsonMode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
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
|
||||
if (config.getResponseFormat().type() == TbResponseFormat.TbResponseFormatType.JSON) {
|
||||
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…
x
Reference in New Issue
Block a user