AI rule node: refactor model config data structure; rename from AI settings to AI model settings
This commit is contained in:
parent
459cc6d27e
commit
1343c4af3b
@ -14,15 +14,12 @@
|
||||
-- limitations under the License.
|
||||
--
|
||||
|
||||
CREATE TABLE ai_settings (
|
||||
CREATE TABLE ai_model_settings (
|
||||
id UUID NOT NULL PRIMARY KEY,
|
||||
created_time BIGINT NOT NULL,
|
||||
tenant_id UUID NOT NULL,
|
||||
version BIGINT NOT NULL DEFAULT 1,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
provider VARCHAR(255) NOT NULL,
|
||||
provider_config JSONB NOT NULL,
|
||||
model VARCHAR(255) NOT NULL,
|
||||
model_config JSONB,
|
||||
CONSTRAINT ai_settings_name_unq_key UNIQUE (tenant_id, name)
|
||||
configuration JSONB NOT NULL,
|
||||
CONSTRAINT ai_model_settings_name_unq_key UNIQUE (tenant_id, name)
|
||||
);
|
||||
|
||||
@ -35,7 +35,7 @@ import org.thingsboard.rule.engine.api.DeviceStateManager;
|
||||
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.RuleEngineAiService;
|
||||
import org.thingsboard.rule.engine.api.RuleEngineAiModelService;
|
||||
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.AiSettingsService;
|
||||
import org.thingsboard.server.dao.ai.AiModelSettingsService;
|
||||
import org.thingsboard.server.dao.alarm.AlarmCommentService;
|
||||
import org.thingsboard.server.dao.asset.AssetProfileService;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
@ -314,11 +314,11 @@ public class ActorSystemContext {
|
||||
|
||||
@Autowired
|
||||
@Getter
|
||||
private RuleEngineAiService aiService;
|
||||
private RuleEngineAiModelService aiModelService;
|
||||
|
||||
@Autowired
|
||||
@Getter
|
||||
private AiSettingsService aiSettingsService;
|
||||
private AiModelSettingsService aiModelSettingsService;
|
||||
|
||||
@Autowired
|
||||
@Getter
|
||||
|
||||
@ -28,7 +28,7 @@ import org.thingsboard.rule.engine.api.DeviceStateManager;
|
||||
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.RuleEngineAiService;
|
||||
import org.thingsboard.rule.engine.api.RuleEngineAiModelService;
|
||||
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.AiSettingsService;
|
||||
import org.thingsboard.server.dao.ai.AiModelSettingsService;
|
||||
import org.thingsboard.server.dao.alarm.AlarmCommentService;
|
||||
import org.thingsboard.server.dao.asset.AssetProfileService;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
@ -1016,13 +1016,13 @@ public class DefaultTbContext implements TbContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuleEngineAiService getAiService() {
|
||||
return mainCtx.getAiService();
|
||||
public RuleEngineAiModelService getAiModelService() {
|
||||
return mainCtx.getAiModelService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiSettingsService getAiSettingsService() {
|
||||
return mainCtx.getAiSettingsService();
|
||||
public AiModelSettingsService getAiModelSettingsService() {
|
||||
return mainCtx.getAiModelSettingsService();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -26,9 +26,9 @@ 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.AiSettings;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
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;
|
||||
@ -47,70 +47,70 @@ import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERT
|
||||
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/ai-settings")
|
||||
public class AiSettingsController extends BaseController {
|
||||
@RequestMapping("/api/ai-model-settings")
|
||||
public class AiModelSettingsController extends BaseController {
|
||||
|
||||
private static final Set<String> ALLOWED_SORT_PROPERTIES = Set.of("createdTime", "name", "provider", "model");
|
||||
private static final Set<String> ALLOWED_SORT_PROPERTIES = Set.of("createdTime", "name");
|
||||
|
||||
@ApiOperation(
|
||||
value = "Create or update AI settings (saveAiSettings)",
|
||||
notes = "Creates or updates an AI settings record.\n\n" +
|
||||
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 settings will be taken from the authenticated user making the request, regardless of any value provided in the request body." +
|
||||
"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 AiSettings saveAiSettings(@RequestBody AiSettings aiSettings) throws ThingsboardException {
|
||||
public AiModelSettings saveAiModelSettings(@RequestBody AiModelSettings settings) throws ThingsboardException {
|
||||
var user = getCurrentUser();
|
||||
aiSettings.setTenantId(user.getTenantId());
|
||||
checkEntity(aiSettings.getId(), aiSettings, Resource.AI_SETTINGS);
|
||||
return tbAiSettingsService.save(aiSettings, user);
|
||||
settings.setTenantId(user.getTenantId());
|
||||
checkEntity(settings.getId(), settings, Resource.AI_MODEL_SETTINGS);
|
||||
return tbAiModelSettingsService.save(settings, user);
|
||||
}
|
||||
|
||||
@ApiOperation(
|
||||
value = "Get AI settings by ID (getAiSettingsById)",
|
||||
notes = "Fetches an AI settings record by its `id`." +
|
||||
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("/{aiSettingsId}")
|
||||
public AiSettings getAiSettingsById(
|
||||
@GetMapping("/{aiModelSettingsId}")
|
||||
public AiModelSettings getAiModelSettingsById(
|
||||
@Parameter(
|
||||
description = "ID of the AI settings record",
|
||||
description = "ID of the AI model settings record",
|
||||
required = true,
|
||||
example = "de7900d4-30e2-11f0-9cd2-0242ac120002"
|
||||
)
|
||||
@PathVariable("aiSettingsId") UUID aiSettingsUuid
|
||||
@PathVariable("aiModelSettingsId") UUID aiModelSettingsUuid
|
||||
) throws ThingsboardException {
|
||||
return checkAiSettingsId(new AiSettingsId(aiSettingsUuid), Operation.READ);
|
||||
return checkAiModelSettingsId(new AiModelSettingsId(aiModelSettingsUuid), Operation.READ);
|
||||
}
|
||||
|
||||
@ApiOperation(
|
||||
value = "Get AI settings (getAiSettings)",
|
||||
notes = "Returns a page of AI settings. " +
|
||||
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<AiSettings> getAiSettings(
|
||||
public PageData<AiModelSettings> getAiModelSettings(
|
||||
@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
|
||||
@RequestParam int pageSize,
|
||||
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
|
||||
@RequestParam int page,
|
||||
@Parameter(description = AI_SETTINGS_TEXT_SEARCH_DESCRIPTION)
|
||||
@RequestParam(required = false) String textSearch,
|
||||
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name", "provider", "model"}))
|
||||
@Parameter(description = SORT_PROPERTY_DESCRIPTION, schema = @Schema(allowableValues = {"createdTime", "name"}))
|
||||
@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_SETTINGS, Operation.READ);
|
||||
accessControlService.checkPermission(user, Resource.AI_MODEL_SETTINGS, Operation.READ);
|
||||
validateSortProperty(sortProperty);
|
||||
var pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
|
||||
return aiSettingsService.findAiSettingsByTenantId(user.getTenantId(), pageLink);
|
||||
return aiModelSettingsService.findAiModelSettingsByTenantId(user.getTenantId(), pageLink);
|
||||
}
|
||||
|
||||
private static void validateSortProperty(String sortProperty) {
|
||||
@ -120,31 +120,31 @@ public class AiSettingsController extends BaseController {
|
||||
}
|
||||
|
||||
@ApiOperation(
|
||||
value = "Delete AI settings by ID (deleteAiSettingsById)",
|
||||
notes = "Deletes the AI settings record by its `id`. " +
|
||||
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("/{aiSettingsId}")
|
||||
public boolean deleteAiSettingsById(
|
||||
@DeleteMapping("/{aiModelSettingsId}")
|
||||
public boolean deleteAiModelSettingsById(
|
||||
@Parameter(
|
||||
description = "ID of the AI settings record",
|
||||
description = "ID of the AI model settings record",
|
||||
required = true,
|
||||
example = "de7900d4-30e2-11f0-9cd2-0242ac120002"
|
||||
)
|
||||
@PathVariable("aiSettingsId") UUID aiSettingsUuid
|
||||
@PathVariable("aiModelSettingsId") UUID aiModelSettingsUuid
|
||||
) throws ThingsboardException {
|
||||
var user = getCurrentUser();
|
||||
var aiSettingsId = new AiSettingsId(aiSettingsUuid);
|
||||
accessControlService.checkPermission(user, Resource.AI_SETTINGS, Operation.DELETE);
|
||||
Optional<AiSettings> aiSettingsOpt = aiSettingsService.findAiSettingsByTenantIdAndId(user.getTenantId(), aiSettingsId);
|
||||
if (aiSettingsOpt.isEmpty()) {
|
||||
var settingsId = new AiModelSettingsId(aiModelSettingsUuid);
|
||||
accessControlService.checkPermission(user, Resource.AI_MODEL_SETTINGS, Operation.DELETE);
|
||||
Optional<AiModelSettings> toDelete = aiModelSettingsService.findAiModelSettingsByTenantIdAndId(user.getTenantId(), settingsId);
|
||||
if (toDelete.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
accessControlService.checkPermission(user, Resource.AI_SETTINGS, Operation.DELETE, aiSettingsId, aiSettingsOpt.get());
|
||||
return tbAiSettingsService.delete(aiSettingsOpt.get(), user);
|
||||
accessControlService.checkPermission(user, Resource.AI_MODEL_SETTINGS, Operation.DELETE, settingsId, toDelete.get());
|
||||
return tbAiModelSettingsService.delete(toDelete.get(), user);
|
||||
}
|
||||
|
||||
}
|
||||
@ -63,7 +63,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.AiSettings;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
import org.thingsboard.server.common.data.alarm.Alarm;
|
||||
import org.thingsboard.server.common.data.alarm.AlarmComment;
|
||||
import org.thingsboard.server.common.data.alarm.AlarmInfo;
|
||||
@ -78,7 +78,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.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.id.AiModelSettingsId;
|
||||
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.AiSettingsService;
|
||||
import org.thingsboard.server.dao.ai.AiModelSettingsService;
|
||||
import org.thingsboard.server.dao.alarm.AlarmCommentService;
|
||||
import org.thingsboard.server.dao.asset.AssetProfileService;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
@ -177,7 +177,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.TbAiSettingsService;
|
||||
import org.thingsboard.server.service.entitiy.ai.TbAiModelSettingsService;
|
||||
import org.thingsboard.server.service.entitiy.user.TbUserSettingsService;
|
||||
import org.thingsboard.server.service.ota.OtaPackageStateService;
|
||||
import org.thingsboard.server.service.profile.TbAssetProfileCache;
|
||||
@ -378,10 +378,10 @@ public abstract class BaseController {
|
||||
protected CalculatedFieldService calculatedFieldService;
|
||||
|
||||
@Autowired
|
||||
protected AiSettingsService aiSettingsService;
|
||||
protected AiModelSettingsService aiModelSettingsService;
|
||||
|
||||
@Autowired
|
||||
protected TbAiSettingsService tbAiSettingsService;
|
||||
protected TbAiModelSettingsService tbAiModelSettingsService;
|
||||
|
||||
@Value("${server.log_controller_error_stack_trace}")
|
||||
@Getter
|
||||
@ -691,8 +691,8 @@ public abstract class BaseController {
|
||||
case CALCULATED_FIELD:
|
||||
checkCalculatedFieldId(new CalculatedFieldId(entityId.getId()), operation);
|
||||
return;
|
||||
case AI_SETTINGS:
|
||||
checkAiSettingsId(new AiSettingsId(entityId.getId()), operation);
|
||||
case AI_MODEL_SETTINGS:
|
||||
checkAiModelSettingsId(new AiModelSettingsId(entityId.getId()), operation);
|
||||
return;
|
||||
default:
|
||||
checkEntityId(entityId, entitiesService::findEntityByTenantIdAndId, operation);
|
||||
@ -894,8 +894,8 @@ public abstract class BaseController {
|
||||
return checkEntityId(notificationTargetId, notificationTargetService::findNotificationTargetById, operation);
|
||||
}
|
||||
|
||||
AiSettings checkAiSettingsId(AiSettingsId aiSettingsId, Operation operation) throws ThingsboardException {
|
||||
return checkEntityId(aiSettingsId, (tenantId, id) -> aiSettingsService.findAiSettingsByTenantIdAndId(tenantId, id).orElse(null), operation);
|
||||
AiModelSettings checkAiModelSettingsId(AiModelSettingsId settingsId, Operation operation) throws ThingsboardException {
|
||||
return checkEntityId(settingsId, (tenantId, id) -> aiModelSettingsService.findAiModelSettingsByTenantIdAndId(tenantId, id).orElse(null), operation);
|
||||
}
|
||||
|
||||
protected <I extends EntityId> I emptyId(EntityType entityType) {
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.service.ai;
|
||||
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.rule.engine.api.RuleEngineAiModelService;
|
||||
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 RuleEngineAiModelService {
|
||||
|
||||
private final Langchain4jChatModelConfigurer chatModelConfigurer;
|
||||
|
||||
@Override
|
||||
public <C extends AiChatModelConfig<C>> ChatModel configureChatModel(AiChatModel<C> chatModel) {
|
||||
return chatModel.configure(chatModelConfigurer);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,106 +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.service.ai;
|
||||
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.googleai.GoogleAiGeminiChatModel;
|
||||
import dev.langchain4j.model.mistralai.MistralAiChatModel;
|
||||
import dev.langchain4j.model.openai.OpenAiChatModel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.rule.engine.api.RuleEngineAiService;
|
||||
import org.thingsboard.server.common.data.ai.AiSettings;
|
||||
import org.thingsboard.server.common.data.ai.model.AiModelConfig;
|
||||
import org.thingsboard.server.common.data.ai.model.GoogleAiGeminiChatModelConfig;
|
||||
import org.thingsboard.server.common.data.ai.model.MistralAiChatModelConfig;
|
||||
import org.thingsboard.server.common.data.ai.model.OpenAiChatModelConfig;
|
||||
import org.thingsboard.server.common.data.ai.provider.AiProviderConfig;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.dao.ai.AiSettingsService;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
class AiServiceImpl implements RuleEngineAiService {
|
||||
|
||||
private final AiSettingsService aiSettingsService;
|
||||
|
||||
@Override
|
||||
public ChatModel configureChatModel(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
Optional<AiSettings> aiSettingsOpt = aiSettingsService.findAiSettingsById(tenantId, aiSettingsId);
|
||||
if (aiSettingsOpt.isEmpty()) {
|
||||
throw new NoSuchElementException("AI settings with ID: " + aiSettingsId + " were not found");
|
||||
}
|
||||
var aiSettings = aiSettingsOpt.get();
|
||||
return configureChatModel(aiSettings.getProviderConfig(), aiSettings.getModelConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModel configureChatModel(AiProviderConfig providerConfig, AiModelConfig modelConfig) {
|
||||
return switch (providerConfig.getProvider()) {
|
||||
case OPENAI -> {
|
||||
var modelBuilder = OpenAiChatModel.builder()
|
||||
.apiKey(providerConfig.getApiKey())
|
||||
.modelName(modelConfig.getModel());
|
||||
|
||||
if (modelConfig instanceof OpenAiChatModelConfig config) {
|
||||
modelBuilder.temperature(config.getTemperature());
|
||||
if (config.getTimeoutSeconds() != null) {
|
||||
modelBuilder.timeout(Duration.ofSeconds(config.getTimeoutSeconds()));
|
||||
}
|
||||
modelBuilder.maxRetries(config.getMaxRetries());
|
||||
}
|
||||
|
||||
yield modelBuilder.build();
|
||||
}
|
||||
case MISTRAL_AI -> {
|
||||
var modelBuilder = MistralAiChatModel.builder()
|
||||
.apiKey(providerConfig.getApiKey())
|
||||
.modelName(modelConfig.getModel());
|
||||
|
||||
if (modelConfig instanceof MistralAiChatModelConfig config) {
|
||||
modelBuilder.temperature(config.getTemperature());
|
||||
if (config.getTimeoutSeconds() != null) {
|
||||
modelBuilder.timeout(Duration.ofSeconds(config.getTimeoutSeconds()));
|
||||
}
|
||||
modelBuilder.maxRetries(config.getMaxRetries());
|
||||
}
|
||||
|
||||
yield modelBuilder.build();
|
||||
}
|
||||
case GOOGLE_AI_GEMINI -> {
|
||||
var modelBuilder = GoogleAiGeminiChatModel.builder()
|
||||
.apiKey(providerConfig.getApiKey())
|
||||
.modelName(modelConfig.getModel());
|
||||
|
||||
if (modelConfig instanceof GoogleAiGeminiChatModelConfig config) {
|
||||
modelBuilder.temperature(config.getTemperature());
|
||||
if (config.getTimeoutSeconds() != null) {
|
||||
modelBuilder.timeout(Duration.ofSeconds(config.getTimeoutSeconds()));
|
||||
}
|
||||
modelBuilder.maxRetries(config.getMaxRetries());
|
||||
}
|
||||
|
||||
yield modelBuilder.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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.service.ai;
|
||||
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.GoogleAiGeminiChatModel;
|
||||
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 java.time.Duration;
|
||||
|
||||
@Component
|
||||
public 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(chatModel.modelId())
|
||||
.temperature(modelConfig.temperature())
|
||||
.timeout(toDuration(modelConfig.timeoutSeconds()))
|
||||
.maxRetries(modelConfig.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(chatModel.modelId())
|
||||
.temperature(modelConfig.temperature())
|
||||
.timeout(toDuration(modelConfig.timeoutSeconds()))
|
||||
.maxRetries(modelConfig.maxRetries())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModel configureChatModel(MistralAiChatModel chatModel) {
|
||||
MistralAiChatModel.Config modelConfig = chatModel.modelConfig();
|
||||
return dev.langchain4j.model.mistralai.MistralAiChatModel.builder()
|
||||
.apiKey(chatModel.providerConfig().apiKey())
|
||||
.modelName(chatModel.modelId())
|
||||
.temperature(modelConfig.temperature())
|
||||
.timeout(toDuration(modelConfig.timeoutSeconds()))
|
||||
.maxRetries(modelConfig.maxRetries())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static Duration toDuration(Integer timeoutSeconds) {
|
||||
return timeoutSeconds != null ? Duration.ofSeconds(timeoutSeconds) : null;
|
||||
}
|
||||
|
||||
}
|
||||
@ -112,7 +112,7 @@ public class EdgeEventSourcingListener {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (EntityType.TENANT == entityType || EntityType.EDGE == entityType || EntityType.AI_SETTINGS == entityType) {
|
||||
if (EntityType.TENANT == entityType || EntityType.EDGE == entityType || EntityType.AI_MODEL_SETTINGS == 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_SETTINGS:
|
||||
case API_USAGE_STATE, EDGE, AI_MODEL_SETTINGS:
|
||||
return false;
|
||||
case DOMAIN:
|
||||
if (entity instanceof Domain domain) {
|
||||
|
||||
@ -68,7 +68,7 @@ public class RelatedEdgesSourcingListener {
|
||||
|
||||
@TransactionalEventListener(
|
||||
fallbackExecution = true,
|
||||
condition = "#event.entityId.getEntityType() != T(org.thingsboard.server.common.data.EntityType).AI_SETTINGS"
|
||||
condition = "#event.entityId.getEntityType() != T(org.thingsboard.server.common.data.EntityType).AI_MODEL_SETTINGS"
|
||||
)
|
||||
public void handleEvent(DeleteEntityEvent<?> event) {
|
||||
executorService.submit(() -> {
|
||||
|
||||
@ -96,7 +96,7 @@ public class EntityStateSourcingListener {
|
||||
case ASSET -> {
|
||||
onAssetUpdate(event.getEntity(), event.getOldEntity());
|
||||
}
|
||||
case ASSET_PROFILE, ENTITY_VIEW, NOTIFICATION_RULE, AI_SETTINGS -> {
|
||||
case ASSET_PROFILE, ENTITY_VIEW, NOTIFICATION_RULE, AI_MODEL_SETTINGS -> {
|
||||
tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent);
|
||||
}
|
||||
case RULE_CHAIN -> {
|
||||
@ -158,7 +158,7 @@ public class EntityStateSourcingListener {
|
||||
Asset asset = (Asset) event.getEntity();
|
||||
tbClusterService.onAssetDeleted(tenantId, asset, null);
|
||||
}
|
||||
case ASSET_PROFILE, ENTITY_VIEW, CUSTOMER, EDGE, NOTIFICATION_RULE, AI_SETTINGS -> {
|
||||
case ASSET_PROFILE, ENTITY_VIEW, CUSTOMER, EDGE, NOTIFICATION_RULE, AI_MODEL_SETTINGS -> {
|
||||
tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, ComponentLifecycleEvent.DELETED);
|
||||
}
|
||||
case NOTIFICATION_REQUEST -> {
|
||||
|
||||
@ -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.AiSettings;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
import org.thingsboard.server.common.data.audit.ActionType;
|
||||
import org.thingsboard.server.dao.ai.AiSettingsService;
|
||||
import org.thingsboard.server.dao.ai.AiModelSettingsService;
|
||||
import org.thingsboard.server.queue.util.TbCoreComponent;
|
||||
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
|
||||
|
||||
@ -30,22 +30,22 @@ import static java.util.Objects.requireNonNullElseGet;
|
||||
@Service
|
||||
@TbCoreComponent
|
||||
@RequiredArgsConstructor
|
||||
class DefaultTbAiSettingsService extends AbstractTbEntityService implements TbAiSettingsService {
|
||||
class DefaultTbAiModelSettingsService extends AbstractTbEntityService implements TbAiModelSettingsService {
|
||||
|
||||
private final AiSettingsService aiSettingsService;
|
||||
private final AiModelSettingsService aiModelSettingsService;
|
||||
|
||||
@Override
|
||||
public AiSettings save(AiSettings aiSettings, User user) {
|
||||
var actionType = aiSettings.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
|
||||
public AiModelSettings save(AiModelSettings settings, User user) {
|
||||
var actionType = settings.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
|
||||
|
||||
var tenantId = user.getTenantId();
|
||||
aiSettings.setTenantId(tenantId);
|
||||
settings.setTenantId(tenantId);
|
||||
|
||||
AiSettings savedSettings;
|
||||
AiModelSettings savedSettings;
|
||||
try {
|
||||
savedSettings = aiSettingsService.save(aiSettings);
|
||||
savedSettings = aiModelSettingsService.save(settings);
|
||||
} catch (Exception e) {
|
||||
logEntityActionService.logEntityAction(tenantId, requireNonNullElseGet(aiSettings.getId(), () -> emptyId(EntityType.AI_SETTINGS)), aiSettings, actionType, user, e);
|
||||
logEntityActionService.logEntityAction(tenantId, requireNonNullElseGet(settings.getId(), () -> emptyId(EntityType.AI_MODEL_SETTINGS)), settings, actionType, user, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
@ -55,22 +55,22 @@ class DefaultTbAiSettingsService extends AbstractTbEntityService implements TbAi
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete(AiSettings aiSettings, User user) {
|
||||
public boolean delete(AiModelSettings settings, User user) {
|
||||
var actionType = ActionType.DELETED;
|
||||
|
||||
var tenantId = user.getTenantId();
|
||||
var aiSettingsId = aiSettings.getId();
|
||||
var settingsId = settings.getId();
|
||||
|
||||
boolean deleted;
|
||||
try {
|
||||
deleted = aiSettingsService.deleteByTenantIdAndId(tenantId, aiSettingsId);
|
||||
deleted = aiModelSettingsService.deleteByTenantIdAndId(tenantId, settingsId);
|
||||
} catch (Exception e) {
|
||||
logEntityActionService.logEntityAction(tenantId, aiSettingsId, aiSettings, actionType, user, e, aiSettingsId.toString());
|
||||
logEntityActionService.logEntityAction(tenantId, settingsId, settings, actionType, user, e, settingsId.toString());
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (deleted) {
|
||||
logEntityActionService.logEntityAction(tenantId, aiSettingsId, aiSettings, actionType, user, aiSettingsId.toString());
|
||||
logEntityActionService.logEntityAction(tenantId, settingsId, settings, actionType, user, settingsId.toString());
|
||||
}
|
||||
|
||||
return deleted;
|
||||
@ -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.AiSettings;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
|
||||
public interface TbAiSettingsService {
|
||||
public interface TbAiModelSettingsService {
|
||||
|
||||
AiSettings save(AiSettings aiSettings, User user);
|
||||
AiModelSettings save(AiModelSettings settings, User user);
|
||||
|
||||
boolean delete(AiSettings aiSettings, User user);
|
||||
boolean delete(AiModelSettings settings, User user);
|
||||
|
||||
}
|
||||
@ -590,7 +590,7 @@ public class DefaultTbClusterService implements TbClusterService {
|
||||
EntityType.ENTITY_VIEW,
|
||||
EntityType.NOTIFICATION_RULE,
|
||||
EntityType.CALCULATED_FIELD,
|
||||
EntityType.AI_SETTINGS,
|
||||
EntityType.AI_MODEL_SETTINGS,
|
||||
EntityType.TENANT_PROFILE,
|
||||
EntityType.DEVICE_PROFILE,
|
||||
EntityType.ASSET_PROFILE)
|
||||
|
||||
@ -52,7 +52,7 @@ public enum Resource {
|
||||
EntityType.NOTIFICATION_REQUEST, EntityType.NOTIFICATION_RULE),
|
||||
MOBILE_APP_SETTINGS,
|
||||
CALCULATED_FIELD(EntityType.CALCULATED_FIELD),
|
||||
AI_SETTINGS(EntityType.AI_SETTINGS);
|
||||
AI_MODEL_SETTINGS(EntityType.AI_MODEL_SETTINGS);
|
||||
|
||||
private final Set<EntityType> entityTypes;
|
||||
|
||||
|
||||
@ -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.AiSettings;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
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.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.CALCULATED_FIELD, tenantEntityPermissionChecker);
|
||||
put(Resource.AI_SETTINGS, aiSettingsPermissionChecker);
|
||||
put(Resource.AI_MODEL_SETTINGS, aiModelSettingsPermissionChecker);
|
||||
}
|
||||
|
||||
public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {
|
||||
@ -149,7 +149,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
|
||||
|
||||
};
|
||||
|
||||
private static final PermissionChecker<AiSettingsId, AiSettings> aiSettingsPermissionChecker = new PermissionChecker<>() {
|
||||
private static final PermissionChecker<AiModelSettingsId, AiModelSettings> aiModelSettingsPermissionChecker = 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, AiSettingsId entityId, AiSettings entity) {
|
||||
public boolean hasPermission(SecurityUser user, Operation operation, AiModelSettingsId entityId, AiModelSettings entity) {
|
||||
return user.getTenantId().equals(entity.getTenantId());
|
||||
}
|
||||
|
||||
|
||||
@ -657,9 +657,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
|
||||
aiSettings:
|
||||
timeToLiveInMinutes: "${CACHE_SPECS_AI_SETTINGS_TTL:1440}" # AI settings cache TTL
|
||||
maxSize: "${CACHE_SPECS_AI_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
|
||||
|
||||
# Deliberately placed outside the 'specs' group above
|
||||
notificationRules:
|
||||
@ -875,7 +875,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_settings": "${AUDIT_LOG_MASK_AI_SETTINGS:W}" # AI settings logging levels.
|
||||
"ai_model_settings": "${AUDIT_LOG_MASK_AI_MODEL_SETTINGS:W}" # AI model settings logging levels.
|
||||
sink:
|
||||
# Type of external sink. possible options: none, elasticsearch
|
||||
type: "${AUDIT_LOG_SINK_TYPE:none}"
|
||||
|
||||
@ -16,8 +16,8 @@
|
||||
package org.thingsboard.server.dao.ai;
|
||||
|
||||
import com.google.common.util.concurrent.FluentFuture;
|
||||
import org.thingsboard.server.common.data.ai.AiSettings;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
import org.thingsboard.server.common.data.id.AiModelSettingsId;
|
||||
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 AiSettingsService extends EntityDaoService {
|
||||
public interface AiModelSettingsService extends EntityDaoService {
|
||||
|
||||
AiSettings save(AiSettings aiSettings);
|
||||
AiModelSettings save(AiModelSettings settings);
|
||||
|
||||
Optional<AiSettings> findAiSettingsById(TenantId tenantId, AiSettingsId aiSettingsId);
|
||||
Optional<AiModelSettings> findAiModelSettingsById(TenantId tenantId, AiModelSettingsId settingsId);
|
||||
|
||||
PageData<AiSettings> findAiSettingsByTenantId(TenantId tenantId, PageLink pageLink);
|
||||
PageData<AiModelSettings> findAiModelSettingsByTenantId(TenantId tenantId, PageLink pageLink);
|
||||
|
||||
Optional<AiSettings> findAiSettingsByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId);
|
||||
Optional<AiModelSettings> findAiModelSettingsByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId);
|
||||
|
||||
FluentFuture<Optional<AiSettings>> findAiSettingsByTenantIdAndIdAsync(TenantId tenantId, AiSettingsId aiSettingsId);
|
||||
FluentFuture<Optional<AiModelSettings>> findAiModelSettingsByTenantIdAndIdAsync(TenantId tenantId, AiModelSettingsId settingsId);
|
||||
|
||||
boolean deleteByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId);
|
||||
boolean deleteByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId);
|
||||
|
||||
}
|
||||
@ -112,6 +112,10 @@
|
||||
<artifactId>leshan-core</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -37,7 +37,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_SETTINGS_CACHE = "aiSettings";
|
||||
public static final String AI_MODEL_SETTINGS_CACHE = "aiModelSettings";
|
||||
|
||||
public static final String ASSET_PROFILE_CACHE = "assetProfiles";
|
||||
public static final String ATTRIBUTES_CACHE = "attributes";
|
||||
|
||||
@ -64,10 +64,10 @@ public enum EntityType {
|
||||
MOBILE_APP_BUNDLE(38),
|
||||
CALCULATED_FIELD(39),
|
||||
CALCULATED_FIELD_LINK(40),
|
||||
AI_SETTINGS(41, "ai_settings") {
|
||||
AI_MODEL_SETTINGS(41, "ai_model_settings") {
|
||||
@Override
|
||||
public String getNormalName() {
|
||||
return "AI settings";
|
||||
return "AI model settings";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -24,10 +24,8 @@ import org.thingsboard.server.common.data.BaseData;
|
||||
import org.thingsboard.server.common.data.HasName;
|
||||
import org.thingsboard.server.common.data.HasTenantId;
|
||||
import org.thingsboard.server.common.data.HasVersion;
|
||||
import org.thingsboard.server.common.data.ai.model.AiModelConfig;
|
||||
import org.thingsboard.server.common.data.ai.provider.AiProvider;
|
||||
import org.thingsboard.server.common.data.ai.provider.AiProviderConfig;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.ai.model.AiModel;
|
||||
import org.thingsboard.server.common.data.id.AiModelSettingsId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
|
||||
import java.io.Serial;
|
||||
@ -36,7 +34,7 @@ import java.io.Serial;
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public final class AiSettings extends BaseData<AiSettingsId> implements HasTenantId, HasVersion, HasName {
|
||||
public final class AiModelSettings extends BaseData<AiModelSettingsId> implements HasTenantId, HasVersion, HasName {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 9017108678716011604L;
|
||||
@ -44,7 +42,7 @@ public final class AiSettings extends BaseData<AiSettingsId> implements HasTenan
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_ONLY,
|
||||
description = "JSON object representing the ID of the tenant associated with these AI settings",
|
||||
description = "JSON object representing the ID of the tenant associated with these AI model settings",
|
||||
example = "e3c4b7d2-5678-4a9b-0c1d-2e3f4a5b6c7d"
|
||||
)
|
||||
TenantId tenantId;
|
||||
@ -52,7 +50,7 @@ public final class AiSettings extends BaseData<AiSettingsId> implements HasTenan
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_ONLY,
|
||||
description = "Version of the AI settings; increments automatically whenever the settings are changed",
|
||||
description = "Version of the AI model settings; increments automatically whenever the settings are changed",
|
||||
example = "7",
|
||||
defaultValue = "1"
|
||||
)
|
||||
@ -61,49 +59,21 @@ public final class AiSettings extends BaseData<AiSettingsId> implements HasTenan
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Human-readable name of the AI settings; must be unique within the scope of the tenant",
|
||||
description = "Human-readable name of the AI model settings; must be unique within the scope of the tenant",
|
||||
example = "Default AI Settings"
|
||||
)
|
||||
String name;
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Name of the AI provider",
|
||||
example = "OPENAI",
|
||||
allowableValues = {"OPENAI", "GOOGLE_AI_GEMINI", "MISTRAL_AI"},
|
||||
type = "string"
|
||||
)
|
||||
AiProvider provider;
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Configuration specific to the AI provider"
|
||||
)
|
||||
AiProviderConfig providerConfig;
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Identifier of the AI model",
|
||||
example = "gpt-4o-mini"
|
||||
)
|
||||
String model;
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.NOT_REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = """
|
||||
Optional configuration specific to the AI model.
|
||||
If provided, it must be one of the known `AiModelConfig` subtypes and any settings
|
||||
you specify will override the model’s defaults; if omitted, the model will run with its built-in defaults."""
|
||||
description = "Configuration of the AI model"
|
||||
)
|
||||
AiModelConfig modelConfig;
|
||||
AiModel<?> configuration;
|
||||
|
||||
public AiSettings() {}
|
||||
public AiModelSettings() {}
|
||||
|
||||
public AiSettings(AiSettingsId id) {
|
||||
public AiModelSettings(AiModelSettingsId id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.GoogleAiGeminiChatModel;
|
||||
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.AiProviderConfig;
|
||||
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = "modelId",
|
||||
visible = true
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = OpenAiChatModel.class, name = "gpt-4o"),
|
||||
@JsonSubTypes.Type(value = GoogleAiGeminiChatModel.class, name = "gemini-2.5-flash"),
|
||||
@JsonSubTypes.Type(value = MistralAiChatModel.class, name = "mistral-medium-latest")
|
||||
})
|
||||
public interface AiModel<C extends AiModelConfig<C>> {
|
||||
|
||||
AiProviderConfig providerConfig();
|
||||
|
||||
AiModelType modelType();
|
||||
|
||||
String modelId();
|
||||
|
||||
C modelConfig();
|
||||
|
||||
AiModel<C> withModelConfig(C config);
|
||||
|
||||
}
|
||||
@ -15,42 +15,4 @@
|
||||
*/
|
||||
package org.thingsboard.server.common.data.ai.model;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.EXISTING_PROPERTY,
|
||||
property = "model",
|
||||
visible = true
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = OpenAiChatModelConfig.class, name = "gpt-4o"),
|
||||
@JsonSubTypes.Type(value = OpenAiChatModelConfig.class, name = "gpt-4o-mini"),
|
||||
@JsonSubTypes.Type(value = GoogleAiGeminiChatModelConfig.class, name = "gemini-2.0-flash"),
|
||||
@JsonSubTypes.Type(value = MistralAiChatModelConfig.class, name = "mistral-medium-latest")
|
||||
})
|
||||
public abstract class AiModelConfig {
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Identifier of the AI model"
|
||||
)
|
||||
private String model;
|
||||
|
||||
public abstract Integer getTimeoutSeconds();
|
||||
|
||||
public abstract void setTimeoutSeconds(Integer timeoutSeconds);
|
||||
|
||||
public abstract Integer getMaxRetries();
|
||||
|
||||
public abstract void setMaxRetries(Integer timeoutSeconds);
|
||||
|
||||
|
||||
}
|
||||
public interface AiModelConfig<C extends AiModelConfig<C>> {}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public enum AiModelType {
|
||||
|
||||
CHAT
|
||||
|
||||
}
|
||||
@ -1,62 +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 io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@NoArgsConstructor
|
||||
@Schema(
|
||||
name = "GoogleAiGeminiChatModelConfig",
|
||||
description = "Configuration for Google AI Gemini chat models"
|
||||
)
|
||||
public final class GoogleAiGeminiChatModelConfig extends AiModelConfig {
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Identifier of the AI model",
|
||||
allowableValues = "gemini-2.0-flash",
|
||||
example = "gemini-2.0-flash"
|
||||
)
|
||||
public String getModel() {
|
||||
return super.getModel();
|
||||
}
|
||||
|
||||
@Schema(
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Sampling temperature to control randomness: 0.0 (most deterministic) to 1.0 (most creative)",
|
||||
example = "0.7"
|
||||
)
|
||||
private Double temperature;
|
||||
|
||||
@Schema(
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Timeout (in seconds) for establishing HTTP connection"
|
||||
)
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
@Schema(
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Maximum number of times to retry an LLM call upon exception (except for non-retriable ones like authentication or invalid request errors)"
|
||||
)
|
||||
private Integer maxRetries;
|
||||
|
||||
}
|
||||
@ -1,62 +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 io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@NoArgsConstructor
|
||||
@Schema(
|
||||
name = "MistralAiChatModelConfig",
|
||||
description = "Configuration for Mistral AI chat models"
|
||||
)
|
||||
public final class MistralAiChatModelConfig extends AiModelConfig {
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Identifier of the AI model",
|
||||
allowableValues = "mistral-medium-latest",
|
||||
example = "mistral-medium-latest"
|
||||
)
|
||||
public String getModel() {
|
||||
return super.getModel();
|
||||
}
|
||||
|
||||
@Schema(
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Sampling temperature to control randomness: 0.0 (most deterministic) to 1.0 (most creative)",
|
||||
example = "0.7"
|
||||
)
|
||||
private Double temperature;
|
||||
|
||||
@Schema(
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Timeout (in seconds) for the entire HTTP call: applied to connect, read, and write operations"
|
||||
)
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
@Schema(
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Maximum number of times to retry an LLM call upon exception (except for non-retriable ones like authentication or invalid request errors)"
|
||||
)
|
||||
private Integer maxRetries;
|
||||
|
||||
}
|
||||
@ -1,62 +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 io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@NoArgsConstructor
|
||||
@Schema(
|
||||
name = "OpenAiChatModelConfig",
|
||||
description = "Configuration for OpenAI chat models"
|
||||
)
|
||||
public final class OpenAiChatModelConfig extends AiModelConfig {
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Identifier of the AI model",
|
||||
allowableValues = {"gpt-4o", "gpt-4o-mini"},
|
||||
example = "gpt-4o"
|
||||
)
|
||||
public String getModel() {
|
||||
return super.getModel();
|
||||
}
|
||||
|
||||
@Schema(
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Sampling temperature to control randomness: 0.0 (most deterministic) to 1.0 (most creative)",
|
||||
example = "0.7"
|
||||
)
|
||||
private Double temperature;
|
||||
|
||||
@Schema(
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Timeout (in seconds) for both establishing HTTP connection and receiving a response"
|
||||
)
|
||||
private Integer timeoutSeconds;
|
||||
|
||||
@Schema(
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Maximum number of times to retry an LLM call upon exception (except for non-retriable ones like authentication or invalid request errors)"
|
||||
)
|
||||
private Integer maxRetries;
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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<C extends AiChatModelConfig<C>> extends AiModel<C>
|
||||
permits OpenAiChatModel, GoogleAiGeminiChatModel, MistralAiChatModel {
|
||||
|
||||
ChatModel configure(Langchain4jChatModelConfigurer configurer);
|
||||
|
||||
@Override
|
||||
default AiModelType modelType() {
|
||||
return AiModelType.CHAT;
|
||||
}
|
||||
|
||||
@Override
|
||||
C modelConfig();
|
||||
|
||||
@Override
|
||||
AiChatModel<C> withModelConfig(C config);
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 org.thingsboard.server.common.data.ai.model.AiModelConfig;
|
||||
|
||||
public sealed interface AiChatModelConfig<C extends AiChatModelConfig<C>> extends AiModelConfig<C>
|
||||
permits OpenAiChatModel.Config, GoogleAiGeminiChatModel.Config, MistralAiChatModel.Config {
|
||||
|
||||
Double temperature();
|
||||
|
||||
Integer timeoutSeconds();
|
||||
|
||||
Integer maxRetries();
|
||||
|
||||
C withTemperature(Double temperature);
|
||||
|
||||
C withTimeoutSeconds(Integer timeoutSeconds);
|
||||
|
||||
C withMaxRetries(Integer maxRetries);
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright © 2016-2025 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.common.data.ai.model.chat;
|
||||
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import org.thingsboard.server.common.data.ai.provider.GoogleAiGeminiProviderConfig;
|
||||
|
||||
public record GoogleAiGeminiChatModel(
|
||||
GoogleAiGeminiProviderConfig providerConfig,
|
||||
String modelId,
|
||||
Config modelConfig
|
||||
) implements AiChatModel<GoogleAiGeminiChatModel.Config> {
|
||||
|
||||
public record Config(
|
||||
Double temperature,
|
||||
Integer timeoutSeconds,
|
||||
Integer maxRetries
|
||||
) implements AiChatModelConfig<GoogleAiGeminiChatModel.Config> {
|
||||
|
||||
@Override
|
||||
public Config withTemperature(Double temperature) {
|
||||
return new Config(temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config withTimeoutSeconds(Integer timeoutSeconds) {
|
||||
return new Config(temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config withMaxRetries(Integer maxRetries) {
|
||||
return new Config(temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModel configure(Langchain4jChatModelConfigurer configurer) {
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GoogleAiGeminiChatModel withModelConfig(GoogleAiGeminiChatModel.Config config) {
|
||||
return new GoogleAiGeminiChatModel(providerConfig, modelId, config);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public interface Langchain4jChatModelConfigurer {
|
||||
|
||||
ChatModel configureChatModel(OpenAiChatModel chatModel);
|
||||
|
||||
ChatModel configureChatModel(GoogleAiGeminiChatModel chatModel);
|
||||
|
||||
ChatModel configureChatModel(MistralAiChatModel chatModel);
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright © 2016-2025 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.common.data.ai.model.chat;
|
||||
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import org.thingsboard.server.common.data.ai.provider.MistralAiProviderConfig;
|
||||
|
||||
public record MistralAiChatModel(
|
||||
MistralAiProviderConfig providerConfig,
|
||||
String modelId,
|
||||
Config modelConfig
|
||||
) implements AiChatModel<MistralAiChatModel.Config> {
|
||||
|
||||
public record Config(
|
||||
Double temperature,
|
||||
Integer timeoutSeconds,
|
||||
Integer maxRetries
|
||||
) implements AiChatModelConfig<MistralAiChatModel.Config> {
|
||||
|
||||
@Override
|
||||
public Config withTemperature(Double temperature) {
|
||||
return new Config(temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config withTimeoutSeconds(Integer timeoutSeconds) {
|
||||
return new Config(temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config withMaxRetries(Integer maxRetries) {
|
||||
return new Config(temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModel configure(Langchain4jChatModelConfigurer configurer) {
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MistralAiChatModel withModelConfig(Config config) {
|
||||
return new MistralAiChatModel(providerConfig, modelId, config);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright © 2016-2025 The Thingsboard Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.thingsboard.server.common.data.ai.model.chat;
|
||||
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import org.thingsboard.server.common.data.ai.provider.OpenAiProviderConfig;
|
||||
|
||||
public record OpenAiChatModel(
|
||||
OpenAiProviderConfig providerConfig,
|
||||
String modelId,
|
||||
Config modelConfig
|
||||
) implements AiChatModel<OpenAiChatModel.Config> {
|
||||
|
||||
public record Config(
|
||||
Double temperature,
|
||||
Integer timeoutSeconds,
|
||||
Integer maxRetries
|
||||
) implements AiChatModelConfig<OpenAiChatModel.Config> {
|
||||
|
||||
@Override
|
||||
public OpenAiChatModel.Config withTemperature(Double temperature) {
|
||||
return new Config(temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenAiChatModel.Config withTimeoutSeconds(Integer timeoutSeconds) {
|
||||
return new Config(temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenAiChatModel.Config withMaxRetries(Integer maxRetries) {
|
||||
return new Config(temperature, timeoutSeconds, maxRetries);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChatModel configure(Langchain4jChatModelConfigurer configurer) {
|
||||
return configurer.configureChatModel(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OpenAiChatModel withModelConfig(OpenAiChatModel.Config config) {
|
||||
return new OpenAiChatModel(providerConfig, modelId, config);
|
||||
}
|
||||
|
||||
}
|
||||
@ -17,32 +17,22 @@ package org.thingsboard.server.common.data.ai.provider;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@JsonTypeInfo(
|
||||
use = JsonTypeInfo.Id.NAME,
|
||||
include = JsonTypeInfo.As.EXISTING_PROPERTY,
|
||||
property = "provider",
|
||||
visible = true
|
||||
include = JsonTypeInfo.As.PROPERTY,
|
||||
property = "provider"
|
||||
)
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = OpenAiProviderConfig.class, name = "OPENAI"),
|
||||
@JsonSubTypes.Type(value = GoogleAiGeminiProviderConfig.class, name = "GOOGLE_AI_GEMINI"),
|
||||
@JsonSubTypes.Type(value = MistralAiProviderConfig.class, name = "MISTRAL_AI")
|
||||
})
|
||||
public abstract class AiProviderConfig {
|
||||
public sealed interface AiProviderConfig
|
||||
permits OpenAiProviderConfig, GoogleAiGeminiProviderConfig, MistralAiProviderConfig {
|
||||
|
||||
public abstract AiProvider getProvider();
|
||||
AiProvider provider();
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "API key for authenticating with the AI provider"
|
||||
)
|
||||
private String apiKey;
|
||||
String apiKey();
|
||||
|
||||
}
|
||||
|
||||
@ -15,26 +15,16 @@
|
||||
*/
|
||||
package org.thingsboard.server.common.data.ai.provider;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
public record GoogleAiGeminiProviderConfig(String apiKey) implements AiProviderConfig {
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(
|
||||
name = "GoogleAiGeminiProviderConfig",
|
||||
description = "Configuration for the Google AI Gemini provider"
|
||||
)
|
||||
public final class GoogleAiGeminiProviderConfig extends AiProviderConfig {
|
||||
@Override
|
||||
public AiProvider provider() {
|
||||
return AiProvider.GOOGLE_AI_GEMINI;
|
||||
}
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Name of the AI provider",
|
||||
example = "GOOGLE_AI_GEMINI",
|
||||
allowableValues = "GOOGLE_AI_GEMINI",
|
||||
type = "string"
|
||||
)
|
||||
private AiProvider provider = AiProvider.GOOGLE_AI_GEMINI;
|
||||
@Override
|
||||
public String apiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -15,26 +15,16 @@
|
||||
*/
|
||||
package org.thingsboard.server.common.data.ai.provider;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
public record MistralAiProviderConfig(String apiKey) implements AiProviderConfig {
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(
|
||||
name = "MistralAiProviderConfig",
|
||||
description = "Configuration for the Mistral AI provider"
|
||||
)
|
||||
public final class MistralAiProviderConfig extends AiProviderConfig {
|
||||
@Override
|
||||
public AiProvider provider() {
|
||||
return AiProvider.MISTRAL_AI;
|
||||
}
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Name of the AI provider",
|
||||
example = "MISTRAL_AI",
|
||||
allowableValues = "MISTRAL_AI",
|
||||
type = "string"
|
||||
)
|
||||
private AiProvider provider = AiProvider.MISTRAL_AI;
|
||||
@Override
|
||||
public String apiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -15,26 +15,16 @@
|
||||
*/
|
||||
package org.thingsboard.server.common.data.ai.provider;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
public record OpenAiProviderConfig(String apiKey) implements AiProviderConfig {
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Schema(
|
||||
name = "OpenAiProviderConfig",
|
||||
description = "Configuration for the OpenAI provider"
|
||||
)
|
||||
public final class OpenAiProviderConfig extends AiProviderConfig {
|
||||
@Override
|
||||
public AiProvider provider() {
|
||||
return AiProvider.OPENAI;
|
||||
}
|
||||
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
accessMode = Schema.AccessMode.READ_WRITE,
|
||||
description = "Name of the AI provider",
|
||||
example = "OPENAI",
|
||||
allowableValues = "OPENAI",
|
||||
type = "string"
|
||||
)
|
||||
private AiProvider provider = AiProvider.OPENAI;
|
||||
@Override
|
||||
public String apiKey() {
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -23,29 +23,29 @@ import org.thingsboard.server.common.data.EntityType;
|
||||
import java.io.Serial;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class AiSettingsId extends UUIDBased implements EntityId {
|
||||
public final class AiModelSettingsId extends UUIDBased implements EntityId {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 3021036138554389754L;
|
||||
|
||||
@JsonCreator
|
||||
public AiSettingsId(@JsonProperty("id") UUID id) {
|
||||
public AiModelSettingsId(@JsonProperty("id") UUID id) {
|
||||
super(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Schema(
|
||||
requiredMode = Schema.RequiredMode.REQUIRED,
|
||||
description = "Entity type of the AI settings",
|
||||
example = "AI_SETTINGS",
|
||||
allowableValues = "AI_SETTINGS"
|
||||
description = "Entity type of the AI model settings",
|
||||
example = "AI_MODEL_SETTINGS",
|
||||
allowableValues = "AI_MODEL_SETTINGS"
|
||||
)
|
||||
public EntityType getEntityType() {
|
||||
return EntityType.AI_SETTINGS;
|
||||
return EntityType.AI_MODEL_SETTINGS;
|
||||
}
|
||||
|
||||
public static AiSettingsId fromString(String uuid) {
|
||||
return new AiSettingsId(UUID.fromString(uuid));
|
||||
public static AiModelSettingsId fromString(String uuid) {
|
||||
return new AiModelSettingsId(UUID.fromString(uuid));
|
||||
}
|
||||
|
||||
}
|
||||
@ -84,7 +84,7 @@ public class EntityIdFactory {
|
||||
case MOBILE_APP_BUNDLE -> new MobileAppBundleId(uuid);
|
||||
case CALCULATED_FIELD -> new CalculatedFieldId(uuid);
|
||||
case CALCULATED_FIELD_LINK -> new CalculatedFieldLinkId(uuid);
|
||||
case AI_SETTINGS -> new AiSettingsId(uuid);
|
||||
case AI_MODEL_SETTINGS -> new AiModelSettingsId(uuid);
|
||||
default -> throw new IllegalArgumentException("EntityType " + type + " is not supported!");
|
||||
};
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ enum EntityTypeProto {
|
||||
MOBILE_APP_BUNDLE = 38;
|
||||
CALCULATED_FIELD = 39;
|
||||
CALCULATED_FIELD_LINK = 40;
|
||||
AI_SETTINGS = 41;
|
||||
AI_MODEL_SETTINGS = 41;
|
||||
}
|
||||
|
||||
enum ApiUsageRecordKeyProto {
|
||||
|
||||
@ -15,15 +15,15 @@
|
||||
*/
|
||||
package org.thingsboard.server.dao.ai;
|
||||
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.id.AiModelSettingsId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
record AiSettingsCacheEvictEvent(Set<AiSettingsCacheKey> keys) {
|
||||
record AiModelSettingsCacheEvictEvent(Set<AiModelSettingsCacheKey> keys) {
|
||||
|
||||
static AiSettingsCacheEvictEvent of(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
return new AiSettingsCacheEvictEvent(Set.of(AiSettingsCacheKey.of(tenantId, aiSettingsId)));
|
||||
static AiModelSettingsCacheEvictEvent of(TenantId tenantId, AiModelSettingsId settingsId) {
|
||||
return new AiModelSettingsCacheEvictEvent(Set.of(AiModelSettingsCacheKey.of(tenantId, settingsId)));
|
||||
}
|
||||
|
||||
}
|
||||
@ -17,22 +17,22 @@ 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.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.id.AiModelSettingsId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
record AiSettingsCacheKey(UUID tenantId, UUID aiSettingsId) implements VersionedCacheKey {
|
||||
record AiModelSettingsCacheKey(UUID tenantId, UUID settingsId) implements VersionedCacheKey {
|
||||
|
||||
AiSettingsCacheKey {
|
||||
AiModelSettingsCacheKey {
|
||||
requireNonNull(tenantId);
|
||||
requireNonNull(aiSettingsId);
|
||||
requireNonNull(settingsId);
|
||||
}
|
||||
|
||||
static AiSettingsCacheKey of(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
return new AiSettingsCacheKey(tenantId.getId(), aiSettingsId.getId());
|
||||
static AiModelSettingsCacheKey of(TenantId tenantId, AiModelSettingsId settingsId) {
|
||||
return new AiModelSettingsCacheKey(tenantId.getId(), settingsId.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -43,7 +43,7 @@ record AiSettingsCacheKey(UUID tenantId, UUID aiSettingsId) implements Versioned
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return /* cache name */ "_" + tenantId + "_" + aiSettingsId;
|
||||
return /* cache name */ "_" + tenantId + "_" + settingsId;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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.AiSettings;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
|
||||
@Component("AiSettingsCache")
|
||||
@Component("AiModelSettingsCache")
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
|
||||
class AiSettingsCaffeineCache extends VersionedCaffeineTbCache<AiSettingsCacheKey, AiSettings> {
|
||||
class AiModelSettingsCaffeineCache extends VersionedCaffeineTbCache<AiModelSettingsCacheKey, AiModelSettings> {
|
||||
|
||||
AiSettingsCaffeineCache(CacheManager cacheManager) {
|
||||
super(cacheManager, CacheConstants.AI_SETTINGS_CACHE);
|
||||
AiModelSettingsCaffeineCache(CacheManager cacheManager) {
|
||||
super(cacheManager, CacheConstants.AI_MODEL_SETTINGS_CACHE);
|
||||
}
|
||||
|
||||
}
|
||||
@ -15,22 +15,22 @@
|
||||
*/
|
||||
package org.thingsboard.server.dao.ai;
|
||||
|
||||
import org.thingsboard.server.common.data.ai.AiSettings;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
import org.thingsboard.server.common.data.id.AiModelSettingsId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.dao.Dao;
|
||||
import org.thingsboard.server.dao.TenantEntityDao;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface AiSettingsDao extends Dao<AiSettings>, TenantEntityDao<AiSettings> {
|
||||
public interface AiModelSettingsDao extends Dao<AiModelSettings>, TenantEntityDao<AiModelSettings> {
|
||||
|
||||
Optional<AiSettings> findByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId);
|
||||
Optional<AiModelSettings> findByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId);
|
||||
|
||||
boolean deleteById(TenantId tenantId, AiSettingsId aiSettingsId);
|
||||
boolean deleteById(TenantId tenantId, AiModelSettingsId settingsId);
|
||||
|
||||
int deleteByTenantId(TenantId tenantId);
|
||||
|
||||
boolean deleteByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId);
|
||||
boolean deleteByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId);
|
||||
|
||||
}
|
||||
@ -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.AiSettings;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
|
||||
@Component("AiSettingsCache")
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
|
||||
class AiSettingsRedisCache extends VersionedRedisTbCache<AiSettingsCacheKey, AiSettings> {
|
||||
class AiModelSettingsRedisCache extends VersionedRedisTbCache<AiModelSettingsCacheKey, AiModelSettings> {
|
||||
|
||||
AiSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
|
||||
super(CacheConstants.AI_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(AiSettings.class));
|
||||
AiModelSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
|
||||
super(CacheConstants.AI_MODEL_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbJsonRedisSerializer<>(AiModelSettings.class));
|
||||
}
|
||||
|
||||
}
|
||||
@ -22,8 +22,8 @@ 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.AiSettings;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
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;
|
||||
@ -43,29 +43,29 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
class AiSettingsServiceImpl extends CachedVersionedEntityService<AiSettingsCacheKey, AiSettings, AiSettingsCacheEvictEvent> implements AiSettingsService {
|
||||
class AiModelSettingsServiceImpl extends CachedVersionedEntityService<AiModelSettingsCacheKey, AiModelSettings, AiModelSettingsCacheEvictEvent> implements AiModelSettingsService {
|
||||
|
||||
private final DataValidator<AiSettings> aiSettingsValidator;
|
||||
private final DataValidator<AiModelSettings> aiModelSettingsValidator;
|
||||
|
||||
private final JpaExecutorService jpaExecutor;
|
||||
private final AiSettingsDao aiSettingsDao;
|
||||
private final AiModelSettingsDao aiModelSettingsDao;
|
||||
|
||||
@Override
|
||||
@TransactionalEventListener
|
||||
public void handleEvictEvent(AiSettingsCacheEvictEvent event) {
|
||||
public void handleEvictEvent(AiModelSettingsCacheEvictEvent event) {
|
||||
cache.evict(event.keys());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public AiSettings save(AiSettings aiSettings) {
|
||||
AiSettings oldSettings = aiSettingsValidator.validate(aiSettings, AiSettings::getTenantId);
|
||||
public AiModelSettings save(AiModelSettings settings) {
|
||||
AiModelSettings oldSettings = aiModelSettingsValidator.validate(settings, AiModelSettings::getTenantId);
|
||||
|
||||
AiSettings savedSettings;
|
||||
AiModelSettings savedSettings;
|
||||
try {
|
||||
savedSettings = aiSettingsDao.saveAndFlush(aiSettings.getTenantId(), aiSettings);
|
||||
savedSettings = aiModelSettingsDao.saveAndFlush(settings.getTenantId(), settings);
|
||||
} catch (Exception e) {
|
||||
checkConstraintViolation(e, "ai_settings_name_unq_key", "AI settings record with such name already exists!");
|
||||
checkConstraintViolation(e, "ai_model_settings_name_unq_key", "AI model settings with such name already exist!");
|
||||
throw e;
|
||||
}
|
||||
|
||||
@ -78,65 +78,65 @@ class AiSettingsServiceImpl extends CachedVersionedEntityService<AiSettingsCache
|
||||
.broadcastEvent(true)
|
||||
.build());
|
||||
|
||||
publishEvictEvent(AiSettingsCacheEvictEvent.of(savedSettings.getTenantId(), savedSettings.getId()));
|
||||
publishEvictEvent(AiModelSettingsCacheEvictEvent.of(savedSettings.getTenantId(), savedSettings.getId()));
|
||||
|
||||
return savedSettings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AiSettings> findAiSettingsById(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
return Optional.ofNullable(aiSettingsDao.findById(tenantId, aiSettingsId.getId()));
|
||||
public Optional<AiModelSettings> findAiModelSettingsById(TenantId tenantId, AiModelSettingsId settingsId) {
|
||||
return Optional.ofNullable(aiModelSettingsDao.findById(tenantId, settingsId.getId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<AiSettings> findAiSettingsByTenantId(TenantId tenantId, PageLink pageLink) {
|
||||
public PageData<AiModelSettings> findAiModelSettingsByTenantId(TenantId tenantId, PageLink pageLink) {
|
||||
validatePageLink(pageLink);
|
||||
return aiSettingsDao.findAllByTenantId(tenantId, pageLink);
|
||||
return aiModelSettingsDao.findAllByTenantId(tenantId, pageLink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<AiSettings> findAiSettingsByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
var cacheKey = AiSettingsCacheKey.of(tenantId, aiSettingsId);
|
||||
return Optional.ofNullable(cache.get(cacheKey, () -> aiSettingsDao.findByTenantIdAndId(tenantId, aiSettingsId).orElse(null)));
|
||||
public Optional<AiModelSettings> 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<Optional<AiSettings>> findAiSettingsByTenantIdAndIdAsync(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
return FluentFuture.from(jpaExecutor.submit(() -> findAiSettingsByTenantIdAndId(tenantId, aiSettingsId)));
|
||||
public FluentFuture<Optional<AiModelSettings>> findAiModelSettingsByTenantIdAndIdAsync(TenantId tenantId, AiModelSettingsId settingsId) {
|
||||
return FluentFuture.from(jpaExecutor.submit(() -> findAiModelSettingsByTenantIdAndId(tenantId, settingsId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean deleteByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
return deleteByTenantIdAndIdInternal(tenantId, aiSettingsId);
|
||||
public boolean deleteByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId) {
|
||||
return deleteByTenantIdAndIdInternal(tenantId, settingsId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
|
||||
return findAiSettingsByTenantIdAndId(tenantId, (AiSettingsId) entityId)
|
||||
.map(aiSettings -> aiSettings); // necessary to cast to HasId<?>
|
||||
return findAiModelSettingsByTenantIdAndId(tenantId, (AiModelSettingsId) entityId)
|
||||
.map(settings -> settings); // necessary to cast to HasId<?>
|
||||
}
|
||||
|
||||
@Override
|
||||
public long countByTenantId(TenantId tenantId) {
|
||||
return aiSettingsDao.countByTenantId(tenantId);
|
||||
return aiModelSettingsDao.countByTenantId(tenantId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
|
||||
deleteByTenantIdAndIdInternal(tenantId, new AiSettingsId(id.getId()));
|
||||
deleteByTenantIdAndIdInternal(tenantId, new AiModelSettingsId(id.getId()));
|
||||
}
|
||||
|
||||
private boolean deleteByTenantIdAndIdInternal(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
Optional<AiSettings> aiSettingsOpt = aiSettingsDao.findByTenantIdAndId(tenantId, aiSettingsId);
|
||||
if (aiSettingsOpt.isEmpty()) {
|
||||
private boolean deleteByTenantIdAndIdInternal(TenantId tenantId, AiModelSettingsId settingsId) {
|
||||
Optional<AiModelSettings> toDeleteOpt = aiModelSettingsDao.findByTenantIdAndId(tenantId, settingsId);
|
||||
if (toDeleteOpt.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean deleted = aiSettingsDao.deleteByTenantIdAndId(tenantId, aiSettingsId);
|
||||
boolean deleted = aiModelSettingsDao.deleteByTenantIdAndId(tenantId, settingsId);
|
||||
if (deleted) {
|
||||
publishDeleteEvent(aiSettingsOpt.get());
|
||||
publishEvictEvent(AiSettingsCacheEvictEvent.of(tenantId, aiSettingsId));
|
||||
publishDeleteEvent(toDeleteOpt.get());
|
||||
publishEvictEvent(AiModelSettingsCacheEvictEvent.of(tenantId, settingsId));
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
@ -144,23 +144,23 @@ class AiSettingsServiceImpl extends CachedVersionedEntityService<AiSettingsCache
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteByTenantId(TenantId tenantId) {
|
||||
List<AiSettings> deletedSettings = aiSettingsDao.findAllByTenantId(tenantId, new PageLink(Integer.MAX_VALUE)).getData();
|
||||
if (deletedSettings.isEmpty()) {
|
||||
List<AiModelSettings> toDelete = aiModelSettingsDao.findAllByTenantId(tenantId, new PageLink(Integer.MAX_VALUE)).getData();
|
||||
if (toDelete.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
aiSettingsDao.deleteByTenantId(tenantId);
|
||||
aiModelSettingsDao.deleteByTenantId(tenantId);
|
||||
|
||||
Set<AiSettingsCacheKey> cacheKeys = Sets.newHashSetWithExpectedSize(deletedSettings.size());
|
||||
deletedSettings.forEach(settings -> {
|
||||
Set<AiModelSettingsCacheKey> cacheKeys = Sets.newHashSetWithExpectedSize(toDelete.size());
|
||||
toDelete.forEach(settings -> {
|
||||
publishDeleteEvent(settings);
|
||||
cacheKeys.add(AiSettingsCacheKey.of(settings.getTenantId(), settings.getId()));
|
||||
cacheKeys.add(AiModelSettingsCacheKey.of(settings.getTenantId(), settings.getId()));
|
||||
});
|
||||
|
||||
publishEvictEvent(new AiSettingsCacheEvictEvent(cacheKeys));
|
||||
publishEvictEvent(new AiModelSettingsCacheEvictEvent(cacheKeys));
|
||||
}
|
||||
|
||||
private void publishDeleteEvent(AiSettings settings) {
|
||||
private void publishDeleteEvent(AiModelSettings settings) {
|
||||
eventPublisher.publishEvent(DeleteEntityEvent.builder()
|
||||
.tenantId(settings.getTenantId())
|
||||
.entityId(settings.getId())
|
||||
@ -170,7 +170,7 @@ class AiSettingsServiceImpl extends CachedVersionedEntityService<AiSettingsCache
|
||||
|
||||
@Override
|
||||
public EntityType getEntityType() {
|
||||
return EntityType.AI_SETTINGS;
|
||||
return EntityType.AI_MODEL_SETTINGS;
|
||||
}
|
||||
|
||||
}
|
||||
@ -46,7 +46,7 @@ public class CleanUpService {
|
||||
private final Set<EntityType> 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_SETTINGS
|
||||
EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_RULE, EntityType.AI_MODEL_SETTINGS
|
||||
);
|
||||
|
||||
@TransactionalEventListener(fallbackExecution = true) // after transaction commit
|
||||
|
||||
@ -740,15 +740,12 @@ public class ModelConstants {
|
||||
public static final String CALCULATED_FIELD_LINK_CALCULATED_FIELD_ID = "calculated_field_id";
|
||||
|
||||
/**
|
||||
* AI settings constants.
|
||||
* AI model settings constants.
|
||||
*/
|
||||
public static final String AI_SETTINGS_TABLE_NAME = "ai_settings";
|
||||
public static final String AI_SETTINGS_TENANT_ID_COLUMN_NAME = TENANT_ID_COLUMN;
|
||||
public static final String AI_SETTINGS_NAME_COLUMN_NAME = NAME_PROPERTY;
|
||||
public static final String AI_SETTINGS_PROVIDER_COLUMN_NAME = "provider";
|
||||
public static final String AI_SETTINGS_PROVIDER_CONFIG_COLUMN_NAME = "provider_config";
|
||||
public static final String AI_SETTINGS_MODEL_COLUMN_NAME = "model";
|
||||
public static final String AI_SETTINGS_MODEL_CONFIG_COLUMN_NAME = "model_config";
|
||||
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";
|
||||
|
||||
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};
|
||||
|
||||
|
||||
@ -18,23 +18,20 @@ package org.thingsboard.server.dao.model.sql;
|
||||
import io.hypersistence.utils.hibernate.type.json.JsonBinaryType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.thingsboard.server.common.data.ai.AiSettings;
|
||||
import org.thingsboard.server.common.data.ai.model.AiModelConfig;
|
||||
import org.thingsboard.server.common.data.ai.provider.AiProvider;
|
||||
import org.thingsboard.server.common.data.ai.provider.AiProviderConfig;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
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.id.TenantId;
|
||||
import org.thingsboard.server.dao.model.BaseVersionedEntity;
|
||||
import org.thingsboard.server.dao.model.ModelConstants;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -42,53 +39,40 @@ import java.util.UUID;
|
||||
@Setter
|
||||
@ToString
|
||||
@Entity
|
||||
@Table(name = ModelConstants.AI_SETTINGS_TABLE_NAME)
|
||||
public class AiSettingsEntity extends BaseVersionedEntity<AiSettings> {
|
||||
@Table(name = ModelConstants.AI_MODEL_SETTINGS_TABLE_NAME)
|
||||
public class AiModelSettingsEntity extends BaseVersionedEntity<AiModelSettings> {
|
||||
|
||||
@Column(name = ModelConstants.AI_SETTINGS_TENANT_ID_COLUMN_NAME, nullable = false, columnDefinition = "UUID")
|
||||
public static final Map<String, String> COLUMN_MAP = Map.of(
|
||||
"createdTime", "created_time"
|
||||
);
|
||||
|
||||
@Column(name = ModelConstants.AI_MODEL_SETTINGS_TENANT_ID_COLUMN_NAME, nullable = false, columnDefinition = "UUID")
|
||||
private UUID tenantId;
|
||||
|
||||
@Column(name = ModelConstants.AI_SETTINGS_NAME_COLUMN_NAME, nullable = false)
|
||||
@Column(name = ModelConstants.AI_MODEL_SETTINGS_NAME_COLUMN_NAME, nullable = false)
|
||||
private String name;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = ModelConstants.AI_SETTINGS_PROVIDER_COLUMN_NAME, nullable = false)
|
||||
private AiProvider provider;
|
||||
|
||||
@Type(JsonBinaryType.class)
|
||||
@Column(name = ModelConstants.AI_SETTINGS_PROVIDER_CONFIG_COLUMN_NAME, nullable = false, columnDefinition = "JSONB")
|
||||
private AiProviderConfig providerConfig;
|
||||
@Column(name = ModelConstants.AI_MODEL_SETTINGS_CONFIGURATION_COLUMN_NAME, nullable = false, columnDefinition = "JSONB")
|
||||
private AiModel<?> configuration;
|
||||
|
||||
@Column(name = ModelConstants.AI_SETTINGS_MODEL_COLUMN_NAME, nullable = false)
|
||||
private String model;
|
||||
public AiModelSettingsEntity() {}
|
||||
|
||||
@Type(JsonBinaryType.class)
|
||||
@Column(name = ModelConstants.AI_SETTINGS_MODEL_CONFIG_COLUMN_NAME, columnDefinition = "JSONB")
|
||||
private AiModelConfig modelConfig;
|
||||
|
||||
public AiSettingsEntity() {}
|
||||
|
||||
public AiSettingsEntity(AiSettings aiSettings) {
|
||||
super(aiSettings);
|
||||
tenantId = getTenantUuid(aiSettings.getTenantId());
|
||||
name = aiSettings.getName();
|
||||
provider = aiSettings.getProvider();
|
||||
providerConfig = aiSettings.getProviderConfig();
|
||||
model = aiSettings.getModel();
|
||||
modelConfig = aiSettings.getModelConfig();
|
||||
public AiModelSettingsEntity(AiModelSettings aiModelSettings) {
|
||||
super(aiModelSettings);
|
||||
tenantId = getTenantUuid(aiModelSettings.getTenantId());
|
||||
name = aiModelSettings.getName();
|
||||
configuration = aiModelSettings.getConfiguration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiSettings toData() {
|
||||
var settings = new AiSettings(new AiSettingsId(id));
|
||||
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.setProvider(provider);
|
||||
settings.setProviderConfig(providerConfig);
|
||||
settings.setModel(model);
|
||||
settings.setModelConfig(modelConfig);
|
||||
settings.setConfiguration(configuration);
|
||||
return settings;
|
||||
}
|
||||
|
||||
@ -99,7 +83,7 @@ public class AiSettingsEntity extends BaseVersionedEntity<AiSettings> {
|
||||
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;
|
||||
AiSettingsEntity that = (AiSettingsEntity) o;
|
||||
AiModelSettingsEntity that = (AiModelSettingsEntity) o;
|
||||
return getId() != null && Objects.equals(getId(), that.getId());
|
||||
}
|
||||
|
||||
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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.service.validator;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.TenantId;
|
||||
import org.thingsboard.server.dao.ai.AiModelSettingsDao;
|
||||
import org.thingsboard.server.dao.exception.DataValidationException;
|
||||
import org.thingsboard.server.dao.service.DataValidator;
|
||||
import org.thingsboard.server.dao.tenant.TenantService;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
class AiModelSettingsDataValidator extends DataValidator<AiModelSettings> {
|
||||
|
||||
private final TenantService tenantService;
|
||||
private final AiModelSettingsDao aiModelSettingsDao;
|
||||
|
||||
@Override
|
||||
protected void validateCreate(TenantId tenantId, AiModelSettings settings) {
|
||||
validateNumberOfEntitiesPerTenant(tenantId, EntityType.AI_MODEL_SETTINGS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AiModelSettings validateUpdate(TenantId tenantId, AiModelSettings settings) {
|
||||
Optional<AiModelSettings> existing = aiModelSettingsDao.findByTenantIdAndId(tenantId, settings.getId());
|
||||
if (existing.isEmpty()) {
|
||||
throw new DataValidationException("Cannot update non-existent AI model settings!");
|
||||
}
|
||||
return existing.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateDataImpl(TenantId tenantId, AiModelSettings settings) {
|
||||
// ID validation
|
||||
if (settings.getId() != null) {
|
||||
if (settings.getUuidId() == null) {
|
||||
throw new DataValidationException("AI model settings UUID should be specified!");
|
||||
}
|
||||
if (settings.getId().isNullUid()) {
|
||||
throw new DataValidationException("AI model settings 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 (settings.getTenantId().isSysTenantId()) {
|
||||
throw new DataValidationException("AI model settings cannot be assigned to the system tenant!");
|
||||
}
|
||||
if (!tenantService.tenantExists(tenantId)) {
|
||||
throw new DataValidationException("AI model settings reference a non-existent tenant!");
|
||||
}
|
||||
|
||||
// name validation
|
||||
validateString("AI model settings name", settings.getName());
|
||||
if (settings.getName().length() > 255) {
|
||||
throw new DataValidationException("AI model settings name should be between 1 and 255 symbols!");
|
||||
}
|
||||
|
||||
// model config validation
|
||||
if (settings.getConfiguration() == null) {
|
||||
throw new DataValidationException("AI model settings configuration should be specified!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,109 +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.service.validator;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.thingsboard.server.common.data.EntityType;
|
||||
import org.thingsboard.server.common.data.ai.AiSettings;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.dao.ai.AiSettingsDao;
|
||||
import org.thingsboard.server.dao.exception.DataValidationException;
|
||||
import org.thingsboard.server.dao.service.DataValidator;
|
||||
import org.thingsboard.server.dao.tenant.TenantService;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
class AiSettingsDataValidator extends DataValidator<AiSettings> {
|
||||
|
||||
private final TenantService tenantService;
|
||||
private final AiSettingsDao aiSettingsDao;
|
||||
|
||||
@Override
|
||||
protected void validateCreate(TenantId tenantId, AiSettings aiSettings) {
|
||||
validateNumberOfEntitiesPerTenant(tenantId, EntityType.AI_SETTINGS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AiSettings validateUpdate(TenantId tenantId, AiSettings aiSettings) {
|
||||
Optional<AiSettings> old = aiSettingsDao.findByTenantIdAndId(tenantId, aiSettings.getId());
|
||||
if (old.isEmpty()) {
|
||||
throw new DataValidationException("Cannot update non-existent AI settings!");
|
||||
}
|
||||
return old.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateDataImpl(TenantId tenantId, AiSettings aiSettings) {
|
||||
// ID validation
|
||||
if (aiSettings.getId() != null) {
|
||||
if (aiSettings.getUuidId() == null) {
|
||||
throw new DataValidationException("AI settings UUID should be specified!");
|
||||
}
|
||||
if (aiSettings.getId().isNullUid()) {
|
||||
throw new DataValidationException("AI settings UUID must not be the reserved null value!");
|
||||
}
|
||||
}
|
||||
|
||||
// tenant ID validation
|
||||
if (aiSettings.getTenantId() == null || aiSettings.getTenantId().getId() == null) {
|
||||
throw new DataValidationException("AI settings should be assigned to tenant!");
|
||||
}
|
||||
if (aiSettings.getTenantId().isSysTenantId()) {
|
||||
throw new DataValidationException("AI settings cannot be assigned to the system tenant!");
|
||||
}
|
||||
if (!tenantService.tenantExists(tenantId)) {
|
||||
throw new DataValidationException("AI settings reference a non-existent tenant!");
|
||||
}
|
||||
|
||||
// name validation
|
||||
validateString("AI settings name", aiSettings.getName());
|
||||
if (aiSettings.getName().length() > 255) {
|
||||
throw new DataValidationException("AI settings name should be between 1 and 255 symbols!");
|
||||
}
|
||||
|
||||
// provider validation
|
||||
if (aiSettings.getProvider() == null) {
|
||||
throw new DataValidationException("AI provider should be specified!");
|
||||
}
|
||||
|
||||
// provider config validation
|
||||
if (aiSettings.getProviderConfig() == null) {
|
||||
throw new DataValidationException("AI provider config should be specified!");
|
||||
}
|
||||
if (aiSettings.getProviderConfig().getProvider() != aiSettings.getProvider()) {
|
||||
throw new DataValidationException("AI provider configuration should match the selected AI provider!");
|
||||
}
|
||||
validateString("AI provider API key", aiSettings.getProviderConfig().getApiKey());
|
||||
|
||||
// model identifier validation
|
||||
validateString("AI model identifier", aiSettings.getModel());
|
||||
if (aiSettings.getModel().length() > 255) {
|
||||
throw new DataValidationException("AI model identifier should be between 1 and 255 symbols!");
|
||||
}
|
||||
|
||||
// model config validation
|
||||
if (aiSettings.getModelConfig() != null) {
|
||||
if (!Objects.equals(aiSettings.getModelConfig().getModel(), aiSettings.getModel())) {
|
||||
throw new DataValidationException("AI model configuration should match the selected AI model!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -22,30 +22,32 @@ import org.springframework.data.jpa.repository.Modifying;
|
||||
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.model.sql.AiSettingsEntity;
|
||||
import org.thingsboard.server.dao.model.sql.AiModelSettingsEntity;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface AiSettingsRepository extends JpaRepository<AiSettingsEntity, UUID> {
|
||||
interface AiModelSettingsRepository extends JpaRepository<AiModelSettingsEntity, UUID> {
|
||||
|
||||
@Query("SELECT ai " +
|
||||
"FROM AiSettingsEntity ai " +
|
||||
"WHERE ai.tenantId = :tenantId " +
|
||||
"AND (:textSearch IS NULL " +
|
||||
"OR ilike(ai.name, CONCAT('%', :textSearch, '%')) = true " +
|
||||
"OR ilike(ai.provider, CONCAT('%', :textSearch, '%')) = true " +
|
||||
"OR ilike(ai.model, CONCAT('%', :textSearch, '%')) = true)")
|
||||
Page<AiSettingsEntity> findByTenantId(@Param("tenantId") UUID tenantId, @Param("textSearch") String textSearch, Pageable pageable);
|
||||
@Query(nativeQuery = true, value = """
|
||||
SELECT *
|
||||
FROM ai_model_settings ai_model
|
||||
WHERE ai_model.tenant_id = :tenantId
|
||||
AND (:textSearch IS NULL
|
||||
OR ai_model.name ILIKE '%' || :textSearch || '%'
|
||||
OR (ai_model.configuration -> 'providerConfig' ->> 'provider') ILIKE '%' || :textSearch || '%'
|
||||
OR (ai_model.configuration ->> 'modelId') ILIKE '%' || :textSearch || '%')
|
||||
""")
|
||||
Page<AiModelSettingsEntity> findByTenantId(@Param("tenantId") UUID tenantId, @Param("textSearch") String textSearch, Pageable pageable);
|
||||
|
||||
Optional<AiSettingsEntity> findByTenantIdAndId(UUID tenantId, UUID id);
|
||||
Optional<AiModelSettingsEntity> findByTenantIdAndId(UUID tenantId, UUID id);
|
||||
|
||||
long countByTenantId(UUID tenantId);
|
||||
|
||||
@Transactional
|
||||
@Modifying
|
||||
@Query("DELETE FROM AiSettingsEntity ai WHERE ai.id IN (:ids)")
|
||||
@Query("DELETE FROM AiModelSettingsEntity ai_model WHERE ai_model.id IN (:ids)")
|
||||
int deleteByIdIn(@Param("ids") Set<UUID> ids);
|
||||
|
||||
@Transactional
|
||||
@ -53,7 +55,7 @@ public interface AiSettingsRepository extends JpaRepository<AiSettingsEntity, UU
|
||||
|
||||
@Transactional
|
||||
@Modifying
|
||||
@Query("DELETE FROM AiSettingsEntity ai WHERE ai.tenantId = :tenantId AND ai.id IN (:ids)")
|
||||
@Query("DELETE FROM AiModelSettingsEntity ai_model WHERE ai_model.tenantId = :tenantId AND ai_model.id IN (:ids)")
|
||||
int deleteByTenantIdAndIdIn(@Param("tenantId") UUID tenantId, @Param("ids") Set<UUID> ids);
|
||||
|
||||
}
|
||||
@ -20,14 +20,14 @@ import org.apache.commons.lang3.StringUtils;
|
||||
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.AiSettings;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
import org.thingsboard.server.common.data.id.AiModelSettingsId;
|
||||
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.DaoUtil;
|
||||
import org.thingsboard.server.dao.ai.AiSettingsDao;
|
||||
import org.thingsboard.server.dao.model.sql.AiSettingsEntity;
|
||||
import org.thingsboard.server.dao.ai.AiModelSettingsDao;
|
||||
import org.thingsboard.server.dao.model.sql.AiModelSettingsEntity;
|
||||
import org.thingsboard.server.dao.sql.JpaAbstractDao;
|
||||
import org.thingsboard.server.dao.util.SqlDao;
|
||||
|
||||
@ -38,55 +38,55 @@ import java.util.UUID;
|
||||
@SqlDao
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
class JpaAiSettingsDao extends JpaAbstractDao<AiSettingsEntity, AiSettings> implements AiSettingsDao {
|
||||
class JpaAiSettingsDao extends JpaAbstractDao<AiModelSettingsEntity, AiModelSettings> implements AiModelSettingsDao {
|
||||
|
||||
private final AiSettingsRepository aiSettingsRepository;
|
||||
private final AiModelSettingsRepository aiModelSettingsRepository;
|
||||
|
||||
@Override
|
||||
public Optional<AiSettings> findByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
return aiSettingsRepository.findByTenantIdAndId(tenantId.getId(), aiSettingsId.getId()).map(DaoUtil::getData);
|
||||
public Optional<AiModelSettings> findByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId) {
|
||||
return aiModelSettingsRepository.findByTenantIdAndId(tenantId.getId(), settingsId.getId()).map(DaoUtil::getData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageData<AiSettings> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
|
||||
return DaoUtil.toPageData(aiSettingsRepository.findByTenantId(
|
||||
tenantId.getId(), StringUtils.defaultIfEmpty(pageLink.getTextSearch(), null), DaoUtil.toPageable(pageLink))
|
||||
public PageData<AiModelSettings> findAllByTenantId(TenantId tenantId, PageLink pageLink) {
|
||||
return DaoUtil.toPageData(aiModelSettingsRepository.findByTenantId(
|
||||
tenantId.getId(), StringUtils.defaultIfEmpty(pageLink.getTextSearch(), null), DaoUtil.toPageable(pageLink, AiModelSettingsEntity.COLUMN_MAP))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countByTenantId(TenantId tenantId) {
|
||||
return aiSettingsRepository.countByTenantId(tenantId.getId());
|
||||
return aiModelSettingsRepository.countByTenantId(tenantId.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteById(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
return aiSettingsRepository.deleteByIdIn(Set.of(aiSettingsId.getId())) > 0;
|
||||
public boolean deleteById(TenantId tenantId, AiModelSettingsId settingsId) {
|
||||
return aiModelSettingsRepository.deleteByIdIn(Set.of(settingsId.getId())) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteByTenantId(TenantId tenantId) {
|
||||
return aiSettingsRepository.deleteByTenantId(tenantId.getId());
|
||||
return aiModelSettingsRepository.deleteByTenantId(tenantId.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteByTenantIdAndId(TenantId tenantId, AiSettingsId aiSettingsId) {
|
||||
return aiSettingsRepository.deleteByTenantIdAndIdIn(tenantId.getId(), Set.of(aiSettingsId.getId())) > 0;
|
||||
public boolean deleteByTenantIdAndId(TenantId tenantId, AiModelSettingsId settingsId) {
|
||||
return aiModelSettingsRepository.deleteByTenantIdAndIdIn(tenantId.getId(), Set.of(settingsId.getId())) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType getEntityType() {
|
||||
return EntityType.AI_SETTINGS;
|
||||
return EntityType.AI_MODEL_SETTINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<AiSettingsEntity> getEntityClass() {
|
||||
return AiSettingsEntity.class;
|
||||
protected Class<AiModelSettingsEntity> getEntityClass() {
|
||||
return AiModelSettingsEntity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JpaRepository<AiSettingsEntity, UUID> getRepository() {
|
||||
return aiSettingsRepository;
|
||||
protected JpaRepository<AiModelSettingsEntity, UUID> getRepository() {
|
||||
return aiModelSettingsRepository;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -183,7 +183,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
|
||||
EntityType.NOTIFICATION_REQUEST, EntityType.NOTIFICATION_RULE, EntityType.NOTIFICATION_TEMPLATE,
|
||||
EntityType.NOTIFICATION_TARGET, EntityType.QUEUE_STATS, EntityType.CUSTOMER,
|
||||
EntityType.DOMAIN, EntityType.MOBILE_APP_BUNDLE, EntityType.MOBILE_APP, EntityType.OAUTH2_CLIENT,
|
||||
EntityType.AI_SETTINGS
|
||||
EntityType.AI_MODEL_SETTINGS
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -949,15 +949,12 @@ CREATE TABLE IF NOT EXISTS cf_debug_event (
|
||||
e_error varchar
|
||||
) PARTITION BY RANGE (ts);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ai_settings (
|
||||
CREATE TABLE IF NOT EXISTS ai_model_settings (
|
||||
id UUID NOT NULL PRIMARY KEY,
|
||||
created_time BIGINT NOT NULL,
|
||||
tenant_id UUID NOT NULL,
|
||||
version BIGINT NOT NULL DEFAULT 1,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
provider VARCHAR(255) NOT NULL,
|
||||
provider_config JSONB NOT NULL,
|
||||
model VARCHAR(255) NOT NULL,
|
||||
model_config JSONB,
|
||||
CONSTRAINT ai_settings_name_unq_key UNIQUE (tenant_id, name)
|
||||
configuration JSONB NOT NULL,
|
||||
CONSTRAINT ai_model_settings_name_unq_key UNIQUE (tenant_id, name)
|
||||
);
|
||||
|
||||
@ -16,15 +16,11 @@
|
||||
package org.thingsboard.rule.engine.api;
|
||||
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import org.thingsboard.server.common.data.ai.model.AiModelConfig;
|
||||
import org.thingsboard.server.common.data.ai.provider.AiProviderConfig;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.AiChatModel;
|
||||
import org.thingsboard.server.common.data.ai.model.chat.AiChatModelConfig;
|
||||
|
||||
public interface RuleEngineAiService {
|
||||
public interface RuleEngineAiModelService {
|
||||
|
||||
ChatModel configureChatModel(TenantId tenantId, AiSettingsId aiSettingsId);
|
||||
|
||||
ChatModel configureChatModel(AiProviderConfig providerConfig, AiModelConfig modelConfig);
|
||||
<C extends AiChatModelConfig<C>> ChatModel configureChatModel(AiChatModel<C> chatModel);
|
||||
|
||||
}
|
||||
@ -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.AiSettingsService;
|
||||
import org.thingsboard.server.dao.ai.AiModelSettingsService;
|
||||
import org.thingsboard.server.dao.alarm.AlarmCommentService;
|
||||
import org.thingsboard.server.dao.asset.AssetProfileService;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
@ -418,9 +418,9 @@ public interface TbContext {
|
||||
|
||||
AuditLogService getAuditLogService();
|
||||
|
||||
RuleEngineAiService getAiService();
|
||||
RuleEngineAiModelService getAiModelService();
|
||||
|
||||
AiSettingsService getAiSettingsService();
|
||||
AiModelSettingsService getAiModelSettingsService();
|
||||
|
||||
AiRequestsExecutor getAiRequestsExecutor();
|
||||
|
||||
|
||||
@ -36,9 +36,11 @@ 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.model.AiModelConfig;
|
||||
import org.thingsboard.server.common.data.ai.provider.AiProviderConfig;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
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.plugin.ComponentType;
|
||||
import org.thingsboard.server.common.data.rule.RuleChainType;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
@ -46,6 +48,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
||||
import static org.thingsboard.server.dao.service.ConstraintValidator.validateFields;
|
||||
@ -64,7 +67,7 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
|
||||
private String userPrompt;
|
||||
private ResponseFormat responseFormat;
|
||||
private int timeoutSeconds;
|
||||
private AiSettingsId aiSettingsId;
|
||||
private AiModelSettingsId modelSettingsId;
|
||||
|
||||
@Override
|
||||
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
|
||||
@ -86,11 +89,16 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
|
||||
systemPrompt = config.getSystemPrompt();
|
||||
userPrompt = config.getUserPrompt();
|
||||
timeoutSeconds = config.getTimeoutSeconds();
|
||||
modelSettingsId = config.getAiModelSettingsId();
|
||||
|
||||
if (!aiSettingsExist(ctx, config.getAiSettingsId())) {
|
||||
throw new TbNodeException("[" + ctx.getTenantId() + "] AI settings with ID: " + config.getAiSettingsId() + " were not found", true);
|
||||
Optional<AiModelSettings> 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);
|
||||
}
|
||||
AiModelType modelType = modelSettings.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);
|
||||
}
|
||||
aiSettingsId = config.getAiSettingsId();
|
||||
}
|
||||
|
||||
private static JsonSchema getJsonSchema(ResponseFormatType responseFormatType, ObjectNode jsonSchema) {
|
||||
@ -100,12 +108,8 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
|
||||
return responseFormatType == ResponseFormatType.JSON && jsonSchema != null ? Langchain4jJsonSchemaAdapter.fromJsonNode(jsonSchema) : null;
|
||||
}
|
||||
|
||||
private static boolean aiSettingsExist(TbContext ctx, AiSettingsId aiSettingsId) {
|
||||
return ctx.getAiSettingsService().findAiSettingsByTenantIdAndId(ctx.getTenantId(), aiSettingsId).isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
|
||||
public void onMsg(TbContext ctx, TbMsg msg) {
|
||||
var ackedMsg = ackIfNeeded(ctx, msg);
|
||||
|
||||
var systemMessage = SystemMessage.from(TbNodeUtils.processPattern(systemPrompt, ackedMsg));
|
||||
@ -137,19 +141,25 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
|
||||
}, directExecutor());
|
||||
}
|
||||
|
||||
private FluentFuture<ChatModel> configureChatModelAsync(TbContext ctx) {
|
||||
return ctx.getAiSettingsService().findAiSettingsByTenantIdAndIdAsync(ctx.getTenantId(), aiSettingsId).transform(aiSettingsOpt -> {
|
||||
if (aiSettingsOpt.isEmpty()) {
|
||||
throw new NoSuchElementException("AI settings with ID: " + aiSettingsId + " were not found");
|
||||
private <C extends AiChatModelConfig<C>> FluentFuture<ChatModel> configureChatModelAsync(TbContext ctx) {
|
||||
return ctx.getAiModelSettingsService().findAiModelSettingsByTenantIdAndIdAsync(ctx.getTenantId(), modelSettingsId).transform(settingsOpt -> {
|
||||
if (settingsOpt.isEmpty()) {
|
||||
throw new NoSuchElementException("[" + ctx.getTenantId() + "] AI model settings with ID: [" + modelSettingsId + "] were not found");
|
||||
}
|
||||
AiModelSettings settings = settingsOpt.get();
|
||||
AiModelType modelType = settings.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);
|
||||
}
|
||||
|
||||
AiProviderConfig providerConfig = aiSettingsOpt.get().getProviderConfig();
|
||||
AiModelConfig modelConfig = aiSettingsOpt.get().getModelConfig();
|
||||
@SuppressWarnings("unchecked")
|
||||
AiChatModel<C> chatModel = (AiChatModel<C>) settingsOpt.get().getConfiguration();
|
||||
|
||||
modelConfig.setTimeoutSeconds(timeoutSeconds);
|
||||
modelConfig.setMaxRetries(0); // disable retries to respect timeout set in rule node config
|
||||
chatModel = chatModel.withModelConfig(chatModel.modelConfig()
|
||||
.withTimeoutSeconds(timeoutSeconds)
|
||||
.withMaxRetries(0)); // disable retries to respect timeout set in rule node config
|
||||
|
||||
return ctx.getAiService().configureChatModel(providerConfig, modelConfig);
|
||||
return ctx.getAiModelService().configureChatModel(chatModel);
|
||||
}, ctx.getDbCallbackExecutor());
|
||||
}
|
||||
|
||||
@ -172,7 +182,7 @@ public final class TbAiNode extends TbAbstractExternalNode implements TbNode {
|
||||
systemPrompt = null;
|
||||
userPrompt = null;
|
||||
responseFormat = null;
|
||||
aiSettingsId = null;
|
||||
modelSettingsId = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -26,14 +26,14 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.thingsboard.common.util.JsonSchemaUtils;
|
||||
import org.thingsboard.rule.engine.api.NodeConfiguration;
|
||||
import org.thingsboard.server.common.data.id.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.id.AiModelSettingsId;
|
||||
import org.thingsboard.server.common.data.validation.Length;
|
||||
|
||||
@Data
|
||||
public class TbAiNodeConfiguration implements NodeConfiguration<TbAiNodeConfiguration> {
|
||||
|
||||
@NotNull
|
||||
private AiSettingsId aiSettingsId;
|
||||
private AiModelSettingsId aiModelSettingsId;
|
||||
|
||||
@NotBlank
|
||||
@Length(min = 1, max = 1000)
|
||||
|
||||
@ -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.AiSettingsId;
|
||||
import org.thingsboard.server.common.data.id.AiModelSettingsId;
|
||||
import org.thingsboard.server.common.data.id.AlarmId;
|
||||
import org.thingsboard.server.common.data.id.ApiUsageStateId;
|
||||
import org.thingsboard.server.common.data.id.AssetId;
|
||||
@ -176,8 +176,8 @@ public class TenantIdLoader {
|
||||
tenantEntity = null;
|
||||
}
|
||||
break;
|
||||
case AI_SETTINGS:
|
||||
tenantEntity = ctx.getAiSettingsService().findAiSettingsById(ctxTenantId, new AiSettingsId(id)).orElse(null);
|
||||
case AI_MODEL_SETTINGS:
|
||||
tenantEntity = ctx.getAiModelSettingsService().findAiModelSettingsById(ctxTenantId, new AiModelSettingsId(id)).orElse(null);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unexpected entity type: " + entityId.getEntityType());
|
||||
|
||||
@ -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.AiSettings;
|
||||
import org.thingsboard.server.common.data.ai.AiModelSettings;
|
||||
import org.thingsboard.server.common.data.alarm.Alarm;
|
||||
import org.thingsboard.server.common.data.asset.Asset;
|
||||
import org.thingsboard.server.common.data.asset.AssetProfile;
|
||||
@ -69,7 +69,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.AiSettingsService;
|
||||
import org.thingsboard.server.dao.ai.AiModelSettingsService;
|
||||
import org.thingsboard.server.dao.asset.AssetService;
|
||||
import org.thingsboard.server.dao.cf.CalculatedFieldService;
|
||||
import org.thingsboard.server.dao.customer.CustomerService;
|
||||
@ -164,7 +164,7 @@ public class TenantIdLoaderTest {
|
||||
@Mock
|
||||
private CalculatedFieldService calculatedFieldService;
|
||||
@Mock
|
||||
private AiSettingsService aiSettingsService;
|
||||
private AiModelSettingsService aiModelSettingsService;
|
||||
|
||||
private TenantId tenantId;
|
||||
private TenantProfileId tenantProfileId;
|
||||
@ -424,11 +424,11 @@ public class TenantIdLoaderTest {
|
||||
when(ctx.getCalculatedFieldService()).thenReturn(calculatedFieldService);
|
||||
doReturn(calculatedFieldLink).when(calculatedFieldService).findCalculatedFieldLinkById(eq(tenantId), any());
|
||||
break;
|
||||
case AI_SETTINGS:
|
||||
AiSettings aiSettings = new AiSettings();
|
||||
aiSettings.setTenantId(tenantId);
|
||||
when(ctx.getAiSettingsService()).thenReturn(aiSettingsService);
|
||||
doReturn(Optional.of(aiSettings)).when(aiSettingsService).findAiSettingsById(eq(tenantId), any());
|
||||
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());
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Unexpected originator EntityType " + entityType);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user