Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#27959: Adding ITs for AI View Tool main methods. #28308

Merged
merged 2 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1514,6 +1514,12 @@
<version>2.6</version>
</dependency>

<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>3.5.3</version>
</dependency>

<!-- Graalvm Js Engine -->
<dependency>
<groupId>org.graalvm.sdk</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public JSONObject sendRawRequest(final JSONObject prompt) {

prompt.remove("prompt");

return new JSONObject(doRequest(config.getApiUrl(), "POST", config.getApiKey(), prompt));
return new JSONObject(doRequest(config.getApiUrl(), config.getApiKey(), prompt));
}

@Override
Expand All @@ -44,10 +44,8 @@ public JSONObject sendTextPrompt(final String textPrompt) {
}

@VisibleForTesting
String doRequest(final String urlIn,
final String method,
final String openAiAPIKey,
final JSONObject json) {
return OpenAIRequest.doRequest(urlIn, method, openAiAPIKey, json);
String doRequest(final String urlIn, final String openAiAPIKey, final JSONObject json) {
return OpenAIRequest.doRequest(urlIn, "POST", openAiAPIKey, json);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public JSONObject sendRequest(final JSONObject jsonObject) {

String responseString = "";
try {
responseString = doRequest(config.getApiImageUrl(), "POST", config.getApiKey(), jsonObject);
responseString = doRequest(config.getApiImageUrl(), config.getApiKey(), jsonObject);

JSONObject returnObject = new JSONObject(responseString);
if (returnObject.containsKey("error")) {
Expand All @@ -89,6 +89,7 @@ public JSONObject sendRequest(final JSONObject jsonObject) {
}
}


@Override
public JSONObject sendRawRequest(final String prompt) {
return sendRequest(new JSONObject(prompt));
Expand All @@ -112,7 +113,7 @@ public JSONObject sendTextPrompt(final String textPrompt) {
private JSONObject createTempFile(final JSONObject imageResponse) {
final String url = imageResponse.optString("url");
if (UtilMethods.isEmpty(() -> url)) {
Logger.warn(this.getClass(), "imageResponse does not include URL:" + imageResponse.toString());
Logger.warn(this.getClass(), "imageResponse does not include URL:" + imageResponse);
throw new DotRuntimeException("Image Response does not include URL:" + imageResponse);
}

Expand All @@ -122,6 +123,7 @@ private JSONObject createTempFile(final JSONObject imageResponse) {

final DotTempFile file = tempFileApi.createTempFileFromUrl(fileName, getRequest(), new URL(url), 20);
imageResponse.put("response", file.id);
imageResponse.put("tempFile", file.file.getAbsolutePath());

return imageResponse;
} catch (Exception e) {
Expand Down Expand Up @@ -162,7 +164,7 @@ HttpServletRequest getRequest() {
return requestProxy;
}

private String generateFileName(final String originalPrompt){
private String generateFileName(final String originalPrompt) {
final SimpleDateFormat dateToString = new SimpleDateFormat("yyyyMMdd_hhmmss");
try {
String newFileName = originalPrompt.toLowerCase();
Expand All @@ -183,11 +185,8 @@ private String generateFileName(final String originalPrompt){
}

@VisibleForTesting
String doRequest(final String urlIn,
final String method,
final String openAiAPIKey,
final JSONObject json) {
return OpenAIRequest.doRequest(urlIn, method, openAiAPIKey, json);
String doRequest(final String urlIn, final String openAiAPIKey, final JSONObject json) {
return OpenAIRequest.doRequest(urlIn, "POST", openAiAPIKey, json);
}

@VisibleForTesting
Expand All @@ -200,7 +199,7 @@ AIImageRequestDTO.Builder getDtoBuilder() {
return new AIImageRequestDTO.Builder();
}

public static void setStopWordsUtil(StopWordsUtil stopWordsUtil) {
public static void setStopWordsUtil(final StopWordsUtil stopWordsUtil) {
OpenAIImageServiceImpl.stopWordsUtil = stopWordsUtil;
}

Expand Down
173 changes: 112 additions & 61 deletions dotCMS/src/main/java/com/dotcms/ai/util/OpenAIRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,119 +7,170 @@
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.json.JSONObject;
import io.vavr.control.Try;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

import javax.ws.rs.core.MediaType;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.concurrent.ConcurrentHashMap;

/**
* The OpenAIRequest class is a utility class that handles HTTP requests to the OpenAI API.
* It provides methods for sending GET, POST, PUT, DELETE, and PATCH requests.
* This class also manages rate limiting for the OpenAI API by keeping track of the last time a request was made.
*
* This class is implemented as a singleton, meaning that only one instance of the class is created throughout the execution of the program.
*/
public class OpenAIRequest {

static final ConcurrentHashMap<OpenAIModel,Long> lastRestCall = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<OpenAIModel,Long> lastRestCall = new ConcurrentHashMap<>();

private OpenAIRequest() {}



private OpenAIRequest() {
}

public static String doRequest(String url, String method, String openAiAPIKey, JSONObject json) {

ByteArrayOutputStream out = new ByteArrayOutputStream();
/**
* Sends a request to the specified URL with the specified method, OpenAI API key, and JSON payload.
* The response from the request is returned as a string.
*
* @param url the URL to send the request to
* @param method the HTTP method to use for the request
* @param openAiAPIKey the OpenAI API key to use for the request
* @param json the JSON payload to send with the request
* @return the response from the request as a string
*/
public static String doRequest(final String url,
final String method,
final String openAiAPIKey,
final JSONObject json) {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
doRequest(url, method, openAiAPIKey, json, out);
return out.toString();


return out.toString();
}


private static HttpUriRequest resolveMethod(String method, String urlIn) {
if ("post" .equalsIgnoreCase(method)) {
return new HttpPost(urlIn);
}
if ("put" .equalsIgnoreCase(method)) {
return new HttpPut(urlIn);
}
if ("delete" .equalsIgnoreCase(method)) {
return new HttpDelete(urlIn);
}
if ("patch" .equalsIgnoreCase(method)) {
return new HttpPatch(urlIn);
}

return new HttpGet(urlIn);

}
public static void doPost(String urlIn, String openAiAPIKey, JSONObject json, OutputStream out) {
/**
* Sends a POST request to the specified URL with the specified OpenAI API key and JSON payload.
* The response from the request is written to the provided OutputStream.
*
* @param urlIn the URL to send the request to
* @param openAiAPIKey the OpenAI API key to use for the request
* @param json the JSON payload to send with the request
* @param out the OutputStream to write the response to
*/
public static void doPost(final String urlIn,
final String openAiAPIKey,
final JSONObject json,
final OutputStream out) {
doRequest(urlIn,"post",openAiAPIKey,json,out);
}

public static void doGet(String urlIn, String openAiAPIKey, JSONObject json, OutputStream out) {
/**
* Sends a GET request to the specified URL with the specified OpenAI API key and JSON payload.
* The response from the request is written to the provided OutputStream.
*
* @param urlIn the URL to send the request to
* @param openAiAPIKey the OpenAI API key to use for the request
* @param json the JSON payload to send with the request
* @param out the OutputStream to write the response to
*/
public static void doGet(final String urlIn,
final String openAiAPIKey,
final JSONObject json,
final OutputStream out) {
doRequest(urlIn,"get",openAiAPIKey,json,out);
}


/**
* this allows for a streaming response. It also attempts to rate limit requests based on OpenAI limits
* @param urlIn
* @param method
* @param openAiAPIKey
* @param json
* @param out
* Sends a request to the specified URL with the specified method, OpenAI API key, and JSON payload.
* The response from the request is written to the provided OutputStream.
* This method also manages rate limiting for the OpenAI API by keeping track of the last time a request was made.
*
* @param urlIn the URL to send the request to
* @param method the HTTP method to use for the request
* @param openAiAPIKey the OpenAI API key to use for the request
* @param json the JSON payload to send with the request
* @param out the OutputStream to write the response to
*/
public static void doRequest(String urlIn, String method, String openAiAPIKey, JSONObject json, OutputStream out) {
if(ConfigService.INSTANCE.config().getConfigBoolean(AppKeys.DEBUG_LOGGING)) {
Logger.info(OpenAIRequest.class, "posting:" + json);
public static void doRequest(final String urlIn,
final String method,
final String openAiAPIKey,
final JSONObject json,
final OutputStream out) {

if (ConfigService.INSTANCE.config().getConfigBoolean(AppKeys.DEBUG_LOGGING)) {
Logger.debug(OpenAIRequest.class, "posting:" + json);
}
final OpenAIModel model = OpenAIModel.resolveModel(json.optString("model"));


long sleep = lastRestCall.computeIfAbsent(model, m -> 0L) + model.minIntervalBetweenCalls() - System.currentTimeMillis();
final OpenAIModel model = OpenAIModel.resolveModel(json.optString("model"));
final long sleep = lastRestCall.computeIfAbsent(model, m -> 0L)
+ model.minIntervalBetweenCalls()
- System.currentTimeMillis();
if (sleep > 0) {
Logger.info(OpenAIRequest.class, "Rate limit:" + model.apiPerMinute + "/minute, or 1 every " + (60000 / model.apiPerMinute) + "ms. Sleeping:" + sleep);
Logger.info(
OpenAIRequest.class,
"Rate limit:"
+ model.apiPerMinute
+ "/minute, or 1 every "
+ (60000 / model.apiPerMinute)
+ "ms. Sleeping:"
+ sleep);
Try.run(() -> Thread.sleep(sleep));
}

lastRestCall.put(model, System.currentTimeMillis());

try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
final StringEntity jsonEntity = new StringEntity(json.toString(), ContentType.APPLICATION_JSON);
final HttpUriRequest httpRequest = resolveMethod(method, urlIn);
httpRequest.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
httpRequest.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + openAiAPIKey);


StringEntity jsonEntity = new StringEntity(json.toString(), ContentType.APPLICATION_JSON);
HttpUriRequest httpRequest = resolveMethod(method, urlIn);
httpRequest.setHeader("Content-Type", "application/json");
httpRequest.setHeader("Authorization", "Bearer " + openAiAPIKey);
if (null !=json && !json.getAsMap().isEmpty()) {
if (!json.getAsMap().isEmpty()) {
Try.run(() -> ((HttpEntityEnclosingRequestBase) httpRequest).setEntity(jsonEntity));
}

try (CloseableHttpResponse response = httpClient.execute(httpRequest)) {
BufferedInputStream in = new BufferedInputStream(response.getEntity().getContent());
byte[] buffer = new byte[1024];
final BufferedInputStream in = new BufferedInputStream(response.getEntity().getContent());
final byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
out.flush();
}

}

} catch (Exception e) {
if(ConfigService.INSTANCE.config().getConfigBoolean(AppKeys.DEBUG_LOGGING)){
Logger.warn(OpenAIRequest.class, "INVALID REQUEST: " + e.getMessage(),e);
Logger.warn(OpenAIRequest.class, " - " + method + " : " +json.toString());
}else{
if (ConfigService.INSTANCE.config().getConfigBoolean(AppKeys.DEBUG_LOGGING)){
Logger.warn(OpenAIRequest.class, "INVALID REQUEST: " + e.getMessage(), e);
} else {
Logger.warn(OpenAIRequest.class, "INVALID REQUEST: " + e.getMessage());
Logger.warn(OpenAIRequest.class, " - " + method + " : " +json.toString());
}

Logger.warn(OpenAIRequest.class, " - " + method + " : " +json);

throw new DotRuntimeException(e);
}

}

private static HttpUriRequest resolveMethod(final String method, final String urlIn) {
if ("post".equalsIgnoreCase(method)) {
return new HttpPost(urlIn);
}
if ("put".equalsIgnoreCase(method)) {
return new HttpPut(urlIn);
}
if ("delete".equalsIgnoreCase(method)) {
return new HttpDelete(urlIn);
}
if ("patch".equalsIgnoreCase(method)) {
return new HttpPatch(urlIn);
}

return new HttpGet(urlIn);
}

}
24 changes: 24 additions & 0 deletions dotCMS/src/main/java/com/dotcms/ai/util/StopWordsUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@
import java.util.TreeSet;
import java.util.stream.Collectors;

/**
* This utility class is used for handling stop words in a given text.
* Stop words are words which are filtered out during the processing of text.
* When building the vocabulary of a text data, it may be a good idea to consider these words as noise.
*
* The class provides a method to remove stop words from a given string.
* It uses a predefined set of stop words, which are common words that do not contain important meaning and are usually removed from texts.
*
* This class is implemented as a singleton, meaning that only one instance of the class is created throughout the execution of the program.
*/
public class StopWordsUtil {

// twitter stopwords - stopwords_twitter.txt
Expand Down Expand Up @@ -61,12 +71,26 @@ public class StopWordsUtil {

private static final Lazy<StopWordsUtil> STOP_WORDS_UTILS = Lazy.of(StopWordsUtil::new);

/**
* Returns the singleton instance of the StopWordsUtil class.
* The instance is created only once and reused for all subsequent calls.
*
* @return the singleton instance of the StopWordsUtil class
*/
public static StopWordsUtil get(){
return STOP_WORDS_UTILS.get();
}

private StopWordsUtil() {}

/**
* Removes all stop words from the given string.
* The input string is split into words, and any word that is a stop word is removed.
* The remaining words are joined back together with spaces in between.
*
* @param incoming the string from which to remove stop words
* @return a new string with all stop words removed
*/
public String removeStopWords(final String incoming) {
return Optional
.ofNullable(incoming)
Expand Down
5 changes: 2 additions & 3 deletions dotCMS/src/main/java/com/dotcms/ai/viewtool/AIToolInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ public String getClassname () {
}

@Override
public Object getInstance ( Object initData ) {
public Object getInstance (final Object initData) {

AIViewTool viewTool = new AIViewTool();
viewTool.init( initData );
AIViewTool viewTool = new AIViewTool(initData);

setScope( ViewContext.REQUEST );

Expand Down