Browse Source

AI rule node: add validation using annotations

pull/13371/head
Dmytro Skarzhynets 1 year ago
parent
commit
f2278f86c9
No known key found for this signature in database GPG Key ID: 2B51652F224037DF
  1. 5
      application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java
  2. 10
      common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModelSettings.java
  3. 22
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java
  4. 24
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModel.java
  5. 22
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModel.java
  6. 22
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModel.java
  7. 24
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModel.java
  8. 24
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModel.java
  9. 22
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModel.java
  10. 22
      common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModel.java
  11. 8
      common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AmazonBedrockProviderConfig.java
  12. 6
      common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AnthropicProviderConfig.java
  13. 8
      common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AzureOpenAiProviderConfig.java
  14. 6
      common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GitHubModelsProviderConfig.java
  15. 6
      common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleAiGeminiProviderConfig.java
  16. 10
      common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleVertexAiGeminiProviderConfig.java
  17. 6
      common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/MistralAiProviderConfig.java
  18. 6
      common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java
  19. 39
      common/data/src/main/java/org/thingsboard/server/common/data/validation/NoNullChar.java
  20. 8
      dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java
  21. 29
      dao/src/main/java/org/thingsboard/server/dao/service/NoNullCharValidator.java
  22. 11
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelSettingsDataValidator.java

5
application/src/main/java/org/thingsboard/server/controller/AiModelSettingsController.java

@ -17,7 +17,9 @@ package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -45,6 +47,7 @@ import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_D
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
@Validated
@RestController @RestController
@RequestMapping("/api/ai/model/settings") @RequestMapping("/api/ai/model/settings")
class AiModelSettingsController extends BaseController { class AiModelSettingsController extends BaseController {
@ -59,7 +62,7 @@ class AiModelSettingsController extends BaseController {
) )
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@PostMapping @PostMapping
public AiModelSettings saveAiModelSettings(@RequestBody AiModelSettings settings) throws ThingsboardException { public AiModelSettings saveAiModelSettings(@RequestBody @Valid AiModelSettings settings) throws ThingsboardException {
var user = getCurrentUser(); var user = getCurrentUser();
settings.setTenantId(user.getTenantId()); settings.setTenantId(user.getTenantId());
checkEntity(settings.getId(), settings, Resource.AI_MODEL_SETTINGS); checkEntity(settings.getId(), settings, Resource.AI_MODEL_SETTINGS);

10
common/data/src/main/java/org/thingsboard/server/common/data/ai/AiModelSettings.java

@ -16,6 +16,9 @@
package org.thingsboard.server.common.data.ai; package org.thingsboard.server.common.data.ai;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@ -27,6 +30,8 @@ import org.thingsboard.server.common.data.HasVersion;
import org.thingsboard.server.common.data.ai.model.AiModel; import org.thingsboard.server.common.data.ai.model.AiModel;
import org.thingsboard.server.common.data.id.AiModelSettingsId; import org.thingsboard.server.common.data.id.AiModelSettingsId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoNullChar;
import java.io.Serial; import java.io.Serial;
@ -56,6 +61,9 @@ public final class AiModelSettings extends BaseData<AiModelSettingsId> implement
) )
private Long version; private Long version;
@NotBlank
@NoNullChar
@Length(min = 1, max = 255)
@Schema( @Schema(
requiredMode = Schema.RequiredMode.REQUIRED, requiredMode = Schema.RequiredMode.REQUIRED,
accessMode = Schema.AccessMode.READ_WRITE, accessMode = Schema.AccessMode.READ_WRITE,
@ -64,6 +72,8 @@ public final class AiModelSettings extends BaseData<AiModelSettingsId> implement
) )
private String name; private String name;
@NotNull
@Valid
@Schema( @Schema(
requiredMode = Schema.RequiredMode.NOT_REQUIRED, requiredMode = Schema.RequiredMode.NOT_REQUIRED,
accessMode = Schema.AccessMode.READ_WRITE, accessMode = Schema.AccessMode.READ_WRITE,

22
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AmazonBedrockChatModel.java

@ -16,6 +16,12 @@
package org.thingsboard.server.common.data.ai.model.chat; package org.thingsboard.server.common.data.ai.model.chat;
import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.ChatModel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.With; import lombok.With;
import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.model.AiModelType;
import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AiProvider;
@ -25,8 +31,8 @@ import java.util.List;
public record AmazonBedrockChatModel( public record AmazonBedrockChatModel(
AiModelType modelType, AiModelType modelType,
AmazonBedrockProviderConfig providerConfig, @NotNull @Valid AmazonBedrockProviderConfig providerConfig,
@With Config modelConfig @With @NotNull @Valid Config modelConfig
) implements AiChatModel<AmazonBedrockChatModel.Config> { ) implements AiChatModel<AmazonBedrockChatModel.Config> {
@Override @Override
@ -36,13 +42,13 @@ public record AmazonBedrockChatModel(
@With @With
public record Config( public record Config(
String modelId, @NotBlank String modelId,
Double temperature, @PositiveOrZero Double temperature,
Double topP, @Positive @Max(1) Double topP,
Integer maxOutputTokens, @Positive Integer maxOutputTokens,
List<String> stopSequences, List<String> stopSequences,
Integer timeoutSeconds, @Positive Integer timeoutSeconds,
Integer maxRetries @PositiveOrZero Integer maxRetries
) implements AiChatModelConfig<AmazonBedrockChatModel.Config> {} ) implements AiChatModelConfig<AmazonBedrockChatModel.Config> {}
@Override @Override

24
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AnthropicChatModel.java

@ -16,6 +16,12 @@
package org.thingsboard.server.common.data.ai.model.chat; package org.thingsboard.server.common.data.ai.model.chat;
import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.ChatModel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.With; import lombok.With;
import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.model.AiModelType;
import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AiProvider;
@ -25,8 +31,8 @@ import java.util.List;
public record AnthropicChatModel( public record AnthropicChatModel(
AiModelType modelType, AiModelType modelType,
AnthropicProviderConfig providerConfig, @NotNull @Valid AnthropicProviderConfig providerConfig,
@With Config modelConfig @With @NotNull @Valid Config modelConfig
) implements AiChatModel<AnthropicChatModel.Config> { ) implements AiChatModel<AnthropicChatModel.Config> {
@Override @Override
@ -36,14 +42,14 @@ public record AnthropicChatModel(
@With @With
public record Config( public record Config(
String modelId, @NotBlank String modelId,
Double temperature, @PositiveOrZero Double temperature,
Double topP, @Positive @Max(1) Double topP,
Integer topK, @Positive Integer topK,
Integer maxOutputTokens, @Positive Integer maxOutputTokens,
List<String> stopSequences, List<String> stopSequences,
Integer timeoutSeconds, @Positive Integer timeoutSeconds,
Integer maxRetries @PositiveOrZero Integer maxRetries
) implements AiChatModelConfig<AnthropicChatModel.Config> {} ) implements AiChatModelConfig<AnthropicChatModel.Config> {}
@Override @Override

22
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/AzureOpenAiChatModel.java

@ -16,6 +16,12 @@
package org.thingsboard.server.common.data.ai.model.chat; package org.thingsboard.server.common.data.ai.model.chat;
import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.ChatModel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.With; import lombok.With;
import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.model.AiModelType;
import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AiProvider;
@ -25,8 +31,8 @@ import java.util.List;
public record AzureOpenAiChatModel( public record AzureOpenAiChatModel(
AiModelType modelType, AiModelType modelType,
AzureOpenAiProviderConfig providerConfig, @NotNull @Valid AzureOpenAiProviderConfig providerConfig,
@With Config modelConfig @With @NotNull @Valid Config modelConfig
) implements AiChatModel<AzureOpenAiChatModel.Config> { ) implements AiChatModel<AzureOpenAiChatModel.Config> {
@Override @Override
@ -36,15 +42,15 @@ public record AzureOpenAiChatModel(
@With @With
public record Config( public record Config(
String modelId, @NotBlank String modelId,
Double temperature, @PositiveOrZero Double temperature,
Double topP, @Positive @Max(1) Double topP,
Double frequencyPenalty, Double frequencyPenalty,
Double presencePenalty, Double presencePenalty,
Integer maxOutputTokens, @Positive Integer maxOutputTokens,
List<String> stopSequences, List<String> stopSequences,
Integer timeoutSeconds, @Positive Integer timeoutSeconds,
Integer maxRetries @PositiveOrZero Integer maxRetries
) implements AiChatModelConfig<AzureOpenAiChatModel.Config> {} ) implements AiChatModelConfig<AzureOpenAiChatModel.Config> {}
@Override @Override

22
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GitHubModelsChatModel.java

@ -16,6 +16,12 @@
package org.thingsboard.server.common.data.ai.model.chat; package org.thingsboard.server.common.data.ai.model.chat;
import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.ChatModel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.With; import lombok.With;
import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.model.AiModelType;
import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AiProvider;
@ -25,8 +31,8 @@ import java.util.List;
public record GitHubModelsChatModel( public record GitHubModelsChatModel(
AiModelType modelType, AiModelType modelType,
GitHubModelsProviderConfig providerConfig, @NotNull @Valid GitHubModelsProviderConfig providerConfig,
@With Config modelConfig @With @NotNull @Valid Config modelConfig
) implements AiChatModel<GitHubModelsChatModel.Config> { ) implements AiChatModel<GitHubModelsChatModel.Config> {
@Override @Override
@ -36,15 +42,15 @@ public record GitHubModelsChatModel(
@With @With
public record Config( public record Config(
String modelId, @NotBlank String modelId,
Double temperature, @PositiveOrZero Double temperature,
Double topP, @Positive @Max(1) Double topP,
Double frequencyPenalty, Double frequencyPenalty,
Double presencePenalty, Double presencePenalty,
Integer maxOutputTokens, @Positive Integer maxOutputTokens,
List<String> stopSequences, List<String> stopSequences,
Integer timeoutSeconds, @Positive Integer timeoutSeconds,
Integer maxRetries @PositiveOrZero Integer maxRetries
) implements AiChatModelConfig<GitHubModelsChatModel.Config> {} ) implements AiChatModelConfig<GitHubModelsChatModel.Config> {}
@Override @Override

24
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleAiGeminiChatModel.java

@ -16,6 +16,12 @@
package org.thingsboard.server.common.data.ai.model.chat; package org.thingsboard.server.common.data.ai.model.chat;
import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.ChatModel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.With; import lombok.With;
import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.model.AiModelType;
import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AiProvider;
@ -25,8 +31,8 @@ import java.util.List;
public record GoogleAiGeminiChatModel( public record GoogleAiGeminiChatModel(
AiModelType modelType, AiModelType modelType,
GoogleAiGeminiProviderConfig providerConfig, @NotNull @Valid GoogleAiGeminiProviderConfig providerConfig,
@With Config modelConfig @With @NotNull @Valid Config modelConfig
) implements AiChatModel<GoogleAiGeminiChatModel.Config> { ) implements AiChatModel<GoogleAiGeminiChatModel.Config> {
@Override @Override
@ -36,16 +42,16 @@ public record GoogleAiGeminiChatModel(
@With @With
public record Config( public record Config(
String modelId, @NotBlank String modelId,
Double temperature, @PositiveOrZero Double temperature,
Double topP, @Positive @Max(1) Double topP,
Integer topK, @Positive Integer topK,
Double frequencyPenalty, Double frequencyPenalty,
Double presencePenalty, Double presencePenalty,
Integer maxOutputTokens, @Positive Integer maxOutputTokens,
List<String> stopSequences, List<String> stopSequences,
Integer timeoutSeconds, @Positive Integer timeoutSeconds,
Integer maxRetries @PositiveOrZero Integer maxRetries
) implements AiChatModelConfig<GoogleAiGeminiChatModel.Config> {} ) implements AiChatModelConfig<GoogleAiGeminiChatModel.Config> {}
@Override @Override

24
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/GoogleVertexAiGeminiChatModel.java

@ -16,6 +16,12 @@
package org.thingsboard.server.common.data.ai.model.chat; package org.thingsboard.server.common.data.ai.model.chat;
import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.ChatModel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.With; import lombok.With;
import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.model.AiModelType;
import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AiProvider;
@ -25,8 +31,8 @@ import java.util.List;
public record GoogleVertexAiGeminiChatModel( public record GoogleVertexAiGeminiChatModel(
AiModelType modelType, AiModelType modelType,
GoogleVertexAiGeminiProviderConfig providerConfig, @NotNull @Valid GoogleVertexAiGeminiProviderConfig providerConfig,
@With Config modelConfig @With @NotNull @Valid Config modelConfig
) implements AiChatModel<GoogleVertexAiGeminiChatModel.Config> { ) implements AiChatModel<GoogleVertexAiGeminiChatModel.Config> {
@Override @Override
@ -36,16 +42,16 @@ public record GoogleVertexAiGeminiChatModel(
@With @With
public record Config( public record Config(
String modelId, @NotBlank String modelId,
Double temperature, @PositiveOrZero Double temperature,
Double topP, @Positive @Max(1) Double topP,
Integer topK, @Positive Integer topK,
Double frequencyPenalty, Double frequencyPenalty,
Double presencePenalty, Double presencePenalty,
Integer maxOutputTokens, @Positive Integer maxOutputTokens,
List<String> stopSequences, List<String> stopSequences,
Integer timeoutSeconds, @Positive Integer timeoutSeconds,
Integer maxRetries @PositiveOrZero Integer maxRetries
) implements AiChatModelConfig<GoogleVertexAiGeminiChatModel.Config> {} ) implements AiChatModelConfig<GoogleVertexAiGeminiChatModel.Config> {}
@Override @Override

22
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/MistralAiChatModel.java

@ -16,6 +16,12 @@
package org.thingsboard.server.common.data.ai.model.chat; package org.thingsboard.server.common.data.ai.model.chat;
import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.ChatModel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.With; import lombok.With;
import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.model.AiModelType;
import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AiProvider;
@ -25,8 +31,8 @@ import java.util.List;
public record MistralAiChatModel( public record MistralAiChatModel(
AiModelType modelType, AiModelType modelType,
MistralAiProviderConfig providerConfig, @NotNull @Valid MistralAiProviderConfig providerConfig,
@With Config modelConfig @With @NotNull @Valid Config modelConfig
) implements AiChatModel<MistralAiChatModel.Config> { ) implements AiChatModel<MistralAiChatModel.Config> {
@Override @Override
@ -36,15 +42,15 @@ public record MistralAiChatModel(
@With @With
public record Config( public record Config(
String modelId, @NotBlank String modelId,
Double temperature, @PositiveOrZero Double temperature,
Double topP, @Positive @Max(1) Double topP,
Double frequencyPenalty, Double frequencyPenalty,
Double presencePenalty, Double presencePenalty,
Integer maxOutputTokens, @Positive Integer maxOutputTokens,
List<String> stopSequences, List<String> stopSequences,
Integer timeoutSeconds, @Positive Integer timeoutSeconds,
Integer maxRetries @PositiveOrZero Integer maxRetries
) implements AiChatModelConfig<MistralAiChatModel.Config> {} ) implements AiChatModelConfig<MistralAiChatModel.Config> {}
@Override @Override

22
common/data/src/main/java/org/thingsboard/server/common/data/ai/model/chat/OpenAiChatModel.java

@ -16,6 +16,12 @@
package org.thingsboard.server.common.data.ai.model.chat; package org.thingsboard.server.common.data.ai.model.chat;
import dev.langchain4j.model.chat.ChatModel; import dev.langchain4j.model.chat.ChatModel;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.With; import lombok.With;
import org.thingsboard.server.common.data.ai.model.AiModelType; import org.thingsboard.server.common.data.ai.model.AiModelType;
import org.thingsboard.server.common.data.ai.provider.AiProvider; import org.thingsboard.server.common.data.ai.provider.AiProvider;
@ -25,8 +31,8 @@ import java.util.List;
public record OpenAiChatModel( public record OpenAiChatModel(
AiModelType modelType, AiModelType modelType,
OpenAiProviderConfig providerConfig, @NotNull @Valid OpenAiProviderConfig providerConfig,
@With Config modelConfig @With @NotNull @Valid Config modelConfig
) implements AiChatModel<OpenAiChatModel.Config> { ) implements AiChatModel<OpenAiChatModel.Config> {
@Override @Override
@ -36,15 +42,15 @@ public record OpenAiChatModel(
@With @With
public record Config( public record Config(
String modelId, @NotBlank String modelId,
Double temperature, @PositiveOrZero Double temperature,
Double topP, @Positive @Max(1) Double topP,
Double frequencyPenalty, Double frequencyPenalty,
Double presencePenalty, Double presencePenalty,
Integer maxOutputTokens, @Positive Integer maxOutputTokens,
List<String> stopSequences, List<String> stopSequences,
Integer timeoutSeconds, @Positive Integer timeoutSeconds,
Integer maxRetries @PositiveOrZero Integer maxRetries
) implements AiChatModelConfig<OpenAiChatModel.Config> {} ) implements AiChatModelConfig<OpenAiChatModel.Config> {}
@Override @Override

8
common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AmazonBedrockProviderConfig.java

@ -15,4 +15,10 @@
*/ */
package org.thingsboard.server.common.data.ai.provider; package org.thingsboard.server.common.data.ai.provider;
public record AmazonBedrockProviderConfig(String region, String accessKeyId, String secretAccessKey) implements AiProviderConfig {} import jakarta.validation.constraints.NotBlank;
public record AmazonBedrockProviderConfig(
@NotBlank String region,
@NotBlank String accessKeyId,
@NotBlank String secretAccessKey
) implements AiProviderConfig {}

6
common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AnthropicProviderConfig.java

@ -15,4 +15,8 @@
*/ */
package org.thingsboard.server.common.data.ai.provider; package org.thingsboard.server.common.data.ai.provider;
public record AnthropicProviderConfig(String apiKey) implements AiProviderConfig {} import jakarta.validation.constraints.NotBlank;
public record AnthropicProviderConfig(
@NotBlank String apiKey
) implements AiProviderConfig {}

8
common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/AzureOpenAiProviderConfig.java

@ -15,4 +15,10 @@
*/ */
package org.thingsboard.server.common.data.ai.provider; package org.thingsboard.server.common.data.ai.provider;
public record AzureOpenAiProviderConfig(String endpoint, String serviceVersion, String apiKey) implements AiProviderConfig {} import jakarta.validation.constraints.NotBlank;
public record AzureOpenAiProviderConfig(
@NotBlank String endpoint,
String serviceVersion,
@NotBlank String apiKey
) implements AiProviderConfig {}

6
common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GitHubModelsProviderConfig.java

@ -15,4 +15,8 @@
*/ */
package org.thingsboard.server.common.data.ai.provider; package org.thingsboard.server.common.data.ai.provider;
public record GitHubModelsProviderConfig(String personalAccessToken) implements AiProviderConfig {} import jakarta.validation.constraints.NotBlank;
public record GitHubModelsProviderConfig(
@NotBlank String personalAccessToken
) implements AiProviderConfig {}

6
common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleAiGeminiProviderConfig.java

@ -15,4 +15,8 @@
*/ */
package org.thingsboard.server.common.data.ai.provider; package org.thingsboard.server.common.data.ai.provider;
public record GoogleAiGeminiProviderConfig(String apiKey) implements AiProviderConfig {} import jakarta.validation.constraints.NotBlank;
public record GoogleAiGeminiProviderConfig(
@NotBlank String apiKey
) implements AiProviderConfig {}

10
common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/GoogleVertexAiGeminiProviderConfig.java

@ -15,9 +15,11 @@
*/ */
package org.thingsboard.server.common.data.ai.provider; package org.thingsboard.server.common.data.ai.provider;
import jakarta.validation.constraints.NotBlank;
public record GoogleVertexAiGeminiProviderConfig( public record GoogleVertexAiGeminiProviderConfig(
String fileName, // not used on BE, but needed for UI @NotBlank String fileName, // not used on BE, but needed for UI
String projectId, @NotBlank String projectId,
String location, @NotBlank String location,
String serviceAccountKey @NotBlank String serviceAccountKey
) implements AiProviderConfig {} ) implements AiProviderConfig {}

6
common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/MistralAiProviderConfig.java

@ -15,4 +15,8 @@
*/ */
package org.thingsboard.server.common.data.ai.provider; package org.thingsboard.server.common.data.ai.provider;
public record MistralAiProviderConfig(String apiKey) implements AiProviderConfig {} import jakarta.validation.constraints.NotBlank;
public record MistralAiProviderConfig(
@NotBlank String apiKey
) implements AiProviderConfig {}

6
common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java

@ -15,4 +15,8 @@
*/ */
package org.thingsboard.server.common.data.ai.provider; package org.thingsboard.server.common.data.ai.provider;
public record OpenAiProviderConfig(String apiKey) implements AiProviderConfig {} import jakarta.validation.constraints.NotBlank;
public record OpenAiProviderConfig(
@NotBlank String apiKey
) implements AiProviderConfig {}

39
common/data/src/main/java/org/thingsboard/server/common/data/validation/NoNullChar.java

@ -0,0 +1,39 @@
/**
* 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.validation;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Constraint(validatedBy = {})
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.RECORD_COMPONENT})
@Retention(RetentionPolicy.RUNTIME)
public @interface NoNullChar {
String message() default "should not contain 0x00 symbol";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

8
dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java

@ -21,7 +21,6 @@ import jakarta.validation.Validation;
import jakarta.validation.Validator; import jakarta.validation.Validator;
import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.metadata.ConstraintDescriptor; import jakarta.validation.metadata.ConstraintDescriptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hibernate.validator.HibernateValidator; import org.hibernate.validator.HibernateValidator;
import org.hibernate.validator.HibernateValidatorConfiguration; import org.hibernate.validator.HibernateValidatorConfiguration;
@ -32,6 +31,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoNullChar;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
import org.thingsboard.server.common.data.validation.RateLimit; import org.thingsboard.server.common.data.validation.RateLimit;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -40,7 +40,6 @@ import java.util.Collection;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j
@Configuration @Configuration
public class ConstraintValidator { public class ConstraintValidator {
@ -88,7 +87,9 @@ public class ConstraintValidator {
ConstraintMapping constraintMapping = getCustomConstraintMapping(); ConstraintMapping constraintMapping = getCustomConstraintMapping();
validatorConfiguration.addMapping(constraintMapping); validatorConfiguration.addMapping(constraintMapping);
fieldsValidator = validatorConfiguration.buildValidatorFactory().getValidator(); try (var validatorFactory = validatorConfiguration.buildValidatorFactory()) {
fieldsValidator = validatorFactory.getValidator();
}
} }
@Bean @Bean
@ -105,6 +106,7 @@ public class ConstraintValidator {
constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class); constraintMapping.constraintDefinition(NoXss.class).validatedBy(NoXssValidator.class);
constraintMapping.constraintDefinition(Length.class).validatedBy(StringLengthValidator.class); constraintMapping.constraintDefinition(Length.class).validatedBy(StringLengthValidator.class);
constraintMapping.constraintDefinition(RateLimit.class).validatedBy(RateLimitValidator.class); constraintMapping.constraintDefinition(RateLimit.class).validatedBy(RateLimitValidator.class);
constraintMapping.constraintDefinition(NoNullChar.class).validatedBy(NoNullCharValidator.class);
return constraintMapping; return constraintMapping;
} }

29
dao/src/main/java/org/thingsboard/server/dao/service/NoNullCharValidator.java

@ -0,0 +1,29 @@
/**
* 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;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.thingsboard.server.common.data.validation.NoNullChar;
public final class NoNullCharValidator implements ConstraintValidator<NoNullChar, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return value == null || !value.contains("\u0000");
}
}

11
dao/src/main/java/org/thingsboard/server/dao/service/validator/AiModelSettingsDataValidator.java

@ -64,17 +64,6 @@ class AiModelSettingsDataValidator extends DataValidator<AiModelSettings> {
if (!tenantService.tenantExists(tenantId)) { if (!tenantService.tenantExists(tenantId)) {
throw new DataValidationException("AI model settings reference a non-existent tenant!"); 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!");
}
} }
} }

Loading…
Cancel
Save