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);
|
C withMaxRetries(Integer maxRetries);
|
||||||
|
|
||||||
|
boolean supportsJsonMode();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,4 +48,9 @@ public record AmazonBedrockChatModelConfig(
|
|||||||
return configurer.configureChatModel(this);
|
return configurer.configureChatModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsJsonMode() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,4 +49,9 @@ public record AnthropicChatModelConfig(
|
|||||||
return configurer.configureChatModel(this);
|
return configurer.configureChatModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsJsonMode() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,4 +50,9 @@ public record AzureOpenAiChatModelConfig(
|
|||||||
return configurer.configureChatModel(this);
|
return configurer.configureChatModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsJsonMode() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,4 +50,9 @@ public record GitHubModelsChatModelConfig(
|
|||||||
return configurer.configureChatModel(this);
|
return configurer.configureChatModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsJsonMode() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,4 +51,9 @@ public record GoogleAiGeminiChatModelConfig(
|
|||||||
return configurer.configureChatModel(this);
|
return configurer.configureChatModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsJsonMode() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -51,4 +51,9 @@ public record GoogleVertexAiGeminiChatModelConfig(
|
|||||||
return configurer.configureChatModel(this);
|
return configurer.configureChatModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsJsonMode() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,4 +50,9 @@ public record MistralAiChatModelConfig(
|
|||||||
return configurer.configureChatModel(this);
|
return configurer.configureChatModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsJsonMode() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,4 +50,9 @@ public record OpenAiChatModelConfig(
|
|||||||
return configurer.configureChatModel(this);
|
return configurer.configureChatModel(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsJsonMode() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,6 +48,7 @@ import java.util.NoSuchElementException;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
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;
|
import static org.thingsboard.server.dao.service.ConstraintValidator.validateFields;
|
||||||
|
|
||||||
@RuleNode(
|
@RuleNode(
|
||||||
@ -91,8 +92,21 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
|
|||||||
throw new TbNodeException(e, true);
|
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
|
// LangChain4j AnthropicChatModel rejects requests with non-null ResponseFormat even if ResponseFormatType is TEXT
|
||||||
if (config.getResponseFormat().type() == TbResponseFormat.TbResponseFormatType.JSON) {
|
|
||||||
responseFormat = config.getResponseFormat().toLangChainResponseFormat();
|
responseFormat = config.getResponseFormat().toLangChainResponseFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,15 +115,11 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
|
|||||||
timeoutSeconds = config.getTimeoutSeconds();
|
timeoutSeconds = config.getTimeoutSeconds();
|
||||||
modelId = config.getModelId();
|
modelId = config.getModelId();
|
||||||
super.forceAck = config.isForceAck() || super.forceAck; // force ack if node config says so, or if env variable (super.forceAck) says so
|
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);
|
private static boolean isJsonModeConfigured(TbAiNodeConfiguration config) {
|
||||||
if (model.isEmpty()) {
|
var responseFormatType = config.getResponseFormat().type();
|
||||||
throw new TbNodeException("[" + ctx.getTenantId() + "] AI model with ID: [" + modelId + "] was not found", true);
|
return responseFormatType == TbResponseFormatType.JSON || responseFormatType == TbResponseFormatType.JSON_SCHEMA;
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user