diff --git a/application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java b/application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java
index 2cb6c2097f..c631f1a3d0 100644
--- a/application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java
+++ b/application/src/main/java/org/thingsboard/server/service/ai/Langchain4jChatModelConfigurerImpl.java
@@ -70,6 +70,7 @@ class Langchain4jChatModelConfigurerImpl implements Langchain4jChatModelConfigur
@Override
public ChatModel configureChatModel(OpenAiChatModelConfig chatModelConfig) {
return OpenAiChatModel.builder()
+ .baseUrl(chatModelConfig.providerConfig().baseUrl())
.apiKey(chatModelConfig.providerConfig().apiKey())
.modelName(chatModelConfig.modelId())
.temperature(chatModelConfig.temperature())
diff --git a/application/src/test/java/org/thingsboard/server/controller/AiModelControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AiModelControllerTest.java
index ae2972b0cc..53648af441 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AiModelControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AiModelControllerTest.java
@@ -104,7 +104,10 @@ public class AiModelControllerTest extends AbstractControllerTest {
var model = doPost("/api/ai/model", constructValidOpenAiModel("Test model"), AiModel.class);
var newModelConfig = OpenAiChatModelConfig.builder()
- .providerConfig(new OpenAiProviderConfig("test-api-key-updated"))
+ .providerConfig(OpenAiProviderConfig.builder()
+ .baseUrl(OpenAiProviderConfig.OPENAI_OFFICIAL_BASE_URL)
+ .apiKey("test-api-key-updated")
+ .build())
.modelId("o4-mini")
.temperature(0.2)
.topP(0.4)
@@ -270,7 +273,7 @@ public class AiModelControllerTest extends AbstractControllerTest {
.tenantId(tenantId)
.name("Test model 1")
.configuration(OpenAiChatModelConfig.builder()
- .providerConfig(new OpenAiProviderConfig("test-api-key"))
+ .providerConfig(OpenAiProviderConfig.builder().apiKey("test-api-key").build())
.modelId("o3-pro")
.build())
.build(), AiModel.class);
@@ -594,7 +597,10 @@ public class AiModelControllerTest extends AbstractControllerTest {
private AiModel constructValidOpenAiModel(String name) {
var modelConfig = OpenAiChatModelConfig.builder()
- .providerConfig(new OpenAiProviderConfig("test-api-key"))
+ .providerConfig(OpenAiProviderConfig.builder()
+ .baseUrl(OpenAiProviderConfig.OPENAI_OFFICIAL_BASE_URL)
+ .apiKey("test-api-key")
+ .build())
.modelId("gpt-4o")
.temperature(0.5)
.topP(0.3)
diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelServiceTest.java
index 2321446b44..b6be136f82 100644
--- a/application/src/test/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/entitiy/ai/DefaultTbAiModelServiceTest.java
@@ -253,7 +253,7 @@ class DefaultTbAiModelServiceTest {
private static AiModelConfig constructValidOpenAiModelConfig() {
return OpenAiChatModelConfig.builder()
- .providerConfig(new OpenAiProviderConfig("test-api-key"))
+ .providerConfig(OpenAiProviderConfig.builder().apiKey("test-api-key").build())
.modelId("gpt-4o")
.temperature(0.5)
.topP(0.3)
diff --git a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
index da20b9489c..a146730465 100644
--- a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
@@ -682,7 +682,10 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
private AiModel constructValidOpenAiModel(String name) {
var modelConfig = OpenAiChatModelConfig.builder()
- .providerConfig(new OpenAiProviderConfig("test-api-key"))
+ .providerConfig(OpenAiProviderConfig.builder()
+ .baseUrl(OpenAiProviderConfig.OPENAI_OFFICIAL_BASE_URL)
+ .apiKey("test-api-key")
+ .build())
.modelId("gpt-4o")
.temperature(0.5)
.topP(0.3)
@@ -699,6 +702,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
.configuration(modelConfig)
.build();
}
+
@Test
public void testFindTenantResourcesByTenantId() throws Exception {
loginSysAdmin();
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java
index 09ffda837b..f7864db6e3 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/ai/provider/OpenAiProviderConfig.java
@@ -15,8 +15,32 @@
*/
package org.thingsboard.server.common.data.ai.provider;
-import jakarta.validation.constraints.NotNull;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.validation.constraints.AssertTrue;
+import lombok.Builder;
+import org.apache.commons.lang3.StringUtils;
+import java.util.Objects;
+
+@Builder
public record OpenAiProviderConfig(
- @NotNull String apiKey
-) implements AiProviderConfig {}
+ String baseUrl,
+ String apiKey
+) implements AiProviderConfig {
+
+ public static final String OPENAI_OFFICIAL_BASE_URL = "https://api.openai.com/v1";
+
+ public OpenAiProviderConfig {
+ baseUrl = Objects.requireNonNullElse(baseUrl, OPENAI_OFFICIAL_BASE_URL);
+ }
+
+ @JsonIgnore
+ @AssertTrue(message = "API key is required when using the official OpenAI API")
+ public boolean isValid() {
+ if (baseUrl.equals(OPENAI_OFFICIAL_BASE_URL)) {
+ return StringUtils.isNotBlank(apiKey);
+ }
+ return true;
+ }
+
+}
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/ai/TbAiNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/ai/TbAiNodeTest.java
index c5b7f2c44b..a786cdd536 100644
--- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/ai/TbAiNodeTest.java
+++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/ai/TbAiNodeTest.java
@@ -129,7 +129,10 @@ class TbAiNodeTest {
config = new TbAiNodeConfiguration();
modelConfig = OpenAiChatModelConfig.builder()
- .providerConfig(new OpenAiProviderConfig("test-api-key"))
+ .providerConfig(OpenAiProviderConfig.builder()
+ .baseUrl(OpenAiProviderConfig.OPENAI_OFFICIAL_BASE_URL)
+ .apiKey("test-api-key")
+ .build())
.modelId("gpt-4o")
.temperature(0.5)
.topP(0.3)
diff --git a/ui-ngx/src/app/modules/home/components/ai-model/ai-model-dialog.component.html b/ui-ngx/src/app/modules/home/components/ai-model/ai-model-dialog.component.html
index abfe8500b4..44fe9dcda7 100644
--- a/ui-ngx/src/app/modules/home/components/ai-model/ai-model-dialog.component.html
+++ b/ui-ngx/src/app/modules/home/components/ai-model/ai-model-dialog.component.html
@@ -116,14 +116,24 @@
}
+ @if (providerFieldsList.includes('baseUrl')) {
+