From 6f11163038a537a76dc07934d3c9edf75160d7bb Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Mon, 23 Mar 2026 10:34:39 -0700 Subject: [PATCH] fix: use anyio_to_thread for async file uploads, fix outer hasattr guards in llm_http_handler --- litellm/llms/custom_httpx/llm_http_handler.py | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 004db8ae4e1..46636a71e60 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -16,6 +16,7 @@ from typing import ( ) import httpx # type: ignore +from anyio import to_thread as anyio_to_thread from openai.types.file_deleted import FileDeleted import litellm @@ -3018,8 +3019,9 @@ class BaseLLMHTTPHandler: data=presigned_request["data"], timeout=timeout, ) - elif isinstance(transformed_request, (str, bytes)) or isinstance( - transformed_request, io.IOBase + elif isinstance(transformed_request, (str, bytes)) or ( + hasattr(transformed_request, "read") + and hasattr(transformed_request, "seek") ): # Handle traditional file uploads (str, bytes, or IO[bytes] for streaming) http_method = provider_config.file_upload_http_method.upper() @@ -3189,19 +3191,34 @@ class BaseLLMHTTPHandler: data=presigned_request["data"], timeout=timeout, ) - elif isinstance(transformed_request, (str, bytes)) or isinstance( - transformed_request, io.IOBase + elif isinstance(transformed_request, (str, bytes)) or ( + hasattr(transformed_request, "read") + and hasattr(transformed_request, "seek") ): # Handle traditional file uploads (str, bytes, or IO[bytes] for streaming) http_method = provider_config.file_upload_http_method.upper() if hasattr(transformed_request, "read") and hasattr( transformed_request, "seek" ): - # Stream the IO object without loading it fully into memory + # Wrap sync IO in an async generator so disk reads are dispatched + # to a thread executor and do not block the event loop. + file_obj = transformed_request + + async def _async_file_chunks( + fp: Any, chunk_size: int = 65536 + ) -> "AsyncIterator[bytes]": + while True: + chunk = await anyio_to_thread.run_sync( + lambda: fp.read(chunk_size) + ) + if not chunk: + break + yield chunk + async_upload_kwargs: Dict[str, Any] = { "url": api_base, "headers": headers, - "content": transformed_request, + "content": _async_file_chunks(file_obj), "timeout": timeout, } else: