diff --git a/litellm/llms/bedrock/files/transformation.py b/litellm/llms/bedrock/files/transformation.py index 0a95cf9168f..fdcbe1a8242 100644 --- a/litellm/llms/bedrock/files/transformation.py +++ b/litellm/llms/bedrock/files/transformation.py @@ -1,12 +1,14 @@ import json import os import time -from litellm._uuid import uuid from typing import Any, Dict, List, Optional, Tuple, Union +import httpx from httpx import Headers, Response +from openai.types.file_deleted import FileDeleted from litellm._logging import verbose_logger +from litellm._uuid import uuid from litellm.files.utils import FilesAPIUtils from litellm.litellm_core_utils.prompt_templates.common_utils import extract_file_data from litellm.llms.base_llm.chat.transformation import BaseLLMException @@ -18,6 +20,7 @@ from litellm.types.llms.openai import ( AllMessageValues, CreateFileRequest, FileTypes, + HttpxBinaryResponseContent, OpenAICreateFileRequestOptionalParams, OpenAIFileObject, PathLike, @@ -539,6 +542,70 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): status_code=status_code, message=error_message, headers=headers ) + def transform_retrieve_file_request( + self, + file_id: str, + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("BedrockFilesConfig does not support file retrieval") + + def transform_retrieve_file_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> OpenAIFileObject: + raise NotImplementedError("BedrockFilesConfig does not support file retrieval") + + def transform_delete_file_request( + self, + file_id: str, + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("BedrockFilesConfig does not support file deletion") + + def transform_delete_file_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> FileDeleted: + raise NotImplementedError("BedrockFilesConfig does not support file deletion") + + def transform_list_files_request( + self, + purpose: Optional[str], + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("BedrockFilesConfig does not support file listing") + + def transform_list_files_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> List[OpenAIFileObject]: + raise NotImplementedError("BedrockFilesConfig does not support file listing") + + def transform_file_content_request( + self, + file_content_request, + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("BedrockFilesConfig does not support file content retrieval") + + def transform_file_content_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> HttpxBinaryResponseContent: + raise NotImplementedError("BedrockFilesConfig does not support file content retrieval") + class BedrockJsonlFilesTransformation: """ diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 9768e485f10..7a19efa81af 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -2879,41 +2879,7 @@ class BaseLLMHTTPHandler: else: sync_httpx_client = client - if isinstance(transformed_request, dict) and "method" in transformed_request: - # Handle pre-signed requests (e.g., from Bedrock S3 uploads) - upload_response = getattr( - sync_httpx_client, transformed_request["method"].lower() - )( - url=transformed_request["url"], - headers=transformed_request["headers"], - data=transformed_request["data"], - timeout=timeout, - ) - elif isinstance(transformed_request, str) or isinstance( - transformed_request, bytes - ): - # Handle traditional file uploads - # Ensure transformed_request is a string for httpx compatibility - if isinstance(transformed_request, bytes): - transformed_request = transformed_request.decode("utf-8") - - # Use the HTTP method specified by the provider config - http_method = provider_config.file_upload_http_method.upper() - if http_method == "PUT": - upload_response = sync_httpx_client.put( - url=api_base, - headers=headers, - data=transformed_request, - timeout=timeout, - ) - else: # Default to POST - upload_response = sync_httpx_client.post( - url=api_base, - headers=headers, - data=transformed_request, - timeout=timeout, - ) - else: + if isinstance(transformed_request, dict) and "initial_request" in transformed_request: # Handle two-step uploads (TwoStepFileUploadConfig) # Used by providers like Manus, Google Cloud Storage try: @@ -2955,6 +2921,43 @@ class BaseLLMHTTPHandler: e=e, provider_config=provider_config, ) + elif isinstance(transformed_request, dict) and "method" in transformed_request and "initial_request" not in transformed_request: + # Handle pre-signed requests (e.g., from Bedrock S3 uploads) + # Type narrowing: this is a plain dict, not TwoStepFileUploadConfig + upload_response = getattr( + sync_httpx_client, transformed_request["method"].lower() + )( + url=transformed_request["url"], + headers=transformed_request["headers"], + data=transformed_request["data"], + timeout=timeout, + ) + elif isinstance(transformed_request, str) or isinstance( + transformed_request, bytes + ): + # Handle traditional file uploads + # Ensure transformed_request is a string for httpx compatibility + if isinstance(transformed_request, bytes): + transformed_request = transformed_request.decode("utf-8") + + # Use the HTTP method specified by the provider config + http_method = provider_config.file_upload_http_method.upper() + if http_method == "PUT": + upload_response = sync_httpx_client.put( + url=api_base, + headers=headers, + data=transformed_request, + timeout=timeout, + ) + else: # Default to POST + upload_response = sync_httpx_client.post( + url=api_base, + headers=headers, + data=transformed_request, + timeout=timeout, + ) + else: + raise ValueError(f"Unsupported transformed_request type: {type(transformed_request)}") # Store the upload URL in litellm_params for the transformation method litellm_params_with_url = dict(litellm_params) @@ -3001,41 +3004,7 @@ class BaseLLMHTTPHandler: }, ) - if isinstance(transformed_request, dict) and "method" in transformed_request: - # Handle pre-signed requests (e.g., from Bedrock S3 uploads) - upload_response = await getattr( - async_httpx_client, transformed_request["method"].lower() - )( - url=transformed_request["url"], - headers=transformed_request["headers"], - data=transformed_request["data"], - timeout=timeout, - ) - elif isinstance(transformed_request, str) or isinstance( - transformed_request, bytes - ): - # Handle traditional file uploads - # Ensure transformed_request is a string for httpx compatibility - if isinstance(transformed_request, bytes): - transformed_request = transformed_request.decode("utf-8") - - # Use the HTTP method specified by the provider config - http_method = provider_config.file_upload_http_method.upper() - if http_method == "PUT": - upload_response = await async_httpx_client.put( - url=api_base, - headers=headers, - data=transformed_request, - timeout=timeout, - ) - else: # Default to POST - upload_response = await async_httpx_client.post( - url=api_base, - headers=headers, - data=transformed_request, - timeout=timeout, - ) - else: + if isinstance(transformed_request, dict) and "initial_request" in transformed_request: # Handle two-step uploads (TwoStepFileUploadConfig) # Used by providers like Manus, Google Cloud Storage try: @@ -3078,6 +3047,43 @@ class BaseLLMHTTPHandler: e=e, provider_config=provider_config, ) + elif isinstance(transformed_request, dict) and "method" in transformed_request and "initial_request" not in transformed_request: + # Handle pre-signed requests (e.g., from Bedrock S3 uploads) + # Type narrowing: this is a plain dict, not TwoStepFileUploadConfig + upload_response = await getattr( + async_httpx_client, transformed_request["method"].lower() + )( + url=transformed_request["url"], + headers=transformed_request["headers"], + data=transformed_request["data"], + timeout=timeout, + ) + elif isinstance(transformed_request, str) or isinstance( + transformed_request, bytes + ): + # Handle traditional file uploads + # Ensure transformed_request is a string for httpx compatibility + if isinstance(transformed_request, bytes): + transformed_request = transformed_request.decode("utf-8") + + # Use the HTTP method specified by the provider config + http_method = provider_config.file_upload_http_method.upper() + if http_method == "PUT": + upload_response = await async_httpx_client.put( + url=api_base, + headers=headers, + data=transformed_request, + timeout=timeout, + ) + else: # Default to POST + upload_response = await async_httpx_client.post( + url=api_base, + headers=headers, + data=transformed_request, + timeout=timeout, + ) + else: + raise ValueError(f"Unsupported transformed_request type: {type(transformed_request)}") return provider_config.transform_create_file_response( model=None, diff --git a/litellm/llms/gemini/files/transformation.py b/litellm/llms/gemini/files/transformation.py index e98e76dabc8..d9ebf69a97a 100644 --- a/litellm/llms/gemini/files/transformation.py +++ b/litellm/llms/gemini/files/transformation.py @@ -7,6 +7,7 @@ import time from typing import List, Optional import httpx +from openai.types.file_deleted import FileDeleted from litellm._logging import verbose_logger from litellm.litellm_core_utils.prompt_templates.common_utils import extract_file_data @@ -17,6 +18,7 @@ from litellm.llms.base_llm.files.transformation import ( from litellm.types.llms.gemini import GeminiCreateFilesResponseObject from litellm.types.llms.openai import ( CreateFileRequest, + HttpxBinaryResponseContent, OpenAICreateFileRequestOptionalParams, OpenAIFileObject, ) @@ -171,3 +173,67 @@ class GoogleAIStudioFilesHandler(GeminiModelInfo, BaseFilesConfig): except Exception as e: verbose_logger.exception(f"Error parsing file upload response: {str(e)}") raise ValueError(f"Error parsing file upload response: {str(e)}") + + def transform_retrieve_file_request( + self, + file_id: str, + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("GoogleAIStudioFilesHandler does not support file retrieval") + + def transform_retrieve_file_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> OpenAIFileObject: + raise NotImplementedError("GoogleAIStudioFilesHandler does not support file retrieval") + + def transform_delete_file_request( + self, + file_id: str, + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("GoogleAIStudioFilesHandler does not support file deletion") + + def transform_delete_file_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> FileDeleted: + raise NotImplementedError("GoogleAIStudioFilesHandler does not support file deletion") + + def transform_list_files_request( + self, + purpose: Optional[str], + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("GoogleAIStudioFilesHandler does not support file listing") + + def transform_list_files_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> List[OpenAIFileObject]: + raise NotImplementedError("GoogleAIStudioFilesHandler does not support file listing") + + def transform_file_content_request( + self, + file_content_request, + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("GoogleAIStudioFilesHandler does not support file content retrieval") + + def transform_file_content_response( + self, + raw_response: httpx.Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> HttpxBinaryResponseContent: + raise NotImplementedError("GoogleAIStudioFilesHandler does not support file content retrieval") diff --git a/litellm/llms/vertex_ai/files/transformation.py b/litellm/llms/vertex_ai/files/transformation.py index 01f6c86fd4d..b3612113ec2 100644 --- a/litellm/llms/vertex_ai/files/transformation.py +++ b/litellm/llms/vertex_ai/files/transformation.py @@ -1,11 +1,12 @@ import json import os import time -from litellm._uuid import uuid from typing import Any, Dict, List, Optional, Tuple, Union from httpx import Headers, Response +from openai.types.file_deleted import FileDeleted +from litellm._uuid import uuid from litellm.files.utils import FilesAPIUtils from litellm.litellm_core_utils.prompt_templates.common_utils import extract_file_data from litellm.llms.base_llm.chat.transformation import BaseLLMException @@ -24,6 +25,7 @@ from litellm.types.llms.openai import ( AllMessageValues, CreateFileRequest, FileTypes, + HttpxBinaryResponseContent, OpenAICreateFileRequestOptionalParams, OpenAIFileObject, PathLike, @@ -333,6 +335,70 @@ class VertexAIFilesConfig(VertexBase, BaseFilesConfig): status_code=status_code, message=error_message, headers=headers ) + def transform_retrieve_file_request( + self, + file_id: str, + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("VertexAIFilesConfig does not support file retrieval") + + def transform_retrieve_file_response( + self, + raw_response: Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> OpenAIFileObject: + raise NotImplementedError("VertexAIFilesConfig does not support file retrieval") + + def transform_delete_file_request( + self, + file_id: str, + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("VertexAIFilesConfig does not support file deletion") + + def transform_delete_file_response( + self, + raw_response: Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> FileDeleted: + raise NotImplementedError("VertexAIFilesConfig does not support file deletion") + + def transform_list_files_request( + self, + purpose: Optional[str], + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("VertexAIFilesConfig does not support file listing") + + def transform_list_files_response( + self, + raw_response: Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> List[OpenAIFileObject]: + raise NotImplementedError("VertexAIFilesConfig does not support file listing") + + def transform_file_content_request( + self, + file_content_request, + optional_params: dict, + litellm_params: dict, + ) -> tuple[str, dict]: + raise NotImplementedError("VertexAIFilesConfig does not support file content retrieval") + + def transform_file_content_response( + self, + raw_response: Response, + logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> HttpxBinaryResponseContent: + raise NotImplementedError("VertexAIFilesConfig does not support file content retrieval") + class VertexAIJsonlFilesTransformation(VertexGeminiConfig): """