-
Notifications
You must be signed in to change notification settings - Fork 186
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
12 changed files
with
811 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ on: | |
branches: | ||
- main | ||
pull_request: | ||
types: [ opened, synchronize, edited ] | ||
branches: | ||
- main | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
AI | ||
== | ||
|
||
AI allows to send an intelligence request to supported large language models and returns | ||
an answer based on the provided prompt and items. | ||
|
||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
|
||
- [Send AI request](#send-ai-request) | ||
- [Send AI text generation request](#send-ai-text-generation-request) | ||
|
||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
|
||
Send AI request | ||
-------------------------- | ||
|
||
To send an AI request, call static | ||
[`sendAIRequest(String prompt, List<BoxAIItem> items, Mode mode)`][send-ai-request] method. | ||
In the request you have to provide a prompt, a list of items that your prompt refers to and a mode of the request. | ||
There are two modes available: `SINGLE_ITEM_QA` and `MULTI_ITEM_QA`, which specifies if this request refers to | ||
for a single or multiple items. | ||
|
||
<!-- sample post_ai_ask --> | ||
```java | ||
BoxAIResponse response = BoxAI.sendAIRequest( | ||
api, | ||
"What is the content of the file?", | ||
Collections.singletonList("123456", BoxAIItem.Type.FILE)), | ||
BoxAI.Mode.SINGLE_ITEM_QA | ||
); | ||
``` | ||
|
||
NOTE: The AI endpoint may return a 412 status code if you use for your request a file which has just been updated to the box. | ||
It usually takes a few seconds for the file to be indexed and available for the AI endpoint. | ||
|
||
[send-ai-request]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAI.html#sendAIRequest-com.box.sdk.BoxAPIConnection-java.lang.String- | ||
|
||
Send AI text generation request | ||
-------------- | ||
|
||
To send an AI request specifically focused on the creation of new text, call static | ||
[`sendAITextGenRequest(String prompt, List<BoxAIItem> items, List<BoxAIDialogueEntry> dialogueHistory)`][send-ai-text-gen-request] method. | ||
In the request you have to provide a prompt, a list of items that your prompt refers to and optionally a dialogue history, | ||
which provides additional context to the LLM in generating the response. | ||
|
||
<!-- sample post_ai_text_gen --> | ||
```java | ||
List<BoxAIDialogueEntry> dialogueHistory = new ArrayList<>(); | ||
dialogueHistory.add( | ||
new BoxAIDialogueEntry( | ||
"Make my email about public APIs sound more professional", | ||
"Here is the first draft of your professional email about public APIs.", | ||
BoxDateFormat.parse("2013-05-16T15:26:57-07:00") | ||
) | ||
); | ||
BoxAIResponse response = BoxAI.sendAITextGenRequest( | ||
api, | ||
"Write an email to a client about the importance of public APIs.", | ||
Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE)), | ||
dialogueHistory | ||
); | ||
``` | ||
|
||
[send-ai-text-gen-request]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAI.html#sendAITextGenRequest-com.box.sdk.BoxAPIConnection-java.lang.String- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package com.box.sdk; | ||
|
||
import static com.box.sdk.BoxApiProvider.jwtApiForServiceAccount; | ||
import static com.box.sdk.CleanupTools.deleteFile; | ||
import static com.box.sdk.Retry.retry; | ||
import static com.box.sdk.UniqueTestFolder.removeUniqueFolder; | ||
import static com.box.sdk.UniqueTestFolder.setupUniqeFolder; | ||
import static com.box.sdk.UniqueTestFolder.uploadFileToUniqueFolder; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.hamcrest.Matchers.containsString; | ||
import static org.hamcrest.Matchers.equalTo; | ||
import static org.hamcrest.Matchers.is; | ||
|
||
import java.text.ParseException; | ||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.Date; | ||
import java.util.List; | ||
import org.junit.AfterClass; | ||
import org.junit.BeforeClass; | ||
import org.junit.Test; | ||
|
||
|
||
/** | ||
* {@link BoxGroup} related integration tests. | ||
*/ | ||
public class BoxAIIT { | ||
|
||
@BeforeClass | ||
public static void setup() { | ||
setupUniqeFolder(); | ||
} | ||
|
||
@AfterClass | ||
public static void afterClass() { | ||
removeUniqueFolder(); | ||
} | ||
|
||
|
||
@Test | ||
public void askAISingleItem() throws InterruptedException { | ||
BoxAPIConnection api = jwtApiForServiceAccount(); | ||
String fileName = "[askAISingleItem] Test File.txt"; | ||
BoxFile uploadedFile = uploadFileToUniqueFolder(api, fileName, "Test file"); | ||
|
||
try { | ||
BoxFile.Info uploadedFileInfo = uploadedFile.getInfo(); | ||
// When a file has been just uploaded, AI service may not be ready to return text response | ||
// and 412 is returned | ||
retry(() -> { | ||
BoxAIResponse response = BoxAI.sendAIRequest( | ||
api, | ||
"What is the name of the file?", | ||
Collections.singletonList(new BoxAIItem(uploadedFileInfo.getID(), BoxAIItem.Type.FILE)), | ||
BoxAI.Mode.SINGLE_ITEM_QA | ||
); | ||
assertThat(response.getAnswer(), containsString("Test file")); | ||
assert response.getCreatedAt().before(new Date(System.currentTimeMillis())); | ||
assertThat(response.getCompletionReason(), equalTo("done")); | ||
}, 2, 2000); | ||
|
||
} finally { | ||
deleteFile(uploadedFile); | ||
} | ||
} | ||
|
||
@Test | ||
public void askAIMultipleItems() throws InterruptedException { | ||
BoxAPIConnection api = jwtApiForServiceAccount(); | ||
String fileName1 = "[askAIMultipleItems] Test File.txt"; | ||
BoxFile uploadedFile1 = uploadFileToUniqueFolder(api, fileName1, "Test file"); | ||
|
||
try { | ||
String fileName2 = "[askAIMultipleItems] Weather forecast.txt"; | ||
BoxFile uploadedFile2 = uploadFileToUniqueFolder(api, fileName2, "Test file"); | ||
|
||
try { | ||
BoxFile.Info uploadedFileInfo1 = uploadedFile1.getInfo(); | ||
BoxFile.Info uploadedFileInfo2 = uploadedFile2.getInfo(); | ||
|
||
List<BoxAIItem> items = new ArrayList<>(); | ||
items.add(new BoxAIItem(uploadedFileInfo1.getID(), BoxAIItem.Type.FILE)); | ||
items.add(new BoxAIItem(uploadedFileInfo2.getID(), BoxAIItem.Type.FILE)); | ||
|
||
// When a file has been just uploaded, AI service may not be ready to return text response | ||
// and 412 is returned | ||
retry(() -> { | ||
BoxAIResponse response = BoxAI.sendAIRequest( | ||
api, | ||
"What is the content of these files?", | ||
items, | ||
BoxAI.Mode.MULTIPLE_ITEM_QA | ||
); | ||
assertThat(response.getAnswer(), containsString("Test file")); | ||
assert response.getCreatedAt().before(new Date(System.currentTimeMillis())); | ||
assertThat(response.getCompletionReason(), equalTo("done")); | ||
}, 2, 2000); | ||
} finally { | ||
deleteFile(uploadedFile2); | ||
} | ||
|
||
} finally { | ||
deleteFile(uploadedFile1); | ||
} | ||
} | ||
|
||
@Test | ||
public void askAITextGenItemWithDialogueHistory() throws ParseException, InterruptedException { | ||
BoxAPIConnection api = jwtApiForServiceAccount(); | ||
String fileName = "[askAITextGenItemWithDialogueHistory] Test File.txt"; | ||
Date date1 = BoxDateFormat.parse("2013-05-16T15:27:57-07:00"); | ||
Date date2 = BoxDateFormat.parse("2013-05-16T15:26:57-07:00"); | ||
|
||
BoxFile uploadedFile = uploadFileToUniqueFolder(api, fileName, "Test file"); | ||
try { | ||
// When a file has been just uploaded, AI service may not be ready to return text response | ||
// and 412 is returned | ||
retry(() -> { | ||
BoxFile.Info uploadedFileInfo = uploadedFile.getInfo(); | ||
assertThat(uploadedFileInfo.getName(), is(equalTo(fileName))); | ||
|
||
List<BoxAIDialogueEntry> dialogueHistory = new ArrayList<>(); | ||
dialogueHistory.add( | ||
new BoxAIDialogueEntry("What is the name of the file?", "Test file", date1) | ||
); | ||
dialogueHistory.add( | ||
new BoxAIDialogueEntry("What is the size of the file?", "10kb", date2) | ||
); | ||
BoxAIResponse response = BoxAI.sendAITextGenRequest( | ||
api, | ||
"What is the name of the file?", | ||
Collections.singletonList(new BoxAIItem(uploadedFileInfo.getID(), BoxAIItem.Type.FILE)), | ||
dialogueHistory | ||
); | ||
assertThat(response.getAnswer(), containsString("name")); | ||
assert response.getCreatedAt().before(new Date(System.currentTimeMillis())); | ||
assertThat(response.getCompletionReason(), equalTo("done")); | ||
}, 2, 2000); | ||
|
||
} finally { | ||
deleteFile(uploadedFile); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package com.box.sdk; | ||
|
||
import com.box.sdk.http.HttpMethod; | ||
import com.eclipsesource.json.Json; | ||
import com.eclipsesource.json.JsonArray; | ||
import com.eclipsesource.json.JsonObject; | ||
import java.net.URL; | ||
import java.util.List; | ||
|
||
|
||
public final class BoxAI { | ||
|
||
/** | ||
* Ask AI url. | ||
*/ | ||
public static final URLTemplate SEND_AI_REQUEST_URL = new URLTemplate("ai/ask"); | ||
/** | ||
* Text gen AI url. | ||
*/ | ||
public static final URLTemplate SEND_AI_TEXT_GEN_REQUEST_URL = new URLTemplate("ai/text_gen"); | ||
|
||
private BoxAI() { | ||
} | ||
|
||
/** | ||
* Sends an AI request to supported LLMs and returns an answer specifically focused | ||
* on the user's question given the provided items. | ||
* @param api the API connection to be used by the created user. | ||
* @param prompt The prompt provided by the client to be answered by the LLM. | ||
* @param items The items to be processed by the LLM, currently only files are supported. | ||
* @param mode The mode specifies if this request is for a single or multiple items. | ||
* @return The response from the AI. | ||
*/ | ||
public static BoxAIResponse sendAIRequest(BoxAPIConnection api, String prompt, List<BoxAIItem> items, Mode mode) { | ||
URL url = SEND_AI_REQUEST_URL.build(api.getBaseURL()); | ||
JsonObject requestJSON = new JsonObject(); | ||
requestJSON.add("mode", mode.toString()); | ||
requestJSON.add("prompt", prompt); | ||
|
||
JsonArray itemsJSON = new JsonArray(); | ||
for (BoxAIItem item : items) { | ||
itemsJSON.add(item.getJSONObject()); | ||
} | ||
requestJSON.add("items", itemsJSON); | ||
|
||
BoxJSONRequest req = new BoxJSONRequest(api, url, HttpMethod.POST); | ||
req.setBody(requestJSON.toString()); | ||
|
||
try (BoxJSONResponse response = req.send()) { | ||
JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); | ||
return new BoxAIResponse(responseJSON); | ||
} | ||
} | ||
|
||
/** | ||
* Sends an AI request to supported LLMs and returns an answer specifically focused on the creation of new text. | ||
* @param api the API connection to be used by the created user. | ||
* @param prompt The prompt provided by the client to be answered by the LLM. | ||
* @param items The items to be processed by the LLM, currently only files are supported. | ||
* @return The response from the AI. | ||
*/ | ||
public static BoxAIResponse sendAITextGenRequest(BoxAPIConnection api, String prompt, List<BoxAIItem> items) { | ||
return sendAITextGenRequest(api, prompt, items, null); | ||
} | ||
|
||
/** | ||
* Sends an AI request to supported LLMs and returns an answer specifically focused on the creation of new text. | ||
* @param api the API connection to be used by the created user. | ||
* @param prompt The prompt provided by the client to be answered by the LLM. | ||
* @param items The items to be processed by the LLM, currently only files are supported. | ||
* @param dialogueHistory The history of prompts and answers previously passed to the LLM. | ||
* This provides additional context to the LLM in generating the response. | ||
* @return The response from the AI. | ||
*/ | ||
public static BoxAIResponse sendAITextGenRequest( | ||
BoxAPIConnection api, String prompt, List<BoxAIItem> items, List<BoxAIDialogueEntry> dialogueHistory | ||
) { | ||
URL url = SEND_AI_TEXT_GEN_REQUEST_URL.build(api.getBaseURL()); | ||
JsonObject requestJSON = new JsonObject(); | ||
requestJSON.add("prompt", prompt); | ||
|
||
JsonArray itemsJSON = new JsonArray(); | ||
for (BoxAIItem item : items) { | ||
itemsJSON.add(item.getJSONObject()); | ||
} | ||
requestJSON.add("items", itemsJSON); | ||
|
||
if (dialogueHistory != null) { | ||
JsonArray dialogueHistoryJSON = new JsonArray(); | ||
for (BoxAIDialogueEntry dialogueEntry : dialogueHistory) { | ||
dialogueHistoryJSON.add(dialogueEntry.getJSONObject()); | ||
} | ||
requestJSON.add("dialogue_history", dialogueHistoryJSON); | ||
} | ||
|
||
BoxJSONRequest req = new BoxJSONRequest(api, url, HttpMethod.POST); | ||
req.setBody(requestJSON.toString()); | ||
|
||
try (BoxJSONResponse response = req.send()) { | ||
JsonObject responseJSON = Json.parse(response.getJSON()).asObject(); | ||
return new BoxAIResponse(responseJSON); | ||
} | ||
} | ||
|
||
public enum Mode { | ||
/** | ||
* Multiple items | ||
*/ | ||
MULTIPLE_ITEM_QA("multiple_item_qa"), | ||
|
||
/** | ||
* Single item | ||
*/ | ||
SINGLE_ITEM_QA("single_item_qa"); | ||
|
||
private final String mode; | ||
|
||
Mode(String mode) { | ||
this.mode = mode; | ||
} | ||
|
||
static BoxAI.Mode fromJSONValue(String jsonValue) { | ||
if (jsonValue.equals("multiple_item_qa")) { | ||
return BoxAI.Mode.MULTIPLE_ITEM_QA; | ||
} else if (jsonValue.equals("single_item_qa")) { | ||
return BoxAI.Mode.SINGLE_ITEM_QA; | ||
} else { | ||
System.out.print("Invalid AI mode."); | ||
return null; | ||
} | ||
} | ||
|
||
String toJSONValue() { | ||
return this.mode; | ||
} | ||
|
||
public String toString() { | ||
return this.mode; | ||
} | ||
} | ||
} |
Oops, something went wrong.