Skip to content

Commit

Permalink
#29281: Applying feedback and fixing dirty models bug
Browse files Browse the repository at this point in the history
  • Loading branch information
victoralfaro-dotcms committed Aug 2, 2024
1 parent f54a174 commit 9d69eb4
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 246 deletions.
2 changes: 1 addition & 1 deletion dotCMS/src/main/java/com/dotcms/ai/app/AIAppUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public String discoverSecret(final Map<String, Secret> secrets, final AppKeys ke
* @return the list of split secret values
*/
public List<String> splitDiscoveredSecret(final Map<String, Secret> secrets, final AppKeys key) {
return Arrays.stream(discoverSecret(secrets, key).split(","))
return Arrays.stream(Optional.ofNullable(discoverSecret(secrets, key)).orElse(StringPool.BLANK).split(","))
.map(String::trim)
.map(String::toLowerCase)
.collect(Collectors.toList());
Expand Down
68 changes: 32 additions & 36 deletions dotCMS/src/main/java/com/dotcms/ai/app/AIModels.java
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,16 @@ public Optional<AIModel> findModel(final String host, final AIModelType type) {
*/
public void resetModels(final Host host) {
final String hostKey = host.getHostname();
synchronized (AIModels.class) {
Optional.ofNullable(internalModels.get(hostKey)).ifPresent(models -> {
models.clear();
internalModels.remove(hostKey);
});
modelsByName.keySet()
.stream()
.filter(key -> key._1.equals(hostKey))
.collect(Collectors.toSet())
.forEach(modelsByName::remove);
ConfigService.INSTANCE.config(host);
}
Optional.ofNullable(internalModels.get(hostKey)).ifPresent(models -> {
models.clear();
internalModels.remove(hostKey);
});
modelsByName.keySet()
.stream()
.filter(key -> key._1.equals(hostKey))
.collect(Collectors.toSet())
.forEach(modelsByName::remove);
ConfigService.INSTANCE.config(host);
}

/**
Expand All @@ -152,31 +150,29 @@ public void resetModels(final Host host) {
* @return a list of supported model names
*/
public List<String> getOrPullSupportedModels() {
synchronized (supportedModelsCache) {
final List<String> cached = supportedModelsCache.getIfPresent(SUPPORTED_MODELS_KEY);
if (CollectionUtils.isNotEmpty(cached)) {
return cached;
}

final AppConfig appConfig = appConfigSupplier.get();
if (!appConfig.isEnabled()) {
Logger.debug(this, "OpenAI is not enabled, returning empty list of supported models");
return List.of();
}

final List<String> supported = Try.of(() ->
fetchOpenAIModels(appConfig)
.getResponse()
.getData()
.stream()
.map(OpenAIModel::getId)
.map(String::toLowerCase)
.collect(Collectors.toList()))
.getOrElse(Optional.ofNullable(cached).orElse(List.of()));
supportedModelsCache.put(SUPPORTED_MODELS_KEY, supported);

return supported;
final List<String> cached = supportedModelsCache.getIfPresent(SUPPORTED_MODELS_KEY);
if (CollectionUtils.isNotEmpty(cached)) {
return cached;
}

final AppConfig appConfig = appConfigSupplier.get();
if (!appConfig.isEnabled()) {
Logger.debug(this, "OpenAI is not enabled, returning empty list of supported models");
return List.of();
}

final List<String> supported = Try.of(() ->
fetchOpenAIModels(appConfig)
.getResponse()
.getData()
.stream()
.map(OpenAIModel::getId)
.map(String::toLowerCase)
.collect(Collectors.toList()))
.getOrElse(Optional.ofNullable(cached).orElse(List.of()));
supportedModelsCache.put(SUPPORTED_MODELS_KEY, supported);

return supported;
}

/**
Expand Down
33 changes: 16 additions & 17 deletions dotCMS/src/main/java/com/dotcms/ai/app/AppConfig.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.dotcms.ai.app;

import com.dotcms.ai.util.OpenAIRequest;
import com.dotcms.security.apps.Secret;
import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.util.Config;
Expand Down Expand Up @@ -44,7 +43,6 @@ public AppConfig(final String host, final Map<String, Secret> secrets) {
this.host = host;

final AIAppUtil aiAppUtil = AIAppUtil.get();
apiKey = aiAppUtil.discoverEnvSecret(secrets, AppKeys.API_KEY);

AIModels.get().loadModels(
this.host,
Expand All @@ -53,13 +51,14 @@ public AppConfig(final String host, final Map<String, Secret> secrets) {
aiAppUtil.createImageModel(secrets),
aiAppUtil.createEmbeddingsModel(secrets)));

model = resolveModel(AIModelType.TEXT);
imageModel = resolveModel(AIModelType.IMAGE);
embeddingsModel = resolveModel(AIModelType.EMBEDDINGS);

apiUrl = aiAppUtil.discoverEnvSecret(secrets, AppKeys.API_URL);
apiImageUrl = aiAppUtil.discoverEnvSecret(secrets, AppKeys.API_IMAGE_URL);
apiEmbeddingsUrl = discoverEmbeddingsApiUrl(secrets);
apiKey = aiAppUtil.discoverEnvSecret(secrets, AppKeys.API_KEY);

model = resolveModel(AIModelType.TEXT);
imageModel = resolveModel(AIModelType.IMAGE);
embeddingsModel = resolveModel(AIModelType.EMBEDDINGS);

rolePrompt = aiAppUtil.discoverSecret(secrets, AppKeys.ROLE_PROMPT);
textPrompt = aiAppUtil.discoverSecret(secrets, AppKeys.TEXT_PROMPT);
Expand All @@ -73,13 +72,13 @@ public AppConfig(final String host, final Map<String, Secret> secrets) {
Logger.debug(getClass(), () -> "apiUrl: " + apiUrl);
Logger.debug(getClass(), () -> "apiImageUrl: " + apiImageUrl);
Logger.debug(getClass(), () -> "embeddingsUrl: " + apiEmbeddingsUrl);
Logger.debug(getClass(), () -> "model: " + model);
Logger.debug(getClass(), () -> "imageModel: " + imageModel);
Logger.debug(getClass(), () -> "embeddingsModel: " + embeddingsModel);
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);
}

Expand Down Expand Up @@ -263,24 +262,24 @@ public AIModel resolveModel(final AIModelType type) {
* @param modelName the name of the model to find
*/
public AIModel resolveModelOrThrow(final String modelName) {
final AIModel model = AIModels.get()
final AIModel aiModel = AIModels.get()
.findModel(host, modelName)
.orElseThrow(() -> {
final String supported = String.join(", ", AIModels.get().getOrPullSupportedModels());
return new DotRuntimeException(
"Unable to find model: [" + modelName + "]. Only [" + supported + "] are supported ");
});

if (!model.isOperational()) {
Logger.debug(
OpenAIRequest.class,
String.format(
if (!aiModel.isOperational()) {
debugLogger(
AppConfig.class,
() -> String.format(
"Resolved model [%s] is not operational, avoiding its usage",
model.getCurrentModel()));
throw new DotRuntimeException(String.format("Model [%s] is not operational", model.getCurrentModel()));
aiModel.getCurrentModel()));
throw new DotRuntimeException(String.format("Model [%s] is not operational", aiModel.getCurrentModel()));
}

return model;
return aiModel;
}

/**
Expand Down
28 changes: 14 additions & 14 deletions dotCMS/src/main/java/com/dotcms/ai/app/AppKeys.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ public enum AppKeys {
TEXT_PROMPT("textPrompt", "Use Descriptive writing style."),
IMAGE_PROMPT("imagePrompt", "Use 16:9 aspect ratio."),
IMAGE_SIZE("imageSize", "1024x1024"),
TEXT_MODEL_NAMES("textModelNames", "gpt-3.5-turbo-16k"),
TEXT_MODEL_TOKENS_PER_MINUTE("textModelTokensPerMinute", "1000"),
TEXT_MODEL_API_PER_MINUTE("textModelApiPerMinute", "1000"),
TEXT_MODEL_MAX_TOKENS("textModelMaxTokens", "1000"),
TEXT_MODEL_NAMES("textModelNames", null),
TEXT_MODEL_TOKENS_PER_MINUTE("textModelTokensPerMinute", "180000"),
TEXT_MODEL_API_PER_MINUTE("textModelApiPerMinute", "3500"),
TEXT_MODEL_MAX_TOKENS("textModelMaxTokens", "16384"),
TEXT_MODEL_COMPLETION("textModelCompletion", "true"),
IMAGE_MODEL_NAMES("imageModelNames", "dall-e-3"),
IMAGE_MODEL_TOKENS_PER_MINUTE("imageModelTokensPerMinute", "1000"),
IMAGE_MODEL_API_PER_MINUTE("imageModelApiPerMinute", "1000"),
IMAGE_MODEL_MAX_TOKENS("imageModelMaxTokens", "1000"),
IMAGE_MODEL_COMPLETION("imageModelCompletion", "true"),
EMBEDDINGS_MODEL_NAMES("embeddingsModelNames", "text-embedding-ada-002"),
EMBEDDINGS_MODEL_TOKENS_PER_MINUTE("embeddingsModelTokensPerMinute", "1000"),
EMBEDDINGS_MODEL_API_PER_MINUTE("embeddingsModelApiPerMinute", "1000"),
EMBEDDINGS_MODEL_MAX_TOKENS("embeddingsModelMaxTokens", "1000"),
EMBEDDINGS_MODEL_COMPLETION("embeddingsModelCompletion", "true"),
IMAGE_MODEL_NAMES("imageModelNames", null),
IMAGE_MODEL_TOKENS_PER_MINUTE("imageModelTokensPerMinute", "0"),
IMAGE_MODEL_API_PER_MINUTE("imageModelApiPerMinute", "50"),
IMAGE_MODEL_MAX_TOKENS("imageModelMaxTokens", "0"),
IMAGE_MODEL_COMPLETION("imageModelCompletion", "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", "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"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public void notify(final AppSecretSavedEvent event) {
final String hostId = event.getHostIdentifier();
final Host host = Try.of(() -> hostAPI.find(hostId, APILocator.systemUser(), false)).getOrNull();

Optional.ofNullable(host).ifPresent(found -> AIModels.get().resetModels(found));
Optional.ofNullable(host).ifPresent(found -> AIModels.get().resetModels(found));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.dotcms.ai.api.EmbeddingsAPI;
import com.dotcms.ai.app.AppConfig;
import com.dotcms.ai.app.AppKeys;
import com.dotcms.ai.app.ConfigService;
import com.dotcms.ai.db.EmbeddingsDTO;
import com.dotcms.content.elasticsearch.business.event.ContentletArchiveEvent;
Expand Down Expand Up @@ -127,10 +126,7 @@ private void addToIndexesIfNeeded(final Contentlet contentlet) {
.stream()
.filter(typeFields -> contentType.equalsIgnoreCase(typeFields.getKey()))
.forEach(e -> EmbeddingsAPI.impl()
.generateEmbeddingsForContent(
contentlet,
e.getValue(),
indexName));
.generateEmbeddingsForContent(contentlet, e.getValue(), indexName));
}
}

Expand Down
102 changes: 65 additions & 37 deletions dotCMS/src/main/resources/apps/dotAI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,41 +28,8 @@ params:
label: "Text Prompt"
hint: "A prompt describing writing style."
required: false
imagePrompt:
value: "Use 16:9 aspect ratio."
hidden: false
type: "STRING"
label: "Image Prompt"
hint: "Aspect ratio to use for image."
required: false
imageSize:
hidden: false
type: "SELECT"
label: "Image size"
hint: "Image size to generate"
required: true
value:
- label: "1792x1024 (Blog Image 3:2)"
value: "1792x1024"
selected: true
- label: "1024x1792 (Blog Image Vertical 2:3)"
value: "1024x1792"
- label: "1024x1024 (Large Square 1:1)"
value: "1024x1024"
- label: "1280x720 (Hero Image 16:9)"
value: "1280x720"
- label: "1200x630 (Image 3:2)"
value: "1200x630"
- label: "630x1200 (Image Vertical 2:3)"
value: "630x1200"
- label: "512x512 (Medium Square 1:1)"
value: "512x512"
- label: "1920x1080 (Background 16:9)"
value: "1920x1080"
- label: "256x256 (Small Square 1:1)"
value: "256x256"
textModelNames:
value: ""
value: "gpt-3.5-turbo-16k"
hidden: false
type: "STRING"
label: "Model Names"
Expand Down Expand Up @@ -90,14 +57,47 @@ params:
hint: "Maximum number of tokens used to generate OpenAI API response."
required: false
textModelCompletion:
value: "false"
value: "true"
hidden: false
type: "BOOL"
label: "Completion model enabled"
hint: "Enable completion model used to generate OpenAI API response."
required: false
imagePrompt:
value: "Use 16:9 aspect ratio."
hidden: false
type: "STRING"
label: "Image Prompt"
hint: "Aspect ratio to use for image."
required: false
imageSize:
hidden: false
type: "SELECT"
label: "Image size"
hint: "Image size to generate"
required: true
value:
- label: "1792x1024 (Blog Image 3:2)"
value: "1792x1024"
selected: true
- label: "1024x1792 (Blog Image Vertical 2:3)"
value: "1024x1792"
- label: "1024x1024 (Large Square 1:1)"
value: "1024x1024"
- label: "1280x720 (Hero Image 16:9)"
value: "1280x720"
- label: "1200x630 (Image 3:2)"
value: "1200x630"
- label: "630x1200 (Image Vertical 2:3)"
value: "630x1200"
- label: "512x512 (Medium Square 1:1)"
value: "512x512"
- label: "1920x1080 (Background 16:9)"
value: "1920x1080"
- label: "256x256 (Small Square 1:1)"
value: "256x256"
imageModelNames:
value: ""
value: "dall-e-3"
hidden: false
type: "STRING"
label: "Image Model Names"
Expand Down Expand Up @@ -132,12 +132,40 @@ params:
hint: "Enable completion model used to generate OpenAI API response."
required: false
embeddingsModelNames:
value: ""
value: "text-embedding-ada-002"
hidden: false
type: "STRING"
label: "Embeddings Model Names"
hint: "Comma delimited list of embeddings models used to generate OpenAI API response."
required: true
embeddingsModelTokensPerMinute:
value: "1000000"
hidden: false
type: "STRING"
label: "Embeddings Tokens per Minute"
hint: "Tokens per minute used to generate OpenAI API response."
required: false
embeddingsModelApiPerMinute:
value: "3000"
hidden: false
type: "STRING"
label: "Embeddings API per Minute"
hint: "API per minute used to generate OpenAI API response."
required: false
embeddingsModelMaxTokens:
value: "8191"
hidden: false
type: "STRING"
label: "Embeddings Max Tokens"
hint: "Maximum number of tokens used to generate OpenAI API response."
required: false
embeddingsModelCompletion:
value: "false"
hidden: false
type: "BOOL"
label: "Embeddings Completion model enabled"
hint: "Enable completion model used to generate OpenAI API response."
required: false
listenerIndexer:
value: ""
hidden: false
Expand Down
Loading

0 comments on commit 9d69eb4

Please sign in to comment.