diff --git a/dotCMS/src/main/java/com/dotcms/ai/app/AIModel.java b/dotCMS/src/main/java/com/dotcms/ai/app/AIModel.java index d84e2ff86728..495e68320278 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/app/AIModel.java +++ b/dotCMS/src/main/java/com/dotcms/ai/app/AIModel.java @@ -113,11 +113,14 @@ public long minIntervalBetweenCalls() { @Override public String toString() { return "AIModel{" + - "name='" + names + '\'' + + "type=" + type + + ", names=" + names + ", tokensPerMinute=" + tokensPerMinute + ", apiPerMinute=" + apiPerMinute + ", maxTokens=" + maxTokens + ", isCompletion=" + isCompletion + + ", current=" + current + + ", decommissioned=" + decommissioned + '}'; } diff --git a/dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java b/dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java index 26e397f03237..dbcbeabe9bd0 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java +++ b/dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java @@ -19,6 +19,7 @@ import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -164,6 +165,10 @@ public List getOrPullSupportedModels() { .of(() -> fetchOpenAIModels(appConfig)) .getOrElseThrow(() -> new DotRuntimeException("Error fetching OpenAI supported models")); + if (Objects.nonNull(response.getResponse().getError())) { + throw new DotRuntimeException("Found error in AI response: " + response.getResponse().getError().getMessage()); + } + final List supported = response .getResponse() .getData() diff --git a/dotCMS/src/main/java/com/dotcms/ai/app/AppConfig.java b/dotCMS/src/main/java/com/dotcms/ai/app/AppConfig.java index fe1c7d067b05..a1abe360cce6 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/app/AppConfig.java +++ b/dotCMS/src/main/java/com/dotcms/ai/app/AppConfig.java @@ -4,12 +4,16 @@ import com.dotmarketing.exception.DotRuntimeException; import com.dotmarketing.util.Logger; import com.dotmarketing.util.UtilMethods; +import com.liferay.util.StringPool; import io.vavr.control.Try; import org.apache.commons.lang3.StringUtils; import java.io.Serializable; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -25,9 +29,11 @@ public class AppConfig implements Serializable { private static final String AI_API_URL_KEY = "AI_API_URL"; private static final String AI_IMAGE_API_URL_KEY = "AI_IMAGE_API_URL"; private static final String AI_EMBEDDINGS_API_URL_KEY = "AI_EMBEDDINGS_API_URL"; - + private static final String SYSTEM_HOST = "System Host"; public static final Pattern SPLITTER = Pattern.compile("\\s?,\\s?"); + private static final AtomicReference SYSTEM_HOST_CONFIG = new AtomicReference<>(); + private final String host; private final String apiKey; private final transient AIModel model; @@ -45,6 +51,9 @@ public class AppConfig implements Serializable { public AppConfig(final String host, final Map secrets) { this.host = host; + if (SYSTEM_HOST.equalsIgnoreCase(host)) { + setSystemHostConfig(this); + } final AIAppUtil aiAppUtil = AIAppUtil.get(); apiKey = aiAppUtil.discoverEnvSecret(secrets, AppKeys.API_KEY, AI_API_KEY_KEY); @@ -73,18 +82,55 @@ public AppConfig(final String host, final Map secrets) { configValues = secrets.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - Logger.debug(getClass(), () -> "apiKey: " + apiKey); - Logger.debug(getClass(), () -> "apiUrl: " + apiUrl); - Logger.debug(getClass(), () -> "apiImageUrl: " + apiImageUrl); - Logger.debug(getClass(), () -> "embeddingsUrl: " + apiEmbeddingsUrl); - Logger.debug(getClass(), () -> "rolePrompt: " + rolePrompt); - Logger.debug(getClass(), () -> "textPrompt: " + textPrompt); - Logger.debug(getClass(), () -> "model: " + model); - Logger.debug(getClass(), () -> "imagePrompt: " + imagePrompt); - Logger.debug(getClass(), () -> "imageModel: " + imageModel); - Logger.debug(getClass(), () -> "imageSize: " + imageSize); - Logger.debug(getClass(), () -> "embeddingsModel: " + embeddingsModel); - Logger.debug(getClass(), () -> "listerIndexer: " + listenerIndexer); + Logger.debug(this, this::toString); + } + + @Override + public String toString() { + return "AppConfig{" + + "host='" + host + '\'' + + ", apiKey='" + Optional.ofNullable(apiKey).map(key -> "*****").orElse(StringPool.BLANK) + '\'' + + ", model=" + model + + ", imageModel=" + imageModel + + ", embeddingsModel=" + embeddingsModel + + ", apiUrl='" + apiUrl + '\'' + + ", apiImageUrl='" + apiImageUrl + '\'' + + ", apiEmbeddingsUrl='" + apiEmbeddingsUrl + '\'' + + ", rolePrompt='" + rolePrompt + '\'' + + ", textPrompt='" + textPrompt + '\'' + + ", imagePrompt='" + imagePrompt + '\'' + + ", imageSize='" + imageSize + '\'' + + ", listenerIndexer='" + listenerIndexer + '\'' + + '}'; + } + + /** + * Retrieves the system host configuration. + * + * @return the system host configuration + */ + public static AppConfig getSystemHostConfig() { + if (Objects.isNull(SYSTEM_HOST_CONFIG.get())) { + setSystemHostConfig(ConfigService.INSTANCE.config()); + } + return SYSTEM_HOST_CONFIG.get(); + } + + /** + * Prints a specific error message to the log, based on the {@link AppKeys#DEBUG_LOGGING} + * property instead of the usual Log4j configuration. + * + * @param clazz The {@link Class} to log the message for. + * @param message The {@link Supplier} with the message to log. + */ + public static void debugLogger(final Class clazz, final Supplier message) { + if (getSystemHostConfig().getConfigBoolean(AppKeys.DEBUG_LOGGING)) { + Logger.info(clazz, message.get()); + } + } + + public static void setSystemHostConfig(final AppConfig systemHostConfig) { + AppConfig.SYSTEM_HOST_CONFIG.set(systemHostConfig); } /** @@ -287,19 +333,6 @@ public AIModel resolveModelOrThrow(final String modelName) { return aiModel; } - /** - * Prints a specific error message to the log, based on the {@link AppKeys#DEBUG_LOGGING} - * property instead of the usual Log4j configuration. - * - * @param clazz The {@link Class} to log the message for. - * @param message The {@link Supplier} with the message to log. - */ - public static void debugLogger(final Class clazz, final Supplier message) { - if (ConfigService.INSTANCE.config().getConfigBoolean(AppKeys.DEBUG_LOGGING)) { - Logger.info(clazz, message.get()); - } - } - /** * Checks if the configuration is enabled. * diff --git a/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java b/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java index 7afdad1c380b..a40c57c959f8 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java +++ b/dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java @@ -1,5 +1,7 @@ package com.dotcms.ai.app; +import com.liferay.util.StringPool; + public enum AppKeys { API_KEY("apiKey", null), @@ -22,12 +24,12 @@ public enum AppKeys { IMAGE_MODEL_TOKENS_PER_MINUTE("imageModelTokensPerMinute", "0"), IMAGE_MODEL_API_PER_MINUTE("imageModelApiPerMinute", "50"), IMAGE_MODEL_MAX_TOKENS("imageModelMaxTokens", "0"), - IMAGE_MODEL_COMPLETION("imageModelCompletion", AppKeys.FALSE), + IMAGE_MODEL_COMPLETION("imageModelCompletion", StringPool.FALSE), EMBEDDINGS_MODEL_NAMES("embeddingsModelNames", null), EMBEDDINGS_MODEL_TOKENS_PER_MINUTE("embeddingsModelTokensPerMinute", "1000000"), EMBEDDINGS_MODEL_API_PER_MINUTE("embeddingsModelApiPerMinute", "3000"), EMBEDDINGS_MODEL_MAX_TOKENS("embeddingsModelMaxTokens", "8191"), - EMBEDDINGS_MODEL_COMPLETION("embeddingsModelCompletion", AppKeys.FALSE), + EMBEDDINGS_MODEL_COMPLETION("embeddingsModelCompletion", StringPool.FALSE), EMBEDDINGS_SPLIT_AT_TOKENS("com.dotcms.ai.embeddings.split.at.tokens", "512"), EMBEDDINGS_MINIMUM_TEXT_LENGTH_TO_INDEX("com.dotcms.ai.embeddings.minimum.text.length", "64"), EMBEDDINGS_MINIMUM_FILE_SIZE_TO_INDEX("com.dotcms.ai.embeddings.minimum.file.size", "1024"), @@ -39,7 +41,7 @@ public enum AppKeys { EMBEDDINGS_CACHE_TTL_SECONDS("com.dotcms.ai.embeddings.cache.ttl.seconds", "600"), EMBEDDINGS_CACHE_SIZE("com.dotcms.ai.embeddings.cache.size", "1000"), EMBEDDINGS_DB_DELETE_OLD_ON_UPDATE("com.dotcms.ai.embeddings.delete.old.on.update", "true"), - DEBUG_LOGGING("com.dotcms.ai.debug.logging", AppKeys.FALSE), + DEBUG_LOGGING("com.dotcms.ai.debug.logging", StringPool.FALSE), COMPLETION_TEMPERATURE("com.dotcms.ai.completion.default.temperature", "1"), COMPLETION_ROLE_PROMPT( "com.dotcms.ai.completion.role.prompt", @@ -52,8 +54,6 @@ public enum AppKeys { AI_MODELS_CACHE_TTL("com.dotcms.ai.models.supported.ttl", "28800"), AI_MODELS_CACHE_SIZE("com.dotcms.ai.models.supported.size", "64"); - private static final String FALSE = "false"; - public static final String APP_KEY = "dotAI"; public final String key; diff --git a/dotCMS/src/main/java/com/dotcms/ai/util/OpenAIRequest.java b/dotCMS/src/main/java/com/dotcms/ai/util/OpenAIRequest.java index d022156f15e3..c8716f3de039 100644 --- a/dotCMS/src/main/java/com/dotcms/ai/util/OpenAIRequest.java +++ b/dotCMS/src/main/java/com/dotcms/ai/util/OpenAIRequest.java @@ -52,18 +52,23 @@ public static void doRequest(final String urlIn, final AppConfig appConfig, final JSONObject json, final OutputStream out) { + AppConfig.debugLogger( + OpenAIRequest.class, + () -> String.format( + "Posting to [%s] with method [%s]%swith app config:%s%sthe payload: %s", + urlIn, + method, + System.lineSeparator(), + appConfig.toString(), + System.lineSeparator(), + json.toString(2))); if (!appConfig.isEnabled()) { - AppConfig.debugLogger(OpenAIRequest.class, () -> "dotAI is not enabled and will not send request."); + AppConfig.debugLogger(OpenAIRequest.class, () -> "App dotAI is not enabled and will not send request."); throw new DotRuntimeException("App dotAI config without API urls or API key"); } final AIModel model = appConfig.resolveModelOrThrow(json.optString(AiKeys.MODEL)); - - if (appConfig.getConfigBoolean(AppKeys.DEBUG_LOGGING)) { - Logger.debug(OpenAIRequest.class, "posting: " + json); - } - final long sleep = lastRestCall.computeIfAbsent(model, m -> 0L) + model.minIntervalBetweenCalls() - System.currentTimeMillis(); diff --git a/dotCMS/src/main/java/com/liferay/util/StringPool.java b/dotCMS/src/main/java/com/liferay/util/StringPool.java index 80b5d2c29f57..478ef31f3dc6 100644 --- a/dotCMS/src/main/java/com/liferay/util/StringPool.java +++ b/dotCMS/src/main/java/com/liferay/util/StringPool.java @@ -89,4 +89,6 @@ public class StringPool { public static final String TRUE = Boolean.TRUE.toString(); + public static final String FALSE = Boolean.FALSE.toString(); + } diff --git a/dotcms-integration/src/test/java/com/dotcms/ai/app/AIModelsTest.java b/dotcms-integration/src/test/java/com/dotcms/ai/app/AIModelsTest.java index 15483a02ad4e..fab42ee3dea1 100644 --- a/dotcms-integration/src/test/java/com/dotcms/ai/app/AIModelsTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/ai/app/AIModelsTest.java @@ -163,8 +163,7 @@ public void test_getOrPullSupportedModules_invalidEndpoint() { IPUtils.disabledIpPrivateSubnet(false); final List supported = aiModels.getOrPullSupportedModels(); - assertNotNull(supported); - assertTrue(supported.isEmpty()); + assertSupported(supported); IPUtils.disabledIpPrivateSubnet(true); AIModels.get().setAppConfigSupplier(ConfigService.INSTANCE::config); @@ -207,4 +206,9 @@ private static void assertModels(final Optional model1, assertSame(type, model2.get().getType()); } + private static void assertSupported(List supported) { + assertNotNull(supported); + assertTrue(supported.isEmpty()); + } + }