Skip to content

Commit 7ba90b9

Browse files
authored
feat: Support AI extract and AI extract structured (#1266)
1 parent 3cb2c7c commit 7ba90b9

17 files changed

+1096
-45
lines changed

doc/ai.md

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ an answer based on the provided prompt and items.
1111
- [Send AI request](#send-ai-request)
1212
- [Send AI text generation request](#send-ai-text-generation-request)
1313
- [Get AI Agent default configuration](#get-ai-agent-default-configuration)
14+
- [Extract metadata freeform](#extract-metadata-freeform)
15+
- [Extract metadata structured](#extract-metadata-structured)
1416

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

@@ -87,4 +89,60 @@ BoxAIAgentConfig config = BoxAI.getAiAgentDefaultConfig(
8789
);
8890
```
8991

90-
[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-
92+
[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-
93+
94+
Extract metadata freeform
95+
--------------------------
96+
97+
To send an AI request to supported Large Language Models (LLMs) and extract metadata in form of key-value pairs, call static
98+
[`extractMetadataFreeform(BoxAPIConnection api, String prompt, List<BoxAIItem> items)`][extract-metadata-freeform] method.
99+
In the request you have to provide a prompt, a list of items that your prompt refers to and an optional agent configuration.
100+
101+
<!-- sample post_ai_extract -->
102+
```java
103+
BoxAIResponse response = BoxAI.extractMetadataFreeform(
104+
api,
105+
"firstName, lastName, location, yearOfBirth, company",
106+
Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE))
107+
);
108+
```
109+
110+
[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-
111+
112+
Extract metadata structured
113+
--------------------------
114+
115+
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.
116+
117+
To send an AI request to extract metadata from files with a predefined metadata template, call static
118+
[`extractMetadataStructured extractMetadataStructured(BoxAPIConnection api, List<BoxAIItem> items, BoxAIExtractMetadataTemplate template)`][extract-metadata-structured-metadata-template] method.
119+
120+
<!-- sample post_ai_extract_structured -->
121+
```java
122+
BoxAIExtractMetadataTemplate template = new BoxAIExtractMetadataTemplate("templateKey", "enterprise");
123+
BoxAIExtractStructuredResponse result = BoxAI.extractMetadataStructured(
124+
api,
125+
Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE)),
126+
template
127+
);
128+
JsonObject sourceJson = result.getSourceJson();
129+
```
130+
131+
To send an AI request to extract metadata from files with custom fields, call static
132+
[`extractMetadataStructured extractMetadataStructured(BoxAPIConnection api, List<BoxAIItem> items, List<BoxAIExtractField> fields)`][extract-metadata-structured-fields] method.
133+
134+
<!-- sample post_ai_extract_structured_fields -->
135+
```java
136+
List<BoxAIExtractField> fields = new ArrayList<>();
137+
fields.add(new BoxAIExtractField("firstName"));
138+
139+
BoxAIExtractStructuredResponse result = BoxAI.extractMetadataStructured(
140+
api,
141+
Collections.singletonList(new BoxAIItem("123456", BoxAIItem.Type.FILE)),
142+
fields
143+
);
144+
JsonObject sourceJson = result.getSourceJson();
145+
```
146+
147+
[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-
148+
[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-

src/intTest/java/com/box/sdk/BoxAIIT.java

Lines changed: 150 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import static org.hamcrest.Matchers.equalTo;
1212
import static org.hamcrest.Matchers.is;
1313

14+
import com.eclipsesource.json.Json;
15+
import com.eclipsesource.json.JsonObject;
1416
import java.text.ParseException;
1517
import java.util.ArrayList;
1618
import java.util.Collections;
@@ -49,10 +51,10 @@ public void askAISingleItem() throws InterruptedException {
4951
// and 412 is returned
5052
retry(() -> {
5153
BoxAIResponse response = BoxAI.sendAIRequest(
52-
api,
53-
"What is the name of the file?",
54-
Collections.singletonList(new BoxAIItem(uploadedFileInfo.getID(), BoxAIItem.Type.FILE)),
55-
BoxAI.Mode.SINGLE_ITEM_QA
54+
api,
55+
"What is the name of the file?",
56+
Collections.singletonList(new BoxAIItem(uploadedFileInfo.getID(), BoxAIItem.Type.FILE)),
57+
BoxAI.Mode.SINGLE_ITEM_QA
5658
);
5759
assertThat(response.getAnswer(), containsString("Test file"));
5860
assert response.getCreatedAt().before(new Date(System.currentTimeMillis()));
@@ -86,10 +88,10 @@ public void askAIMultipleItems() throws InterruptedException {
8688
// and 412 is returned
8789
retry(() -> {
8890
BoxAIResponse response = BoxAI.sendAIRequest(
89-
api,
90-
"What is the content of these files?",
91-
items,
92-
BoxAI.Mode.MULTIPLE_ITEM_QA
91+
api,
92+
"What is the content of these files?",
93+
items,
94+
BoxAI.Mode.MULTIPLE_ITEM_QA
9395
);
9496
assertThat(response.getAnswer(), containsString("Test file"));
9597
assert response.getCreatedAt().before(new Date(System.currentTimeMillis()));
@@ -111,7 +113,7 @@ public void askAITextGenItemWithDialogueHistory() throws ParseException, Interru
111113
Date date1 = BoxDateFormat.parse("2013-05-16T15:27:57-07:00");
112114
Date date2 = BoxDateFormat.parse("2013-05-16T15:26:57-07:00");
113115

114-
BoxFile uploadedFile = uploadFileToUniqueFolder(api, fileName, "Test file");
116+
BoxFile uploadedFile = uploadFileToUniqueFolder(api, fileName, "Test file");
115117
try {
116118
// When a file has been just uploaded, AI service may not be ready to return text response
117119
// and 412 is returned
@@ -121,16 +123,16 @@ public void askAITextGenItemWithDialogueHistory() throws ParseException, Interru
121123

122124
List<BoxAIDialogueEntry> dialogueHistory = new ArrayList<>();
123125
dialogueHistory.add(
124-
new BoxAIDialogueEntry("What is the name of the file?", "Test file", date1)
126+
new BoxAIDialogueEntry("What is the name of the file?", "Test file", date1)
125127
);
126128
dialogueHistory.add(
127-
new BoxAIDialogueEntry("What is the size of the file?", "10kb", date2)
129+
new BoxAIDialogueEntry("What is the size of the file?", "10kb", date2)
128130
);
129131
BoxAIResponse response = BoxAI.sendAITextGenRequest(
130-
api,
131-
"What is the name of the file?",
132-
Collections.singletonList(new BoxAIItem(uploadedFileInfo.getID(), BoxAIItem.Type.FILE)),
133-
dialogueHistory
132+
api,
133+
"What is the name of the file?",
134+
Collections.singletonList(new BoxAIItem(uploadedFileInfo.getID(), BoxAIItem.Type.FILE)),
135+
dialogueHistory
134136
);
135137
assertThat(response.getAnswer(), containsString("name"));
136138
assert response.getCreatedAt().before(new Date(System.currentTimeMillis()));
@@ -192,4 +194,137 @@ public void askAISingleItemWithAgent() throws InterruptedException {
192194
deleteFile(uploadedFile);
193195
}
194196
}
197+
198+
@Test
199+
public void aiExtract() throws InterruptedException {
200+
BoxAPIConnection api = jwtApiForServiceAccount();
201+
BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.EXTRACT, "en-US", null);
202+
BoxAIAgentExtract agentExtract = (BoxAIAgentExtract) agent;
203+
204+
BoxFile uploadedFile = uploadFileToUniqueFolder(api, "[aiExtract] Test File.txt",
205+
"My name is John Doe. I live in San Francisco. I was born in 1990. I work at Box.");
206+
207+
try {
208+
// When a file has been just uploaded, AI service may not be ready to return text response
209+
// and 412 is returned
210+
retry(() -> {
211+
BoxAIResponse response = BoxAI.extractMetadataFreeform(api,
212+
"firstName, lastName, location, yearOfBirth, company",
213+
Collections.singletonList(new BoxAIItem(uploadedFile.getID(), BoxAIItem.Type.FILE)),
214+
agentExtract);
215+
assertThat(response.getAnswer(), containsString("John"));
216+
assertThat(response.getCompletionReason(), equalTo("done"));
217+
}, 2, 2000);
218+
} finally {
219+
deleteFile(uploadedFile);
220+
}
221+
}
222+
223+
@Test
224+
public void aiExtractStructuredWithFields() throws InterruptedException {
225+
BoxAPIConnection api = jwtApiForServiceAccount();
226+
BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.EXTRACT_STRUCTURED, "en-US", null);
227+
BoxAIAgentExtractStructured agentExtractStructured = (BoxAIAgentExtractStructured) agent;
228+
229+
BoxFile uploadedFile = uploadFileToUniqueFolder(api, "[aiExtractStructuredWithFields] Test File.txt",
230+
"My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar and books.");
231+
232+
try {
233+
// When a file has been just uploaded, AI service may not be ready to return text response
234+
// and 412 is returned
235+
retry(() -> {
236+
BoxAIExtractStructuredResponse response = BoxAI.extractMetadataStructured(api,
237+
Collections.singletonList(new BoxAIItem(uploadedFile.getID(), BoxAIItem.Type.FILE)),
238+
new ArrayList<BoxAIExtractField>() {{
239+
add(new BoxAIExtractField("firstName"));
240+
add(new BoxAIExtractField("lastName"));
241+
add(new BoxAIExtractField("date",
242+
"Person date of birth",
243+
"Birth date",
244+
"dateOfBirth",
245+
null,
246+
"What is the date of your birth?"));
247+
add(new BoxAIExtractField("float",
248+
"Person age",
249+
"Age",
250+
"age",
251+
null,
252+
"How old are you?"));
253+
add(new BoxAIExtractField("multiSelect",
254+
"Person hobby",
255+
"Hobby",
256+
"hobby",
257+
new ArrayList<BoxAIExtractFieldOption>() {{
258+
add(new BoxAIExtractFieldOption("guitar"));
259+
add(new BoxAIExtractFieldOption("books"));
260+
}},
261+
"What is your hobby?"));
262+
}},
263+
agentExtractStructured);
264+
JsonObject sourceJson = response.getSourceJson();
265+
assertThat(sourceJson.get("firstName").asString(), is(equalTo("John")));
266+
assertThat(sourceJson.get("lastName").asString(), is(equalTo("Doe")));
267+
assertThat(sourceJson.get("dateOfBirth").asString(), is(equalTo("1990-07-04")));
268+
assertThat(sourceJson.get("age").asInt(), is(equalTo(34)));
269+
assertThat(sourceJson.get("hobby").asArray().get(0).asString(), is(equalTo("guitar")));
270+
assertThat(sourceJson.get("hobby").asArray().get(1).asString(), is(equalTo("books")));
271+
}, 2, 2000);
272+
} finally {
273+
deleteFile(uploadedFile);
274+
}
275+
}
276+
277+
@Test
278+
public void aiExtractStructuredWithMetadataTemplate() throws InterruptedException {
279+
BoxAPIConnection api = jwtApiForServiceAccount();
280+
BoxAIAgent agent = BoxAI.getAiAgentDefaultConfig(api, BoxAIAgent.Mode.EXTRACT_STRUCTURED, "en-US", null);
281+
BoxAIAgentExtractStructured agentExtractStructured = (BoxAIAgentExtractStructured) agent;
282+
283+
BoxFile uploadedFile = uploadFileToUniqueFolder(api, "[aiExtractStructuredWithMetadataTemplate] Test File.txt",
284+
"My name is John Doe. I was born in 4th July 1990. I am 34 years old. My hobby is guitar and books.");
285+
String templateKey = "key" + java.util.UUID.randomUUID().toString();
286+
MetadataTemplate template = MetadataTemplate.createMetadataTemplate(api,
287+
"enterprise",
288+
templateKey,
289+
templateKey,
290+
false,
291+
new ArrayList<MetadataTemplate.Field>() {{
292+
add(new MetadataTemplate.Field(Json.parse(
293+
"{\"key\":\"firstName\",\"displayName\":\"First name\","
294+
+ "\"description\":\"Person first name\",\"type\":\"string\"}").asObject()));
295+
add(new MetadataTemplate.Field(Json.parse(
296+
"{\"key\":\"lastName\",\"displayName\":\"Last name\","
297+
+ "\"description\":\"Person last name\",\"type\":\"string\"}").asObject()));
298+
add(new MetadataTemplate.Field(Json.parse(
299+
"{\"key\":\"dateOfBirth\",\"displayName\":\"Birth date\","
300+
+ "\"description\":\"Person date of birth\",\"type\":\"date\"}").asObject()));
301+
add(new MetadataTemplate.Field(Json.parse(
302+
"{\"key\":\"age\",\"displayName\":\"Age\","
303+
+ "\"description\":\"Person age\",\"type\":\"float\"}").asObject()));
304+
add(new MetadataTemplate.Field(Json.parse(
305+
"{\"key\":\"hobby\",\"displayName\":\"Hobby\","
306+
+ "\"description\":\"Person hobby\",\"type\":\"multiSelect\"}").asObject()));
307+
}});
308+
309+
try {
310+
// When a file has been just uploaded, AI service may not be ready to return text response
311+
// and 412 is returned
312+
retry(() -> {
313+
BoxAIExtractStructuredResponse response = BoxAI.extractMetadataStructured(api,
314+
Collections.singletonList(new BoxAIItem(uploadedFile.getID(), BoxAIItem.Type.FILE)),
315+
new BoxAIExtractMetadataTemplate(templateKey, "enterprise"),
316+
agentExtractStructured);
317+
JsonObject sourceJson = response.getSourceJson();
318+
assertThat(sourceJson.get("firstName").asString(), is(equalTo("John")));
319+
assertThat(sourceJson.get("lastName").asString(), is(equalTo("Doe")));
320+
assertThat(sourceJson.get("dateOfBirth").asString(), is(equalTo("1990-07-04")));
321+
assertThat(sourceJson.get("age").asInt(), is(equalTo(34)));
322+
assertThat(sourceJson.get("hobby").asArray().get(0).asString(), is(equalTo("guitar")));
323+
assertThat(sourceJson.get("hobby").asArray().get(1).asString(), is(equalTo("books")));
324+
}, 2, 2000);
325+
} finally {
326+
deleteFile(uploadedFile);
327+
MetadataTemplate.deleteMetadataTemplate(api, template.getScope(), template.getTemplateKey());
328+
}
329+
}
195330
}

0 commit comments

Comments
 (0)