Skip to content

Commit 7959822

Browse files
Merge pull request #55 from gleanwork/cfreeman/agent-file-upload-dx
[DX] Improve agent file upload error messages with helpful guidance
2 parents 417db63 + f1f21a4 commit 7959822

File tree

3 files changed

+198
-10
lines changed

3 files changed

+198
-10
lines changed

examples/AgentWithFileUpload.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package examples;
2+
3+
import com.glean.api_client.glean_api_client.Glean;
4+
import com.glean.api_client.glean_api_client.models.components.AgentRunCreate;
5+
import com.glean.api_client.glean_api_client.models.components.ChatFile;
6+
import com.glean.api_client.glean_api_client.models.components.UploadChatFilesRequest;
7+
import com.glean.api_client.glean_api_client.models.components.UploadChatFilesResponse;
8+
import com.glean.api_client.glean_api_client.models.operations.CreateAndWaitRunResponse;
9+
import com.glean.api_client.glean_api_client.models.operations.UploadchatfilesResponse;
10+
11+
import java.io.File;
12+
import java.util.HashMap;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.Optional;
16+
17+
/**
18+
* Example demonstrating the correct workflow for uploading files and using them with agent runs.
19+
*
20+
* This example shows the two-step process required:
21+
* 1. Upload the file using chat.uploadFiles() to get a file ID
22+
* 2. Pass the file ID (as a string) to agents.run() in the input map
23+
*/
24+
public class AgentWithFileUpload {
25+
26+
public static void main(String[] args) throws Exception {
27+
// Initialize the SDK client
28+
Glean sdk = Glean.builder()
29+
.apiToken(System.getenv().getOrDefault("GLEAN_API_TOKEN", ""))
30+
.build();
31+
32+
// Step 1: Upload a file to get a file ID
33+
File sampleFile = new File("sample_data.csv");
34+
35+
UploadchatfilesResponse uploadResponse = sdk.client().chat().uploadFiles(
36+
UploadChatFilesRequest.builder()
37+
.files(List.of(sampleFile))
38+
.build());
39+
40+
// Extract the file ID from the response
41+
String fileId = uploadResponse.uploadChatFilesResponse()
42+
.flatMap(UploadChatFilesResponse::files)
43+
.map(files -> {
44+
if (files.isEmpty()) {
45+
throw new RuntimeException("No files returned from upload");
46+
}
47+
return files.get(0);
48+
})
49+
.flatMap(ChatFile::id)
50+
.orElseThrow(() -> new RuntimeException("File ID not found in upload response"));
51+
52+
System.out.println("File uploaded successfully. File ID: " + fileId);
53+
54+
// Step 2: Create an agent run with the file ID in the input map
55+
// Note: The file ID must be passed as a string, not as a file object
56+
Map<String, Object> input = new HashMap<>();
57+
input.put("fileId", fileId);
58+
// Add any other input parameters your agent requires
59+
input.put("query", "Analyze this file");
60+
61+
AgentRunCreate runRequest = AgentRunCreate.builder()
62+
.agentId("your-agent-id")
63+
.input(Optional.of(input))
64+
.build();
65+
66+
CreateAndWaitRunResponse response = sdk.client().agents().run(runRequest);
67+
68+
// Handle the response
69+
if (response.agentRunWaitResponse().isPresent()) {
70+
System.out.println("Agent run completed successfully");
71+
// Process the response as needed
72+
}
73+
}
74+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package com.glean.api_client.glean_api_client.hooks;
2+
3+
import com.glean.api_client.glean_api_client.models.errors.APIException;
4+
import com.glean.api_client.glean_api_client.models.errors.AsyncAPIException;
5+
import com.glean.api_client.glean_api_client.utils.AsyncHook;
6+
import com.glean.api_client.glean_api_client.utils.Hook;
7+
import com.glean.api_client.glean_api_client.utils.Utils;
8+
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.net.http.HttpResponse;
12+
import java.nio.charset.StandardCharsets;
13+
import java.util.Optional;
14+
import java.util.concurrent.CompletableFuture;
15+
16+
/**
17+
* Hook that provides helpful error messages when developers incorrectly pass file objects
18+
* directly to agent run operations instead of using the two-step upload process.
19+
*/
20+
public final class AgentFileUploadErrorHook {
21+
22+
private AgentFileUploadErrorHook() {
23+
// prevent instantiation
24+
}
25+
26+
/**
27+
* Creates a synchronous AfterError hook for agent file upload errors.
28+
*/
29+
public static Hook.AfterError createSyncHook() {
30+
return (context, response, error) -> {
31+
if (response.isPresent()) {
32+
HttpResponse<InputStream> httpResponse = response.get();
33+
String operationId = context.operationId();
34+
35+
if (("createAndWaitRun".equals(operationId) || "createAndStreamRun".equals(operationId))
36+
&& httpResponse.statusCode() == 400) {
37+
try {
38+
byte[] bodyBytes = Utils.extractByteArrayFromBody(httpResponse);
39+
String bodyText = new String(bodyBytes, StandardCharsets.UTF_8).toLowerCase();
40+
41+
if (bodyText.contains("permission")) {
42+
String helpfulMessage = buildFileUploadErrorMessage();
43+
throw APIException.from(helpfulMessage, httpResponse);
44+
}
45+
} catch (IOException e) {
46+
// If we can't read the body, fall through to original error handling
47+
}
48+
}
49+
}
50+
51+
if (error.isPresent()) {
52+
throw error.get();
53+
}
54+
return response.get();
55+
};
56+
}
57+
58+
/**
59+
* Creates an asynchronous AfterError hook for agent file upload errors.
60+
*/
61+
public static AsyncHook.AfterError createAsyncHook() {
62+
return (context, response, error) -> {
63+
if (response != null) {
64+
String operationId = context.operationId();
65+
66+
if (("createAndWaitRun".equals(operationId) || "createAndStreamRun".equals(operationId))
67+
&& response.statusCode() == 400) {
68+
return response.body().toByteArray()
69+
.thenApply(bodyBytes -> {
70+
String bodyText = new String(bodyBytes, StandardCharsets.UTF_8).toLowerCase();
71+
72+
if (bodyText.contains("permission")) {
73+
String helpfulMessage = buildFileUploadErrorMessage();
74+
throw new AsyncAPIException(
75+
helpfulMessage,
76+
response.statusCode(),
77+
bodyBytes,
78+
response,
79+
null);
80+
}
81+
82+
return response;
83+
})
84+
.exceptionally(ex -> {
85+
if (ex instanceof AsyncAPIException) {
86+
throw (AsyncAPIException) ex;
87+
}
88+
return response;
89+
});
90+
}
91+
}
92+
93+
if (error != null) {
94+
return CompletableFuture.failedFuture(error);
95+
}
96+
return CompletableFuture.completedFuture(response);
97+
};
98+
}
99+
100+
private static String buildFileUploadErrorMessage() {
101+
return "File upload error: Agent runs require a two-step process for file inputs.\n\n" +
102+
"Step 1: Upload the file using chat.uploadFiles() to get a file ID:\n" +
103+
" UploadchatfilesResponse uploadResponse = sdk.client().chat().uploadFiles(\n" +
104+
" UploadChatFilesRequest.builder()\n" +
105+
" .files(List.of(new File(\"path/to/file.csv\")))\n" +
106+
" .build());\n" +
107+
" String fileId = uploadResponse.uploadChatFilesResponse()\n" +
108+
" .flatMap(UploadChatFilesResponse::files)\n" +
109+
" .map(files -> files.get(0))\n" +
110+
" .flatMap(ChatFile::id)\n" +
111+
" .orElseThrow();\n\n" +
112+
"Step 2: Pass the file ID (as a string) to agents.run() in the input map:\n" +
113+
" AgentRunCreate runRequest = AgentRunCreate.builder()\n" +
114+
" .agentId(\"your-agent-id\")\n" +
115+
" .input(Map.of(\"fileId\", fileId))\n" +
116+
" .build();\n" +
117+
" CreateAndWaitRunResponse response = sdk.client().agents().run(runRequest);\n\n" +
118+
"See examples/AgentWithFileUpload.java for a complete working example.";
119+
}
120+
}

src/main/java/com/glean/api_client/glean_api_client/hooks/SDKHooks.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,18 @@ private SDKHooks() {
1313
}
1414

1515
public static void initialize(com.glean.api_client.glean_api_client.utils.Hooks hooks) {
16-
// register synchronous hooks here
17-
// hooks.registerBeforeRequest(...);
18-
// hooks.registerAfterSuccess(...);
19-
// hooks.registerAfterError(...);
16+
hooks.registerAfterError(AgentFileUploadErrorHook.createSyncHook());
2017

2118
// for more information see
2219
// https://www.speakeasy.com/docs/additional-features/sdk-hooks
2320
}
2421

2522
public static void initialize(com.glean.api_client.glean_api_client.utils.AsyncHooks asyncHooks) {
26-
// register async hooks here
27-
// asyncHooks.registerBeforeRequest(...);
28-
// asyncHooks.registerAfterSuccess(...);
29-
// asyncHooks.registerAfterError(...);
30-
23+
asyncHooks.registerAfterError(AgentFileUploadErrorHook.createAsyncHook());
24+
3125
// NOTE: If you have existing synchronous hooks, you can adapt them using HookAdapters:
3226
// asyncHooks.registerAfterError(com.glean.api_client.glean_api_client.utils.HookAdapters.adapt(mySyncHook));
33-
27+
3428
// PERFORMANCE TIP: For better performance, implement async hooks directly using
3529
// non-blocking I/O (NIO) APIs instead of adapting synchronous hooks, as adapters
3630
// offload execution to the ForkJoinPool which can introduce overhead.

0 commit comments

Comments
 (0)