From cf2be424da31d886bc31a93aa72fc492c37ab044 Mon Sep 17 00:00:00 2001 From: Chris Freeman Date: Fri, 21 Nov 2025 12:54:50 -0700 Subject: [PATCH] [dx] Improve Agent file upload DX with error hook and example --- examples/agent_with_file_upload.py | 43 ++++++++++ .../_hooks/agent_file_upload_error_hook.py | 81 +++++++++++++++++++ src/glean/api_client/_hooks/registration.py | 4 + 3 files changed, 128 insertions(+) create mode 100644 examples/agent_with_file_upload.py create mode 100644 src/glean/api_client/_hooks/agent_file_upload_error_hook.py diff --git a/examples/agent_with_file_upload.py b/examples/agent_with_file_upload.py new file mode 100644 index 00000000..24a0cf4a --- /dev/null +++ b/examples/agent_with_file_upload.py @@ -0,0 +1,43 @@ +# To run the example: +# poetry install +# poetry run python examples/agent_with_file_upload.py + +from glean.api_client import Glean, models +import os + + +def main(): + with Glean( + api_token=os.getenv("GLEAN_API_TOKEN", ""), + domain=os.getenv("GLEAN_DOMAIN", "customerName"), + ) as glean: + + # 1. Upload the file first + file_content = b"name,role\nAlice,Engineer\nBob,Manager" + + upload_result = glean.client.chat.upload_files( + files=[ + models.File( + file_name="employees.csv", + content=file_content + ) + ] + ) + + # 2. Get the ID (string) from the response + file_id = upload_result.files[0].id + + # 3. Run the agent passing the file ID (NOT the file object) + res = glean.client.agents.run( + agent_id=os.getenv("GLEAN_AGENT_ID", ""), + input={ + # Pass the file ID string to the input parameter + "file": file_id, + "query": "Who is the manager?" + } + ) + + print(res) + +if __name__ == "__main__": + main() diff --git a/src/glean/api_client/_hooks/agent_file_upload_error_hook.py b/src/glean/api_client/_hooks/agent_file_upload_error_hook.py new file mode 100644 index 00000000..8b11a7c6 --- /dev/null +++ b/src/glean/api_client/_hooks/agent_file_upload_error_hook.py @@ -0,0 +1,81 @@ +"""Custom hook to provide helpful error messages for agent file upload issues.""" + +from typing import Optional, Tuple, Union +import httpx +from glean.api_client._hooks.types import AfterErrorContext, AfterErrorHook + + +class AgentFileUploadErrorHook(AfterErrorHook): + """ + Hook that detects when users incorrectly pass file objects to agents.run() + and provides clear guidance on the correct two-step upload workflow. + + This hook intercepts 400 errors from agent run operations that contain + "permission" in the error message, which typically indicates a file was + passed incorrectly instead of a file ID. + """ + + def after_error( + self, + hook_ctx: AfterErrorContext, + response: Optional[httpx.Response], + error: Optional[Exception], + ) -> Union[Tuple[Optional[httpx.Response], Optional[Exception]], Exception]: + """ + Intercept agent run errors and enhance them with helpful file upload guidance. + + Args: + hook_ctx: Context about the operation being performed + response: The HTTP response (if available) + error: The exception that was raised + + Returns: + Either a tuple of (response, error) to continue normal error handling, + or a new Exception to replace the original error. + """ + # Only intercept 400 errors from agent run operations + if ( + response is not None + and response.status_code == 400 + and hook_ctx.operation_id in ["createAndWaitRun", "createAndStreamRun"] + ): + error_message = str(error) if error else "" + + # Check if this looks like a file upload error + # (API returns "permission" error when file objects are passed incorrectly) + if "permission" in error_message.lower(): + # Create enhanced error message with clear instructions + enhanced_message = ( + "Agent file upload error: When using agents with file inputs, " + "you must follow a two-step process:\n" + "\n" + "1. First, upload files using client.chat.upload_files():\n" + " from glean.api_client import models\n" + " \n" + " # Upload the file\n" + " upload_result = client.chat.upload_files(\n" + " files=[\n" + " models.File(\n" + " file_name='data.csv',\n" + " content=file_content # bytes or file-like object\n" + " )\n" + " ]\n" + " )\n" + "\n" + "2. Then, pass the returned file IDs (not file objects) in the input field:\n" + " result = client.agents.run(\n" + " agent_id='',\n" + " input={\n" + " 'my_file': upload_result.files[0].id # Use the file ID string\n" + " }\n" + " )\n" + "\n" + "For a complete example, see: examples/agent_with_file_upload.py\n" + f"\nOriginal error: {error_message}" + ) + + # Return new exception with enhanced message + return Exception(enhanced_message) + + # Pass through all other errors unchanged + return response, error diff --git a/src/glean/api_client/_hooks/registration.py b/src/glean/api_client/_hooks/registration.py index 39bfe14e..01498ddf 100644 --- a/src/glean/api_client/_hooks/registration.py +++ b/src/glean/api_client/_hooks/registration.py @@ -1,5 +1,6 @@ from .types import Hooks from .multipart_fix_hook import MultipartFileFieldFixHook +from .agent_file_upload_error_hook import AgentFileUploadErrorHook # This file is only ever generated once on the first generation and then is free to be modified. @@ -15,3 +16,6 @@ def init_hooks(hooks: Hooks): # Register hook to fix multipart file field names that incorrectly have '[]' suffix hooks.register_sdk_init_hook(MultipartFileFieldFixHook()) + + # Register hook to provide helpful error messages for agent file upload issues + hooks.register_after_error_hook(AgentFileUploadErrorHook())