Skip to content

Commit

Permalink
feat: Support AI Agent (#1265)
Browse files Browse the repository at this point in the history
  • Loading branch information
congminh1254 authored Sep 6, 2024
1 parent 02193eb commit 3cb2c7c
Show file tree
Hide file tree
Showing 20 changed files with 2,022 additions and 29 deletions.
35 changes: 30 additions & 5 deletions doc/ai.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ 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)
- [AI](#ai)
- [Send AI request](#send-ai-request)
- [Send AI text generation request](#send-ai-text-generation-request)
- [Get AI Agent default configuration](#get-ai-agent-default-configuration)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand All @@ -26,15 +28,17 @@ for a single or multiple items.
BoxAIResponse response = BoxAI.sendAIRequest(
api,
"What is the content of the file?",
Collections.singletonList("123456", BoxAIItem.Type.FILE)),
Collections.singletonList("123456", BoxAIItem.Type.FILE),
BoxAI.Mode.SINGLE_ITEM_QA
);
```

You can also provide a list of dialogue history entries to provide additional context to the LLM in generating the response, AI Agent configuration and flag to indicate whether citations should be returned.

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-request]: https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAI.html#sendAIRequest-com.box.sdk.BoxAPIConnection-java.lang.String-java.util.List-com.box.sdk.BoxAI.Mode-

Send AI text generation request
--------------
Expand Down Expand Up @@ -62,4 +66,25 @@ BoxAIResponse response = BoxAI.sendAITextGenRequest(
);
```

[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-
You can also provide an AI Agent configuration to customize the behavior of the AI response generation.

[send-ai-text-gen-request]: https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAI.html#sendAITextGenRequest-com.box.sdk.BoxAPIConnection-java.lang.String-java.util.List-java.util.List-

Get AI Agent default configuration
--------------------------

To get the default configuration of the AI Agent, call static
[`getAiAgentDefaultConfig(BoxAPIConnection api, BoxAIAgent.Mode mode, String language, String model)`][get-ai-agent-default-config] method.
In the request you have to provide the mode of the AI Agent, the language and the model, with the model is required while the language and mode are optional.

<!-- sample get_ai_agent_default -->
```java
BoxAIAgentConfig config = BoxAI.getAiAgentDefaultConfig(
api,
BoxAIAgent.Mode.ASK,
"en",
"openai__gpt_3_5_turbo"
);
```

[get-ai-agent-default-config]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAI.html#getAiAgentDefaultConfig-com.box.sdk.BoxAPIConnection-com.box.sdk.ai.BoxAIAgent.Mode-java.lang.String-java.lang.String-
51 changes: 51 additions & 0 deletions src/intTest/java/com/box/sdk/BoxAIIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,55 @@ public void askAITextGenItemWithDialogueHistory() throws ParseException, Interru
deleteFile(uploadedFile);
}
}

@Test
public void getAIAgentDefaultConfiguration() {
BoxAPIConnection api = jwtApiForServiceAccount();
BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.ASK,
"en", "openai__gpt_3_5_turbo");
BoxAIAgentAsk askAgent = (BoxAIAgentAsk) agent;

assertThat(askAgent.getType(), is(equalTo(BoxAIAgentAsk.TYPE)));
assertThat(askAgent.getBasicText().getModel(), is(equalTo("openai__gpt_3_5_turbo")));

BoxAIAgent agent2 = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.TEXT_GEN,
"en", "openai__gpt_3_5_turbo");
BoxAIAgentTextGen textGenAgent = (BoxAIAgentTextGen) agent2;

assertThat(textGenAgent.getType(), is(equalTo(BoxAIAgentTextGen.TYPE)));
assertThat(textGenAgent.getBasicGen().getModel(), is(equalTo("openai__gpt_3_5_turbo")));
}

@Test
public void askAISingleItemWithAgent() throws InterruptedException {
BoxAPIConnection api = jwtApiForServiceAccount();
String fileName = "[askAISingleItem] Test File.txt";
BoxFile uploadedFile = uploadFileToUniqueFolder(api, fileName, "Test file");
BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.ASK,
"en", "openai__gpt_3_5_turbo_16k");
BoxAIAgentAsk askAgent = (BoxAIAgentAsk) agent;

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,
null,
askAgent,
true
);
assertThat(response.getAnswer(), containsString("Test file"));
assert response.getCreatedAt().before(new Date(System.currentTimeMillis()));
assertThat(response.getCompletionReason(), equalTo("done"));
}, 2, 2000);

} finally {
deleteFile(uploadedFile);
}
}
}
123 changes: 112 additions & 11 deletions src/main/java/com/box/sdk/BoxAI.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,45 @@ public final class BoxAI {
* Text gen AI url.
*/
public static final URLTemplate SEND_AI_TEXT_GEN_REQUEST_URL = new URLTemplate("ai/text_gen");
/**
* AI agent default config url.
*/
public static final URLTemplate AI_AGENT_DEFAULT_CONFIG_URL = new URLTemplate("ai_agent_default");

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 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.
* @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) {
return sendAIRequest(api, prompt, items, mode, null, null, null);
}

/**
* 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.
* @param dialogueHistory The history of prompts and answers previously passed to the LLM.
* This provides additional context to the LLM in generating the response.
* @param agent The AI agent configuration to be used for the request.
* @param includeCitations Whether to include citations in the response.
* @return The response from the AI.
*/
public static BoxAIResponse sendAIRequest(BoxAPIConnection api, String prompt, List<BoxAIItem> items, Mode mode,
List<BoxAIDialogueEntry> dialogueHistory, BoxAIAgentAsk agent,
Boolean includeCitations) {
URL url = SEND_AI_REQUEST_URL.build(api.getBaseURL());
JsonObject requestJSON = new JsonObject();
requestJSON.add("mode", mode.toString());
Expand All @@ -43,6 +68,20 @@ public static BoxAIResponse sendAIRequest(BoxAPIConnection api, String prompt, L
}
requestJSON.add("items", itemsJSON);

if (dialogueHistory != null) {
JsonArray dialogueHistoryJSON = new JsonArray();
for (BoxAIDialogueEntry dialogueEntry : dialogueHistory) {
dialogueHistoryJSON.add(dialogueEntry.getJSONObject());
}
requestJSON.add("dialogue_history", dialogueHistoryJSON);
}
if (agent != null) {
requestJSON.add("ai_agent", agent.getJSONObject());
}
if (includeCitations != null) {
requestJSON.add("include_citations", includeCitations);
}

BoxJSONRequest req = new BoxJSONRequest(api, url, HttpMethod.POST);
req.setBody(requestJSON.toString());

Expand All @@ -54,9 +93,10 @@ public static BoxAIResponse sendAIRequest(BoxAPIConnection api, String prompt, L

/**
* 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 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 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) {
Expand All @@ -65,16 +105,33 @@ public static BoxAIResponse sendAITextGenRequest(BoxAPIConnection api, String pr

/**
* 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 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
) {
public static BoxAIResponse sendAITextGenRequest(BoxAPIConnection api, String prompt, List<BoxAIItem> items,
List<BoxAIDialogueEntry> dialogueHistory) {
return sendAITextGenRequest(api, prompt, items, dialogueHistory, 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.
* @param agent The AI agent configuration to be used for the request.
* @return The response from the AI.
*/
public static BoxAIResponse sendAITextGenRequest(BoxAPIConnection api, String prompt, List<BoxAIItem> items,
List<BoxAIDialogueEntry> dialogueHistory,
BoxAIAgentTextGen agent) {
URL url = SEND_AI_TEXT_GEN_REQUEST_URL.build(api.getBaseURL());
JsonObject requestJSON = new JsonObject();
requestJSON.add("prompt", prompt);
Expand All @@ -93,6 +150,10 @@ public static BoxAIResponse sendAITextGenRequest(
requestJSON.add("dialogue_history", dialogueHistoryJSON);
}

if (agent != null) {
requestJSON.add("ai_agent", agent.getJSONObject());
}

BoxJSONRequest req = new BoxJSONRequest(api, url, HttpMethod.POST);
req.setBody(requestJSON.toString());

Expand All @@ -102,6 +163,46 @@ public static BoxAIResponse sendAITextGenRequest(
}
}

/**
* Get the default AI Agent use for the given mode.
*
* @param api The API connection to be used by the created user.
* @param mode The mode to filter the agent config to return.
* @return A successful response including the default agent configuration.
*/
public static BoxAIAgent getAiAgentDefaultConfig(BoxAPIConnection api, BoxAIAgent.Mode mode) {
return getAiAgentDefaultConfig(api, mode, null, null);
}

/**
* Get the default AI Agent use for the given mode.
*
* @param api The API connection to be used by the created user.
* @param mode The mode to filter the agent config to return.
* @param language The language to filter the agent config to return.
* @param model The model to filter the agent config to return.
* @return A successful response including the default agent configuration.
*/
public static BoxAIAgent getAiAgentDefaultConfig(BoxAPIConnection api,
BoxAIAgent.Mode mode,
String language,
String model) {
QueryStringBuilder builder = new QueryStringBuilder();
builder.appendParam("mode", mode.toString());
if (language != null) {
builder.appendParam("language", language);
}
if (model != null) {
builder.appendParam("model", model);
}
URL url = AI_AGENT_DEFAULT_CONFIG_URL.buildWithQuery(api.getBaseURL(), builder.toString());
BoxAPIRequest req = new BoxAPIRequest(api, url, HttpMethod.GET);
try (BoxJSONResponse response = (BoxJSONResponse) req.send()) {
JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
return BoxAIAgent.parse(responseJSON);
}
}

public enum Mode {
/**
* Multiple items
Expand Down
Loading

0 comments on commit 3cb2c7c

Please sign in to comment.