diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index cd4a88314b..ea46ce86eb 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -35,7 +35,7 @@ import org.thingsboard.rule.engine.api.JobManager; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MqttClientSettings; import org.thingsboard.rule.engine.api.NotificationCenter; -import org.thingsboard.rule.engine.api.RuleEngineAiModelService; +import org.thingsboard.rule.engine.api.RuleEngineAiChatModelService; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.notification.SlackService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; @@ -63,7 +63,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.stats.TbApiUsageReportClient; -import org.thingsboard.server.dao.ai.AiModelSettingsService; +import org.thingsboard.server.dao.ai.AiModelService; import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; @@ -315,11 +315,11 @@ public class ActorSystemContext { @Autowired @Getter - private RuleEngineAiModelService aiModelService; + private RuleEngineAiChatModelService aiChatModelService; @Autowired @Getter - private AiModelSettingsService aiModelSettingsService; + private AiModelService aiModelService; @Autowired @Getter diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index b4235c7262..6374e4016d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -28,7 +28,7 @@ import org.thingsboard.rule.engine.api.JobManager; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MqttClientSettings; import org.thingsboard.rule.engine.api.NotificationCenter; -import org.thingsboard.rule.engine.api.RuleEngineAiModelService; +import org.thingsboard.rule.engine.api.RuleEngineAiChatModelService; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.RuleEngineApiUsageStateService; import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache; @@ -77,7 +77,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgProcessingStackItem; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; -import org.thingsboard.server.dao.ai.AiModelSettingsService; +import org.thingsboard.server.dao.ai.AiModelService; import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; @@ -1027,13 +1027,13 @@ public class DefaultTbContext implements TbContext { } @Override - public RuleEngineAiModelService getAiModelService() { - return mainCtx.getAiModelService(); + public RuleEngineAiChatModelService getAiChatModelService() { + return mainCtx.getAiChatModelService(); } @Override - public AiModelSettingsService getAiModelSettingsService() { - return mainCtx.getAiModelSettingsService(); + public AiModelService getAiModelService() { + return mainCtx.getAiModelService(); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/AiModelController.java b/application/src/main/java/org/thingsboard/server/controller/AiModelController.java index c7dfce90bc..5a00e26f17 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AiModelController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AiModelController.java @@ -17,31 +17,141 @@ package org.thingsboard.server.controller; import com.google.common.util.concurrent.ListenableFuture; import dev.langchain4j.model.chat.request.ChatRequest; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.common.data.ai.AiModel; import org.thingsboard.server.common.data.ai.dto.TbChatRequest; import org.thingsboard.server.common.data.ai.dto.TbChatResponse; -import org.thingsboard.server.common.data.ai.model.chat.AiChatModel; +import org.thingsboard.server.common.data.ai.model.chat.AiChatModelConfig; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.AiModelId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.config.annotations.ApiOperation; -import org.thingsboard.server.service.ai.AiModelService; +import org.thingsboard.server.service.ai.AiChatModelService; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import java.time.Duration; +import java.util.Optional; +import java.util.UUID; import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static org.thingsboard.server.controller.ControllerConstants.AI_MODEL_TEXT_SEARCH_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; +@Validated @RestController @RequiredArgsConstructor @RequestMapping("/api/ai/model") class AiModelController extends BaseController { - private final AiModelService aiModelService; + private final AiChatModelService aiChatModelService; + + @ApiOperation( + value = "Create or update AI model (saveAiModel)", + notes = "Creates or updates an AI model record.\n\n" + + "• **Create:** Omit the `id` to create a new record. The platform assigns a UUID to the new record and returns it in the `id` field of the response.\n\n" + + "• **Update:** Include an existing `id` to modify that record. If no matching record exists, the API responds with **404 Not Found**.\n\n" + + "Tenant ID for the AI model will be taken from the authenticated user making the request, regardless of any value provided in the request body." + + TENANT_AUTHORITY_PARAGRAPH + ) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @PostMapping + public AiModel saveAiModel(@RequestBody @Valid AiModel model) throws ThingsboardException { + var user = getCurrentUser(); + model.setTenantId(user.getTenantId()); + checkEntity(model.getId(), model, Resource.AI_MODEL); + return tbAiModelService.save(model, user); + } + + @ApiOperation( + value = "Get AI model by ID (getAiModelById)", + notes = "Fetches an AI model record by its `id`." + + TENANT_AUTHORITY_PARAGRAPH + ) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping("/{modelUuid}") + public AiModel getAiModelById( + @Parameter( + description = "ID of the AI model record", + required = true, + example = "de7900d4-30e2-11f0-9cd2-0242ac120002" + ) + @PathVariable UUID modelUuid + ) throws ThingsboardException { + return checkAiModelId(new AiModelId(modelUuid), Operation.READ); + } + + @ApiOperation( + value = "Get AI models (getAiModels)", + notes = "Returns a page of AI models. " + + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH + ) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping + public PageData getAiModels( + @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @Parameter(description = AI_MODEL_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "provider", "modelId"})) + @RequestParam(required = false) String sortProperty, + @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) + @RequestParam(required = false) String sortOrder + ) throws ThingsboardException { + var user = getCurrentUser(); + accessControlService.checkPermission(user, Resource.AI_MODEL, Operation.READ); + var pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return aiModelService.findAiModelsByTenantId(user.getTenantId(), pageLink); + } + + @ApiOperation( + value = "Delete AI model by ID (deleteAiModelById)", + notes = "Deletes the AI model record by its `id`. " + + "If a record with the specified `id` exists, the record is deleted and the endpoint returns `true`. " + + "If no such record exists, the endpoint returns `false`." + + TENANT_AUTHORITY_PARAGRAPH + ) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @DeleteMapping("/{modelUuid}") + public boolean deleteAiModelById( + @Parameter( + description = "ID of the AI model record", + required = true, + example = "de7900d4-30e2-11f0-9cd2-0242ac120002" + ) + @PathVariable UUID modelUuid + ) throws ThingsboardException { + var user = getCurrentUser(); + var modelId = new AiModelId(modelUuid); + accessControlService.checkPermission(user, Resource.AI_MODEL, Operation.DELETE); + Optional toDelete = aiModelService.findAiModelByTenantIdAndId(user.getTenantId(), modelId); + if (toDelete.isEmpty()) { + return false; + } + accessControlService.checkPermission(user, Resource.AI_MODEL, Operation.DELETE, modelId, toDelete.get()); + return tbAiModelService.delete(toDelete.get(), user); + } @ApiOperation( value = "Send request to AI chat model (sendChatRequest)", @@ -53,13 +163,13 @@ class AiModelController extends BaseController { @PostMapping("/chat") public DeferredResult sendChatRequest(@Valid @RequestBody TbChatRequest tbChatRequest) { ChatRequest langChainChatRequest = tbChatRequest.toLangChainChatRequest(); - AiChatModel chatModel = tbChatRequest.chatModel(); + AiChatModelConfig chatModelConfig = tbChatRequest.chatModelConfig(); - ListenableFuture future = aiModelService.sendChatRequestAsync(chatModel, langChainChatRequest) + ListenableFuture future = aiChatModelService.sendChatRequestAsync(chatModelConfig, langChainChatRequest) .transform(chatResponse -> (TbChatResponse) new TbChatResponse.Success(chatResponse.aiMessage().text()), directExecutor()) .catching(Throwable.class, ex -> new TbChatResponse.Failure(ex.getMessage()), directExecutor()); - Integer requestTimeoutSeconds = chatModel.modelConfig().timeoutSeconds(); + Integer requestTimeoutSeconds = chatModelConfig.timeoutSeconds(); return requestTimeoutSeconds != null ? wrapFuture(future, Duration.ofSeconds(requestTimeoutSeconds).toMillis()) : wrapFuture(future); } diff --git a/application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java b/application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java deleted file mode 100644 index b729d9f11e..0000000000 --- a/application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java +++ /dev/null @@ -1,143 +0,0 @@ -/** - * 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.controller; - -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.thingsboard.server.common.data.ai.AiModelSettings; -import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.AiModelSettingsId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.config.annotations.ApiOperation; -import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.security.permission.Resource; - -import java.util.Optional; -import java.util.UUID; - -import static org.thingsboard.server.controller.ControllerConstants.AI_MODEL_SETTINGS_TEXT_SEARCH_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; -import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; -import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; - -@Validated -@RestController -@RequestMapping("/api/ai/model/settings") -class AiModelSettingsController extends BaseController { - - @ApiOperation( - value = "Create or update AI model settings (saveAiModelSettings)", - notes = "Creates or updates an AI model settings record.\n\n" + - "• **Create:** Omit the `id` to create a new record. The platform assigns a UUID to the new settings and returns it in the `id` field of the response.\n\n" + - "• **Update:** Include an existing `id` to modify that record. If no matching record exists, the API responds with **404 Not Found**.\n\n" + - "Tenant ID for the AI model settings will be taken from the authenticated user making the request, regardless of any value provided in the request body." + - TENANT_AUTHORITY_PARAGRAPH - ) - @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @PostMapping - public AiModelSettings saveAiModelSettings(@RequestBody @Valid AiModelSettings settings) throws ThingsboardException { - var user = getCurrentUser(); - settings.setTenantId(user.getTenantId()); - checkEntity(settings.getId(), settings, Resource.AI_MODEL_SETTINGS); - return tbAiModelSettingsService.save(settings, user); - } - - @ApiOperation( - value = "Get AI model settings by ID (getAiModelSettingsById)", - notes = "Fetches an AI model settings record by its `id`." + - TENANT_AUTHORITY_PARAGRAPH - ) - @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @GetMapping("/{settingsUuid}") - public AiModelSettings getAiModelSettingsById( - @Parameter( - description = "ID of the AI model settings record", - required = true, - example = "de7900d4-30e2-11f0-9cd2-0242ac120002" - ) - @PathVariable UUID settingsUuid - ) throws ThingsboardException { - return checkAiModelSettingsId(new AiModelSettingsId(settingsUuid), Operation.READ); - } - - @ApiOperation( - value = "Get AI model settings (getAiModelSettings)", - notes = "Returns a page of AI model settings. " + - PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH - ) - @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @GetMapping - public PageData getAiModelSettings( - @Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) - @RequestParam int pageSize, - @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) - @RequestParam int page, - @Parameter(description = AI_MODEL_SETTINGS_TEXT_SEARCH_DESCRIPTION) - @RequestParam(required = false) String textSearch, - @Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "provider", "modelId"})) - @RequestParam(required = false) String sortProperty, - @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"})) - @RequestParam(required = false) String sortOrder - ) throws ThingsboardException { - var user = getCurrentUser(); - accessControlService.checkPermission(user, Resource.AI_MODEL_SETTINGS, Operation.READ); - var pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return aiModelSettingsService.findAiModelSettingsByTenantId(user.getTenantId(), pageLink); - } - - @ApiOperation( - value = "Delete AI model settings by ID (deleteAiModelSettingsById)", - notes = "Deletes the AI model settings record by its `id`. " + - "If a record with the specified `id` exists, the record is deleted and the endpoint returns `true`. " + - "If no such record exists, the endpoint returns `false`." + - TENANT_AUTHORITY_PARAGRAPH - ) - @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @DeleteMapping("/{settingsUuid}") - public boolean deleteAiModelSettingsById( - @Parameter( - description = "ID of the AI model settings record", - required = true, - example = "de7900d4-30e2-11f0-9cd2-0242ac120002" - ) - @PathVariable UUID settingsUuid - ) throws ThingsboardException { - var user = getCurrentUser(); - var settingsId = new AiModelSettingsId(settingsUuid); - accessControlService.checkPermission(user, Resource.AI_MODEL_SETTINGS, Operation.DELETE); - Optional toDelete = aiModelSettingsService.findAiModelSettingsByTenantIdAndId(user.getTenantId(), settingsId); - if (toDelete.isEmpty()) { - return false; - } - accessControlService.checkPermission(user, Resource.AI_MODEL_SETTINGS, Operation.DELETE, settingsId, toDelete.get()); - return tbAiModelSettingsService.delete(toDelete.get(), user); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 0ccd01bb4f..61dcd76b32 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -61,7 +61,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmComment; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -76,7 +76,7 @@ import org.thingsboard.server.common.data.edge.EdgeInfo; import org.thingsboard.server.common.data.exception.EntityVersionMismatchException; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.AlarmCommentId; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; @@ -131,7 +131,7 @@ import org.thingsboard.server.common.data.util.ThrowingBiFunction; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.ai.AiModelSettingsService; +import org.thingsboard.server.dao.ai.AiModelService; import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; @@ -178,7 +178,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.entitiy.TbLogEntityActionService; -import org.thingsboard.server.service.entitiy.ai.TbAiModelSettingsService; +import org.thingsboard.server.service.entitiy.ai.TbAiModelService; import org.thingsboard.server.service.entitiy.user.TbUserSettingsService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbAssetProfileCache; @@ -383,10 +383,10 @@ public abstract class BaseController { protected CalculatedFieldService calculatedFieldService; @Autowired - protected AiModelSettingsService aiModelSettingsService; + protected AiModelService aiModelService; @Autowired - protected TbAiModelSettingsService tbAiModelSettingsService; + protected TbAiModelService tbAiModelService; @Value("${server.log_controller_error_stack_trace}") @Getter @@ -400,7 +400,7 @@ public abstract class BaseController { public void handleControllerException(Exception e, HttpServletResponse response) { ThingsboardException thingsboardException = handleException(e); if (thingsboardException.getErrorCode() == ThingsboardErrorCode.GENERAL && thingsboardException.getCause() instanceof Exception - && StringUtils.equals(thingsboardException.getCause().getMessage(), thingsboardException.getMessage())) { + && StringUtils.equals(thingsboardException.getCause().getMessage(), thingsboardException.getMessage())) { e = (Exception) thingsboardException.getCause(); } else { e = thingsboardException; @@ -448,7 +448,7 @@ public abstract class BaseController { if (exception instanceof ThingsboardException) { return (ThingsboardException) exception; } else if (exception instanceof IllegalArgumentException || exception instanceof IncorrectParameterException - || exception instanceof DataValidationException || cause instanceof IncorrectParameterException) { + || exception instanceof DataValidationException || cause instanceof IncorrectParameterException) { return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS); } else if (exception instanceof MessagingException) { return new ThingsboardException("Unable to send mail", ThingsboardErrorCode.GENERAL); @@ -644,7 +644,7 @@ public abstract class BaseController { case MOBILE_APP -> checkMobileAppId(new MobileAppId(entityId.getId()), operation); case MOBILE_APP_BUNDLE -> checkMobileAppBundleId(new MobileAppBundleId(entityId.getId()), operation); case CALCULATED_FIELD -> checkCalculatedFieldId(new CalculatedFieldId(entityId.getId()), operation); - case AI_MODEL_SETTINGS -> checkAiModelSettingsId(new AiModelSettingsId(entityId.getId()), operation); + case AI_MODEL -> checkAiModelId(new AiModelId(entityId.getId()), operation); default -> (HasId) checkEntityId(entityId, entitiesService::findEntityByTenantIdAndId, operation); }; } catch (Exception e) { @@ -848,8 +848,8 @@ public abstract class BaseController { return checkEntityId(jobId, jobService::findJobById, operation); } - AiModelSettings checkAiModelSettingsId(AiModelSettingsId settingsId, Operation operation) throws ThingsboardException { - return checkEntityId(settingsId, (tenantId, id) -> aiModelSettingsService.findAiModelSettingsByTenantIdAndId(tenantId, id).orElse(null), operation); + AiModel checkAiModelId(AiModelId settingsId, Operation operation) throws ThingsboardException { + return checkEntityId(settingsId, (tenantId, id) -> aiModelService.findAiModelByTenantIdAndId(tenantId, id).orElse(null), operation); } protected I emptyId(EntityType entityType) { diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index 20e350f0b8..a87864726b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -90,7 +90,7 @@ public class ControllerConstants { protected static final String TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the tenant profile name."; protected static final String RULE_CHAIN_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the rule chain name."; protected static final String DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the device profile name."; - protected static final String AI_MODEL_SETTINGS_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the AI model settings name, provider and model ID."; + protected static final String AI_MODEL_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the AI model name, provider and model ID."; protected static final String ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the asset profile name."; protected static final String CUSTOMER_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the customer title."; diff --git a/application/src/main/java/org/thingsboard/server/service/ai/AiModelService.java b/application/src/main/java/org/thingsboard/server/service/ai/AiChatModelService.java similarity index 82% rename from application/src/main/java/org/thingsboard/server/service/ai/AiModelService.java rename to application/src/main/java/org/thingsboard/server/service/ai/AiChatModelService.java index 196a53ce3a..9e00c8ddfd 100644 --- a/application/src/main/java/org/thingsboard/server/service/ai/AiModelService.java +++ b/application/src/main/java/org/thingsboard/server/service/ai/AiChatModelService.java @@ -15,6 +15,6 @@ */ package org.thingsboard.server.service.ai; -import org.thingsboard.rule.engine.api.RuleEngineAiModelService; +import org.thingsboard.rule.engine.api.RuleEngineAiChatModelService; -public interface AiModelService extends RuleEngineAiModelService {} +public interface AiChatModelService extends RuleEngineAiChatModelService {} diff --git a/application/src/main/java/org/thingsboard/server/service/ai/AiModelServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/ai/AiChatModelServiceImpl.java similarity index 79% rename from application/src/main/java/org/thingsboard/server/service/ai/AiModelServiceImpl.java rename to application/src/main/java/org/thingsboard/server/service/ai/AiChatModelServiceImpl.java index 877f80fb26..d6252f57a6 100644 --- a/application/src/main/java/org/thingsboard/server/service/ai/AiModelServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/ai/AiChatModelServiceImpl.java @@ -21,21 +21,20 @@ import dev.langchain4j.model.chat.request.ChatRequest; import dev.langchain4j.model.chat.response.ChatResponse; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.ai.model.chat.AiChatModel; import org.thingsboard.server.common.data.ai.model.chat.AiChatModelConfig; import org.thingsboard.server.common.data.ai.model.chat.Langchain4jChatModelConfigurer; @Service @RequiredArgsConstructor -class AiModelServiceImpl implements AiModelService { +class AiChatModelServiceImpl implements AiChatModelService { private final Langchain4jChatModelConfigurer chatModelConfigurer; private final AiRequestsExecutor aiRequestsExecutor; @Override - public > FluentFuture sendChatRequestAsync(AiChatModel chatModel, ChatRequest chatRequest) { - ChatModel lc4jChatModel = chatModel.configure(chatModelConfigurer); - return aiRequestsExecutor.sendChatRequestAsync(lc4jChatModel, chatRequest); + public > FluentFuture sendChatRequestAsync(AiChatModelConfig chatModelConfig, ChatRequest chatRequest) { + ChatModel langChainChatModel = chatModelConfig.configure(chatModelConfigurer); + return aiRequestsExecutor.sendChatRequestAsync(langChainChatModel, chatRequest); } } 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 10e327b48c..69dd98f47f 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 @@ -24,20 +24,26 @@ import com.google.cloud.vertexai.api.GenerationConfig; import com.google.cloud.vertexai.api.PredictionServiceClient; import com.google.cloud.vertexai.api.PredictionServiceSettings; import com.google.cloud.vertexai.generativeai.GenerativeModel; +import dev.langchain4j.model.anthropic.AnthropicChatModel; +import dev.langchain4j.model.azure.AzureOpenAiChatModel; import dev.langchain4j.model.bedrock.BedrockChatModel; import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.request.ChatRequestParameters; +import dev.langchain4j.model.github.GitHubModelsChatModel; +import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel; +import dev.langchain4j.model.mistralai.MistralAiChatModel; +import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.vertexai.gemini.VertexAiGeminiChatModel; import org.springframework.stereotype.Component; -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.GitHubModelsChatModel; -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.AmazonBedrockChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.AnthropicChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.AzureOpenAiChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.GitHubModelsChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.GoogleAiGeminiChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.GoogleVertexAiGeminiChatModelConfig; 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.model.chat.MistralAiChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.OpenAiChatModelConfig; import org.thingsboard.server.common.data.ai.provider.AmazonBedrockProviderConfig; import org.thingsboard.server.common.data.ai.provider.AzureOpenAiProviderConfig; import org.thingsboard.server.common.data.ai.provider.GoogleVertexAiGeminiProviderConfig; @@ -54,61 +60,57 @@ import java.time.Duration; class Langchain4jChatModelConfigurerImpl implements Langchain4jChatModelConfigurer { @Override - public ChatModel configureChatModel(OpenAiChatModel chatModel) { - OpenAiChatModel.Config modelConfig = chatModel.modelConfig(); - return dev.langchain4j.model.openai.OpenAiChatModel.builder() - .apiKey(chatModel.providerConfig().apiKey()) - .modelName(modelConfig.modelId()) - .temperature(modelConfig.temperature()) - .topP(modelConfig.topP()) - .frequencyPenalty(modelConfig.frequencyPenalty()) - .presencePenalty(modelConfig.presencePenalty()) - .maxTokens(modelConfig.maxOutputTokens()) - .timeout(toDuration(modelConfig.timeoutSeconds())) - .maxRetries(modelConfig.maxRetries()) + public ChatModel configureChatModel(OpenAiChatModelConfig chatModelConfig) { + return OpenAiChatModel.builder() + .apiKey(chatModelConfig.providerConfig().apiKey()) + .modelName(chatModelConfig.modelId()) + .temperature(chatModelConfig.temperature()) + .topP(chatModelConfig.topP()) + .frequencyPenalty(chatModelConfig.frequencyPenalty()) + .presencePenalty(chatModelConfig.presencePenalty()) + .maxTokens(chatModelConfig.maxOutputTokens()) + .timeout(toDuration(chatModelConfig.timeoutSeconds())) + .maxRetries(chatModelConfig.maxRetries()) .build(); } @Override - public ChatModel configureChatModel(AzureOpenAiChatModel chatModel) { - AzureOpenAiProviderConfig providerConfig = chatModel.providerConfig(); - AzureOpenAiChatModel.Config modelConfig = chatModel.modelConfig(); - return dev.langchain4j.model.azure.AzureOpenAiChatModel.builder() + public ChatModel configureChatModel(AzureOpenAiChatModelConfig chatModelConfig) { + AzureOpenAiProviderConfig providerConfig = chatModelConfig.providerConfig(); + return AzureOpenAiChatModel.builder() .endpoint(providerConfig.endpoint()) .serviceVersion(providerConfig.serviceVersion()) .apiKey(providerConfig.apiKey()) - .deploymentName(modelConfig.modelId()) - .temperature(modelConfig.temperature()) - .topP(modelConfig.topP()) - .frequencyPenalty(modelConfig.frequencyPenalty()) - .presencePenalty(modelConfig.presencePenalty()) - .maxTokens(modelConfig.maxOutputTokens()) - .timeout(toDuration(modelConfig.timeoutSeconds())) - .maxRetries(modelConfig.maxRetries()) + .deploymentName(chatModelConfig.modelId()) + .temperature(chatModelConfig.temperature()) + .topP(chatModelConfig.topP()) + .frequencyPenalty(chatModelConfig.frequencyPenalty()) + .presencePenalty(chatModelConfig.presencePenalty()) + .maxTokens(chatModelConfig.maxOutputTokens()) + .timeout(toDuration(chatModelConfig.timeoutSeconds())) + .maxRetries(chatModelConfig.maxRetries()) .build(); } @Override - public ChatModel configureChatModel(GoogleAiGeminiChatModel chatModel) { - GoogleAiGeminiChatModel.Config modelConfig = chatModel.modelConfig(); - return dev.langchain4j.model.googleai.GoogleAiGeminiChatModel.builder() - .apiKey(chatModel.providerConfig().apiKey()) - .modelName(modelConfig.modelId()) - .temperature(modelConfig.temperature()) - .topP(modelConfig.topP()) - .topK(modelConfig.topK()) - .frequencyPenalty(modelConfig.frequencyPenalty()) - .presencePenalty(modelConfig.presencePenalty()) - .maxOutputTokens(modelConfig.maxOutputTokens()) - .timeout(toDuration(modelConfig.timeoutSeconds())) - .maxRetries(modelConfig.maxRetries()) + public ChatModel configureChatModel(GoogleAiGeminiChatModelConfig chatModelConfig) { + return GoogleAiGeminiChatModel.builder() + .apiKey(chatModelConfig.providerConfig().apiKey()) + .modelName(chatModelConfig.modelId()) + .temperature(chatModelConfig.temperature()) + .topP(chatModelConfig.topP()) + .topK(chatModelConfig.topK()) + .frequencyPenalty(chatModelConfig.frequencyPenalty()) + .presencePenalty(chatModelConfig.presencePenalty()) + .maxOutputTokens(chatModelConfig.maxOutputTokens()) + .timeout(toDuration(chatModelConfig.timeoutSeconds())) + .maxRetries(chatModelConfig.maxRetries()) .build(); } @Override - public ChatModel configureChatModel(GoogleVertexAiGeminiChatModel chatModel) { - GoogleVertexAiGeminiProviderConfig providerConfig = chatModel.providerConfig(); - GoogleVertexAiGeminiChatModel.Config modelConfig = chatModel.modelConfig(); + public ChatModel configureChatModel(GoogleVertexAiGeminiChatModelConfig chatModelConfig) { + GoogleVertexAiGeminiProviderConfig providerConfig = chatModelConfig.providerConfig(); // construct service account credentials using service account key JSON ServiceAccountCredentials serviceAccountCredentials; @@ -131,8 +133,8 @@ class Langchain4jChatModelConfigurerImpl implements Langchain4jChatModelConfigur .toBuilder(); // set request timeout from model config - if (modelConfig.timeoutSeconds() != null) { - retrySettings.setTotalTimeout(org.threeten.bp.Duration.ofSeconds(modelConfig.timeoutSeconds())); + if (chatModelConfig.timeoutSeconds() != null) { + retrySettings.setTotalTimeout(org.threeten.bp.Duration.ofSeconds(chatModelConfig.timeoutSeconds())); } // set updated retry settings @@ -154,30 +156,30 @@ class Langchain4jChatModelConfigurerImpl implements Langchain4jChatModelConfigur // map model config to generation config var generationConfigBuilder = GenerationConfig.newBuilder(); - if (modelConfig.temperature() != null) { - generationConfigBuilder.setTemperature(modelConfig.temperature().floatValue()); + if (chatModelConfig.temperature() != null) { + generationConfigBuilder.setTemperature(chatModelConfig.temperature().floatValue()); } - if (modelConfig.topP() != null) { - generationConfigBuilder.setTopP(modelConfig.topP().floatValue()); + if (chatModelConfig.topP() != null) { + generationConfigBuilder.setTopP(chatModelConfig.topP().floatValue()); } - if (modelConfig.topK() != null) { - generationConfigBuilder.setTopK(modelConfig.topK()); + if (chatModelConfig.topK() != null) { + generationConfigBuilder.setTopK(chatModelConfig.topK()); } - if (modelConfig.frequencyPenalty() != null) { - generationConfigBuilder.setFrequencyPenalty(modelConfig.frequencyPenalty().floatValue()); + if (chatModelConfig.frequencyPenalty() != null) { + generationConfigBuilder.setFrequencyPenalty(chatModelConfig.frequencyPenalty().floatValue()); } - if (modelConfig.frequencyPenalty() != null) { - generationConfigBuilder.setPresencePenalty(modelConfig.frequencyPenalty().floatValue()); + if (chatModelConfig.frequencyPenalty() != null) { + generationConfigBuilder.setPresencePenalty(chatModelConfig.frequencyPenalty().floatValue()); } - if (modelConfig.maxOutputTokens() != null) { - generationConfigBuilder.setMaxOutputTokens(modelConfig.maxOutputTokens()); + if (chatModelConfig.maxOutputTokens() != null) { + generationConfigBuilder.setMaxOutputTokens(chatModelConfig.maxOutputTokens()); } var generationConfig = generationConfigBuilder.build(); // construct generative model instance - var generativeModel = new GenerativeModel(modelConfig.modelId(), vertexAI).withGenerationConfig(generationConfig); + var generativeModel = new GenerativeModel(chatModelConfig.modelId(), vertexAI).withGenerationConfig(generationConfig); - return new VertexAiGeminiChatModel(generativeModel, generationConfig, modelConfig.maxRetries()); + return new VertexAiGeminiChatModel(generativeModel, generationConfig, chatModelConfig.maxRetries()); } private static PredictionServiceClient createPredictionServiceClient(PredictionServiceSettings settings) { @@ -189,40 +191,37 @@ class Langchain4jChatModelConfigurerImpl implements Langchain4jChatModelConfigur } @Override - public ChatModel configureChatModel(MistralAiChatModel chatModel) { - MistralAiChatModel.Config modelConfig = chatModel.modelConfig(); - return dev.langchain4j.model.mistralai.MistralAiChatModel.builder() - .apiKey(chatModel.providerConfig().apiKey()) - .modelName(modelConfig.modelId()) - .temperature(modelConfig.temperature()) - .topP(modelConfig.topP()) - .frequencyPenalty(modelConfig.frequencyPenalty()) - .presencePenalty(modelConfig.presencePenalty()) - .maxTokens(modelConfig.maxOutputTokens()) - .timeout(toDuration(modelConfig.timeoutSeconds())) - .maxRetries(modelConfig.maxRetries()) + public ChatModel configureChatModel(MistralAiChatModelConfig chatModelConfig) { + return MistralAiChatModel.builder() + .apiKey(chatModelConfig.providerConfig().apiKey()) + .modelName(chatModelConfig.modelId()) + .temperature(chatModelConfig.temperature()) + .topP(chatModelConfig.topP()) + .frequencyPenalty(chatModelConfig.frequencyPenalty()) + .presencePenalty(chatModelConfig.presencePenalty()) + .maxTokens(chatModelConfig.maxOutputTokens()) + .timeout(toDuration(chatModelConfig.timeoutSeconds())) + .maxRetries(chatModelConfig.maxRetries()) .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()) - .topP(modelConfig.topP()) - .topK(modelConfig.topK()) - .maxTokens(modelConfig.maxOutputTokens()) - .timeout(toDuration(modelConfig.timeoutSeconds())) - .maxRetries(modelConfig.maxRetries()) + public ChatModel configureChatModel(AnthropicChatModelConfig chatModelConfig) { + return AnthropicChatModel.builder() + .apiKey(chatModelConfig.providerConfig().apiKey()) + .modelName(chatModelConfig.modelId()) + .temperature(chatModelConfig.temperature()) + .topP(chatModelConfig.topP()) + .topK(chatModelConfig.topK()) + .maxTokens(chatModelConfig.maxOutputTokens()) + .timeout(toDuration(chatModelConfig.timeoutSeconds())) + .maxRetries(chatModelConfig.maxRetries()) .build(); } @Override - public ChatModel configureChatModel(AmazonBedrockChatModel chatModel) { - AmazonBedrockProviderConfig providerConfig = chatModel.providerConfig(); - AmazonBedrockChatModel.Config modelConfig = chatModel.modelConfig(); + public ChatModel configureChatModel(AmazonBedrockChatModelConfig chatModelConfig) { + AmazonBedrockProviderConfig providerConfig = chatModelConfig.providerConfig(); var credentialsProvider = StaticCredentialsProvider.create( AwsBasicCredentials.create(providerConfig.accessKeyId(), providerConfig.secretAccessKey()) @@ -234,33 +233,32 @@ class Langchain4jChatModelConfigurerImpl implements Langchain4jChatModelConfigur .build(); var defaultChatRequestParams = ChatRequestParameters.builder() - .temperature(modelConfig.temperature()) - .topP(modelConfig.topP()) - .maxOutputTokens(modelConfig.maxOutputTokens()) + .temperature(chatModelConfig.temperature()) + .topP(chatModelConfig.topP()) + .maxOutputTokens(chatModelConfig.maxOutputTokens()) .build(); return BedrockChatModel.builder() .client(bedrockClient) - .modelId(modelConfig.modelId()) + .modelId(chatModelConfig.modelId()) .defaultRequestParameters(defaultChatRequestParams) - .timeout(toDuration(modelConfig.timeoutSeconds())) - .maxRetries(modelConfig.maxRetries()) + .timeout(toDuration(chatModelConfig.timeoutSeconds())) + .maxRetries(chatModelConfig.maxRetries()) .build(); } @Override - public ChatModel configureChatModel(GitHubModelsChatModel chatModel) { - GitHubModelsChatModel.Config modelConfig = chatModel.modelConfig(); - return dev.langchain4j.model.github.GitHubModelsChatModel.builder() - .gitHubToken(chatModel.providerConfig().personalAccessToken()) - .modelName(modelConfig.modelId()) - .temperature(modelConfig.temperature()) - .topP(modelConfig.topP()) - .frequencyPenalty(modelConfig.frequencyPenalty()) - .presencePenalty(modelConfig.presencePenalty()) - .maxTokens(modelConfig.maxOutputTokens()) - .timeout(toDuration(modelConfig.timeoutSeconds())) - .maxRetries(modelConfig.maxRetries()) + public ChatModel configureChatModel(GitHubModelsChatModelConfig chatModelConfig) { + return GitHubModelsChatModel.builder() + .gitHubToken(chatModelConfig.providerConfig().personalAccessToken()) + .modelName(chatModelConfig.modelId()) + .temperature(chatModelConfig.temperature()) + .topP(chatModelConfig.topP()) + .frequencyPenalty(chatModelConfig.frequencyPenalty()) + .presencePenalty(chatModelConfig.presencePenalty()) + .maxTokens(chatModelConfig.maxOutputTokens()) + .timeout(toDuration(chatModelConfig.timeoutSeconds())) + .maxRetries(chatModelConfig.maxRetries()) .build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 8bbed875fe..e31bbe21ac 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -112,7 +112,7 @@ public class EdgeEventSourcingListener { return; } try { - if (EntityType.TENANT == entityType || EntityType.EDGE == entityType || EntityType.AI_MODEL_SETTINGS == entityType) { + if (EntityType.TENANT == entityType || EntityType.EDGE == entityType || EntityType.AI_MODEL == entityType) { return; } log.trace("[{}] DeleteEntityEvent called: {}", tenantId, event); @@ -226,7 +226,7 @@ public class EdgeEventSourcingListener { break; case TENANT: return !event.getCreated(); - case API_USAGE_STATE, EDGE, AI_MODEL_SETTINGS: + case API_USAGE_STATE, EDGE, AI_MODEL: return false; case DOMAIN: if (entity instanceof Domain domain) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/RelatedEdgesSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/RelatedEdgesSourcingListener.java index 5fca13380c..8a111e4d9d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/RelatedEdgesSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/RelatedEdgesSourcingListener.java @@ -68,7 +68,7 @@ public class RelatedEdgesSourcingListener { @TransactionalEventListener( fallbackExecution = true, - condition = "#event.entityId.getEntityType() != T(org.thingsboard.server.common.data.EntityType).AI_MODEL_SETTINGS" + condition = "#event.entityId.getEntityType() != T(org.thingsboard.server.common.data.EntityType).AI_MODEL" ) public void handleEvent(DeleteEntityEvent event) { executorService.submit(() -> { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelService.java similarity index 54% rename from application/src/main/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelSettingsService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelService.java index 69f130461c..264b82dd33 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelService.java @@ -19,9 +19,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.dao.ai.AiModelSettingsService; +import org.thingsboard.server.dao.ai.AiModelService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; @@ -30,48 +30,48 @@ import static java.util.Objects.requireNonNullElseGet; @Service @TbCoreComponent @RequiredArgsConstructor -class DefaultTbAiModelSettingsService extends AbstractTbEntityService implements TbAiModelSettingsService { +class DefaultTbAiModelService extends AbstractTbEntityService implements TbAiModelService { - private final AiModelSettingsService aiModelSettingsService; + private final AiModelService aiModelService; @Override - public AiModelSettings save(AiModelSettings settings, User user) { - var actionType = settings.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + public AiModel save(AiModel model, User user) { + var actionType = model.getId() == null ? ActionType.ADDED : ActionType.UPDATED; var tenantId = user.getTenantId(); - settings.setTenantId(tenantId); + model.setTenantId(tenantId); - AiModelSettings savedSettings; + AiModel savedModel; try { - savedSettings = aiModelSettingsService.save(settings); - autoCommit(user, savedSettings.getId()); + savedModel = aiModelService.save(model); + autoCommit(user, savedModel.getId()); } catch (Exception e) { - logEntityActionService.logEntityAction(tenantId, requireNonNullElseGet(settings.getId(), () -> emptyId(EntityType.AI_MODEL_SETTINGS)), settings, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, requireNonNullElseGet(model.getId(), () -> emptyId(EntityType.AI_MODEL)), model, actionType, user, e); throw e; } - logEntityActionService.logEntityAction(tenantId, savedSettings.getId(), savedSettings, actionType, user); + logEntityActionService.logEntityAction(tenantId, savedModel.getId(), savedModel, actionType, user); - return savedSettings; + return savedModel; } @Override - public boolean delete(AiModelSettings settings, User user) { + public boolean delete(AiModel model, User user) { var actionType = ActionType.DELETED; var tenantId = user.getTenantId(); - var settingsId = settings.getId(); + var modelId = model.getId(); boolean deleted; try { - deleted = aiModelSettingsService.deleteByTenantIdAndId(tenantId, settingsId); + deleted = aiModelService.deleteByTenantIdAndId(tenantId, modelId); } catch (Exception e) { - logEntityActionService.logEntityAction(tenantId, settingsId, settings, actionType, user, e, settingsId.toString()); + logEntityActionService.logEntityAction(tenantId, modelId, model, actionType, user, e, modelId.toString()); throw e; } if (deleted) { - logEntityActionService.logEntityAction(tenantId, settingsId, settings, actionType, user, settingsId.toString()); + logEntityActionService.logEntityAction(tenantId, modelId, model, actionType, user, modelId.toString()); } return deleted; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/ai/TbAiModelSettingsService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ai/TbAiModelService.java similarity index 76% rename from application/src/main/java/org/thingsboard/server/service/entitiy/ai/TbAiModelSettingsService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/ai/TbAiModelService.java index 0d66c171a7..0b09423ffa 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/ai/TbAiModelSettingsService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ai/TbAiModelService.java @@ -16,12 +16,12 @@ package org.thingsboard.server.service.entitiy.ai; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; -public interface TbAiModelSettingsService { +public interface TbAiModelService { - AiModelSettings save(AiModelSettings settings, User user); + AiModel save(AiModel model, User user); - boolean delete(AiModelSettings settings, User user); + boolean delete(AiModel model, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index ba7b810c7d..701fed952b 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -53,7 +53,7 @@ public enum Resource { EntityType.NOTIFICATION_REQUEST, EntityType.NOTIFICATION_RULE), MOBILE_APP_SETTINGS, JOB(EntityType.JOB), - AI_MODEL_SETTINGS(EntityType.AI_MODEL_SETTINGS); + AI_MODEL(EntityType.AI_MODEL); private final Set entityTypes; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 6b2f6f8ccf..7a824ca735 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -18,8 +18,8 @@ package org.thingsboard.server.service.security.permission; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.ai.AiModelSettings; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.ai.AiModel; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; @@ -58,7 +58,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.MOBILE_APP, tenantEntityPermissionChecker); put(Resource.MOBILE_APP_BUNDLE, tenantEntityPermissionChecker); put(Resource.JOB, tenantEntityPermissionChecker); - put(Resource.AI_MODEL_SETTINGS, aiModelSettingsPermissionChecker); + put(Resource.AI_MODEL, aiModelPermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { @@ -149,7 +149,7 @@ public class TenantAdminPermissions extends AbstractPermissions { }; - private static final PermissionChecker aiModelSettingsPermissionChecker = new PermissionChecker<>() { + private static final PermissionChecker aiModelPermissionChecker = new PermissionChecker<>() { @Override public boolean hasPermission(SecurityUser user, Operation operation) { @@ -157,7 +157,7 @@ public class TenantAdminPermissions extends AbstractPermissions { } @Override - public boolean hasPermission(SecurityUser user, Operation operation, AiModelSettingsId entityId, AiModelSettings entity) { + public boolean hasPermission(SecurityUser user, Operation operation, AiModelId entityId, AiModel entity) { return user.getTenantId().equals(entity.getTenantId()); } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java index 82f855b7c2..b000041a26 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java @@ -70,7 +70,7 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS EntityType.DEVICE_PROFILE, EntityType.DEVICE, EntityType.ENTITY_VIEW, EntityType.WIDGET_TYPE, EntityType.WIDGETS_BUNDLE, EntityType.NOTIFICATION_TEMPLATE, EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_RULE, - EntityType.AI_MODEL_SETTINGS + EntityType.AI_MODEL ); @Override diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AiModelSettingsExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AiModelExportService.java similarity index 76% rename from application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AiModelSettingsExportService.java rename to application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AiModelExportService.java index a5ba16e9b1..8d6097b726 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AiModelSettingsExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AiModelExportService.java @@ -17,8 +17,8 @@ package org.thingsboard.server.service.sync.ie.exporting.impl; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.ai.AiModelSettings; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.ai.AiModel; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -26,11 +26,11 @@ import java.util.Set; @Service @TbCoreComponent -class AiModelSettingsExportService extends BaseEntityExportService> { +class AiModelExportService extends BaseEntityExportService> { @Override public Set getSupportedEntityTypes() { - return Set.of(EntityType.AI_MODEL_SETTINGS); + return Set.of(EntityType.AI_MODEL); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AiModelSettingsImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AiModelImportService.java similarity index 50% rename from application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AiModelSettingsImportService.java rename to application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AiModelImportService.java index e5666e5003..34e70adb11 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AiModelSettingsImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AiModelImportService.java @@ -18,60 +18,60 @@ package org.thingsboard.server.service.sync.ie.importing.impl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.ai.AiModelSettings; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.ai.AiModel; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.sync.ie.EntityExportData; -import org.thingsboard.server.dao.ai.AiModelSettingsService; +import org.thingsboard.server.dao.ai.AiModelService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; @Service @TbCoreComponent @RequiredArgsConstructor -class AiModelSettingsImportService extends BaseEntityImportService> { +class AiModelImportService extends BaseEntityImportService> { - private final AiModelSettingsService aiModelSettingsService; + private final AiModelService aiModelService; @Override protected void setOwner( TenantId tenantId, - AiModelSettings settings, - BaseEntityImportService>.IdProvider idProvider + AiModel model, + BaseEntityImportService>.IdProvider idProvider ) { - settings.setTenantId(tenantId); + model.setTenantId(tenantId); } @Override - protected AiModelSettings prepare( + protected AiModel prepare( EntitiesImportCtx ctx, - AiModelSettings settings, - AiModelSettings oldEntity, - EntityExportData exportData, - BaseEntityImportService>.IdProvider idProvider + AiModel model, + AiModel oldModel, + EntityExportData exportData, + BaseEntityImportService>.IdProvider idProvider ) { - return settings; + return model; } @Override - protected AiModelSettings deepCopy(AiModelSettings settings) { - return new AiModelSettings(settings); + protected AiModel deepCopy(AiModel model) { + return new AiModel(model); } @Override - protected AiModelSettings saveOrUpdate( + protected AiModel saveOrUpdate( EntitiesImportCtx ctx, - AiModelSettings settings, - EntityExportData exportData, - BaseEntityImportService>.IdProvider idProvider, + AiModel model, + EntityExportData exportData, + BaseEntityImportService>.IdProvider idProvider, CompareResult compareResult ) { - return aiModelSettingsService.save(settings); + return aiModelService.save(model); } @Override public EntityType getEntityType() { - return EntityType.AI_MODEL_SETTINGS; + return EntityType.AI_MODEL; } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 429bbd4a29..c4bf6bf3c8 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -656,9 +656,9 @@ cache: trendzSettings: timeToLiveInMinutes: "${CACHE_SPECS_TRENDZ_SETTINGS_TTL:1440}" # Trendz settings cache TTL maxSize: "${CACHE_SPECS_TRENDZ_SETTINGS_MAX_SIZE:10000}" # 0 means the cache is disabled - aiModelSettings: - timeToLiveInMinutes: "${CACHE_SPECS_AI_MODEL_SETTINGS_TTL:1440}" # AI model settings cache TTL - maxSize: "${CACHE_SPECS_AI_MODEL_SETTINGS_MAX_SIZE:10000}" # 0 means the cache is disabled + aiModel: + timeToLiveInMinutes: "${CACHE_SPECS_AI_MODEL_TTL:1440}" # AI model cache TTL + maxSize: "${CACHE_SPECS_AI_MODEL_MAX_SIZE:10000}" # 0 means the cache is disabled # Deliberately placed outside the 'specs' group above notificationRules: @@ -874,7 +874,7 @@ audit-log: "tb_resource": "${AUDIT_LOG_MASK_RESOURCE:W}" # TB resource logging levels. "ota_package": "${AUDIT_LOG_MASK_OTA_PACKAGE:W}" # Ota package logging levels. "calculated_field": "${AUDIT_LOG_MASK_CALCULATED_FIELD:W}" # Calculated field logging levels. - "ai_model_settings": "${AUDIT_LOG_MASK_AI_MODEL_SETTINGS:W}" # AI model settings logging levels. + "ai_model": "${AUDIT_LOG_MASK_AI_MODEL:W}" # AI model logging levels. sink: # Type of external sink. possible options: none, elasticsearch type: "${AUDIT_LOG_SINK_TYPE:none}" diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/ai/AiModelService.java similarity index 55% rename from common/dao-api/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsService.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/ai/AiModelService.java index 09219f238e..3ad12048cf 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/ai/AiModelService.java @@ -16,8 +16,8 @@ package org.thingsboard.server.dao.ai; import com.google.common.util.concurrent.FluentFuture; -import org.thingsboard.server.common.data.ai.AiModelSettings; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.ai.AiModel; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -25,18 +25,18 @@ import org.thingsboard.server.dao.entity.EntityDaoService; import java.util.Optional; -public interface AiModelSettingsService extends EntityDaoService { +public interface AiModelService extends EntityDaoService { - AiModelSettings save(AiModelSettings settings); + AiModel save(AiModel model); - Optional findAiModelSettingsById(TenantId tenantId, AiModelSettingsId settingsId); + Optional findAiModelById(TenantId tenantId, AiModelId modelId); - PageData findAiModelSettingsByTenantId(TenantId tenantId, PageLink pageLink); + PageData findAiModelsByTenantId(TenantId tenantId, PageLink pageLink); - Optional findAiModelSettingsByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId); + Optional findAiModelByTenantIdAndId(TenantId tenantId, AiModelId modelId); - FluentFuture> findAiModelSettingsByTenantIdAndIdAsync(TenantId tenantId, AiModelSettingsId settingsId); + FluentFuture> findAiModelByTenantIdAndIdAsync(TenantId tenantId, AiModelId modelId); - boolean deleteByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId); + boolean deleteByTenantIdAndId(TenantId tenantId, AiModelId modelId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index c5df7c10c0..b55453f393 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -17,6 +17,8 @@ package org.thingsboard.server.common.data; public final class CacheConstants { + private CacheConstants() {} + public static final String DEVICE_CREDENTIALS_CACHE = "deviceCredentials"; public static final String RELATIONS_CACHE = "relations"; public static final String DEVICE_CACHE = "devices"; @@ -37,7 +39,7 @@ public final class CacheConstants { public static final String NOTIFICATION_SETTINGS_CACHE = "notificationSettings"; public static final String SENT_NOTIFICATIONS_CACHE = "sentNotifications"; public static final String TRENDZ_SETTINGS_CACHE = "trendzSettings"; - public static final String AI_MODEL_SETTINGS_CACHE = "aiModelSettings"; + public static final String AI_MODEL_CACHE = "aiModel"; public static final String ASSET_PROFILE_CACHE = "assetProfiles"; public static final String ATTRIBUTES_CACHE = "attributes"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index dddafe05bc..8c132a3eba 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -66,10 +66,10 @@ public enum EntityType { CALCULATED_FIELD(39), CALCULATED_FIELD_LINK(40), JOB(41), - AI_MODEL_SETTINGS(42, "ai_model_settings") { + AI_MODEL(42, "ai_model") { @Override public String getNormalName() { - return "AI model settings"; + return "AI model"; } }; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModelSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModel.java similarity index 67% rename from common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModelSettings.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModel.java index 63e94f1974..4d7bb21930 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModelSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModel.java @@ -27,8 +27,8 @@ import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasVersion; -import org.thingsboard.server.common.data.ai.model.AiModel; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.ai.model.AiModelConfig; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoNullChar; @@ -39,7 +39,7 @@ import java.io.Serial; @Builder @AllArgsConstructor @EqualsAndHashCode(callSuper = true) -public final class AiModelSettings extends BaseData implements HasTenantId, HasVersion, ExportableEntity { +public final class AiModel extends BaseData implements HasTenantId, HasVersion, ExportableEntity { @Serial private static final long serialVersionUID = 9017108678716011604L; @@ -47,7 +47,7 @@ public final class AiModelSettings extends BaseData implement @Schema( requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, - description = "JSON object representing the ID of the tenant associated with these AI model settings", + description = "JSON object representing the ID of the tenant associated with this AI model", example = "e3c4b7d2-5678-4a9b-0c1d-2e3f4a5b6c7d" ) private TenantId tenantId; @@ -55,7 +55,7 @@ public final class AiModelSettings extends BaseData implement @Schema( requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, - description = "Version of the AI model settings; increments automatically whenever the settings are changed", + description = "Version of the AI model record; increments automatically whenever the record is changed", example = "7", defaultValue = "1" ) @@ -67,8 +67,8 @@ public final class AiModelSettings extends BaseData implement @Schema( requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_WRITE, - description = "Human-readable name of the AI model settings; must be unique within the scope of the tenant", - example = "Rule node assistant" + description = "Display name for this AI model configuration; not the technical model identifier", + example = "Fast and cost-efficient model" ) private String name; @@ -79,24 +79,24 @@ public final class AiModelSettings extends BaseData implement accessMode = Schema.AccessMode.READ_WRITE, description = "Configuration of the AI model" ) - private AiModel configuration; + private AiModelConfig configuration; - private AiModelSettingsId externalId; + private AiModelId externalId; - public AiModelSettings() {} + public AiModel() {} - public AiModelSettings(AiModelSettingsId id) { + public AiModel(AiModelId id) { super(id); } - public AiModelSettings(AiModelSettings settings) { - super(settings.getId()); - createdTime = settings.getCreatedTime(); - tenantId = settings.getTenantId(); - version = settings.getVersion(); - name = settings.getName(); - configuration = settings.getConfiguration(); - externalId = settings.getExternalId() == null ? null : new AiModelSettingsId(settings.getExternalId().getId()); + public AiModel(AiModel model) { + super(model.getId()); + createdTime = model.getCreatedTime(); + tenantId = model.getTenantId(); + version = model.getVersion(); + name = model.getName(); + configuration = model.getConfiguration(); + externalId = model.getExternalId() == null ? null : new AiModelId(model.getExternalId().getId()); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbChatRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbChatRequest.java index aa737b27d3..7e43520b79 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbChatRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbChatRequest.java @@ -23,7 +23,7 @@ import dev.langchain4j.model.chat.request.ChatRequest; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; -import org.thingsboard.server.common.data.ai.model.chat.AiChatModel; +import org.thingsboard.server.common.data.ai.model.chat.AiChatModelConfig; import java.util.ArrayList; import java.util.List; @@ -51,7 +51,7 @@ public record TbChatRequest( description = "Configuration of the AI chat model that should execute the request" ) @NotNull @Valid - AiChatModel chatModel + AiChatModelConfig chatModelConfig ) { public ChatRequest toLangChainChatRequest() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModel.java deleted file mode 100644 index affa159e06..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModel.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -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.GitHubModelsChatModel; -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; -import org.thingsboard.server.common.data.ai.model.chat.OpenAiChatModel; -import org.thingsboard.server.common.data.ai.provider.AiProvider; -import org.thingsboard.server.common.data.ai.provider.AiProviderConfig; -import org.thingsboard.server.common.data.ai.provider.AmazonBedrockProviderConfig; -import org.thingsboard.server.common.data.ai.provider.AnthropicProviderConfig; -import org.thingsboard.server.common.data.ai.provider.AzureOpenAiProviderConfig; -import org.thingsboard.server.common.data.ai.provider.GitHubModelsProviderConfig; -import org.thingsboard.server.common.data.ai.provider.GoogleAiGeminiProviderConfig; -import org.thingsboard.server.common.data.ai.provider.GoogleVertexAiGeminiProviderConfig; -import org.thingsboard.server.common.data.ai.provider.MistralAiProviderConfig; -import org.thingsboard.server.common.data.ai.provider.OpenAiProviderConfig; - -@JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.PROPERTY, - property = "provider", - visible = true -) -@JsonSubTypes({ - @JsonSubTypes.Type(value = OpenAiChatModel.class, name = "OPENAI"), - @JsonSubTypes.Type(value = AzureOpenAiChatModel.class, name = "AZURE_OPENAI"), - @JsonSubTypes.Type(value = GoogleAiGeminiChatModel.class, name = "GOOGLE_AI_GEMINI"), - @JsonSubTypes.Type(value = GoogleVertexAiGeminiChatModel.class, name = "GOOGLE_VERTEX_AI_GEMINI"), - @JsonSubTypes.Type(value = MistralAiChatModel.class, name = "MISTRAL_AI"), - @JsonSubTypes.Type(value = AnthropicChatModel.class, name = "ANTHROPIC"), - @JsonSubTypes.Type(value = AmazonBedrockChatModel.class, name = "AMAZON_BEDROCK"), - @JsonSubTypes.Type(value = GitHubModelsChatModel.class, name = "GITHUB_MODELS") -}) -public interface AiModel { - - AiProvider provider(); - - @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXTERNAL_PROPERTY, - property = "provider" - ) - @JsonSubTypes({ - @JsonSubTypes.Type(value = OpenAiProviderConfig.class, name = "OPENAI"), - @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 = AnthropicProviderConfig.class, name = "ANTHROPIC"), - @JsonSubTypes.Type(value = AmazonBedrockProviderConfig.class, name = "AMAZON_BEDROCK"), - @JsonSubTypes.Type(value = GitHubModelsProviderConfig.class, name = "GITHUB_MODELS") - }) - AiProviderConfig providerConfig(); - - @JsonProperty("modelType") - AiModelType modelType(); - - C modelConfig(); - - AiModel withModelConfig(C config); - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModelConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModelConfig.java index 1b0ab7921f..d9e6a1753e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModelConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/AiModelConfig.java @@ -15,8 +15,66 @@ */ package org.thingsboard.server.common.data.ai.model; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.ai.model.chat.AmazonBedrockChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.AnthropicChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.AzureOpenAiChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.GitHubModelsChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.GoogleAiGeminiChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.GoogleVertexAiGeminiChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.MistralAiChatModelConfig; +import org.thingsboard.server.common.data.ai.model.chat.OpenAiChatModelConfig; +import org.thingsboard.server.common.data.ai.provider.AiProvider; +import org.thingsboard.server.common.data.ai.provider.AiProviderConfig; +import org.thingsboard.server.common.data.ai.provider.AmazonBedrockProviderConfig; +import org.thingsboard.server.common.data.ai.provider.AnthropicProviderConfig; +import org.thingsboard.server.common.data.ai.provider.AzureOpenAiProviderConfig; +import org.thingsboard.server.common.data.ai.provider.GitHubModelsProviderConfig; +import org.thingsboard.server.common.data.ai.provider.GoogleAiGeminiProviderConfig; +import org.thingsboard.server.common.data.ai.provider.GoogleVertexAiGeminiProviderConfig; +import org.thingsboard.server.common.data.ai.provider.MistralAiProviderConfig; +import org.thingsboard.server.common.data.ai.provider.OpenAiProviderConfig; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "provider", + visible = true +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = OpenAiChatModelConfig.class, name = "OPENAI"), + @JsonSubTypes.Type(value = AzureOpenAiChatModelConfig.class, name = "AZURE_OPENAI"), + @JsonSubTypes.Type(value = GoogleAiGeminiChatModelConfig.class, name = "GOOGLE_AI_GEMINI"), + @JsonSubTypes.Type(value = GoogleVertexAiGeminiChatModelConfig.class, name = "GOOGLE_VERTEX_AI_GEMINI"), + @JsonSubTypes.Type(value = MistralAiChatModelConfig.class, name = "MISTRAL_AI"), + @JsonSubTypes.Type(value = AnthropicChatModelConfig.class, name = "ANTHROPIC"), + @JsonSubTypes.Type(value = AmazonBedrockChatModelConfig.class, name = "AMAZON_BEDROCK"), + @JsonSubTypes.Type(value = GitHubModelsChatModelConfig.class, name = "GITHUB_MODELS") +}) public interface AiModelConfig { - String modelId(); + AiProvider provider(); + + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXTERNAL_PROPERTY, + property = "provider" + ) + @JsonSubTypes({ + @JsonSubTypes.Type(value = OpenAiProviderConfig.class, name = "OPENAI"), + @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 = AnthropicProviderConfig.class, name = "ANTHROPIC"), + @JsonSubTypes.Type(value = AmazonBedrockProviderConfig.class, name = "AMAZON_BEDROCK"), + @JsonSubTypes.Type(value = GitHubModelsProviderConfig.class, name = "GITHUB_MODELS") + }) + AiProviderConfig providerConfig(); + + @JsonProperty("modelType") + AiModelType modelType(); } 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 deleted file mode 100644 index b9a2737b20..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AiChatModel.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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.model.AiModel; -import org.thingsboard.server.common.data.ai.model.AiModelType; - -public sealed interface AiChatModel> extends AiModel - permits - OpenAiChatModel, AzureOpenAiChatModel, GoogleAiGeminiChatModel, - GoogleVertexAiGeminiChatModel, MistralAiChatModel, AnthropicChatModel, - AmazonBedrockChatModel, GitHubModelsChatModel { - - ChatModel configure(Langchain4jChatModelConfigurer configurer); - - @Override - default AiModelType modelType() { - return AiModelType.CHAT; - } - - @Override - C modelConfig(); - - @Override - AiChatModel withModelConfig(C config); - -} 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 67d9827cc6..2284d8b485 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 @@ -15,13 +15,22 @@ */ package org.thingsboard.server.common.data.ai.model.chat; +import dev.langchain4j.model.chat.ChatModel; import org.thingsboard.server.common.data.ai.model.AiModelConfig; +import org.thingsboard.server.common.data.ai.model.AiModelType; public sealed interface AiChatModelConfig> extends AiModelConfig permits - OpenAiChatModel.Config, AzureOpenAiChatModel.Config, GoogleAiGeminiChatModel.Config, - GoogleVertexAiGeminiChatModel.Config, MistralAiChatModel.Config, AnthropicChatModel.Config, - AmazonBedrockChatModel.Config, GitHubModelsChatModel.Config { + OpenAiChatModelConfig, AzureOpenAiChatModelConfig, GoogleAiGeminiChatModelConfig, + GoogleVertexAiGeminiChatModelConfig, MistralAiChatModelConfig, AnthropicChatModelConfig, + AmazonBedrockChatModelConfig, GitHubModelsChatModelConfig { + + ChatModel configure(Langchain4jChatModelConfigurer configurer); + + @Override + default AiModelType modelType() { + return AiModelType.CHAT; + } Integer timeoutSeconds(); 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/AmazonBedrockChatModelConfig.java similarity index 75% rename from common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModelConfig.java index 356bdd5c42..1f4e4645dd 100644 --- 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/AmazonBedrockChatModelConfig.java @@ -27,27 +27,22 @@ import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AmazonBedrockProviderConfig; -public record AmazonBedrockChatModel( +public record AmazonBedrockChatModelConfig( AiModelType modelType, @NotNull @Valid AmazonBedrockProviderConfig providerConfig, - @With @NotNull @Valid Config modelConfig -) implements AiChatModel { + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + @Positive Integer maxOutputTokens, + @With @Positive Integer timeoutSeconds, + @With @PositiveOrZero Integer maxRetries +) implements AiChatModelConfig { @Override public AiProvider provider() { return AiProvider.AMAZON_BEDROCK; } - @With - public record Config( - @NotBlank String modelId, - @PositiveOrZero Double temperature, - @Positive @Max(1) Double topP, - @Positive Integer maxOutputTokens, - @Positive Integer timeoutSeconds, - @PositiveOrZero Integer maxRetries - ) implements AiChatModelConfig {} - @Override public ChatModel configure(Langchain4jChatModelConfigurer configurer) { return configurer.configureChatModel(this); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModelConfig.java similarity index 74% rename from common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModel.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModelConfig.java index 9ab43799fb..69af0f8c2b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModelConfig.java @@ -27,28 +27,23 @@ import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AnthropicProviderConfig; -public record AnthropicChatModel( +public record AnthropicChatModelConfig( AiModelType modelType, @NotNull @Valid AnthropicProviderConfig providerConfig, - @With @NotNull @Valid Config modelConfig -) implements AiChatModel { + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + @Positive Integer topK, + @Positive Integer maxOutputTokens, + @With @Positive Integer timeoutSeconds, + @With @PositiveOrZero Integer maxRetries +) implements AiChatModelConfig { @Override public AiProvider provider() { return AiProvider.ANTHROPIC; } - @With - public record Config( - @NotBlank String modelId, - @PositiveOrZero Double temperature, - @Positive @Max(1) Double topP, - @Positive Integer topK, - @Positive Integer maxOutputTokens, - @Positive Integer timeoutSeconds, - @PositiveOrZero Integer maxRetries - ) implements AiChatModelConfig {} - @Override public ChatModel configure(Langchain4jChatModelConfigurer configurer) { return configurer.configureChatModel(this); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModelConfig.java similarity index 72% rename from common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModel.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModelConfig.java index f2cb47190b..afe5f6c563 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModelConfig.java @@ -27,29 +27,24 @@ import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AzureOpenAiProviderConfig; -public record AzureOpenAiChatModel( +public record AzureOpenAiChatModelConfig( AiModelType modelType, @NotNull @Valid AzureOpenAiProviderConfig providerConfig, - @With @NotNull @Valid Config modelConfig -) implements AiChatModel { + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + Double frequencyPenalty, + Double presencePenalty, + @Positive Integer maxOutputTokens, + @With @Positive Integer timeoutSeconds, + @With @PositiveOrZero Integer maxRetries +) implements AiChatModelConfig { @Override public AiProvider provider() { return AiProvider.AZURE_OPENAI; } - @With - public record Config( - @NotBlank String modelId, - @PositiveOrZero Double temperature, - @Positive @Max(1) Double topP, - Double frequencyPenalty, - Double presencePenalty, - @Positive Integer maxOutputTokens, - @Positive Integer timeoutSeconds, - @PositiveOrZero Integer maxRetries - ) implements AiChatModelConfig {} - @Override public ChatModel configure(Langchain4jChatModelConfigurer configurer) { return configurer.configureChatModel(this); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModelConfig.java similarity index 72% rename from common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModel.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModelConfig.java index 723f69299d..ecadb7d453 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModelConfig.java @@ -27,29 +27,24 @@ import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.GitHubModelsProviderConfig; -public record GitHubModelsChatModel( +public record GitHubModelsChatModelConfig( AiModelType modelType, @NotNull @Valid GitHubModelsProviderConfig providerConfig, - @With @NotNull @Valid Config modelConfig -) implements AiChatModel { + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + Double frequencyPenalty, + Double presencePenalty, + @Positive Integer maxOutputTokens, + @With @Positive Integer timeoutSeconds, + @With @PositiveOrZero Integer maxRetries +) implements AiChatModelConfig { @Override public AiProvider provider() { return AiProvider.GITHUB_MODELS; } - @With - public record Config( - @NotBlank String modelId, - @PositiveOrZero Double temperature, - @Positive @Max(1) Double topP, - Double frequencyPenalty, - Double presencePenalty, - @Positive Integer maxOutputTokens, - @Positive Integer timeoutSeconds, - @PositiveOrZero Integer maxRetries - ) implements AiChatModelConfig {} - @Override public ChatModel configure(Langchain4jChatModelConfigurer configurer) { return configurer.configureChatModel(this); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModelConfig.java similarity index 71% rename from common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModel.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModelConfig.java index b9cb65d3d9..1922dea6a1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModelConfig.java @@ -27,30 +27,25 @@ import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.GoogleAiGeminiProviderConfig; -public record GoogleAiGeminiChatModel( +public record GoogleAiGeminiChatModelConfig( AiModelType modelType, @NotNull @Valid GoogleAiGeminiProviderConfig providerConfig, - @With @NotNull @Valid Config modelConfig -) implements AiChatModel { + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + @Positive Integer topK, + Double frequencyPenalty, + Double presencePenalty, + @Positive Integer maxOutputTokens, + @With @Positive Integer timeoutSeconds, + @With @PositiveOrZero Integer maxRetries +) implements AiChatModelConfig { @Override public AiProvider provider() { return AiProvider.GOOGLE_AI_GEMINI; } - @With - public record Config( - @NotBlank String modelId, - @PositiveOrZero Double temperature, - @Positive @Max(1) Double topP, - @Positive Integer topK, - Double frequencyPenalty, - Double presencePenalty, - @Positive Integer maxOutputTokens, - @Positive Integer timeoutSeconds, - @PositiveOrZero Integer maxRetries - ) implements AiChatModelConfig {} - @Override public ChatModel configure(Langchain4jChatModelConfigurer configurer) { return configurer.configureChatModel(this); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModelConfig.java similarity index 71% rename from common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModel.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModelConfig.java index 83b7506e11..e8691c0926 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModelConfig.java @@ -27,30 +27,25 @@ import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.GoogleVertexAiGeminiProviderConfig; -public record GoogleVertexAiGeminiChatModel( +public record GoogleVertexAiGeminiChatModelConfig( AiModelType modelType, @NotNull @Valid GoogleVertexAiGeminiProviderConfig providerConfig, - @With @NotNull @Valid Config modelConfig -) implements AiChatModel { + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + @Positive Integer topK, + Double frequencyPenalty, + Double presencePenalty, + @Positive Integer maxOutputTokens, + @With @Positive Integer timeoutSeconds, + @With @PositiveOrZero Integer maxRetries +) implements AiChatModelConfig { @Override public AiProvider provider() { return AiProvider.GOOGLE_VERTEX_AI_GEMINI; } - @With - public record Config( - @NotBlank String modelId, - @PositiveOrZero Double temperature, - @Positive @Max(1) Double topP, - @Positive Integer topK, - Double frequencyPenalty, - Double presencePenalty, - @Positive Integer maxOutputTokens, - @Positive Integer timeoutSeconds, - @PositiveOrZero Integer maxRetries - ) implements AiChatModelConfig {} - @Override public ChatModel configure(Langchain4jChatModelConfigurer configurer) { return configurer.configureChatModel(this); 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 ea9fb80e73..c9c1bc3173 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 @@ -19,20 +19,20 @@ import dev.langchain4j.model.chat.ChatModel; public interface Langchain4jChatModelConfigurer { - ChatModel configureChatModel(OpenAiChatModel chatModel); + ChatModel configureChatModel(OpenAiChatModelConfig chatModelConfig); - ChatModel configureChatModel(AzureOpenAiChatModel chatModel); + ChatModel configureChatModel(AzureOpenAiChatModelConfig chatModelConfig); - ChatModel configureChatModel(GoogleAiGeminiChatModel chatModel); + ChatModel configureChatModel(GoogleAiGeminiChatModelConfig chatModelConfig); - ChatModel configureChatModel(GoogleVertexAiGeminiChatModel chatModel); + ChatModel configureChatModel(GoogleVertexAiGeminiChatModelConfig chatModelConfig); - ChatModel configureChatModel(MistralAiChatModel chatModel); + ChatModel configureChatModel(MistralAiChatModelConfig chatModelConfig); - ChatModel configureChatModel(AnthropicChatModel chatModel); + ChatModel configureChatModel(AnthropicChatModelConfig chatModelConfig); - ChatModel configureChatModel(AmazonBedrockChatModel chatModel); + ChatModel configureChatModel(AmazonBedrockChatModelConfig chatModelConfig); - ChatModel configureChatModel(GitHubModelsChatModel chatModel); + ChatModel configureChatModel(GitHubModelsChatModelConfig chatModelConfig); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModelConfig.java similarity index 72% rename from common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModel.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModelConfig.java index 58ed807819..ea2409ba35 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModelConfig.java @@ -27,29 +27,24 @@ import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.MistralAiProviderConfig; -public record MistralAiChatModel( +public record MistralAiChatModelConfig( AiModelType modelType, @NotNull @Valid MistralAiProviderConfig providerConfig, - @With @NotNull @Valid Config modelConfig -) implements AiChatModel { + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + Double frequencyPenalty, + Double presencePenalty, + @Positive Integer maxOutputTokens, + @With @Positive Integer timeoutSeconds, + @With @PositiveOrZero Integer maxRetries +) implements AiChatModelConfig { @Override public AiProvider provider() { return AiProvider.MISTRAL_AI; } - @With - public record Config( - @NotBlank String modelId, - @PositiveOrZero Double temperature, - @Positive @Max(1) Double topP, - Double frequencyPenalty, - Double presencePenalty, - @Positive Integer maxOutputTokens, - @Positive Integer timeoutSeconds, - @PositiveOrZero Integer maxRetries - ) implements AiChatModelConfig {} - @Override public ChatModel configure(Langchain4jChatModelConfigurer configurer) { return configurer.configureChatModel(this); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModel.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModelConfig.java similarity index 73% rename from common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModel.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModelConfig.java index 4250291c32..95f6f1cdc3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModel.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModelConfig.java @@ -27,29 +27,24 @@ import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.OpenAiProviderConfig; -public record OpenAiChatModel( +public record OpenAiChatModelConfig( AiModelType modelType, @NotNull @Valid OpenAiProviderConfig providerConfig, - @With @NotNull @Valid Config modelConfig -) implements AiChatModel { + @NotBlank String modelId, + @PositiveOrZero Double temperature, + @Positive @Max(1) Double topP, + Double frequencyPenalty, + Double presencePenalty, + @Positive Integer maxOutputTokens, + @With @Positive Integer timeoutSeconds, + @With @PositiveOrZero Integer maxRetries +) implements AiChatModelConfig { @Override public AiProvider provider() { return AiProvider.OPENAI; } - @With - public record Config( - @NotBlank String modelId, - @PositiveOrZero Double temperature, - @Positive @Max(1) Double topP, - Double frequencyPenalty, - Double presencePenalty, - @Positive Integer maxOutputTokens, - @Positive Integer timeoutSeconds, - @PositiveOrZero Integer maxRetries - ) implements AiChatModelConfig {} - @Override public ChatModel configure(Langchain4jChatModelConfigurer configurer) { return configurer.configureChatModel(this); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelSettingsId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelId.java similarity index 72% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelSettingsId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelId.java index 83b4fefab2..cac9e8200c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelSettingsId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelId.java @@ -23,29 +23,29 @@ import org.thingsboard.server.common.data.EntityType; import java.io.Serial; import java.util.UUID; -public final class AiModelSettingsId extends UUIDBased implements EntityId { +public final class AiModelId extends UUIDBased implements EntityId { @Serial private static final long serialVersionUID = 3021036138554389754L; @JsonCreator - public AiModelSettingsId(@JsonProperty("id") UUID id) { + public AiModelId(@JsonProperty("id") UUID id) { super(id); } @Override @Schema( requiredMode = Schema.RequiredMode.REQUIRED, - description = "Entity type of the AI model settings", - example = "AI_MODEL_SETTINGS", - allowableValues = "AI_MODEL_SETTINGS" + description = "Entity type of the AI model", + example = "AI_MODEL", + allowableValues = "AI_MODEL" ) public EntityType getEntityType() { - return EntityType.AI_MODEL_SETTINGS; + return EntityType.AI_MODEL; } - public static AiModelSettingsId fromString(String uuid) { - return new AiModelSettingsId(UUID.fromString(uuid)); + public static AiModelId fromString(String uuid) { + return new AiModelId(UUID.fromString(uuid)); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 64504545d4..3638ed1535 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -85,7 +85,7 @@ public class EntityIdFactory { case CALCULATED_FIELD -> new CalculatedFieldId(uuid); case CALCULATED_FIELD_LINK -> new CalculatedFieldLinkId(uuid); case JOB -> new JobId(uuid); - case AI_MODEL_SETTINGS -> new AiModelSettingsId(uuid); + case AI_MODEL -> new AiModelId(uuid); default -> throw new IllegalArgumentException("EntityType " + type + " is not supported!"); }; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java index 6914b75636..a89fc0b670 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.notification.rule.NotificationRule; @@ -60,7 +60,7 @@ import java.lang.annotation.Target; @Type(name = "NOTIFICATION_TARGET", value = NotificationTarget.class), @Type(name = "NOTIFICATION_RULE", value = NotificationRule.class), @Type(name = "TB_RESOURCE", value = TbResource.class), - @Type(name = "AI_MODEL_SETTINGS", value = AiModelSettings.class) + @Type(name = "AI_MODEL", value = AiModel.class) }) @JsonIgnoreProperties(value = {"tenantId", "createdTime", "version"}, ignoreUnknown = true) public @interface JsonTbEntity {} diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index f3cb8a5eea..9d213c7fdb 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -64,7 +64,7 @@ enum EntityTypeProto { CALCULATED_FIELD = 39; CALCULATED_FIELD_LINK = 40; JOB = 41; - AI_MODEL_SETTINGS = 42; + AI_MODEL = 42; } enum ApiUsageRecordKeyProto { diff --git a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsCacheEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelCacheEvictEvent.java similarity index 58% rename from dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsCacheEvictEvent.java rename to dao/src/main/java/org/thingsboard/server/dao/ai/AiModelCacheEvictEvent.java index 3bf7dce9ba..b0d4b6fdb6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsCacheEvictEvent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelCacheEvictEvent.java @@ -15,26 +15,26 @@ */ package org.thingsboard.server.dao.ai; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; import static java.util.Objects.requireNonNull; -import static org.thingsboard.server.dao.ai.AiModelSettingsCacheEvictEvent.Deleted; -import static org.thingsboard.server.dao.ai.AiModelSettingsCacheEvictEvent.Saved; +import static org.thingsboard.server.dao.ai.AiModelCacheEvictEvent.Deleted; +import static org.thingsboard.server.dao.ai.AiModelCacheEvictEvent.Saved; -sealed interface AiModelSettingsCacheEvictEvent permits Saved, Deleted { +sealed interface AiModelCacheEvictEvent permits Saved, Deleted { - AiModelSettingsCacheKey cacheKey(); + AiModelCacheKey cacheKey(); - record Saved(AiModelSettingsCacheKey cacheKey, AiModelSettings savedSettings) implements AiModelSettingsCacheEvictEvent { + record Saved(AiModelCacheKey cacheKey, AiModel savedModel) implements AiModelCacheEvictEvent { public Saved { requireNonNull(cacheKey); - requireNonNull(savedSettings); + requireNonNull(savedModel); } } - record Deleted(AiModelSettingsCacheKey cacheKey) implements AiModelSettingsCacheEvictEvent { + record Deleted(AiModelCacheKey cacheKey) implements AiModelCacheEvictEvent { public Deleted { requireNonNull(cacheKey); diff --git a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelCacheKey.java similarity index 68% rename from dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsCacheKey.java rename to dao/src/main/java/org/thingsboard/server/dao/ai/AiModelCacheKey.java index 60d0ccaee9..6b73ad7b28 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelCacheKey.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.ai; import org.checkerframework.checker.nullness.qual.NonNull; import org.thingsboard.server.cache.VersionedCacheKey; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -25,22 +25,22 @@ import java.util.UUID; import static java.util.Objects.requireNonNull; -record AiModelSettingsCacheKey(UUID tenantId, UUID settingsId) implements VersionedCacheKey { +record AiModelCacheKey(UUID tenantId, UUID modelId) implements VersionedCacheKey { - AiModelSettingsCacheKey { + AiModelCacheKey { requireNonNull(tenantId); - requireNonNull(settingsId); + requireNonNull(modelId); if (TenantId.SYS_TENANT_ID.getId().equals(tenantId)) { throw new IllegalArgumentException("Tenant ID must not be the system tenant ID"); } - if (EntityId.NULL_UUID.equals(settingsId)) { - throw new IllegalArgumentException("Settings ID must not be reserved null UUID"); + if (EntityId.NULL_UUID.equals(modelId)) { + throw new IllegalArgumentException("Model ID must not be reserved null UUID"); } } - static AiModelSettingsCacheKey of(TenantId tenantId, AiModelSettingsId settingsId) { - return new AiModelSettingsCacheKey(tenantId.getId(), settingsId.getId()); + static AiModelCacheKey of(TenantId tenantId, AiModelId modelId) { + return new AiModelCacheKey(tenantId.getId(), modelId.getId()); } @Override @@ -51,7 +51,7 @@ record AiModelSettingsCacheKey(UUID tenantId, UUID settingsId) implements Versio @NonNull @Override public String toString() { - return /* cache name */ "_" + tenantId + "_" + settingsId; + return /* cache name */ "_" + tenantId + "_" + modelId; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelCaffeineCache.java similarity index 75% rename from dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsCaffeineCache.java rename to dao/src/main/java/org/thingsboard/server/dao/ai/AiModelCaffeineCache.java index d758cb21c5..165efcd4e2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsCaffeineCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelCaffeineCache.java @@ -20,14 +20,14 @@ import org.springframework.cache.CacheManager; import org.springframework.stereotype.Component; import org.thingsboard.server.cache.VersionedCaffeineTbCache; import org.thingsboard.server.common.data.CacheConstants; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; -@Component("AiModelSettingsCache") +@Component("AiModelCache") @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) -class AiModelSettingsCaffeineCache extends VersionedCaffeineTbCache { +class AiModelCaffeineCache extends VersionedCaffeineTbCache { - AiModelSettingsCaffeineCache(CacheManager cacheManager) { - super(cacheManager, CacheConstants.AI_MODEL_SETTINGS_CACHE); + AiModelCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.AI_MODEL_CACHE); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelDao.java similarity index 60% rename from dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsDao.java rename to dao/src/main/java/org/thingsboard/server/dao/ai/AiModelDao.java index c8d6b65369..e788685bfa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelDao.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.dao.ai; -import org.thingsboard.server.common.data.ai.AiModelSettings; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.ai.AiModel; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.TenantEntityDao; @@ -24,14 +24,14 @@ import org.thingsboard.server.dao.TenantEntityDao; import java.util.Optional; import java.util.Set; -public interface AiModelSettingsDao extends TenantEntityDao, ExportableEntityDao { +public interface AiModelDao extends TenantEntityDao, ExportableEntityDao { - Optional findByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId); + Optional findByTenantIdAndId(TenantId tenantId, AiModelId modelId); - boolean deleteById(TenantId tenantId, AiModelSettingsId settingsId); + boolean deleteById(TenantId tenantId, AiModelId modelId); - Set deleteByTenantId(TenantId tenantId); + Set deleteByTenantId(TenantId tenantId); - boolean deleteByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId); + boolean deleteByTenantIdAndId(TenantId tenantId, AiModelId modelId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelRedisCache.java similarity index 71% rename from dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsRedisCache.java rename to dao/src/main/java/org/thingsboard/server/dao/ai/AiModelRedisCache.java index 8674c522ff..7bec37875f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelRedisCache.java @@ -23,14 +23,14 @@ import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.cache.TbJsonRedisSerializer; import org.thingsboard.server.cache.VersionedRedisTbCache; import org.thingsboard.server.common.data.CacheConstants; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; -@Component("AiSettingsCache") +@Component("AiModelCache") @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") -class AiModelSettingsRedisCache extends VersionedRedisTbCache { +class AiModelRedisCache extends VersionedRedisTbCache { - AiModelSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.AI_MODEL_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(AiModelSettings.class)); + AiModelRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.AI_MODEL_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(AiModel.class)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelServiceImpl.java new file mode 100644 index 0000000000..b091a29247 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelServiceImpl.java @@ -0,0 +1,149 @@ +/** + * 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.dao.ai; + +import com.google.common.util.concurrent.FluentFuture; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ai.AiModel; +import org.thingsboard.server.common.data.id.AiModelId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.entity.CachedVersionedEntityService; +import org.thingsboard.server.dao.model.sql.AiModelEntity; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.sql.JpaExecutorService; + +import java.util.Optional; +import java.util.Set; + +import static org.thingsboard.server.dao.service.Validator.validatePageLink; + +@Service +@RequiredArgsConstructor +class AiModelServiceImpl extends CachedVersionedEntityService implements AiModelService { + + private final DataValidator aiModelValidator; + + private final JpaExecutorService jpaExecutor; + private final AiModelDao aiModelDao; + + @Override + @TransactionalEventListener + public void handleEvictEvent(AiModelCacheEvictEvent event) { + var cacheKey = event.cacheKey(); + if (event instanceof AiModelCacheEvictEvent.Saved savedEvent) { + cache.put(cacheKey, savedEvent.savedModel()); + } else if (event instanceof AiModelCacheEvictEvent.Deleted) { + cache.evict(cacheKey); + } else { + throw new UnsupportedOperationException("Unsupported event type: " + event.getClass().getSimpleName()); + } + } + + @Override + @Transactional + public AiModel save(AiModel model) { + aiModelValidator.validate(model, AiModel::getTenantId); + + AiModel savedModel; + try { + savedModel = aiModelDao.saveAndFlush(model.getTenantId(), model); + } catch (Exception e) { + checkConstraintViolation(e, + "ai_model_name_unq_key", "AI model with such name already exist!", + "ai_model_external_id_unq_key", "AI model with such external ID already exists!"); + throw e; + } + + var cacheKey = AiModelCacheKey.of(savedModel.getTenantId(), savedModel.getId()); + publishEvictEvent(new AiModelCacheEvictEvent.Saved(cacheKey, savedModel)); + + return savedModel; + } + + @Override + public Optional findAiModelById(TenantId tenantId, AiModelId modelId) { + return Optional.ofNullable(aiModelDao.findById(tenantId, modelId.getId())); + } + + @Override + public PageData findAiModelsByTenantId(TenantId tenantId, PageLink pageLink) { + validatePageLink(pageLink, AiModelEntity.ALLOWED_SORT_PROPERTIES); + return aiModelDao.findAllByTenantId(tenantId, pageLink); + } + + @Override + public Optional findAiModelByTenantIdAndId(TenantId tenantId, AiModelId modelId) { + var cacheKey = AiModelCacheKey.of(tenantId, modelId); + return Optional.ofNullable(cache.get(cacheKey, () -> aiModelDao.findByTenantIdAndId(tenantId, modelId).orElse(null))); + } + + @Override + public FluentFuture> findAiModelByTenantIdAndIdAsync(TenantId tenantId, AiModelId modelId) { + return FluentFuture.from(jpaExecutor.submit(() -> findAiModelByTenantIdAndId(tenantId, modelId))); + } + + @Override + @Transactional + public boolean deleteByTenantIdAndId(TenantId tenantId, AiModelId modelId) { + return deleteByTenantIdAndIdInternal(tenantId, modelId); + } + + @Override + public Optional> findEntity(TenantId tenantId, EntityId entityId) { + return findAiModelByTenantIdAndId(tenantId, (AiModelId) entityId) + .map(model -> model); // necessary to cast to HasId + } + + @Override + public long countByTenantId(TenantId tenantId) { + return aiModelDao.countByTenantId(tenantId); + } + + @Override + @Transactional + public void deleteEntity(TenantId tenantId, EntityId id, boolean force) { + deleteByTenantIdAndIdInternal(tenantId, new AiModelId(id.getId())); + } + + private boolean deleteByTenantIdAndIdInternal(TenantId tenantId, AiModelId modelId) { + boolean deleted = aiModelDao.deleteByTenantIdAndId(tenantId, modelId); + if (deleted) { + publishEvictEvent(new AiModelCacheEvictEvent.Deleted(AiModelCacheKey.of(tenantId, modelId))); + } + return deleted; + } + + @Override + @Transactional + public void deleteByTenantId(TenantId tenantId) { + Set deleted = aiModelDao.deleteByTenantId(tenantId); + deleted.forEach(id -> publishEvictEvent(new AiModelCacheEvictEvent.Deleted(AiModelCacheKey.of(tenantId, id)))); + } + + @Override + public EntityType getEntityType() { + return EntityType.AI_MODEL; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsServiceImpl.java deleted file mode 100644 index 2494ab6a15..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/ai/AiModelSettingsServiceImpl.java +++ /dev/null @@ -1,149 +0,0 @@ -/** - * 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.dao.ai; - -import com.google.common.util.concurrent.FluentFuture; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.event.TransactionalEventListener; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.ai.AiModelSettings; -import org.thingsboard.server.common.data.id.AiModelSettingsId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.dao.entity.CachedVersionedEntityService; -import org.thingsboard.server.dao.model.sql.AiModelSettingsEntity; -import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.sql.JpaExecutorService; - -import java.util.Optional; -import java.util.Set; - -import static org.thingsboard.server.dao.service.Validator.validatePageLink; - -@Service -@RequiredArgsConstructor -class AiModelSettingsServiceImpl extends CachedVersionedEntityService implements AiModelSettingsService { - - private final DataValidator aiModelSettingsValidator; - - private final JpaExecutorService jpaExecutor; - private final AiModelSettingsDao aiModelSettingsDao; - - @Override - @TransactionalEventListener - public void handleEvictEvent(AiModelSettingsCacheEvictEvent event) { - var cacheKey = event.cacheKey(); - if (event instanceof AiModelSettingsCacheEvictEvent.Saved savedEvent) { - cache.put(cacheKey, savedEvent.savedSettings()); - } else if (event instanceof AiModelSettingsCacheEvictEvent.Deleted) { - cache.evict(cacheKey); - } else { - throw new UnsupportedOperationException("Unsupported event type: " + event.getClass().getSimpleName()); - } - } - - @Override - @Transactional - public AiModelSettings save(AiModelSettings settings) { - aiModelSettingsValidator.validate(settings, AiModelSettings::getTenantId); - - AiModelSettings savedSettings; - try { - savedSettings = aiModelSettingsDao.saveAndFlush(settings.getTenantId(), settings); - } catch (Exception e) { - checkConstraintViolation(e, - "ai_model_settings_name_unq_key", "AI model settings with such name already exist!", - "ai_model_settings_external_id_unq_key", "AI model settings with such external ID already exist!"); - throw e; - } - - var cacheKey = AiModelSettingsCacheKey.of(savedSettings.getTenantId(), savedSettings.getId()); - publishEvictEvent(new AiModelSettingsCacheEvictEvent.Saved(cacheKey, savedSettings)); - - return savedSettings; - } - - @Override - public Optional findAiModelSettingsById(TenantId tenantId, AiModelSettingsId settingsId) { - return Optional.ofNullable(aiModelSettingsDao.findById(tenantId, settingsId.getId())); - } - - @Override - public PageData findAiModelSettingsByTenantId(TenantId tenantId, PageLink pageLink) { - validatePageLink(pageLink, AiModelSettingsEntity.ALLOWED_SORT_PROPERTIES); - return aiModelSettingsDao.findAllByTenantId(tenantId, pageLink); - } - - @Override - public Optional findAiModelSettingsByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId) { - var cacheKey = AiModelSettingsCacheKey.of(tenantId, settingsId); - return Optional.ofNullable(cache.get(cacheKey, () -> aiModelSettingsDao.findByTenantIdAndId(tenantId, settingsId).orElse(null))); - } - - @Override - public FluentFuture> findAiModelSettingsByTenantIdAndIdAsync(TenantId tenantId, AiModelSettingsId settingsId) { - return FluentFuture.from(jpaExecutor.submit(() -> findAiModelSettingsByTenantIdAndId(tenantId, settingsId))); - } - - @Override - @Transactional - public boolean deleteByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId) { - return deleteByTenantIdAndIdInternal(tenantId, settingsId); - } - - @Override - public Optional> findEntity(TenantId tenantId, EntityId entityId) { - return findAiModelSettingsByTenantIdAndId(tenantId, (AiModelSettingsId) entityId) - .map(settings -> settings); // necessary to cast to HasId - } - - @Override - public long countByTenantId(TenantId tenantId) { - return aiModelSettingsDao.countByTenantId(tenantId); - } - - @Override - @Transactional - public void deleteEntity(TenantId tenantId, EntityId id, boolean force) { - deleteByTenantIdAndIdInternal(tenantId, new AiModelSettingsId(id.getId())); - } - - private boolean deleteByTenantIdAndIdInternal(TenantId tenantId, AiModelSettingsId settingsId) { - boolean deleted = aiModelSettingsDao.deleteByTenantIdAndId(tenantId, settingsId); - if (deleted) { - publishEvictEvent(new AiModelSettingsCacheEvictEvent.Deleted(AiModelSettingsCacheKey.of(tenantId, settingsId))); - } - return deleted; - } - - @Override - @Transactional - public void deleteByTenantId(TenantId tenantId) { - Set deleted = aiModelSettingsDao.deleteByTenantId(tenantId); - deleted.forEach(id -> publishEvictEvent(new AiModelSettingsCacheEvictEvent.Deleted(AiModelSettingsCacheKey.of(tenantId, id)))); - } - - @Override - public EntityType getEntityType() { - return EntityType.AI_MODEL_SETTINGS; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/CleanUpService.java b/dao/src/main/java/org/thingsboard/server/dao/housekeeper/CleanUpService.java index 28c96c4675..2f23d20f00 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/housekeeper/CleanUpService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/housekeeper/CleanUpService.java @@ -47,7 +47,7 @@ public class CleanUpService { private final Set skippedEntities = EnumSet.of( EntityType.ALARM, EntityType.QUEUE, EntityType.TB_RESOURCE, EntityType.OTA_PACKAGE, EntityType.NOTIFICATION_REQUEST, EntityType.NOTIFICATION_TEMPLATE, - EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_RULE, EntityType.AI_MODEL_SETTINGS + EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_RULE, EntityType.AI_MODEL ); @TransactionalEventListener(fallbackExecution = true) // after transaction commit diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 6245d75214..ca59d3bce0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -752,12 +752,12 @@ public class ModelConstants { public static final String JOB_RESULT_PROPERTY = "result"; /** - * AI model settings constants. + * AI model constants. */ - public static final String AI_MODEL_SETTINGS_TABLE_NAME = "ai_model_settings"; - public static final String AI_MODEL_SETTINGS_TENANT_ID_COLUMN_NAME = TENANT_ID_COLUMN; - public static final String AI_MODEL_SETTINGS_NAME_COLUMN_NAME = NAME_PROPERTY; - public static final String AI_MODEL_SETTINGS_CONFIGURATION_COLUMN_NAME = "configuration"; + public static final String AI_MODEL_TABLE_NAME = "ai_model"; + public static final String AI_MODEL_TENANT_ID_COLUMN_NAME = TENANT_ID_COLUMN; + public static final String AI_MODEL_NAME_COLUMN_NAME = NAME_PROPERTY; + public static final String AI_MODEL_CONFIGURATION_COLUMN_NAME = "configuration"; protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AiModelSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AiModelEntity.java similarity index 62% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/AiModelSettingsEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/AiModelEntity.java index 25aa7a0018..d4f3d36db6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AiModelSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AiModelEntity.java @@ -24,9 +24,9 @@ import lombok.Setter; import lombok.ToString; import org.hibernate.annotations.Type; import org.hibernate.proxy.HibernateProxy; -import org.thingsboard.server.common.data.ai.AiModelSettings; -import org.thingsboard.server.common.data.ai.model.AiModel; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.ai.AiModel; +import org.thingsboard.server.common.data.ai.model.AiModelConfig; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseVersionedEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -43,52 +43,52 @@ import java.util.UUID; @Setter @ToString @Entity -@Table(name = ModelConstants.AI_MODEL_SETTINGS_TABLE_NAME) -public class AiModelSettingsEntity extends BaseVersionedEntity { +@Table(name = ModelConstants.AI_MODEL_TABLE_NAME) +public class AiModelEntity extends BaseVersionedEntity { public static final Map COLUMN_MAP = Map.of( "createdTime", "created_time", "provider", "(configuration ->> 'provider')", - "modelId", "(configuration -> 'modelConfig' ->> 'modelId')" + "modelId", "(configuration ->> 'modelId')" ); public static final Set ALLOWED_SORT_PROPERTIES = Collections.unmodifiableSet( new LinkedHashSet<>(List.of("createdTime", "name", "provider", "modelId")) ); - @Column(name = ModelConstants.AI_MODEL_SETTINGS_TENANT_ID_COLUMN_NAME, nullable = false, columnDefinition = "UUID") + @Column(name = ModelConstants.AI_MODEL_TENANT_ID_COLUMN_NAME, nullable = false, columnDefinition = "UUID") private UUID tenantId; - @Column(name = ModelConstants.AI_MODEL_SETTINGS_NAME_COLUMN_NAME, nullable = false) + @Column(name = ModelConstants.AI_MODEL_NAME_COLUMN_NAME, nullable = false) private String name; @Type(JsonBinaryType.class) - @Column(name = ModelConstants.AI_MODEL_SETTINGS_CONFIGURATION_COLUMN_NAME, nullable = false, columnDefinition = "JSONB") - private AiModel configuration; + @Column(name = ModelConstants.AI_MODEL_CONFIGURATION_COLUMN_NAME, nullable = false, columnDefinition = "JSONB") + private AiModelConfig configuration; @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY, columnDefinition = "UUID") private UUID externalId; - public AiModelSettingsEntity() {} + public AiModelEntity() {} - public AiModelSettingsEntity(AiModelSettings aiModelSettings) { - super(aiModelSettings); - tenantId = getTenantUuid(aiModelSettings.getTenantId()); - name = aiModelSettings.getName(); - configuration = aiModelSettings.getConfiguration(); - externalId = getUuid(aiModelSettings.getExternalId()); + public AiModelEntity(AiModel aiModel) { + super(aiModel); + tenantId = getTenantUuid(aiModel.getTenantId()); + name = aiModel.getName(); + configuration = aiModel.getConfiguration(); + externalId = getUuid(aiModel.getExternalId()); } @Override - public AiModelSettings toData() { - var settings = new AiModelSettings(new AiModelSettingsId(id)); - settings.setCreatedTime(createdTime); - settings.setVersion(version); - settings.setTenantId(TenantId.fromUUID(tenantId)); - settings.setName(name); - settings.setConfiguration(configuration); - settings.setExternalId(getEntityId(externalId, AiModelSettingsId::new)); - return settings; + public AiModel toData() { + var model = new AiModel(new AiModelId(id)); + model.setCreatedTime(createdTime); + model.setVersion(version); + model.setTenantId(TenantId.fromUUID(tenantId)); + model.setName(name); + model.setConfiguration(configuration); + model.setExternalId(getEntityId(externalId, AiModelId::new)); + return model; } @Override @@ -98,7 +98,7 @@ public class AiModelSettingsEntity extends BaseVersionedEntity Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); if (thisEffectiveClass != oEffectiveClass) return false; - AiModelSettingsEntity that = (AiModelSettingsEntity) o; + AiModelEntity that = (AiModelEntity) o; return getId() != null && Objects.equals(getId(), that.getId()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelDataValidator.java similarity index 51% rename from dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelSettingsDataValidator.java rename to dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelDataValidator.java index fdba1f4e0c..fdccf2955f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelSettingsDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelDataValidator.java @@ -17,9 +17,9 @@ package org.thingsboard.server.dao.service.validator; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.ai.AiModelSettingsDao; +import org.thingsboard.server.dao.ai.AiModelDao; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantService; @@ -28,41 +28,41 @@ import java.util.Optional; @Component @RequiredArgsConstructor -class AiModelSettingsDataValidator extends DataValidator { +class AiModelDataValidator extends DataValidator { private final TenantService tenantService; - private final AiModelSettingsDao aiModelSettingsDao; + private final AiModelDao aiModelDao; @Override - protected AiModelSettings validateUpdate(TenantId tenantId, AiModelSettings settings) { - Optional existing = aiModelSettingsDao.findByTenantIdAndId(tenantId, settings.getId()); + protected AiModel validateUpdate(TenantId tenantId, AiModel model) { + Optional existing = aiModelDao.findByTenantIdAndId(tenantId, model.getId()); if (existing.isEmpty()) { - throw new DataValidationException("Cannot update non-existent AI model settings!"); + throw new DataValidationException("Cannot update non-existent AI model!"); } return existing.get(); } @Override - protected void validateDataImpl(TenantId tenantId, AiModelSettings settings) { + protected void validateDataImpl(TenantId tenantId, AiModel model) { // ID validation - if (settings.getId() != null) { - if (settings.getUuidId() == null) { - throw new DataValidationException("AI model settings UUID should be specified!"); + if (model.getId() != null) { + if (model.getUuidId() == null) { + throw new DataValidationException("AI model UUID should be specified!"); } - if (settings.getId().isNullUid()) { - throw new DataValidationException("AI model settings UUID must not be the reserved null value!"); + if (model.getId().isNullUid()) { + throw new DataValidationException("AI model UUID must not be the reserved null value!"); } } // tenant ID validation - if (settings.getTenantId() == null || settings.getTenantId().getId() == null) { - throw new DataValidationException("AI model settings should be assigned to tenant!"); + if (model.getTenantId() == null || model.getTenantId().getId() == null) { + throw new DataValidationException("AI model should be assigned to tenant!"); } - if (settings.getTenantId().isSysTenantId()) { - throw new DataValidationException("AI model settings cannot be assigned to the system tenant!"); + if (model.getTenantId().isSysTenantId()) { + throw new DataValidationException("AI model cannot be assigned to the system tenant!"); } if (!tenantService.tenantExists(tenantId)) { - throw new DataValidationException("AI model settings reference a non-existent tenant!"); + throw new DataValidationException("AI model reference a non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ai/AiModelSettingsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ai/AiModelRepository.java similarity index 55% rename from dao/src/main/java/org/thingsboard/server/dao/sql/ai/AiModelSettingsRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/ai/AiModelRepository.java index 6be7d97f33..0a746f15ab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/ai/AiModelSettingsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ai/AiModelRepository.java @@ -23,58 +23,58 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.ExportableEntityRepository; -import org.thingsboard.server.dao.model.sql.AiModelSettingsEntity; +import org.thingsboard.server.dao.model.sql.AiModelEntity; import java.util.Optional; import java.util.Set; import java.util.UUID; -interface AiModelSettingsRepository extends JpaRepository, ExportableEntityRepository { +interface AiModelRepository extends JpaRepository, ExportableEntityRepository { - Optional findByTenantIdAndId(UUID tenantId, UUID id); + Optional findByTenantIdAndId(UUID tenantId, UUID id); - Optional findByTenantIdAndName(UUID tenantId, String name); + Optional findByTenantIdAndName(UUID tenantId, String name); @Query( value = """ SELECT * - FROM ai_model_settings ai_model - WHERE ai_model.tenant_id = :tenantId + FROM ai_model model + WHERE model.tenant_id = :tenantId AND (:textSearch IS NULL - OR ai_model.name ILIKE '%' || :textSearch || '%' - OR (ai_model.configuration ->> 'provider') ILIKE '%' || :textSearch || '%' - OR (ai_model.configuration -> 'modelConfig' ->> 'modelId') ILIKE '%' || :textSearch || '%') + OR model.name ILIKE '%' || :textSearch || '%' + OR (model.configuration ->> 'provider') ILIKE '%' || :textSearch || '%' + OR (model.configuration ->> 'modelId') ILIKE '%' || :textSearch || '%') """, countQuery = """ SELECT COUNT(*) - FROM ai_model_settings ai_model - WHERE ai_model.tenant_id = :tenantId + FROM ai_model model + WHERE model.tenant_id = :tenantId AND (:textSearch IS NULL - OR ai_model.name ILIKE '%' || :textSearch || '%' - OR (ai_model.configuration ->> 'provider') ILIKE '%' || :textSearch || '%' - OR (ai_model.configuration -> 'modelConfig' ->> 'modelId') ILIKE '%' || :textSearch || '%') + OR model.name ILIKE '%' || :textSearch || '%' + OR (model.configuration ->> 'provider') ILIKE '%' || :textSearch || '%' + OR (model.configuration ->> 'modelId') ILIKE '%' || :textSearch || '%') """, nativeQuery = true ) - Page findByTenantId(@Param("tenantId") UUID tenantId, @Param("textSearch") String textSearch, Pageable pageable); + Page findByTenantId(@Param("tenantId") UUID tenantId, @Param("textSearch") String textSearch, Pageable pageable); - @Query("SELECT ai_model.id FROM AiModelSettingsEntity ai_model WHERE ai_model.tenantId = :tenantId") + @Query("SELECT ai_model.id FROM AiModelEntity ai_model WHERE ai_model.tenantId = :tenantId") Page findIdsByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable); - @Query("SELECT externalId FROM AiModelSettingsEntity WHERE id = :id") + @Query("SELECT externalId FROM AiModelEntity WHERE id = :id") Optional getExternalIdById(@Param("id") UUID id); long countByTenantId(UUID tenantId); @Transactional @Modifying - @Query("DELETE FROM AiModelSettingsEntity ai_model WHERE ai_model.id IN (:ids)") + @Query("DELETE FROM AiModelEntity ai_model WHERE ai_model.id IN (:ids)") int deleteByIdIn(@Param("ids") Set ids); @Transactional @Modifying @Query(value = """ - DELETE FROM ai_model_settings + DELETE FROM ai_model WHERE tenant_id = :tenantId RETURNING id """, nativeQuery = true @@ -83,7 +83,7 @@ interface AiModelSettingsRepository extends JpaRepository ids); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ai/JpaAiModelSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ai/JpaAiModelDao.java similarity index 50% rename from dao/src/main/java/org/thingsboard/server/dao/sql/ai/JpaAiModelSettingsDao.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/ai/JpaAiModelDao.java index 38e7280313..e0c145fe03 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/ai/JpaAiModelSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ai/JpaAiModelDao.java @@ -23,15 +23,15 @@ import org.springframework.data.jpa.domain.JpaSort; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.ai.AiModelSettings; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.ai.AiModel; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.ai.AiModelSettingsDao; -import org.thingsboard.server.dao.model.sql.AiModelSettingsEntity; +import org.thingsboard.server.dao.ai.AiModelDao; +import org.thingsboard.server.dao.model.sql.AiModelEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; @@ -44,40 +44,40 @@ import static java.util.stream.Collectors.toSet; @SqlDao @Component @RequiredArgsConstructor -class JpaAiModelSettingsDao extends JpaAbstractDao implements AiModelSettingsDao { +class JpaAiModelDao extends JpaAbstractDao implements AiModelDao { - private final AiModelSettingsRepository aiModelSettingsRepository; + private final AiModelRepository aiModelRepository; @Override - public Optional findByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId) { - return aiModelSettingsRepository.findByTenantIdAndId(tenantId.getId(), settingsId.getId()).map(DaoUtil::getData); + public Optional findByTenantIdAndId(TenantId tenantId, AiModelId modelId) { + return aiModelRepository.findByTenantIdAndId(tenantId.getId(), modelId.getId()).map(DaoUtil::getData); } @Override - public AiModelSettings findByTenantIdAndName(UUID tenantId, String name) { - return DaoUtil.getData(aiModelSettingsRepository.findByTenantIdAndName(tenantId, name)); + public AiModel findByTenantIdAndName(UUID tenantId, String name) { + return DaoUtil.getData(aiModelRepository.findByTenantIdAndName(tenantId, name)); } @Override - public AiModelSettings findByTenantIdAndExternalId(UUID tenantId, UUID externalId) { - return DaoUtil.getData(aiModelSettingsRepository.findByTenantIdAndExternalId(tenantId, externalId)); + public AiModel findByTenantIdAndExternalId(UUID tenantId, UUID externalId) { + return DaoUtil.getData(aiModelRepository.findByTenantIdAndExternalId(tenantId, externalId)); } @Override - public PageData findAllByTenantId(TenantId tenantId, PageLink pageLink) { + public PageData findAllByTenantId(TenantId tenantId, PageLink pageLink) { return findByTenantId(tenantId.getId(), pageLink); } @Override - public PageData findByTenantId(UUID tenantId, PageLink pageLink) { - return DaoUtil.toPageData(aiModelSettingsRepository.findByTenantId( + public PageData findByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData(aiModelRepository.findByTenantId( tenantId, StringUtils.defaultIfEmpty(pageLink.getTextSearch(), null), toPageRequest(pageLink)) ); } @Override - public PageData findIdsByTenantId(UUID tenantId, PageLink pageLink) { - return DaoUtil.pageToPageData(aiModelSettingsRepository.findIdsByTenantId(tenantId, toPageRequest(pageLink)).map(AiModelSettingsId::new)); + public PageData findIdsByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.pageToPageData(aiModelRepository.findIdsByTenantId(tenantId, toPageRequest(pageLink)).map(AiModelId::new)); } private static PageRequest toPageRequest(PageLink pageLink) { @@ -88,52 +88,52 @@ class JpaAiModelSettingsDao extends JpaAbstractDao 0; + public boolean deleteById(TenantId tenantId, AiModelId modelId) { + return aiModelRepository.deleteByIdIn(Set.of(modelId.getId())) > 0; } @Override - public Set deleteByTenantId(TenantId tenantId) { - return aiModelSettingsRepository.deleteByTenantId(tenantId.getId()).stream() - .map(AiModelSettingsId::new) + public Set deleteByTenantId(TenantId tenantId) { + return aiModelRepository.deleteByTenantId(tenantId.getId()).stream() + .map(AiModelId::new) .collect(toSet()); } @Override - public boolean deleteByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId) { - return aiModelSettingsRepository.deleteByTenantIdAndIdIn(tenantId.getId(), Set.of(settingsId.getId())) > 0; + public boolean deleteByTenantIdAndId(TenantId tenantId, AiModelId modelId) { + return aiModelRepository.deleteByTenantIdAndIdIn(tenantId.getId(), Set.of(modelId.getId())) > 0; } @Override public EntityType getEntityType() { - return EntityType.AI_MODEL_SETTINGS; + return EntityType.AI_MODEL; } @Override - protected Class getEntityClass() { - return AiModelSettingsEntity.class; + protected Class getEntityClass() { + return AiModelEntity.class; } @Override - protected JpaRepository getRepository() { - return aiModelSettingsRepository; + protected JpaRepository getRepository() { + return aiModelRepository; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 9b37e9c2dd..0eb4a60e6d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -183,7 +183,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService> FluentFuture sendChatRequestAsync(AiChatModel chatModel, ChatRequest chatRequest); + > FluentFuture sendChatRequestAsync(AiChatModelConfig chatModelConfig, ChatRequest chatRequest); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 1eeb644bba..d2687a1b10 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -44,7 +44,7 @@ import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.dao.ai.AiModelSettingsService; +import org.thingsboard.server.dao.ai.AiModelService; import org.thingsboard.server.dao.alarm.AlarmCommentService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; @@ -423,9 +423,9 @@ public interface TbContext { AuditLogService getAuditLogService(); - RuleEngineAiModelService getAiModelService(); + RuleEngineAiChatModelService getAiChatModelService(); - AiModelSettingsService getAiModelSettingsService(); + AiModelService getAiModelService(); // Configuration parameters for the MQTT client that is used in the MQTT node and Azure IoT hub node diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java index 62d92ef2ce..93c2cafaae 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNode.java @@ -33,11 +33,10 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.external.TbAbstractExternalNode; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; import org.thingsboard.server.common.data.ai.model.AiModelType; -import org.thingsboard.server.common.data.ai.model.chat.AiChatModel; import org.thingsboard.server.common.data.ai.model.chat.AiChatModelConfig; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.msg.TbMsg; @@ -65,7 +64,7 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode { private String userPrompt; private ResponseFormat responseFormat; private int timeoutSeconds; - private AiModelSettingsId modelSettingsId; + private AiModelId modelId; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { @@ -79,7 +78,7 @@ 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 + // LangChain4j AnthropicChatModel rejects requests with non-null ResponseFormat even if ResponseFormatType is TEXT if (config.getResponseFormat().type() == TbResponseFormat.TbResponseFormatType.JSON) { responseFormat = config.getResponseFormat().toLangChainResponseFormat(); } @@ -87,15 +86,15 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode { systemPrompt = config.getSystemPrompt(); userPrompt = config.getUserPrompt(); timeoutSeconds = config.getTimeoutSeconds(); - modelSettingsId = config.getAiModelSettingsId(); + modelId = config.getAiModelId(); - Optional modelSettings = ctx.getAiModelSettingsService().findAiModelSettingsByTenantIdAndId(ctx.getTenantId(), modelSettingsId); - if (modelSettings.isEmpty()) { - throw new TbNodeException("[" + ctx.getTenantId() + "] AI model settings with ID: [" + modelSettingsId + "] were not found", true); + Optional 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 = modelSettings.get().getConfiguration().modelType(); + AiModelType modelType = model.get().getConfiguration().modelType(); if (modelType != AiModelType.CHAT) { - throw new TbNodeException("[" + ctx.getTenantId() + "] AI model settings with ID: [" + modelSettingsId + "] must be of type CHAT, but was " + modelType, true); + throw new TbNodeException("[" + ctx.getTenantId() + "] AI model with ID: [" + modelId + "] must be of type CHAT, but was " + modelType, true); } } @@ -134,24 +133,24 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode { } private > FluentFuture sendChatRequestAsync(TbContext ctx, ChatRequest chatRequest) { - return ctx.getAiModelSettingsService().findAiModelSettingsByTenantIdAndIdAsync(ctx.getTenantId(), modelSettingsId).transformAsync(settingsOpt -> { - if (settingsOpt.isEmpty()) { - throw new NoSuchElementException("[" + ctx.getTenantId() + "] AI model settings with ID: [" + modelSettingsId + "] were not found"); + return ctx.getAiModelService().findAiModelByTenantIdAndIdAsync(ctx.getTenantId(), modelId).transformAsync(modelOpt -> { + if (modelOpt.isEmpty()) { + throw new NoSuchElementException("[" + ctx.getTenantId() + "] AI model with ID: [" + modelId + "] was not found"); } - AiModelSettings settings = settingsOpt.get(); - AiModelType modelType = settings.getConfiguration().modelType(); + AiModel model = modelOpt.get(); + AiModelType modelType = model.getConfiguration().modelType(); if (modelType != AiModelType.CHAT) { - throw new IllegalStateException("[" + ctx.getTenantId() + "] AI model settings with ID: [" + modelSettingsId + "] must be of type CHAT, but was " + modelType); + throw new IllegalStateException("[" + ctx.getTenantId() + "] AI model with ID: [" + modelId + "] must be of type CHAT, but was " + modelType); } @SuppressWarnings("unchecked") - AiChatModel chatModel = (AiChatModel) settingsOpt.get().getConfiguration(); + AiChatModelConfig chatModelConfig = (AiChatModelConfig) model.getConfiguration(); - chatModel = chatModel.withModelConfig(chatModel.modelConfig() + chatModelConfig = chatModelConfig .withTimeoutSeconds(timeoutSeconds) - .withMaxRetries(0)); // disable retries to respect timeout set in rule node config + .withMaxRetries(0); // disable retries to respect timeout set in rule node config - return ctx.getAiModelService().sendChatRequestAsync(chatModel, chatRequest); + return ctx.getAiChatModelService().sendChatRequestAsync(chatModelConfig, chatRequest); }, ctx.getDbCallbackExecutor()); } @@ -174,7 +173,7 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode { systemPrompt = null; userPrompt = null; responseFormat = null; - modelSettingsId = null; + modelId = null; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNodeConfiguration.java index ebfcf943f7..eba3d5fef9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/ai/TbAiNodeConfiguration.java @@ -23,7 +23,7 @@ import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.validation.Length; import static org.thingsboard.rule.engine.ai.TbResponseFormat.TbJsonResponseFormat; @@ -32,7 +32,7 @@ import static org.thingsboard.rule.engine.ai.TbResponseFormat.TbJsonResponseForm public class TbAiNodeConfiguration implements NodeConfiguration { @NotNull - private AiModelSettingsId aiModelSettingsId; + private AiModelId aiModelId; @Pattern(regexp = ".*\\S.*", message = "must not be blank") @Length(min = 1, max = 10000) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java index 130332ad64..a8ae187c84 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java @@ -19,7 +19,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.cf.CalculatedFieldLink; -import org.thingsboard.server.common.data.id.AiModelSettingsId; +import org.thingsboard.server.common.data.id.AiModelId; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.AssetId; @@ -180,8 +180,8 @@ public class TenantIdLoader { case JOB: tenantEntity = ctx.getJobService().findJobById(ctxTenantId, new JobId(id)); break; - case AI_MODEL_SETTINGS: - tenantEntity = ctx.getAiModelSettingsService().findAiModelSettingsById(ctxTenantId, new AiModelSettingsId(id)).orElse(null); + case AI_MODEL: + tenantEntity = ctx.getAiModelService().findAiModelById(ctxTenantId, new AiModelId(id)).orElse(null); break; default: throw new RuntimeException("Unexpected entity type: " + entityId.getEntityType()); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java index 3713dbea3e..2d7b8a0d30 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java @@ -40,7 +40,7 @@ import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.ai.AiModelSettings; +import org.thingsboard.server.common.data.ai.AiModel; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetProfile; @@ -70,7 +70,7 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.ai.AiModelSettingsService; +import org.thingsboard.server.dao.ai.AiModelService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.cf.CalculatedFieldService; import org.thingsboard.server.dao.customer.CustomerService; @@ -168,7 +168,7 @@ public class TenantIdLoaderTest { @Mock private JobService jobService; @Mock - private AiModelSettingsService aiModelSettingsService; + private AiModelService aiModelService; private TenantId tenantId; private TenantProfileId tenantProfileId; @@ -434,11 +434,11 @@ public class TenantIdLoaderTest { when(ctx.getJobService()).thenReturn(jobService); doReturn(job).when(jobService).findJobById(eq(tenantId), any()); break; - case AI_MODEL_SETTINGS: - AiModelSettings aiModelSettings = new AiModelSettings(); - aiModelSettings.setTenantId(tenantId); - when(ctx.getAiModelSettingsService()).thenReturn(aiModelSettingsService); - doReturn(Optional.of(aiModelSettings)).when(aiModelSettingsService).findAiModelSettingsById(eq(tenantId), any()); + case AI_MODEL: + AiModel aiModel = new AiModel(); + aiModel.setTenantId(tenantId); + when(ctx.getAiModelService()).thenReturn(aiModelService); + doReturn(Optional.of(aiModel)).when(aiModelService).findAiModelById(eq(tenantId), any()); break; default: throw new RuntimeException("Unexpected originator EntityType " + entityType);