Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
26c8c61
feat(dotAI): replace OpenAIClient with LangChain4J abstraction layer …
ihoffmann-dot Mar 24, 2026
48f268b
feat(dotAI): remove legacy config support, require providerConfig
ihoffmann-dot Mar 24, 2026
0e56d8c
test(dotAI): add unit and integration tests for LangChain4J client layer
ihoffmann-dot Mar 25, 2026
5179db3
fix(dotAI): change BOOL+hidden params to STRING in dotAI.yml
ihoffmann-dot Mar 25, 2026
86057b1
fix(dotAI): remove legacy hidden params from dotAI.yml
ihoffmann-dot Mar 25, 2026
3a5387c
fix(dotAI): update dotAI.yml description to reflect LangChain4J integ…
ihoffmann-dot Mar 26, 2026
ebbeaf1
fix(dotAI): remove legacy OpenAI model validation from AIAppValidator
ihoffmann-dot Mar 26, 2026
3de8a67
fix(dotAI): update /completions/config to reflect providerConfig-base…
ihoffmann-dot Mar 26, 2026
cfdd3cf
fix(dotAI): support maxCompletionTokens for o-series OpenAI models
ihoffmann-dot Mar 27, 2026
bfa54d1
fix(dotAI): replace legacy getApiKey guard with isEnabled in ImageRes…
ihoffmann-dot Mar 30, 2026
6a1213d
refactor(dotAI): remove dead OpenAI model-fetch flow from AIModels
ihoffmann-dot Mar 30, 2026
80820a6
fix(dotAI): handle base64 image responses for models that don't retur…
ihoffmann-dot Mar 30, 2026
07f7bc1
fix(dotAI): send text content (not token IDs) to LangChain4J embeddin…
ihoffmann-dot Mar 30, 2026
d79b37c
fix(dotAI): skip token encoding guard when model not in jtokkit registry
ihoffmann-dot Mar 30, 2026
46028c2
fix(dotAI): add missing IPUtils import in AIModelsTest
ihoffmann-dot Mar 31, 2026
abe115e
refactor(dotAI): PR review comments fixes
ihoffmann-dot Apr 1, 2026
e01cb46
refactor(dotAI): extract build helper in LangChain4jModelFactory to r…
ihoffmann-dot Apr 1, 2026
d9078de
refactor(dotAI): remove unused loadModels, activateModels and getAvai…
ihoffmann-dot Apr 1, 2026
ba7f173
refactor(dotAI): convert ProviderConfig to Immutables interface
ihoffmann-dot Apr 1, 2026
805fc7d
fix(dotAI): update unit tests for ProviderConfig Immutables migration
ihoffmann-dot Apr 2, 2026
14c417f
fix(dotAI): remove broken test methods for removed AIModels methods
ihoffmann-dot Apr 2, 2026
433c9c8
fix(dotAI): address Claude bot security and correctness review comments
ihoffmann-dot Apr 2, 2026
91ca878
fix(dotAI): remove maximumSize from model caches, keep TTL only
ihoffmann-dot Apr 2, 2026
92fae06
refactor(dotAI): remove dead AIModels and AIModelFallbackStrategy
ihoffmann-dot Apr 2, 2026
fe8cb0d
fix(dotAI): address remaining PR review comments
ihoffmann-dot Apr 6, 2026
52085fb
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 6, 2026
30b0a59
fix(dotAI): address remaining PR review comments
ihoffmann-dot Apr 6, 2026
598dac9
fix(dotAI): migrate integration tests to providerConfig flow
ihoffmann-dot Apr 6, 2026
4dd52e3
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 6, 2026
61a4c35
refactor(dotAI): address remaining PR review comments
ihoffmann-dot Apr 8, 2026
c5f873a
refactor(dotAI): address remaining PR review comments
ihoffmann-dot Apr 8, 2026
31cb86e
test(dotAI): fix WireMock stubs and CompletionsAPI for LangChain4J co…
ihoffmann-dot Apr 9, 2026
b44c35b
feat(dotAI): validate required ProviderConfig fields in LangChain4jMo…
ihoffmann-dot Apr 9, 2026
1d537af
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 9, 2026
17fd32a
test(dotAI): add missing validation tests to LangChain4jModelFactoryTest
ihoffmann-dot Apr 9, 2026
18c5b44
fix(dotAI): remove legacy fields from config endpoint, fix providerCo…
ihoffmann-dot Apr 10, 2026
5fcd079
feat(dotAI): remove model/temperature params from workflow actionlets
ihoffmann-dot Apr 10, 2026
b1145dc
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 13, 2026
62d2e05
fix(dotAI): address PR review comments (dimensions, maxTokens, isEnab…
ihoffmann-dot Apr 13, 2026
111db46
feat(dotAI): implement streaming chat via LangChain4J StreamingChatModel
ihoffmann-dot Apr 13, 2026
07252a4
fix(dotAI): use plain ObjectMapper for providerConfig redaction to av…
ihoffmann-dot Apr 14, 2026
924f6b2
fix(dotAI): use plain ObjectMapper in AppConfig, add isEnabled diagno…
ihoffmann-dot Apr 14, 2026
7fd1b4f
fix(dotAI): check providerConfig instead of apiKey for config warning…
ihoffmann-dot Apr 14, 2026
f3e1d0f
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 14, 2026
86e1776
fix(dotAI): allow unquoted control chars in providerConfig JSON parsing
ihoffmann-dot Apr 14, 2026
4fbb6a4
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 14, 2026
27cc6ee
fix(ai): strip control chars from providerConfig before JSON parse
ihoffmann-dot Apr 14, 2026
a39bf1c
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 14, 2026
b1dea4c
test(ai): add AppConfigTest for providerConfig JSON parsing with embe…
ihoffmann-dot Apr 14, 2026
4aaa0fd
fix(ai): sanitize providerConfig at construction time so all consumer…
ihoffmann-dot Apr 14, 2026
9e61911
debug(ai): log providerConfig snippet around parse error position
ihoffmann-dot Apr 14, 2026
1822dc8
fix(ai): restore DotObjectMapperProvider and remove diagnostic logging
ihoffmann-dot Apr 14, 2026
9e6a393
fix(ai): address PR review comments on LangChain4J integration
ihoffmann-dot Apr 15, 2026
c4421e5
fix(ai): replace new ObjectMapper() with DotObjectMapperProvider in C…
ihoffmann-dot Apr 15, 2026
febd3d7
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 15, 2026
92be76c
fix(ai): update Postman stubs and collection for LangChain4J migration
ihoffmann-dot Apr 15, 2026
fb72963
fix(ai): make imageSize optional in dotAI.yml
ihoffmann-dot Apr 15, 2026
df7d464
fix(ai): update WireMock body patterns for LangChain4J JSON spacing
ihoffmann-dot Apr 15, 2026
9acf22e
fix(ai): relax embedding count assertion to greaterThan(0)
ihoffmann-dot Apr 15, 2026
aefd7da
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 15, 2026
7587479
fix(ai): relax search result assertions to handle identical WireMock …
ihoffmann-dot Apr 15, 2026
2684167
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 15, 2026
0ca23c2
fix(ai): add missing openAiResponse fields and relax seo assertion
ihoffmann-dot Apr 16, 2026
ca551a5
fix(ai): update config endpoint and test to reflect new providerConfi…
ihoffmann-dot Apr 16, 2026
ff423a1
fix(ai-tests): convert SSE stubs to JSON, add stream-specific variant…
ihoffmann-dot Apr 16, 2026
6b094dd
fix(ai-tests): fix WireMock stub priority and pattern for AIViewToolT…
ihoffmann-dot Apr 16, 2026
d6b5788
fix(dotAI): apply PR review fixes (SSE double newline, null modelName…
ihoffmann-dot Apr 17, 2026
258dc17
Merge branch 'main' into dot-ai-langchain-integration
fabrizzio-dotCMS Apr 17, 2026
1632c7e
Merge branch 'main' into dot-ai-langchain-integration
fabrizzio-dotCMS Apr 17, 2026
af6109b
fix(dotAI): address PR review comments - stale logs, NPE, model overr…
ihoffmann-dot Apr 17, 2026
c79ac52
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 17, 2026
d426c74
fix(dotAI): address PR review comments - temperature guard, dead toke…
ihoffmann-dot Apr 17, 2026
2a3fa03
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 17, 2026
9525fc8
Merge branch 'main' into dot-ai-langchain-integration
fabrizzio-dotCMS Apr 17, 2026
4dbf79e
fix(dotAI): warn on temperature <= 0 in prompt()
ihoffmann-dot Apr 17, 2026
871cbda
fix(dotAI): add missing Logger import in CompletionsAPIImpl
ihoffmann-dot Apr 17, 2026
9e82c57
Merge branch 'main' into dot-ai-langchain-integration
ihoffmann-dot Apr 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<graalvm.polyglot.version>25.0.1</graalvm.polyglot.version>
<micrometer.version>1.13.10</micrometer.version>
<opensearch.version>3.3.0</opensearch.version>
<langchain4j.version>1.0.0</langchain4j.version>
</properties>
<dependencyManagement>

Expand Down Expand Up @@ -70,6 +71,14 @@
<scope>import</scope>
</dependency>

<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-bom</artifactId>
<version>${langchain4j.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>


<!-- Asynchronous NIO client server framework for the jvm -->
<!-- <dependency>
Expand Down
5 changes: 5 additions & 0 deletions dotCMS/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,11 @@
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
</dependency>
<dependency>
<!-- LangChain4J OpenAI provider: Chat, Embedding, Image models via OpenAI API -->
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-open-ai</artifactId>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
Expand Down
1 change: 1 addition & 0 deletions dotCMS/src/main/java/com/dotcms/ai/AiKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public class AiKeys {
public static final String COUNT = "count";
public static final String INPUT = "input";
public static final String RESPONSE_FORMAT = "response_format";
public static final String B64_JSON = "b64_json";

private AiKeys() {}

Expand Down
13 changes: 8 additions & 5 deletions dotCMS/src/main/java/com/dotcms/ai/api/EmbeddingsAPIImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -343,9 +343,11 @@ public Tuple2<Integer, List<Float>> pullOrGenerateEmbeddings(final String conten
.getEncoding()
Comment thread
ihoffmann-dot marked this conversation as resolved.
.map(encoding -> encoding.encode(content))
.orElse(List.of());
final int tokenCount = tokens.isEmpty() ? content.split("\\s+").length : tokens.size();
if (tokens.isEmpty()) {
config.debugLogger(this.getClass(), () -> String.format("No tokens for content ID '%s' were encoded: %s", contentId, content));
return Tuple.of(0, List.of());
config.debugLogger(this.getClass(), () -> String.format(
"Encoding unavailable for content ID '%s', using word count (%d) as token estimate",
contentId, tokenCount));
}

final Tuple3<String, Integer, List<Float>> dbEmbeddings =
Expand All @@ -359,8 +361,8 @@ public Tuple2<Integer, List<Float>> pullOrGenerateEmbeddings(final String conten
}

final Tuple2<Integer, List<Float>> openAiEmbeddings = Tuple.of(
tokens.size(),
sendTokensToOpenAI(contentId, tokens, userId));
tokenCount,
sendTokensToOpenAI(contentId, tokens, content, userId));
saveEmbeddingsForCache(content, openAiEmbeddings);
Comment thread
ihoffmann-dot marked this conversation as resolved.
Outdated
EMBEDDING_CACHE.put(hashed, openAiEmbeddings);

Expand Down Expand Up @@ -436,10 +438,11 @@ private void saveEmbeddingsForCache(final String content, final Tuple2<Integer,
*/
private List<Float> sendTokensToOpenAI(final String contentId,
@NotNull final List<Integer> tokens,
@NotNull final String content,
final String userId) {
final JSONObject json = new JSONObject();
json.put(AiKeys.MODEL, config.getEmbeddingsModel().getCurrentModel());
json.put(AiKeys.INPUT, tokens);
json.put(AiKeys.INPUT, content);
config.debugLogger(this.getClass(), () -> String.format("Content tokens for content ID '%s': %s", contentId, tokens));
final String responseString = AIProxyClient.get()
.callToAI(JSONObjectAIRequest.quickEmbeddings(config, json, userId))
Expand Down
25 changes: 18 additions & 7 deletions dotCMS/src/main/java/com/dotcms/ai/api/OpenAIImageAPIImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
import io.vavr.control.Try;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;

public class OpenAIImageAPIImpl implements ImageAPI {
Expand Down Expand Up @@ -99,21 +101,30 @@ public JSONObject sendTextPrompt(final String textPrompt) {
}

private JSONObject createTempFile(final JSONObject imageResponse) {
final String url = imageResponse.optString(AiKeys.URL);
if (UtilMethods.isEmpty(() -> url)) {
Logger.warn(this.getClass(), "imageResponse does not include URL:" + imageResponse);
throw new DotRuntimeException("Image Response does not include URL:" + imageResponse);
}

try {
final String fileName = generateFileName(imageResponse.getString(AiKeys.ORIGINAL_PROMPT));
imageResponse.put("tempFileName", fileName);

final DotTempFile file = tempFileApi.createTempFileFromUrl(fileName, getRequest(), new URL(url), 20);
final String url = imageResponse.optString(AiKeys.URL);
final String b64 = imageResponse.optString(AiKeys.B64_JSON);
final DotTempFile file;

if (!UtilMethods.isEmpty(() -> url)) {
file = tempFileApi.createTempFileFromUrl(fileName, getRequest(), new URL(url), 20);
} else if (!UtilMethods.isEmpty(() -> b64)) {
Comment thread
ihoffmann-dot marked this conversation as resolved.
final byte[] imageBytes = Base64.getDecoder().decode(b64);
file = tempFileApi.createTempFile(fileName, getRequest(), new ByteArrayInputStream(imageBytes));
} else {
Logger.warn(this.getClass(), "imageResponse does not include URL or base64 data:" + imageResponse);
throw new DotRuntimeException("Image Response does not include URL or base64 data:" + imageResponse);
}

imageResponse.put(AiKeys.RESPONSE, file.id);
imageResponse.put("tempFile", file.file.getAbsolutePath());

return imageResponse;
} catch (DotRuntimeException e) {
throw e;
} catch (Exception e) {
imageResponse.put(AiKeys.RESPONSE, e.getMessage());
imageResponse.put(AiKeys.ERROR, e.getMessage());
Expand Down
141 changes: 5 additions & 136 deletions dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,92 +3,40 @@
import com.dotcms.ai.domain.Model;
import com.dotcms.ai.domain.ModelStatus;
import com.dotcms.ai.exception.DotAIModelNotFoundException;
import com.dotcms.ai.model.OpenAIModel;
import com.dotcms.ai.model.OpenAIModels;
import com.dotcms.ai.model.SimpleModel;
import com.dotcms.business.SystemTableUpdatedKeyEvent;
import com.dotcms.http.CircuitBreakerUrl;
import com.dotcms.system.event.local.model.EventSubscriber;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Config;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UtilMethods;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.annotations.VisibleForTesting;
import io.vavr.Lazy;
import io.vavr.Tuple;
import io.vavr.Tuple2;
import io.vavr.Tuple3;
import org.apache.commons.collections4.CollectionUtils;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UtilMethods;

import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
* Manages the AI models used in the application. This class handles loading, caching,
* and retrieving AI models based on the host and model type. It also fetches supported
* models from external sources and maintains a cache of these models.
* and retrieving AI models based on the host and model type.
*
* @author vico
*/
public class AIModels implements EventSubscriber<SystemTableUpdatedKeyEvent> {
public class AIModels {

private static final String SUPPORTED_MODELS_KEY = "supportedModels";
private static final String AI_MODELS_FETCH_ATTEMPTS_KEY = "ai.models.fetch.attempts";
private static final String AI_MODELS_FETCH_TIMEOUT_KEY = "ai.models.fetch.timeout";
private static final String AI_MODELS_API_URL_KEY = "AI_MODELS_API_URL";
private static final String AI_MODELS_API_URL_DEFAULT = "https://api.openai.com/v1/models";
private static final int AI_MODELS_CACHE_TTL = 28800; // 8 hours
private static final int AI_MODELS_CACHE_SIZE = 256;
Comment thread
ihoffmann-dot marked this conversation as resolved.
private static final Lazy<AIModels> INSTANCE = Lazy.of(AIModels::new);
public static final String BEARER = "Bearer ";

private final ConcurrentMap<String, List<Tuple2<AIModelType, AIModel>>> internalModels;
private final ConcurrentMap<Tuple3<String, Model, AIModelType>, AIModel> modelsByName;
Comment thread
ihoffmann-dot marked this conversation as resolved.
Outdated
private final Cache<String, Set<String>> supportedModelsCache;
private final AtomicInteger modelsFetchAttempts;
private final AtomicLong modelsFetchTimeout;
private final AtomicReference<String> aiModelsApiUrl;

public static AIModels get() {
return INSTANCE.get();
}

private static int resolveModelsFetchAttempts() {
return Config.getIntProperty(AI_MODELS_FETCH_ATTEMPTS_KEY, 90);
}

private static long resolveModelsFetchTimeout() {
return Config.getLongProperty(AI_MODELS_FETCH_TIMEOUT_KEY, 4000);
}

private static String resolveAiModelsApiUrl() {
return Config.getStringProperty(AI_MODELS_API_URL_KEY, AI_MODELS_API_URL_DEFAULT);
}

private AIModels() {
internalModels = new ConcurrentHashMap<>();
modelsByName = new ConcurrentHashMap<>();
supportedModelsCache =
Caffeine.newBuilder()
.expireAfterWrite(Duration.ofSeconds(AI_MODELS_CACHE_TTL))
.maximumSize(AI_MODELS_CACHE_SIZE)
.build();
modelsFetchAttempts = new AtomicInteger(resolveModelsFetchAttempts());
modelsFetchTimeout = new AtomicLong(resolveModelsFetchTimeout());
aiModelsApiUrl = new AtomicReference<>(resolveAiModelsApiUrl());
APILocator.getLocalSystemEventsAPI().subscribe(SystemTableUpdatedKeyEvent.class, this);
}

/**
Expand Down Expand Up @@ -234,42 +182,6 @@ public void resetModels(final String host) {
.filter(key -> key._1.equals(host))
.collect(Collectors.toSet())
.forEach(modelsByName::remove);
cleanSupportedModelsCache();
}

/**
* Retrieves the list of supported models, either from the cache or by fetching them
* from an external source if the cache is empty or expired.
*
* @return a set of supported model names
*/
public Set<String> getOrPullSupportedModels(final AppConfig appConfig) {
final Set<String> cached = supportedModelsCache.getIfPresent(SUPPORTED_MODELS_KEY);
if (CollectionUtils.isNotEmpty(cached)) {
return cached;
}

if (!appConfig.isEnabled()) {
appConfig.debugLogger(getClass(), () -> "dotAI is not enabled, returning empty set of supported models");
return Set.of();
}

final CircuitBreakerUrl.Response<OpenAIModels> response = fetchOpenAIModels(appConfig);

if (Objects.nonNull(response.getResponse().getError())) {
throw new DotRuntimeException("Found error in AI response: " + response.getResponse().getError().getMessage());
}

final Set<String> supported = response
.getResponse()
.getData()
.stream()
.map(OpenAIModel::getId)
.map(String::toLowerCase)
.collect(Collectors.toSet());
supportedModelsCache.put(SUPPORTED_MODELS_KEY, supported);

return supported;
}

/**
Expand All @@ -294,49 +206,6 @@ public List<SimpleModel> getAvailableModels() {
.collect(Collectors.toList());
}

@Override
public void notify(final SystemTableUpdatedKeyEvent event) {
Logger.info(this, String.format("Notify for event [%s]", event.getKey()));
if (event.getKey().contains(AI_MODELS_FETCH_ATTEMPTS_KEY)) {
modelsFetchAttempts.set(resolveModelsFetchAttempts());
} else if (event.getKey().contains(AI_MODELS_FETCH_TIMEOUT_KEY)) {
modelsFetchTimeout.set(Config.getIntProperty(AI_MODELS_FETCH_TIMEOUT_KEY, 14));
} else if (event.getKey().contains(AI_MODELS_API_URL_KEY)) {
aiModelsApiUrl.set(resolveAiModelsApiUrl());
}
}

@VisibleForTesting
void cleanSupportedModelsCache() {
supportedModelsCache.invalidate(SUPPORTED_MODELS_KEY);
}

private CircuitBreakerUrl.Response<OpenAIModels> fetchOpenAIModels(final AppConfig appConfig) {
final CircuitBreakerUrl.Response<OpenAIModels> response = CircuitBreakerUrl.builder()
.setMethod(CircuitBreakerUrl.Method.GET)
.setUrl(aiModelsApiUrl.get())
.setTimeout(modelsFetchTimeout.get())
.setTryAgainAttempts(modelsFetchAttempts.get())
.setAuthHeaders(BEARER + appConfig.getApiKey())
.setThrowWhenError(true)
.build()
.doResponse(OpenAIModels.class);

if (response.getStatusCode() == 401) {
throw new InvalidAIKeyException("AI key authentication failed. Please ensure the key is valid, active, and correctly configured.");
} else if (!CircuitBreakerUrl.isSuccessResponse(response)) {
appConfig.debugLogger(
AIModels.class,
() -> String.format(
"Error fetching OpenAI supported models from [%s] (status code: [%d])",
aiModelsApiUrl.get(),
response.getStatusCode()));
throw new DotRuntimeException("Error fetching OpenAI supported models");
}

return response;
}

private void activateModels(final String host) {
final List<AIModel> aiModels = internalModels.get(host)
.stream()
Expand All @@ -352,7 +221,7 @@ private void activateModels(final String host) {
}
Logger.debug(
this,
String.format("Model [%s] is supported by OpenAI, marking it as [%s]", modelName, status));
String.format("Model [%s] activated with status [%s]", modelName, status));
model.setStatus(status);
}));
}
Expand Down
Loading
Loading