diff --git a/dotCMS/src/main/java/com/dotcms/ai/client/AIClient.java b/dotCMS/src/main/java/com/dotcms/ai/client/AIClient.java new file mode 100644 index 000000000000..5393bcaa35ac --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/client/AIClient.java @@ -0,0 +1,108 @@ +package com.dotcms.ai.client; + +import com.dotcms.ai.domain.AIProvider; +import com.dotcms.ai.domain.AIRequest; +import com.dotcms.ai.domain.JSONObjectAIRequest; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpUriRequest; + +import javax.ws.rs.HttpMethod; +import java.io.OutputStream; +import java.io.Serializable; + +/** + * Interface representing an AI client capable of sending requests to an AI service. + * + *
+ * This interface defines methods for obtaining the AI provider and sending requests + * to the AI service. Implementations of this interface should handle the specifics + * of interacting with the AI service, including request formatting and response handling. + *
+ * + *+ * The interface also provides a NOOP implementation that throws an + * {@link UnsupportedOperationException} for all operations. + *
+ * + * @author vico + */ +public interface AIClient { + + AIClient NOOP = new AIClient() { + @Override + public AIProvider getProvider() { + return AIProvider.NONE; + } + + @Override + public+ * This interface defines a method for applying a strategy to an AI client request, + * allowing for different handling mechanisms to be implemented. The NOOP strategy + * is provided as a default implementation that performs no operations. + *
+ * + *+ * Implementations of this interface should define how to process the AI request + * and handle the response, potentially writing the response to an output stream. + *
+ * + * @author vico + */ +public interface AIClientStrategy { + + AIClientStrategy NOOP = (client, handler, request, output) -> AIResponse.builder().build(); + + /** + * Applies the strategy to the given AI client request and handles the response. + * + * @param client the AI client to which the request is sent + * @param handler the response evaluator to handle the response + * @param request the AI request to be processed + * @param output the output stream to which the response will be written + */ + void applyStrategy(AIClient client, + AIResponseEvaluator handler, + AIRequest extends Serializable> request, + OutputStream output); + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/client/AIDefaultStrategy.java b/dotCMS/src/main/java/com/dotcms/ai/client/AIDefaultStrategy.java new file mode 100644 index 000000000000..22c3a5aec788 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/client/AIDefaultStrategy.java @@ -0,0 +1,34 @@ +package com.dotcms.ai.client; + +import com.dotcms.ai.domain.AIRequest; + +import java.io.OutputStream; +import java.io.Serializable; + +/** + * Default implementation of the {@link AIClientStrategy} interface. + * + *+ * This class provides a default strategy for handling AI client requests by + * directly sending the request using the provided AI client and writing the + * response to the given output stream. + *
+ * + *+ * The default strategy does not perform any additional processing or handling + * of the request or response, delegating the entire operation to the AI client. + *
+ * + * @author vico + */ +public class AIDefaultStrategy implements AIClientStrategy { + + @Override + public void applyStrategy(final AIClient client, + final AIResponseEvaluator handler, + final AIRequest extends Serializable> request, + final OutputStream output) { + client.sendRequest(request, output); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/client/AIProxiedClient.java b/dotCMS/src/main/java/com/dotcms/ai/client/AIProxiedClient.java new file mode 100644 index 000000000000..bd6020317f2a --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/client/AIProxiedClient.java @@ -0,0 +1,86 @@ +package com.dotcms.ai.client; + +import com.dotcms.ai.domain.AIRequest; +import com.dotcms.ai.domain.AIResponse; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Optional; + +/** + * A proxy client for interacting with an AI service using a specified strategy. + * + *+ * This class provides a mechanism to send requests to an AI service through a proxied client, + * applying a given strategy for handling the requests and responses. It supports a NOOP implementation + * that performs no operations. + *
+ * + *+ * The class allows for the creation of proxied clients with different strategies and response evaluators, + * enabling flexible handling of AI service interactions. + *
+ * + * @author vico + */ +public class AIProxiedClient { + + public static final AIProxiedClient NOOP = new AIProxiedClient(null, AIClientStrategy.NOOP, null); + + private final AIClient client; + private final AIClientStrategy strategy; + private final AIResponseEvaluator responseEvaluator; + + private AIProxiedClient(final AIClient client, + final AIClientStrategy strategy, + final AIResponseEvaluator responseEvaluator) { + this.client = client; + this.strategy = strategy; + this.responseEvaluator = responseEvaluator; + } + + /** + * Creates an AIProxiedClient with the specified client, strategy, and response evaluator. + * + * @param client the AI client to be proxied + * @param strategy the strategy to be applied for handling requests and responses + * @param responseParser the response evaluator to process responses + * @return a new instance of AIProxiedClient + */ + public static AIProxiedClient of(final AIClient client, + final AIProxyStrategy strategy, + final AIResponseEvaluator responseParser) { + return new AIProxiedClient(client, strategy.getStrategy(), responseParser); + } + + /** + * Creates an AIProxiedClient with the specified client and strategy. + * + * @param client the AI client to be proxied + * @param strategy the strategy to be applied for handling requests and responses + * @return a new instance of AIProxiedClient + */ + public static AIProxiedClient of(final AIClient client, final AIProxyStrategy strategy) { + return of(client, strategy, null); + } + + /** + * Sends the given AI request to the AI service and writes the response to the provided output stream. + * + * @param+ * This class provides a mechanism to send requests to various AI service providers through proxied clients, + * applying different strategies for handling the requests and responses. It supports adding new clients and + * switching between different AI providers. + *
+ * + *+ * The class allows for flexible handling of AI service interactions by maintaining a map of proxied clients + * and providing methods to send requests to the current or specified provider. + *
+ * + * @author vico + */ +public class AIProxyClient { + + private static final Lazy+ * This enum provides different strategies for handling AI client requests, including + * a default strategy and a model fallback strategy. Each strategy is associated with + * an implementation of the {@link AIClientStrategy} interface. + *
+ * + *+ * The strategies can be used to customize the behavior of AI client interactions, + * allowing for flexible handling of requests and responses. + *
+ * + * @author vico + */ +public enum AIProxyStrategy { + + DEFAULT(new AIDefaultStrategy()), + // TODO: pr-split -> uncomment this line + //MODEL_FALLBACK(new AIModelFallbackStrategy()); + MODEL_FALLBACK(null); + + private final AIClientStrategy strategy; + + AIProxyStrategy(final AIClientStrategy strategy) { + this.strategy = strategy; + } + + public AIClientStrategy getStrategy() { + return strategy; + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/client/AIResponseEvaluator.java b/dotCMS/src/main/java/com/dotcms/ai/client/AIResponseEvaluator.java new file mode 100644 index 000000000000..d7f8d2ba5ce4 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/client/AIResponseEvaluator.java @@ -0,0 +1,36 @@ +package com.dotcms.ai.client; + +import com.dotcms.ai.domain.AIResponseData; + +/** + * Interface for evaluating AI responses. + * It provides methods to process responses and exceptions, updating the provided metadata. + * + *Methods:
+ *+ * This class provides methods to send requests to the OpenAI service and handle responses. + * It includes functionality to manage rate limiting and ensure that models are operational + * before sending requests. + *
+ * + *+ * The class uses a singleton pattern to ensure a single instance of the client is used + * throughout the application. It also maintains a record of the last REST call for each + * model to enforce rate limiting. + *
+ * + * @auhor vico + */ +public class OpenAIClient implements AIClient { + + private static final LazyMethods:
+ *+ * This enum defines various AI service providers that can be used within the application. + * Each provider is associated with a specific name that identifies the AI service. + *
+ * + *+ * The providers can be used to configure and manage interactions with different AI services, + * allowing for flexible integration and switching between multiple AI providers. + *
+ * + * @author vico + */ +public enum AIProvider { + + NONE("None"), + OPEN_AI("OpenAI"), + BEDROCK("Amazon Bedrock"), + GEMINI("Google Gemini"); + + private final String provider; + + AIProvider(final String provider) { + this.provider = provider; + } + + public String getProvider() { + return provider; + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/domain/AIRequest.java b/dotCMS/src/main/java/com/dotcms/ai/domain/AIRequest.java new file mode 100644 index 000000000000..2635267c3e2a --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/domain/AIRequest.java @@ -0,0 +1,219 @@ +package com.dotcms.ai.domain; + +import com.dotcms.ai.app.AIModelType; +import com.dotcms.ai.app.AppConfig; + +import javax.ws.rs.HttpMethod; +import java.io.Serializable; + +/** + * Represents a request to an AI service. + * + *+ * This class encapsulates the details of an AI request, including the URL, HTTP method, + * configuration, model type, payload, and user ID. It provides methods to create and + * configure AI requests for different model types such as text, image, and embeddings. + *
+ * + * @param+ * This class encapsulates the details of an AI response, including the response content. + * It provides methods to build and retrieve the response. + *
+ * + *+ * The class also provides a static instance representing an empty response. + *
+ * + * @author vico + */ +public class AIResponse { + + public static final AIResponse EMPTY = builder().build(); + + private final String response; + + private AIResponse(final Builder builder) { + this.response = builder.response; + } + + public static Builder builder() { + return new Builder(); + } + + public String getResponse() { + return response; + } + + public static class Builder { + + private String response; + + public Builder withResponse(final String response) { + this.response = response; + return this; + } + + + public AIResponse build() { + return new AIResponse(this); + } + + } +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/domain/AIResponseData.java b/dotCMS/src/main/java/com/dotcms/ai/domain/AIResponseData.java new file mode 100644 index 000000000000..85ac2d9d0483 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/domain/AIResponseData.java @@ -0,0 +1,68 @@ +package com.dotcms.ai.domain; + +import com.dotmarketing.exception.DotRuntimeException; +import org.apache.commons.lang3.StringUtils; + +/** + * Represents the data of a response from an AI service. + * + *+ * This class encapsulates the details of an AI response, including the response content, error message, + * status, and any exceptions that may have occurred. It provides methods to retrieve and set these details, + * as well as a method to check if the response was successful. + *
+ * + * @author vico + */ +public class AIResponseData { + + private String response; + private String error; + private ModelStatus status; + private DotRuntimeException exception; + + public String getResponse() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } + + public String getError() { + return error; + } + + public void setError(final String error) { + this.error = error; + } + + public ModelStatus getStatus() { + return status; + } + + public void setStatus(ModelStatus status) { + this.status = status; + } + + public DotRuntimeException getException() { + return exception; + } + + public void setException(DotRuntimeException exception) { + this.exception = exception; + } + + public boolean isSuccess() { + return StringUtils.isBlank(error); + } + + @Override + public String toString() { + return "AIResponseData{" + + "response='" + response + '\'' + + ", error='" + error + '\'' + + ", status=" + status + + '}'; + } +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/domain/JSONObjectAIRequest.java b/dotCMS/src/main/java/com/dotcms/ai/domain/JSONObjectAIRequest.java new file mode 100644 index 000000000000..f111e3b10022 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/domain/JSONObjectAIRequest.java @@ -0,0 +1,106 @@ +package com.dotcms.ai.domain; + +import com.dotcms.ai.app.AIModelType; +import com.dotcms.ai.app.AppConfig; +import com.dotmarketing.util.json.JSONObject; + +/** + * Represents a request to an AI service with a JSON payload. + * + *+ * This class encapsulates the details of an AI request with a JSON payload, including the URL, HTTP method, + * configuration, model type, payload, and user ID. It provides methods to create and configure AI requests + * for different model types such as text, image, and embeddings. + *
+ * + * @author vico + */ +public class JSONObjectAIRequest extends AIRequest+ * This class encapsulates the details of an AI model, including its name, status, and index. + * It provides methods to retrieve and set these details, as well as methods to check if the model is operational. + *
+ * + *+ * The class also provides a builder for constructing instances of the model. + *
+ * + * @author vico + */ +public class Model { + + private final String name; + private final AtomicReference+ * This enum defines various statuses that an AI model can have, such as active, invalid, decommissioned, or unknown. + * Each status may have different implications for the operation of the model. + *
+ * + * @author vico + */ +public enum ModelStatus { + + ACTIVE(false), + INVALID(false), + DECOMMISSIONED(false), + UNKNOWN(true); + + private final boolean needsToThrow; + + ModelStatus(final boolean needsToThrow) { + this.needsToThrow = needsToThrow; + } + + public boolean doesNeedToThrow() { + return needsToThrow; + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIAllModelsExhaustedException.java b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIAllModelsExhaustedException.java new file mode 100644 index 000000000000..6833fdabb252 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIAllModelsExhaustedException.java @@ -0,0 +1,22 @@ +package com.dotcms.ai.exception; + +import com.dotmarketing.exception.DotRuntimeException; + +/** + * Exception thrown when all AI models have been exhausted. + * + *+ * This exception is used to indicate that all available AI models have been exhausted and no further models + * are available for processing. It extends the {@link DotRuntimeException} to provide additional context + * specific to AI model exhaustion scenarios. + *
+ * + * @author vico + */ +public class DotAIAllModelsExhaustedException extends DotRuntimeException { + + public DotAIAllModelsExhaustedException(final String message) { + super(message); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIAppConfigDisabledException.java b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIAppConfigDisabledException.java new file mode 100644 index 000000000000..5b549c74fed5 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIAppConfigDisabledException.java @@ -0,0 +1,22 @@ +package com.dotcms.ai.exception; + +import com.dotmarketing.exception.DotRuntimeException; + +/** + * Exception thrown when the AI application configuration is disabled. + * + *+ * This exception is used to indicate that the AI application configuration is disabled and cannot be used. + * It extends the {@link DotRuntimeException} to provide additional context specific to AI application configuration + * disabled scenarios. + *
+ * + * @author vico + */ +public class DotAIAppConfigDisabledException extends DotRuntimeException { + + public DotAIAppConfigDisabledException(final String message) { + super(message); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIClientConnectException.java b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIClientConnectException.java new file mode 100644 index 000000000000..e17bf9b8d09f --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIClientConnectException.java @@ -0,0 +1,21 @@ +package com.dotcms.ai.exception; + +import com.dotmarketing.exception.DotRuntimeException; + +/** + * Exception thrown when there is a connection error with the AI client. + * + *+ * This exception is used to indicate that there is a connection error with the AI client. It extends the {@link DotRuntimeException} + * to provide additional context specific to AI client connection error scenarios. + *
+ * + * @author vico + */ +public class DotAIClientConnectException extends DotRuntimeException { + + public DotAIClientConnectException(final String message, final Throwable cause) { + super(message, cause); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIModelNotFoundException.java b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIModelNotFoundException.java new file mode 100644 index 000000000000..3bd70811b123 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIModelNotFoundException.java @@ -0,0 +1,21 @@ +package com.dotcms.ai.exception; + +import com.dotmarketing.exception.DotRuntimeException; + +/** + * Exception thrown when an AI model is not found. + * + *+ * This exception is used to indicate that a specific AI model could not be found. It extends the {@link DotRuntimeException} + * to provide additional context specific to AI model not found scenarios. + *
+ * + * @author vico + */ +public class DotAIModelNotFoundException extends DotRuntimeException { + + public DotAIModelNotFoundException(final String message) { + super(message); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIModelNotOperationalException.java b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIModelNotOperationalException.java new file mode 100644 index 000000000000..ddea5f6866f8 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/exception/DotAIModelNotOperationalException.java @@ -0,0 +1,21 @@ +package com.dotcms.ai.exception; + +import com.dotmarketing.exception.DotRuntimeException; + +/** + * Exception thrown when there is a connection error with the AI client. + * + *+ * This exception is used to indicate that there is a connection error with the AI client. It extends the {@link DotRuntimeException} + * to provide additional context specific to AI client connection error scenarios. + *
+ * + * @author vico + */ +public class DotAIModelNotOperationalException extends DotRuntimeException { + + public DotAIModelNotOperationalException(final String message) { + super(message); + } + +} diff --git a/dotCMS/src/main/java/com/dotcms/ai/validator/AIAppValidator.java b/dotCMS/src/main/java/com/dotcms/ai/validator/AIAppValidator.java new file mode 100644 index 000000000000..f2036cba8955 --- /dev/null +++ b/dotCMS/src/main/java/com/dotcms/ai/validator/AIAppValidator.java @@ -0,0 +1,126 @@ +package com.dotcms.ai.validator; + +import com.dotcms.ai.app.AIModel; +import com.dotcms.ai.app.AppConfig; +import com.dotcms.api.system.event.message.MessageSeverity; +import com.dotcms.api.system.event.message.SystemMessageEventUtil; +import com.dotcms.api.system.event.message.builder.SystemMessage; +import com.dotcms.api.system.event.message.builder.SystemMessageBuilder; +import com.dotmarketing.util.DateUtil; +import com.google.common.annotations.VisibleForTesting; +import com.liferay.portal.language.LanguageUtil; +import io.vavr.Lazy; +import io.vavr.control.Try; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +/** + * The AIAppValidator class is responsible for validating AI configurations and model usage. + * It ensures that the AI models specified in the application configuration are supported + * and not exhausted. + * + * @author vico + */ +public class AIAppValidator { + + private static final Lazy