diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/litellm/llms/vertex_ai/files')
-rw-r--r-- | .venv/lib/python3.12/site-packages/litellm/llms/vertex_ai/files/handler.py | 97 | ||||
-rw-r--r-- | .venv/lib/python3.12/site-packages/litellm/llms/vertex_ai/files/transformation.py | 163 |
2 files changed, 260 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/litellm/llms/vertex_ai/files/handler.py b/.venv/lib/python3.12/site-packages/litellm/llms/vertex_ai/files/handler.py new file mode 100644 index 00000000..266169cd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/litellm/llms/vertex_ai/files/handler.py @@ -0,0 +1,97 @@ +from typing import Any, Coroutine, Optional, Union + +import httpx + +from litellm import LlmProviders +from litellm.integrations.gcs_bucket.gcs_bucket_base import ( + GCSBucketBase, + GCSLoggingConfig, +) +from litellm.llms.custom_httpx.http_handler import get_async_httpx_client +from litellm.types.llms.openai import CreateFileRequest, FileObject +from litellm.types.llms.vertex_ai import VERTEX_CREDENTIALS_TYPES + +from .transformation import VertexAIFilesTransformation + +vertex_ai_files_transformation = VertexAIFilesTransformation() + + +class VertexAIFilesHandler(GCSBucketBase): + """ + Handles Calling VertexAI in OpenAI Files API format v1/files/* + + This implementation uploads files on GCS Buckets + """ + + def __init__(self): + super().__init__() + self.async_httpx_client = get_async_httpx_client( + llm_provider=LlmProviders.VERTEX_AI, + ) + + pass + + async def async_create_file( + self, + create_file_data: CreateFileRequest, + api_base: Optional[str], + vertex_credentials: Optional[VERTEX_CREDENTIALS_TYPES], + vertex_project: Optional[str], + vertex_location: Optional[str], + timeout: Union[float, httpx.Timeout], + max_retries: Optional[int], + ): + gcs_logging_config: GCSLoggingConfig = await self.get_gcs_logging_config( + kwargs={} + ) + headers = await self.construct_request_headers( + vertex_instance=gcs_logging_config["vertex_instance"], + service_account_json=gcs_logging_config["path_service_account"], + ) + bucket_name = gcs_logging_config["bucket_name"] + logging_payload, object_name = ( + vertex_ai_files_transformation.transform_openai_file_content_to_vertex_ai_file_content( + openai_file_content=create_file_data.get("file") + ) + ) + gcs_upload_response = await self._log_json_data_on_gcs( + headers=headers, + bucket_name=bucket_name, + object_name=object_name, + logging_payload=logging_payload, + ) + + return vertex_ai_files_transformation.transform_gcs_bucket_response_to_openai_file_object( + create_file_data=create_file_data, + gcs_upload_response=gcs_upload_response, + ) + + def create_file( + self, + _is_async: bool, + create_file_data: CreateFileRequest, + api_base: Optional[str], + vertex_credentials: Optional[VERTEX_CREDENTIALS_TYPES], + vertex_project: Optional[str], + vertex_location: Optional[str], + timeout: Union[float, httpx.Timeout], + max_retries: Optional[int], + ) -> Union[FileObject, Coroutine[Any, Any, FileObject]]: + """ + Creates a file on VertexAI GCS Bucket + + Only supported for Async litellm.acreate_file + """ + + if _is_async: + return self.async_create_file( + create_file_data=create_file_data, + api_base=api_base, + vertex_credentials=vertex_credentials, + vertex_project=vertex_project, + vertex_location=vertex_location, + timeout=timeout, + max_retries=max_retries, + ) + + return None # type: ignore diff --git a/.venv/lib/python3.12/site-packages/litellm/llms/vertex_ai/files/transformation.py b/.venv/lib/python3.12/site-packages/litellm/llms/vertex_ai/files/transformation.py new file mode 100644 index 00000000..a124e205 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/litellm/llms/vertex_ai/files/transformation.py @@ -0,0 +1,163 @@ +import json +import uuid +from typing import Any, Dict, List, Optional, Tuple, Union + +from litellm.llms.vertex_ai.common_utils import ( + _convert_vertex_datetime_to_openai_datetime, +) +from litellm.llms.vertex_ai.gemini.transformation import _transform_request_body +from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( + VertexGeminiConfig, +) +from litellm.types.llms.openai import CreateFileRequest, FileObject, FileTypes, PathLike + + +class VertexAIFilesTransformation(VertexGeminiConfig): + """ + Transforms OpenAI /v1/files/* requests to VertexAI /v1/files/* requests + """ + + def transform_openai_file_content_to_vertex_ai_file_content( + self, openai_file_content: Optional[FileTypes] = None + ) -> Tuple[str, str]: + """ + Transforms OpenAI FileContentRequest to VertexAI FileContentRequest + """ + + if openai_file_content is None: + raise ValueError("contents of file are None") + # Read the content of the file + file_content = self._get_content_from_openai_file(openai_file_content) + + # Split into lines and parse each line as JSON + openai_jsonl_content = [ + json.loads(line) for line in file_content.splitlines() if line.strip() + ] + vertex_jsonl_content = ( + self._transform_openai_jsonl_content_to_vertex_ai_jsonl_content( + openai_jsonl_content + ) + ) + vertex_jsonl_string = "\n".join( + json.dumps(item) for item in vertex_jsonl_content + ) + object_name = self._get_gcs_object_name( + openai_jsonl_content=openai_jsonl_content + ) + return vertex_jsonl_string, object_name + + def _transform_openai_jsonl_content_to_vertex_ai_jsonl_content( + self, openai_jsonl_content: List[Dict[str, Any]] + ): + """ + Transforms OpenAI JSONL content to VertexAI JSONL content + + jsonl body for vertex is {"request": <request_body>} + Example Vertex jsonl + {"request":{"contents": [{"role": "user", "parts": [{"text": "What is the relation between the following video and image samples?"}, {"fileData": {"fileUri": "gs://cloud-samples-data/generative-ai/video/animals.mp4", "mimeType": "video/mp4"}}, {"fileData": {"fileUri": "gs://cloud-samples-data/generative-ai/image/cricket.jpeg", "mimeType": "image/jpeg"}}]}]}} + {"request":{"contents": [{"role": "user", "parts": [{"text": "Describe what is happening in this video."}, {"fileData": {"fileUri": "gs://cloud-samples-data/generative-ai/video/another_video.mov", "mimeType": "video/mov"}}]}]}} + """ + + vertex_jsonl_content = [] + for _openai_jsonl_content in openai_jsonl_content: + openai_request_body = _openai_jsonl_content.get("body") or {} + vertex_request_body = _transform_request_body( + messages=openai_request_body.get("messages", []), + model=openai_request_body.get("model", ""), + optional_params=self._map_openai_to_vertex_params(openai_request_body), + custom_llm_provider="vertex_ai", + litellm_params={}, + cached_content=None, + ) + vertex_jsonl_content.append({"request": vertex_request_body}) + return vertex_jsonl_content + + def _get_gcs_object_name( + self, + openai_jsonl_content: List[Dict[str, Any]], + ) -> str: + """ + Gets a unique GCS object name for the VertexAI batch prediction job + + named as: litellm-vertex-{model}-{uuid} + """ + _model = openai_jsonl_content[0].get("body", {}).get("model", "") + if "publishers/google/models" not in _model: + _model = f"publishers/google/models/{_model}" + object_name = f"litellm-vertex-files/{_model}/{uuid.uuid4()}" + return object_name + + def _map_openai_to_vertex_params( + self, + openai_request_body: Dict[str, Any], + ) -> Dict[str, Any]: + """ + wrapper to call VertexGeminiConfig.map_openai_params + """ + _model = openai_request_body.get("model", "") + vertex_params = self.map_openai_params( + model=_model, + non_default_params=openai_request_body, + optional_params={}, + drop_params=False, + ) + return vertex_params + + def _get_content_from_openai_file(self, openai_file_content: FileTypes) -> str: + """ + Helper to extract content from various OpenAI file types and return as string. + + Handles: + - Direct content (str, bytes, IO[bytes]) + - Tuple formats: (filename, content, [content_type], [headers]) + - PathLike objects + """ + content: Union[str, bytes] = b"" + # Extract file content from tuple if necessary + if isinstance(openai_file_content, tuple): + # Take the second element which is always the file content + file_content = openai_file_content[1] + else: + file_content = openai_file_content + + # Handle different file content types + if isinstance(file_content, str): + # String content can be used directly + content = file_content + elif isinstance(file_content, bytes): + # Bytes content can be decoded + content = file_content + elif isinstance(file_content, PathLike): # PathLike + with open(str(file_content), "rb") as f: + content = f.read() + elif hasattr(file_content, "read"): # IO[bytes] + # File-like objects need to be read + content = file_content.read() + + # Ensure content is string + if isinstance(content, bytes): + content = content.decode("utf-8") + + return content + + def transform_gcs_bucket_response_to_openai_file_object( + self, create_file_data: CreateFileRequest, gcs_upload_response: Dict[str, Any] + ) -> FileObject: + """ + Transforms GCS Bucket upload file response to OpenAI FileObject + """ + gcs_id = gcs_upload_response.get("id", "") + # Remove the last numeric ID from the path + gcs_id = "/".join(gcs_id.split("/")[:-1]) if gcs_id else "" + + return FileObject( + purpose=create_file_data.get("purpose", "batch"), + id=f"gs://{gcs_id}", + filename=gcs_upload_response.get("name", ""), + created_at=_convert_vertex_datetime_to_openai_datetime( + vertex_datetime=gcs_upload_response.get("timeCreated", "") + ), + status="uploaded", + bytes=gcs_upload_response.get("size", 0), + object="file", + ) |