diff --git a/doc/ai.md b/doc/ai.md index e51f93468..fcc3a683e 100644 --- a/doc/ai.md +++ b/doc/ai.md @@ -11,6 +11,8 @@ an answer based on the provided prompt and items. - [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) + - [Extract metadata freeform](#extract-metadata-freeform) + - [Extract metadata structured](#extract-metadata-structured) @@ -87,4 +89,60 @@ BoxAIAgentConfig config = BoxAI.getAiAgentDefaultConfig( ); ``` -[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- \ No newline at end of file +[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- + +Extract metadata freeform +-------------------------- + +To send an AI request to supported Large Language Models (LLMs) and extract metadata in form of key-value pairs, call static +[`extractMetadataFreeform(BoxAPIConnection api, String prompt, List items)`][extract-metadata-freeform] method. +In the request you have to provide a prompt, a list of items that your prompt refers to and an optional agent configuration. + + +```java +BoxAIResponse response = BoxAI.extractMetadataFreeform( + api, + "firstName, lastName, location, yearOfBirth, company", + Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE)) +); +``` + +[extract-metadata-freeform]: https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAI.html#extractMetadataFreeform-com.box.sdk.BoxAPIConnection-java.lang.String-java.util.List- + +Extract metadata structured +-------------------------- + +Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of key-value pairs. For this request, you need to use an already defined metadata template or define a schema yourself. + +To send an AI request to extract metadata from files with a predefined metadata template, call static +[`extractMetadataStructured extractMetadataStructured(BoxAPIConnection api, List items, BoxAIExtractMetadataTemplate template)`][extract-metadata-structured-metadata-template] method. + + +```java +BoxAIExtractMetadataTemplate template = new BoxAIExtractMetadataTemplate("templateKey", "enterprise"); +BoxAIExtractStructuredResponse result = BoxAI.extractMetadataStructured( + api, + Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE)), + template +); +JsonObject sourceJson = result.getSourceJson(); +``` + +To send an AI request to extract metadata from files with custom fields, call static +[`extractMetadataStructured extractMetadataStructured(BoxAPIConnection api, List items, List fields)`][extract-metadata-structured-fields] method. + + +```java +List fields = new ArrayList<>(); +fields.add(new BoxAIExtractField("firstName")); + +BoxAIExtractStructuredResponse result = BoxAI.extractMetadataStructured( + api, + Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE)), + fields +); +JsonObject sourceJson = result.getSourceJson(); +``` + +[extract-metadata-structured-metadata-template]: https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAI.html#extractMetadataStructured-com.box.sdk.BoxAPIConnection-java.util.List-com.box.sdk.ai.metadata.BoxAIExtractMetadataTemplate- +[extract-metadata-structured-fields]: https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAI.html#extractMetadataStructured-com.box.sdk.BoxAPIConnection-java.util.List-java.util.List- \ No newline at end of file diff --git a/src/intTest/java/com/box/sdk/BoxAIIT.java b/src/intTest/java/com/box/sdk/BoxAIIT.java index b0c137aef..9f60f563d 100644 --- a/src/intTest/java/com/box/sdk/BoxAIIT.java +++ b/src/intTest/java/com/box/sdk/BoxAIIT.java @@ -11,6 +11,8 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonObject; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; @@ -49,10 +51,10 @@ public void askAISingleItem() throws InterruptedException { // 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 + 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())); @@ -86,10 +88,10 @@ public void askAIMultipleItems() throws InterruptedException { // and 412 is returned retry(() -> { BoxAIResponse response = BoxAI.sendAIRequest( - api, - "What is the content of these files?", - items, - BoxAI.Mode.MULTIPLE_ITEM_QA + 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())); @@ -111,7 +113,7 @@ public void askAITextGenItemWithDialogueHistory() throws ParseException, Interru 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"); + 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 @@ -121,16 +123,16 @@ public void askAITextGenItemWithDialogueHistory() throws ParseException, Interru List dialogueHistory = new ArrayList<>(); dialogueHistory.add( - new BoxAIDialogueEntry("What is the name of the file?", "Test file", date1) + 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) + 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 + 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())); @@ -192,4 +194,137 @@ public void askAISingleItemWithAgent() throws InterruptedException { deleteFile(uploadedFile); } } + + @Test + public void aiExtract() throws InterruptedException { + BoxAPIConnection api = jwtApiForServiceAccount(); + BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.EXTRACT, "en-US", null); + BoxAIAgentExtract agentExtract = (BoxAIAgentExtract) agent; + + BoxFile uploadedFile = uploadFileToUniqueFolder(api, "[aiExtract] Test File.txt", + "My name is John Doe. I live in San Francisco. I was born in 1990. I work at Box."); + + try { + // 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.extractMetadataFreeform(api, + "firstName, lastName, location, yearOfBirth, company", + Collections.singletonList(new BoxAIItem(uploadedFile.getID(), BoxAIItem.Type.FILE)), + agentExtract); + assertThat(response.getAnswer(), containsString("John")); + assertThat(response.getCompletionReason(), equalTo("done")); + }, 2, 2000); + } finally { + deleteFile(uploadedFile); + } + } + + @Test + public void aiExtractStructuredWithFields() throws InterruptedException { + BoxAPIConnection api = jwtApiForServiceAccount(); + BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.EXTRACT_STRUCTURED, "en-US", null); + BoxAIAgentExtractStructured agentExtractStructured = (BoxAIAgentExtractStructured) agent; + + BoxFile uploadedFile = uploadFileToUniqueFolder(api, "[aiExtractStructuredWithFields] Test File.txt", + "My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar and books."); + + try { + // When a file has been just uploaded, AI service may not be ready to return text response + // and 412 is returned + retry(() -> { + BoxAIExtractStructuredResponse response = BoxAI.extractMetadataStructured(api, + Collections.singletonList(new BoxAIItem(uploadedFile.getID(), BoxAIItem.Type.FILE)), + new ArrayList() {{ + add(new BoxAIExtractField("firstName")); + add(new BoxAIExtractField("lastName")); + add(new BoxAIExtractField("date", + "Person date of birth", + "Birth date", + "dateOfBirth", + null, + "What is the date of your birth?")); + add(new BoxAIExtractField("float", + "Person age", + "Age", + "age", + null, + "How old are you?")); + add(new BoxAIExtractField("multiSelect", + "Person hobby", + "Hobby", + "hobby", + new ArrayList() {{ + add(new BoxAIExtractFieldOption("guitar")); + add(new BoxAIExtractFieldOption("books")); + }}, + "What is your hobby?")); + }}, + agentExtractStructured); + JsonObject sourceJson = response.getSourceJson(); + assertThat(sourceJson.get("firstName").asString(), is(equalTo("John"))); + assertThat(sourceJson.get("lastName").asString(), is(equalTo("Doe"))); + assertThat(sourceJson.get("dateOfBirth").asString(), is(equalTo("1990-07-04"))); + assertThat(sourceJson.get("age").asInt(), is(equalTo(34))); + assertThat(sourceJson.get("hobby").asArray().get(0).asString(), is(equalTo("guitar"))); + assertThat(sourceJson.get("hobby").asArray().get(1).asString(), is(equalTo("books"))); + }, 2, 2000); + } finally { + deleteFile(uploadedFile); + } + } + + @Test + public void aiExtractStructuredWithMetadataTemplate() throws InterruptedException { + BoxAPIConnection api = jwtApiForServiceAccount(); + BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.EXTRACT_STRUCTURED, "en-US", null); + BoxAIAgentExtractStructured agentExtractStructured = (BoxAIAgentExtractStructured) agent; + + BoxFile uploadedFile = uploadFileToUniqueFolder(api, "[aiExtractStructuredWithMetadataTemplate] Test File.txt", + "My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar and books."); + String templateKey = "key" + java.util.UUID.randomUUID().toString(); + MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api, + "enterprise", + templateKey, + templateKey, + false, + new ArrayList() {{ + add(new MetadataTemplate.Field(Json.parse( + "{\"key\":\"firstName\",\"displayName\":\"First name\"," + + "\"description\":\"Person first name\",\"type\":\"string\"}").asObject())); + add(new MetadataTemplate.Field(Json.parse( + "{\"key\":\"lastName\",\"displayName\":\"Last name\"," + + "\"description\":\"Person last name\",\"type\":\"string\"}").asObject())); + add(new MetadataTemplate.Field(Json.parse( + "{\"key\":\"dateOfBirth\",\"displayName\":\"Birth date\"," + + "\"description\":\"Person date of birth\",\"type\":\"date\"}").asObject())); + add(new MetadataTemplate.Field(Json.parse( + "{\"key\":\"age\",\"displayName\":\"Age\"," + + "\"description\":\"Person age\",\"type\":\"float\"}").asObject())); + add(new MetadataTemplate.Field(Json.parse( + "{\"key\":\"hobby\",\"displayName\":\"Hobby\"," + + "\"description\":\"Person hobby\",\"type\":\"multiSelect\"}").asObject())); + }}); + + try { + // When a file has been just uploaded, AI service may not be ready to return text response + // and 412 is returned + retry(() -> { + BoxAIExtractStructuredResponse response = BoxAI.extractMetadataStructured(api, + Collections.singletonList(new BoxAIItem(uploadedFile.getID(), BoxAIItem.Type.FILE)), + new BoxAIExtractMetadataTemplate(templateKey, "enterprise"), + agentExtractStructured); + JsonObject sourceJson = response.getSourceJson(); + assertThat(sourceJson.get("firstName").asString(), is(equalTo("John"))); + assertThat(sourceJson.get("lastName").asString(), is(equalTo("Doe"))); + assertThat(sourceJson.get("dateOfBirth").asString(), is(equalTo("1990-07-04"))); + assertThat(sourceJson.get("age").asInt(), is(equalTo(34))); + assertThat(sourceJson.get("hobby").asArray().get(0).asString(), is(equalTo("guitar"))); + assertThat(sourceJson.get("hobby").asArray().get(1).asString(), is(equalTo("books"))); + }, 2, 2000); + } finally { + deleteFile(uploadedFile); + MetadataTemplate.deleteMetadataTemplate(api, template.getScope(), template.getTemplateKey()); + } + } } diff --git a/src/main/java/com/box/sdk/BoxAI.java b/src/main/java/com/box/sdk/BoxAI.java index 0dc24fa53..a38eb6316 100644 --- a/src/main/java/com/box/sdk/BoxAI.java +++ b/src/main/java/com/box/sdk/BoxAI.java @@ -22,6 +22,14 @@ public final class BoxAI { * AI agent default config url. */ public static final URLTemplate AI_AGENT_DEFAULT_CONFIG_URL = new URLTemplate("ai_agent_default"); + /** + * AI extract metadata freeform url. + */ + public static final URLTemplate EXTRACT_METADATA_FREEFORM_URL = new URLTemplate("ai/extract"); + /** + * AI extract metadata structured url. + */ + public static final URLTemplate EXTRACT_METADATA_STRUCTURED_URL = new URLTemplate("ai/extract_structured"); private BoxAI() { } @@ -239,4 +247,168 @@ public String toString() { return this.mode; } } + + /** + * Sends an AI request to supported Large Language Models (LLMs) and extracts metadata in form of key-value pairs. + * Freeform metadata extraction does not require any metadata template setup before sending the request. + * + * @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 extractMetadataFreeform(BoxAPIConnection api, + String prompt, + List items) { + return extractMetadataFreeform(api, prompt, items, null); + } + + /** + * Sends an AI request to supported Large Language Models (LLMs) and extracts metadata in form of key-value pairs. + * Freeform metadata extraction does not require any metadata template setup before sending the request. + * + * @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 agent The AI agent configuration to be used for the request. + * @return The response from the AI. + */ + public static BoxAIResponse extractMetadataFreeform(BoxAPIConnection api, + String prompt, + List items, + BoxAIAgentExtract agent) { + URL url = EXTRACT_METADATA_FREEFORM_URL.build(api.getBaseURL()); + + JsonObject requestJSON = new JsonObject(); + JsonArray itemsJSON = new JsonArray(); + for (BoxAIItem item : items) { + itemsJSON.add(item.getJSONObject()); + } + requestJSON.add("items", itemsJSON); + requestJSON.add("prompt", prompt); + if (agent != null) { + requestJSON.add("ai_agent", agent.getJSONObject()); + } + + 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 Large Language Models (LLMs) and returns extracted metadata as a set of + * key-value pairs. For this request, you need to use an already defined metadata template or a define a + * schema yourself. + * + * @param api The API connection to be used by the created user. + * @param items The items to be processed by the LLM, currently only files are supported. + * @param template The metadata template to be used for the request. + * @return The response from the AI. + */ + public static BoxAIExtractStructuredResponse extractMetadataStructured(BoxAPIConnection api, List items, + BoxAIExtractMetadataTemplate template) { + return extractMetadataStructured(api, items, template, null, null); + } + + /** + * Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of + * key-value pairs. For this request, you need to use an already defined metadata template or a define a + * schema yourself. + * + * @param api The API connection to be used by the created user. + * @param items The items to be processed by the LLM, currently only files are supported. + * @param template The metadata template to be used for the request. + * @param agent The AI agent configuration to be used for the request. + * @return The response from the AI. + */ + public static BoxAIExtractStructuredResponse extractMetadataStructured(BoxAPIConnection api, List items, + BoxAIExtractMetadataTemplate template, + BoxAIAgentExtractStructured agent) { + return extractMetadataStructured(api, items, template, null, agent); + } + + /** + * Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of + * key-value pairs. For this request, you need to use an already defined metadata template or a define a + * schema yourself. + * + * @param api The API connection to be used by the created user. + * @param items The items to be processed by the LLM, currently only files are supported. + * @param fields The fields to be extracted from the items. + * @return The response from the AI. + */ + public static BoxAIExtractStructuredResponse extractMetadataStructured(BoxAPIConnection api, List items, + List fields) { + return extractMetadataStructured(api, items, null, fields, null); + } + + /** + * Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of + * key-value pairs. For this request, you need to use an already defined metadata template or a define a + * schema yourself. + * + * @param api The API connection to be used by the created user. + * @param items The items to be processed by the LLM, currently only files are supported. + * @param fields The fields to be extracted from the items. + * @param agent The AI agent configuration to be used for the request. + * @return The response from the AI. + */ + public static BoxAIExtractStructuredResponse extractMetadataStructured(BoxAPIConnection api, List items, + List fields, + BoxAIAgentExtractStructured agent) { + return extractMetadataStructured(api, items, null, fields, agent); + } + + /** + * Sends an AI request to supported Large Language Models (LLMs) and returns extracted metadata as a set of + * key-value pairs. For this request, you need to use an already defined metadata template or a define a + * schema yourself. + * + * @param api The API connection to be used by the created user. + * @param items The items to be processed by the LLM, currently only files are supported. + * @param template The metadata template to be used for the request. + * @param fields The fields to be extracted from the items. + * @param agent The AI agent configuration to be used for the request. + * @return The response from the AI. + */ + private static BoxAIExtractStructuredResponse extractMetadataStructured(BoxAPIConnection api, List items, + BoxAIExtractMetadataTemplate template, + List fields, + BoxAIAgentExtractStructured agent) { + URL url = EXTRACT_METADATA_STRUCTURED_URL.build(api.getBaseURL()); + + JsonObject requestJSON = new JsonObject(); + JsonArray itemsJSON = new JsonArray(); + for (BoxAIItem item : items) { + itemsJSON.add(item.getJSONObject()); + } + requestJSON.add("items", itemsJSON); + + if (template != null) { + requestJSON.add("metadata_template", template.getJSONObject()); + } + + if (fields != null) { + JsonArray fieldsJSON = new JsonArray(); + for (BoxAIExtractField field : fields) { + fieldsJSON.add(field.getJSONObject()); + } + requestJSON.add("fields", fieldsJSON); + } + + if (agent != null) { + requestJSON.add("ai_agent", agent.getJSONObject()); + } + + BoxJSONRequest req = new BoxJSONRequest(api, url, HttpMethod.POST); + req.setBody(requestJSON.toString()); + + try (BoxJSONResponse response = req.send()) { + return new BoxAIExtractStructuredResponse(response.getJSON()); + } + } } diff --git a/src/main/java/com/box/sdk/BoxAIAgent.java b/src/main/java/com/box/sdk/BoxAIAgent.java index 08315cefa..4d0c13ee4 100644 --- a/src/main/java/com/box/sdk/BoxAIAgent.java +++ b/src/main/java/com/box/sdk/BoxAIAgent.java @@ -6,14 +6,14 @@ public abstract class BoxAIAgent extends BoxJSONObject { /** * The type of the AI agent. - * Value can be "ai_agent_ask" or "ai_agent_text_gen". + * Value can be "ai_agent_ask" or "ai_agent_text_gen", "ai_agent_extract", "ai_agent_extract_structured". */ private String type; /** * Constructs an AI agent with default settings. * @param type The type of the AI agent. - * Value can be "ai_agent_ask" or "ai_agent_text_gen". + * Value can be "ai_agent_ask", "ai_agent_text_gen", "ai_agent_extract", "ai_agent_extract_structured". */ public BoxAIAgent(String type) { super(); @@ -38,6 +38,10 @@ public static BoxAIAgent parse(JsonObject jsonObject) { return new BoxAIAgentAsk(jsonObject); } else if (type.equals(BoxAIAgentTextGen.TYPE)) { return new BoxAIAgentTextGen(jsonObject); + } else if (type.equals(BoxAIAgentExtract.TYPE)) { + return new BoxAIAgentExtract(jsonObject); + } else if (type.equals(BoxAIAgentExtractStructured.TYPE)) { + return new BoxAIAgentExtractStructured(jsonObject); } else { throw new IllegalArgumentException("Invalid AI agent type: " + type); } @@ -86,7 +90,16 @@ public enum Mode { /** * The type of AI agent used for generating text. */ - TEXT_GEN("text_gen"); + TEXT_GEN("text_gen"), + /** + * The type of AI agent used for extracting metadata freeform. + */ + EXTRACT("extract"), + /** + * The type of AI agent used for extracting metadata structured. + */ + EXTRACT_STRUCTURED("extract_structured"); + private final String value; @@ -99,6 +112,10 @@ static BoxAIAgent.Mode fromJSONValue(String value) { return ASK; } else if (value.equals("text_gen")) { return TEXT_GEN; + } else if (value.equals("extract")) { + return EXTRACT; + } else if (value.equals("extract_structured")) { + return EXTRACT_STRUCTURED; } else { throw new IllegalArgumentException("Invalid AI agent mode: " + value); } diff --git a/src/main/java/com/box/sdk/BoxAIAgentExtract.java b/src/main/java/com/box/sdk/BoxAIAgentExtract.java new file mode 100644 index 000000000..04f0bf998 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxAIAgentExtract.java @@ -0,0 +1,109 @@ +package com.box.sdk; + +import com.box.sdk.internal.utils.JsonUtils; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * Represents an AI Agent used for extraction. + */ +@BoxResourceType("ai_agent_extract") +public class BoxAIAgentExtract extends BoxAIAgent { + + /** + * The type of the AI Ask agent. + */ + public static final String TYPE = "ai_agent_extract"; + + /** + * AI agent tool used to handle basic text. + */ + private BoxAIAgentAskBasicText basicText; + /** + * AI agent tool used to handle longer text. + */ + private BoxAIAgentAskLongText longText; + + /** + * Constructs an AI agent with default settings. + * @param basicText AI agent tool used to handle basic text. + * @param longText AI agent tool used to handle longer text. + */ + public BoxAIAgentExtract(BoxAIAgentAskBasicText basicText, + BoxAIAgentAskLongText longText) { + super(TYPE); + this.basicText = basicText; + this.longText = longText; + } + + /** + * Constructs an AI agent with default settings. + * @param jsonObject JSON object representing the AI agent. + */ + public BoxAIAgentExtract(JsonObject jsonObject) { + super(jsonObject); + } + + /** + * Gets the AI agent tool used to handle basic text. + * @return The AI agent tool used to handle basic text. + */ + public BoxAIAgentAskBasicText getBasicText() { + return basicText; + } + + /** + * Sets the AI agent tool used to handle basic text. + * @param basicText The AI agent tool used to handle basic text. + */ + public void setBasicText(BoxAIAgentAskBasicText basicText) { + this.basicText = basicText; + } + + /** + * Gets the AI agent tool used to handle longer text. + * @return The AI agent tool used to handle longer text. + */ + public BoxAIAgentAskLongText getLongText() { + return longText; + } + + /** + * Sets the AI agent tool used to handle longer text. + * @param longText The AI agent tool used to handle longer text. + */ + public void setLongText(BoxAIAgentAskLongText longText) { + this.longText = longText; + } + + @Override + void parseJSONMember(JsonObject.Member member) { + super.parseJSONMember(member); + String memberName = member.getName(); + JsonValue memberValue = member.getValue(); + try { + switch (memberName) { + case "basic_text": + this.basicText = new BoxAIAgentAskBasicText(memberValue.asObject()); + break; + case "long_text": + this.longText = new BoxAIAgentAskLongText(memberValue.asObject()); + break; + default: + break; + } + } catch (Exception e) { + throw new BoxAPIException("Could not parse JSON response.", e); + } + } + + @Override + public JsonObject getJSONObject() { + JsonObject jsonObject = new JsonObject(); + JsonUtils.addIfNotNull(jsonObject, "type", this.getType()); + JsonUtils.addIfNotNull(jsonObject, "basic_text", this.basicText.getJSONObject()); + JsonUtils.addIfNotNull(jsonObject, "long_text", this.longText.getJSONObject()); + return jsonObject; + } +} + diff --git a/src/main/java/com/box/sdk/BoxAIAgentExtractStructured.java b/src/main/java/com/box/sdk/BoxAIAgentExtractStructured.java new file mode 100644 index 000000000..94e5d6202 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxAIAgentExtractStructured.java @@ -0,0 +1,109 @@ +package com.box.sdk; + +import com.box.sdk.internal.utils.JsonUtils; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; + +/** + * Represents an AI Agent used for extraction. + */ +@BoxResourceType("ai_agent_extract_structured") +public class BoxAIAgentExtractStructured extends BoxAIAgent { + + /** + * The type of the AI Ask agent. + */ + public static final String TYPE = "ai_agent_extract_structured"; + + /** + * AI agent tool used to handle basic text. + */ + private BoxAIAgentAskBasicText basicText; + /** + * AI agent tool used to handle longer text. + */ + private BoxAIAgentAskLongText longText; + + /** + * Constructs an AI agent with default settings. + * @param basicText AI agent tool used to handle basic text. + * @param longText AI agent tool used to handle longer text. + */ + public BoxAIAgentExtractStructured(BoxAIAgentAskBasicText basicText, + BoxAIAgentAskLongText longText) { + super(TYPE); + this.basicText = basicText; + this.longText = longText; + } + + /** + * Constructs an AI agent with default settings. + * @param jsonObject JSON object representing the AI agent. + */ + public BoxAIAgentExtractStructured(JsonObject jsonObject) { + super(jsonObject); + } + + /** + * Gets the AI agent tool used to handle basic text. + * @return The AI agent tool used to handle basic text. + */ + public BoxAIAgentAskBasicText getBasicText() { + return basicText; + } + + /** + * Sets the AI agent tool used to handle basic text. + * @param basicText The AI agent tool used to handle basic text. + */ + public void setBasicText(BoxAIAgentAskBasicText basicText) { + this.basicText = basicText; + } + + /** + * Gets the AI agent tool used to handle longer text. + * @return The AI agent tool used to handle longer text. + */ + public BoxAIAgentAskLongText getLongText() { + return longText; + } + + /** + * Sets the AI agent tool used to handle longer text. + * @param longText The AI agent tool used to handle longer text. + */ + public void setLongText(BoxAIAgentAskLongText longText) { + this.longText = longText; + } + + @Override + void parseJSONMember(JsonObject.Member member) { + super.parseJSONMember(member); + String memberName = member.getName(); + JsonValue memberValue = member.getValue(); + try { + switch (memberName) { + case "basic_text": + this.basicText = new BoxAIAgentAskBasicText(memberValue.asObject()); + break; + case "long_text": + this.longText = new BoxAIAgentAskLongText(memberValue.asObject()); + break; + default: + break; + } + } catch (Exception e) { + throw new BoxAPIException("Could not parse JSON response.", e); + } + } + + @Override + public JsonObject getJSONObject() { + JsonObject jsonObject = new JsonObject(); + JsonUtils.addIfNotNull(jsonObject, "type", this.getType()); + JsonUtils.addIfNotNull(jsonObject, "basic_text", this.basicText.getJSONObject()); + JsonUtils.addIfNotNull(jsonObject, "long_text", this.longText.getJSONObject()); + return jsonObject; + } +} + diff --git a/src/main/java/com/box/sdk/BoxAIAgentLLMEndpointParamsGoogle.java b/src/main/java/com/box/sdk/BoxAIAgentLLMEndpointParamsGoogle.java index b7d28bfed..2a5cabe00 100644 --- a/src/main/java/com/box/sdk/BoxAIAgentLLMEndpointParamsGoogle.java +++ b/src/main/java/com/box/sdk/BoxAIAgentLLMEndpointParamsGoogle.java @@ -17,20 +17,20 @@ public class BoxAIAgentLLMEndpointParamsGoogle extends BoxAIAgentLLMEndpointPara * The temperature is used for sampling during response generation, which occurs when top-P and top-K are applied. * Temperature controls the degree of randomness in token selection. */ - private double temperature; + private Double temperature; /** * Top-K changes how the model selects tokens for output. * A top-K of 1 means the next selected token is the most probable among all tokens in the model's vocabulary * (also called greedy decoding), while a top-K of 3 means that the next token is selected from among the three * most probable tokens by using temperature. */ - private int topK; + private Integer topK; /** * Top-P changes how the model selects tokens for output. * Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the * top-P value. */ - private double topP; + private Double topP; /** * Constructs an AI agent with default settings. @@ -44,7 +44,7 @@ public class BoxAIAgentLLMEndpointParamsGoogle extends BoxAIAgentLLMEndpointPara * Tokens are selected from the most (see top-K) to least probable until the sum of their probabilities equals the * top-P value. */ - public BoxAIAgentLLMEndpointParamsGoogle(double temperature, int topK, double topP) { + public BoxAIAgentLLMEndpointParamsGoogle(Double temperature, Integer topK, Double topP) { super(TYPE); this.temperature = temperature; this.topK = topK; @@ -63,7 +63,7 @@ public BoxAIAgentLLMEndpointParamsGoogle(JsonObject jsonObject) { * Gets the temperature used for sampling during response generation, which occurs when top-P and top-K are applied. * @return The temperature used for sampling during response generation, which occurs when top-P and top-K are applied. */ - public double getTemperature() { + public Double getTemperature() { return temperature; } @@ -71,7 +71,7 @@ public double getTemperature() { * Sets the temperature used for sampling during response generation, which occurs when top-P and top-K are applied. * @param temperature The temperature used for sampling during response generation, which occurs when top-P and top-K are applied. */ - public void setTemperature(double temperature) { + public void setTemperature(Double temperature) { this.temperature = temperature; } @@ -79,7 +79,7 @@ public void setTemperature(double temperature) { * Gets the top-K value. * @return The top-K value. */ - public int getTopK() { + public Integer getTopK() { return topK; } @@ -87,7 +87,7 @@ public int getTopK() { * Sets the top-K value. * @param topK The top-K value. */ - public void setTopK(int topK) { + public void setTopK(Integer topK) { this.topK = topK; } @@ -95,7 +95,7 @@ public void setTopK(int topK) { * Gets the top-P value. * @return The top-P value. */ - public double getTopP() { + public Double getTopP() { return topP; } @@ -103,7 +103,7 @@ public double getTopP() { * Sets the top-P value. * @param topP The top-P value. */ - public void setTopP(double topP) { + public void setTopP(Double topP) { this.topP = topP; } diff --git a/src/main/java/com/box/sdk/BoxAIAgentLLMEndpointParamsOpenAI.java b/src/main/java/com/box/sdk/BoxAIAgentLLMEndpointParamsOpenAI.java index ec555ce99..00ecad81a 100644 --- a/src/main/java/com/box/sdk/BoxAIAgentLLMEndpointParamsOpenAI.java +++ b/src/main/java/com/box/sdk/BoxAIAgentLLMEndpointParamsOpenAI.java @@ -17,12 +17,12 @@ public class BoxAIAgentLLMEndpointParamsOpenAI extends BoxAIAgentLLMEndpointPara * Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text * so far, decreasing the model's likelihood to repeat the same line verbatim. */ - private double frequencyPenalty; + private Double frequencyPenalty; /** * Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, * increasing the model's likelihood to talk about new topics. */ - private double presencePenalty; + private Double presencePenalty; /** * Up to 4 sequences where the API will stop generating further tokens. */ @@ -32,13 +32,13 @@ public class BoxAIAgentLLMEndpointParamsOpenAI extends BoxAIAgentLLMEndpointPara * while lower values like 0.2 will make it more focused and deterministic. * We generally recommend altering this or top_p but not both. */ - private double temperature; + private Double temperature; /** * An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of * the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass * are considered. We generally recommend altering this or temperature but not both. */ - private double topP; + private Double topP; /** * Constructs an AI agent with default settings. @@ -54,11 +54,11 @@ public class BoxAIAgentLLMEndpointParamsOpenAI extends BoxAIAgentLLMEndpointPara * the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass * are considered. We generally recommend altering this or temperature but not both. */ - public BoxAIAgentLLMEndpointParamsOpenAI(double frequencyPenalty, - double presencePenalty, + public BoxAIAgentLLMEndpointParamsOpenAI(Double frequencyPenalty, + Double presencePenalty, String stop, - double temperature, - double topP) { + Double temperature, + Double topP) { super(TYPE); this.frequencyPenalty = frequencyPenalty; this.presencePenalty = presencePenalty; @@ -79,7 +79,7 @@ public BoxAIAgentLLMEndpointParamsOpenAI(JsonObject jsonObject) { * Gets the frequency penalty. * @return The frequency penalty. */ - public double getFrequencyPenalty() { + public Double getFrequencyPenalty() { return frequencyPenalty; } @@ -87,7 +87,7 @@ public double getFrequencyPenalty() { * Sets the frequency penalty. * @param frequencyPenalty The frequency penalty. */ - public void setFrequencyPenalty(double frequencyPenalty) { + public void setFrequencyPenalty(Double frequencyPenalty) { this.frequencyPenalty = frequencyPenalty; } @@ -95,7 +95,7 @@ public void setFrequencyPenalty(double frequencyPenalty) { * Gets the presence penalty. * @return The presence penalty. */ - public double getPresencePenalty() { + public Double getPresencePenalty() { return presencePenalty; } @@ -103,7 +103,7 @@ public double getPresencePenalty() { * Sets the presence penalty. * @param presencePenalty The presence penalty. */ - public void setPresencePenalty(double presencePenalty) { + public void setPresencePenalty(Double presencePenalty) { this.presencePenalty = presencePenalty; } @@ -127,7 +127,7 @@ public void setStop(String stop) { * Gets the temperature. * @return The temperature. */ - public double getTemperature() { + public Double getTemperature() { return temperature; } @@ -135,7 +135,7 @@ public double getTemperature() { * Sets the temperature. * @param temperature The temperature. */ - public void setTemperature(double temperature) { + public void setTemperature(Double temperature) { this.temperature = temperature; } @@ -143,7 +143,7 @@ public void setTemperature(double temperature) { * Gets the top-P. * @return The top-P. */ - public double getTopP() { + public Double getTopP() { return topP; } @@ -151,7 +151,7 @@ public double getTopP() { * Sets the top-P. * @param topP The top-P. */ - public void setTopP(double topP) { + public void setTopP(Double topP) { this.topP = topP; } diff --git a/src/main/java/com/box/sdk/BoxAIExtractField.java b/src/main/java/com/box/sdk/BoxAIExtractField.java new file mode 100644 index 000000000..4aba85906 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxAIExtractField.java @@ -0,0 +1,81 @@ +package com.box.sdk; + +import com.box.sdk.internal.utils.JsonUtils; +import com.eclipsesource.json.JsonArray; +import com.eclipsesource.json.JsonObject; +import java.util.List; + +public class BoxAIExtractField extends BoxJSONObject { + /** + * The type of the field. It include but is not limited to string, float, date, enum, and multiSelect. + */ + private String type; + /** + * The description of the field. + */ + private String description; + /** + * The display name of the field. + */ + private String displayName; + /** + * The key of the field. + */ + private String key; + /** + * A list of options for this field. + * This is most often used in combination with the enum and multiSelect field types. + */ + private List options; + /** + * The prompt of the field. + */ + private String prompt; + + /** + * Constructs a BoxAIExtractField object with a given key. + */ + public BoxAIExtractField(String key) { + this.key = key; + } + + /** + * Constructs a BoxAIExtractField object with a given type, description, display name, key, options, and prompt. + * + * @param type the type of the field. + * @param description the description of the field. + * @param displayName the display name of the field. + * @param key the key of the field. + * @param options a list of options for this field. + * @param prompt the prompt of the field. + */ + public BoxAIExtractField(String type, + String description, + String displayName, + String key, List options, + String prompt) { + this.type = type; + this.description = description; + this.displayName = displayName; + this.key = key; + this.options = options; + this.prompt = prompt; + } + + public JsonObject getJSONObject() { + JsonObject jsonObject = new JsonObject(); + JsonUtils.addIfNotNull(jsonObject, "type", this.type); + JsonUtils.addIfNotNull(jsonObject, "description", this.description); + JsonUtils.addIfNotNull(jsonObject, "displayName", this.displayName); + JsonUtils.addIfNotNull(jsonObject, "key", this.key); + if (this.options != null) { + JsonArray options = new JsonArray(); + for (BoxAIExtractFieldOption option : this.options) { + options.add(option.getJSONObject()); + } + jsonObject.add("options", options); + } + JsonUtils.addIfNotNull(jsonObject, "prompt", this.prompt); + return jsonObject; + } +} diff --git a/src/main/java/com/box/sdk/BoxAIExtractFieldOption.java b/src/main/java/com/box/sdk/BoxAIExtractFieldOption.java new file mode 100644 index 000000000..a1cae5ceb --- /dev/null +++ b/src/main/java/com/box/sdk/BoxAIExtractFieldOption.java @@ -0,0 +1,27 @@ +package com.box.sdk; + + +import com.box.sdk.internal.utils.JsonUtils; +import com.eclipsesource.json.JsonObject; + +public class BoxAIExtractFieldOption extends BoxJSONObject { + /** + * A unique identifier for the option. + */ + private final String key; + + /** + * Constructs a BoxAIExtractFieldOption object with a given key. + * + * @param key the key of the field option. + */ + public BoxAIExtractFieldOption(String key) { + this.key = key; + } + + public JsonObject getJSONObject() { + JsonObject jsonObject = new JsonObject(); + JsonUtils.addIfNotNull(jsonObject, "key", this.key); + return jsonObject; + } +} diff --git a/src/main/java/com/box/sdk/BoxAIExtractMetadataTemplate.java b/src/main/java/com/box/sdk/BoxAIExtractMetadataTemplate.java new file mode 100644 index 000000000..c7fb4c3da --- /dev/null +++ b/src/main/java/com/box/sdk/BoxAIExtractMetadataTemplate.java @@ -0,0 +1,56 @@ +package com.box.sdk; + +import com.box.sdk.internal.utils.JsonUtils; +import com.eclipsesource.json.JsonObject; + +public class BoxAIExtractMetadataTemplate extends BoxJSONObject { + /** + * The type of object this class represents. + */ + public static final String TYPE = "metadata_template"; + /** + * The scope of the metadata template that can either be global or enterprise. + */ + private String scope; + /** + * The template key of the metadata template. + */ + private String templateKey; + + /** + * Constructs a BoxAIExtractMetadataTemplate object with a given scope and template key. + * @param templateKey the template key of the metadata template. + * @param scope the scope of the metadata template. + */ + public BoxAIExtractMetadataTemplate(String templateKey, String scope) { + this.templateKey = templateKey; + this.scope = scope; + } + + @Override + void parseJSONMember(JsonObject.Member member) { + super.parseJSONMember(member); + String memberName = member.getName(); + if (memberName.equals("scope")) { + this.scope = member.getValue().asString(); + } else if (memberName.equals("template_key")) { + this.templateKey = member.getValue().asString(); + } + } + + public JsonObject getJSONObject() { + JsonObject jsonObject = new JsonObject(); + JsonUtils.addIfNotNull(jsonObject, "type", TYPE); + JsonUtils.addIfNotNull(jsonObject, "scope", this.scope); + JsonUtils.addIfNotNull(jsonObject, "template_key", this.templateKey); + return jsonObject; + } + + public String getScope() { + return this.scope; + } + + public String getTemplateKey() { + return this.templateKey; + } +} diff --git a/src/main/java/com/box/sdk/BoxAIExtractStructuredResponse.java b/src/main/java/com/box/sdk/BoxAIExtractStructuredResponse.java new file mode 100644 index 000000000..662244fa5 --- /dev/null +++ b/src/main/java/com/box/sdk/BoxAIExtractStructuredResponse.java @@ -0,0 +1,38 @@ +package com.box.sdk; + +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonObject; + +/** + * AI response to a user request. + */ +public class BoxAIExtractStructuredResponse extends BoxJSONObject { + private final JsonObject sourceJson; + + /** + * Constructs a BoxAIResponse object. + */ + public BoxAIExtractStructuredResponse(String json) { + super(json); + this.sourceJson = Json.parse(json).asObject(); + } + + /** + * Constructs an BoxAIResponse object using an already parsed JSON object. + * + * @param jsonObject the parsed JSON object. + */ + BoxAIExtractStructuredResponse(JsonObject jsonObject) { + super(jsonObject); + this.sourceJson = jsonObject; + } + + /** + * Gets the source JSON of the AI response. + * + * @return the source JSON of the AI response. + */ + public JsonObject getSourceJson() { + return sourceJson; + } +} diff --git a/src/test/Fixtures/BoxAI/ExtractMetadataFreeform200.json b/src/test/Fixtures/BoxAI/ExtractMetadataFreeform200.json new file mode 100644 index 000000000..1ec314c6a --- /dev/null +++ b/src/test/Fixtures/BoxAI/ExtractMetadataFreeform200.json @@ -0,0 +1,5 @@ +{ + "answer": "Public APIs are important because of key and important reasons.", + "completion_reason": "done", + "created_at": "2012-12-12T10:53:43.123-08:00" +} \ No newline at end of file diff --git a/src/test/Fixtures/BoxAI/ExtractMetadataStructured200.json b/src/test/Fixtures/BoxAI/ExtractMetadataStructured200.json new file mode 100644 index 000000000..15e917b2b --- /dev/null +++ b/src/test/Fixtures/BoxAI/ExtractMetadataStructured200.json @@ -0,0 +1,9 @@ +{ + "firstName": "John", + "lastName": "Doe", + "age": 25, + "hobbies": [ + "reading", + "travelling" + ] +} \ No newline at end of file diff --git a/src/test/Fixtures/BoxAI/GetAIAgentDefaultConfigExtract200.json b/src/test/Fixtures/BoxAI/GetAIAgentDefaultConfigExtract200.json new file mode 100644 index 000000000..4089200ff --- /dev/null +++ b/src/test/Fixtures/BoxAI/GetAIAgentDefaultConfigExtract200.json @@ -0,0 +1,38 @@ +{ + "type": "ai_agent_extract", + "basic_text": { + "llm_endpoint_params": { + "type": "openai_params", + "frequency_penalty": 1.5, + "presence_penalty": 1.5, + "stop": "<|im_end|>", + "temperature": 0, + "top_p": 1 + }, + "model": "azure__openai__gpt_3_5_turbo_16k", + "num_tokens_for_completion": 8400, + "prompt_template": "It is `{current_date}`, consider these travel options `{content}` and answer the `{user_question}`.", + "system_message": "You are a helpful travel assistant specialized in budget travel" + }, + "long_text": { + "embeddings": { + "model": "openai__text_embedding_ada_002", + "strategy": { + "id": "basic", + "num_tokens_per_chunk": 64 + } + }, + "llm_endpoint_params": { + "type": "openai_params", + "frequency_penalty": 1.5, + "presence_penalty": 1.5, + "stop": "<|im_end|>", + "temperature": 0, + "top_p": 1 + }, + "model": "azure__openai__gpt_3_5_turbo_16k", + "num_tokens_for_completion": 8400, + "prompt_template": "It is `{current_date}`, consider these travel options `{content}` and answer the `{user_question}`.", + "system_message": "You are a helpful travel assistant specialized in budget travel" + } +} \ No newline at end of file diff --git a/src/test/Fixtures/BoxAI/GetAIAgentDefaultConfigExtractStructured200.json b/src/test/Fixtures/BoxAI/GetAIAgentDefaultConfigExtractStructured200.json new file mode 100644 index 000000000..89555e825 --- /dev/null +++ b/src/test/Fixtures/BoxAI/GetAIAgentDefaultConfigExtractStructured200.json @@ -0,0 +1,38 @@ +{ + "type": "ai_agent_extract_structured", + "basic_text": { + "llm_endpoint_params": { + "type": "openai_params", + "frequency_penalty": 1.5, + "presence_penalty": 1.5, + "stop": "<|im_end|>", + "temperature": 0, + "top_p": 1 + }, + "model": "azure__openai__gpt_3_5_turbo_16k", + "num_tokens_for_completion": 8400, + "prompt_template": "It is `{current_date}`, consider these travel options `{content}` and answer the `{user_question}`.", + "system_message": "You are a helpful travel assistant specialized in budget travel" + }, + "long_text": { + "embeddings": { + "model": "openai__text_embedding_ada_002", + "strategy": { + "id": "basic", + "num_tokens_per_chunk": 64 + } + }, + "llm_endpoint_params": { + "type": "openai_params", + "frequency_penalty": 1.5, + "presence_penalty": 1.5, + "stop": "<|im_end|>", + "temperature": 0, + "top_p": 1 + }, + "model": "azure__openai__gpt_3_5_turbo_16k", + "num_tokens_for_completion": 8400, + "prompt_template": "It is `{current_date}`, consider these travel options `{content}` and answer the `{user_question}`.", + "system_message": "You are a helpful travel assistant specialized in budget travel" + } +} \ No newline at end of file diff --git a/src/test/java/com/box/sdk/BoxAITest.java b/src/test/java/com/box/sdk/BoxAITest.java index 11af93666..a54fbcb5e 100644 --- a/src/test/java/com/box/sdk/BoxAITest.java +++ b/src/test/java/com/box/sdk/BoxAITest.java @@ -1,6 +1,7 @@ package com.box.sdk; import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.junit.WireMockRule; @@ -236,4 +237,162 @@ public void testGetAIAgentDefaultConfigTextGenSuccess() { JsonObject jsonResult = Json.parse(result).asObject(); assertThat(agent.getJSONObject().toString(), equalTo(jsonResult.toString())); } + + @Test + public void testGetAIAgentDefaultConfigExtractSuccess() { + String result = TestUtils.getFixture("BoxAI/GetAIAgentDefaultConfigExtract200"); + String urlPath = "/2.0/ai_agent_default"; + + wireMockRule.stubFor(WireMock.get(WireMock.urlPathEqualTo(urlPath)) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(result))); + + BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.EXTRACT, + "en", "openai__gpt_3_5_turbo"); + BoxAIAgentExtract agentExtract = (BoxAIAgentExtract) agent; + + assertThat(agent.getType(), equalTo("ai_agent_extract")); + assertThat(agentExtract.getBasicText().getModel(), equalTo("azure__openai__gpt_3_5_turbo_16k")); + + JsonObject jsonResult = Json.parse(result).asObject(); + assertThat(agent.getJSONObject().toString(), equalTo(jsonResult.toString())); + } + + @Test + public void testGetAIAgentDefaultConfigExtractStructuredSuccess() { + String result = TestUtils.getFixture("BoxAI/GetAIAgentDefaultConfigExtractStructured200"); + String urlPath = "/2.0/ai_agent_default"; + + wireMockRule.stubFor(WireMock.get(WireMock.urlPathEqualTo(urlPath)) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(result))); + + BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.EXTRACT_STRUCTURED, + "en", "openai__gpt_3_5_turbo"); + BoxAIAgentExtractStructured agentExtract = (BoxAIAgentExtractStructured) agent; + + assertThat(agent.getType(), equalTo("ai_agent_extract_structured")); + assertThat(agentExtract.getBasicText().getModel(), equalTo("azure__openai__gpt_3_5_turbo_16k")); + + JsonObject jsonResult = Json.parse(result).asObject(); + assertThat(agent.getJSONObject().toString(), equalTo(jsonResult.toString())); + } + + @Test + public void testExtractMetadataFreeformSuccess() { + String agentString = TestUtils.getFixture("BoxAI/GetAIAgentDefaultConfigExtract200"); + final String fileId = "12345"; + final String prompt = "Extract data related to contract conditions"; + final BoxAIAgentExtract agent = new BoxAIAgentExtract(Json.parse(agentString).asObject()); + JsonObject expectedRequestBody = new JsonObject(); + expectedRequestBody.add("prompt", prompt); + expectedRequestBody.add("items", new JsonArray().add(new JsonObject() + .add("type", "file") + .add("id", fileId))); + expectedRequestBody.add("ai_agent", agent.getJSONObject()); + + String result = TestUtils.getFixture("BoxAI/ExtractMetadataFreeform200"); + wireMockRule.stubFor(WireMock.post(WireMock.urlPathEqualTo("/2.0/ai/extract")) + .withRequestBody(WireMock.equalToJson( + expectedRequestBody.toString() + )) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(result))); + + BoxAIResponse response = BoxAI.extractMetadataFreeform( + api, + prompt, + Collections.singletonList(new BoxAIItem(fileId, BoxAIItem.Type.FILE)), + agent + ); + + assertThat(response.getAnswer(), equalTo("Public APIs are important because of key and important reasons.")); + assertThat(response.getCreatedAt(), equalTo(new Date(1355338423123L))); + assertThat(response.getCompletionReason(), equalTo("done")); + } + + @Test + public void testExtractMetadataStructuredWithTemplateSuccess() { + String agentString = TestUtils.getFixture("BoxAI/GetAIAgentDefaultConfigExtractStructured200"); + final String fileId = "12345"; + final BoxAIExtractMetadataTemplate template = new BoxAIExtractMetadataTemplate("templateId", "enterprise"); + final List items = Collections.singletonList(new BoxAIItem(fileId, BoxAIItem.Type.FILE)); + final String result = TestUtils.getFixture("BoxAI/ExtractMetadataStructured200"); + final BoxAIAgentExtractStructured agent = new BoxAIAgentExtractStructured(Json.parse(agentString).asObject()); + + final JsonObject expectedRequestBody = new JsonObject() + .add("items", new JsonArray().add(new JsonObject() + .add("type", "file") + .add("id", fileId))) + .add("metadata_template", new JsonObject() + .add("type", "metadata_template") + .add("template_key", template.getTemplateKey()) + .add("scope", template.getScope())) + .add("ai_agent", agent.getJSONObject()); + + wireMockRule.stubFor(WireMock.post(WireMock.urlPathEqualTo("/2.0/ai/extract_structured")) + .withRequestBody(WireMock.equalToJson( + expectedRequestBody.toString() + )) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(result))); + + BoxAIExtractStructuredResponse response = BoxAI.extractMetadataStructured( + api, + items, + template, + agent + ); + + assertThat(response.getSourceJson(), equalTo(Json.parse(result).asObject())); + assertThat(response.getSourceJson().get("firstName").asString(), equalTo("John")); + } + + @Test + public void testExtractMetadataStructuredWithFieldSuccess() { + String agentString = TestUtils.getFixture("BoxAI/GetAIAgentDefaultConfigExtractStructured200"); + final String fileId = "12345"; + final List items = Collections.singletonList(new BoxAIItem(fileId, BoxAIItem.Type.FILE)); + final String result = TestUtils.getFixture("BoxAI/ExtractMetadataStructured200"); + final BoxAIAgentExtractStructured agent = new BoxAIAgentExtractStructured(Json.parse(agentString).asObject()); + final BoxAIExtractField field = new BoxAIExtractField( + "text", + "The name of the file", + "Name", + "name", + new ArrayList() {{ + add(new BoxAIExtractFieldOption("option 1")); + add(new BoxAIExtractFieldOption("option 2")); + }}, + "What is the name of the file?"); + + final JsonObject expectedRequestBody = new JsonObject() + .add("items", new JsonArray().add(new JsonObject() + .add("type", "file") + .add("id", fileId))) + .add("ai_agent", agent.getJSONObject()) + .add("fields", new JsonArray().add(field.getJSONObject())); + + wireMockRule.stubFor(WireMock.post(WireMock.urlPathEqualTo("/2.0/ai/extract_structured")) + .withRequestBody(WireMock.equalToJson( + expectedRequestBody.toString() + )) + .willReturn(WireMock.aResponse() + .withHeader("Content-Type", APPLICATION_JSON) + .withBody(result))); + + BoxAIExtractStructuredResponse response = BoxAI.extractMetadataStructured( + api, + items, + Collections.singletonList(field), + agent + ); + + assertThat(response.getSourceJson(), equalTo(Json.parse(result).asObject())); + assertThat(response.getSourceJson().get("firstName").asString(), equalTo("John")); + } }