From 2d2b32c3f8ab5e51868d363860ea996383336236 Mon Sep 17 00:00:00 2001 From: Lavya Tandel Date: Mon, 10 Aug 2026 23:56:49 +0530 Subject: [PATCH] fix(bedrock): use actual file size when Content-Length missing (#36388) --- litellm/llms/bedrock/files/transformation.py | 11 +++- .../test_bedrock_files_transformation.py | 55 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/litellm/llms/bedrock/files/transformation.py b/litellm/llms/bedrock/files/transformation.py index bd3570d50a3..d92be2104ff 100644 --- a/litellm/llms/bedrock/files/transformation.py +++ b/litellm/llms/bedrock/files/transformation.py @@ -861,6 +861,9 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): ) litellm_params["upload_url"] = api_base + # Store file size for response transformation (S3 PUT response + # doesn't include Content-Length, so we pass it through). + litellm_params["_file_content_size"] = len(file_content) if isinstance(file_content, (str, bytes)) else 0 # Return a dict that tells the HTTP handler exactly what to do return { @@ -1024,6 +1027,12 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): # S3 PUT object returns ETag and other metadata in headers content_length: Final = response_headers.get("Content-Length", "0") + # S3 PUT responses may not include Content-Length, so fall back + # to the actual content size captured during request transformation. + file_size: int = int(content_length) if content_length.isdigit() else 0 + if file_size == 0: + file_size = litellm_params.get("_file_content_size", 0) + # Use the actual upload URL that was used for the S3 upload upload_url: Final = litellm_params.get("upload_url") file_id: str = "" @@ -1038,7 +1047,7 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): filename=filename, created_at=int(time.time()), # Current timestamp status="uploaded", - bytes=int(content_length) if content_length.isdigit() else 0, + bytes=file_size, object="file", ) diff --git a/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py index 270add48e0e..c9debd41b51 100644 --- a/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py +++ b/tests/test_litellm/llms/bedrock/files/test_bedrock_files_transformation.py @@ -2038,6 +2038,61 @@ class TestBedrockFilesS3SignatureEncoding: headers=signed["headers"], ) + def test_transform_create_file_response_bytes_from_content_length(self): + """bytes should reflect actual file size when Content-Length is present.""" + import httpx + + from litellm.llms.bedrock.files.transformation import BedrockFilesConfig + + raw_response = httpx.Response( + status_code=200, + headers={"Content-Length": "4096"}, + content=b"", + request=httpx.Request( + "PUT", + "https://s3.us-east-1.amazonaws.com/my-bucket/litellm-bedrock-files-anthropic.claude-3-5-sonnet-20240620-v1:0-abc123.jsonl", + ), + ) + + result = BedrockFilesConfig().transform_create_file_response( + model=None, + raw_response=raw_response, + logging_obj=MagicMock(), + litellm_params={}, + ) + + assert result.bytes == 4096 + + def test_transform_create_file_response_bytes_falls_back_to_content_size(self): + """When S3 PUT response omits Content-Length, bytes should fall back + to the actual content size captured during request transformation. + + Regression test for #36388. + """ + import httpx + + from litellm.llms.bedrock.files.transformation import BedrockFilesConfig + + raw_response = httpx.Response( + status_code=200, + # S3 PUT responses typically omit Content-Length + headers={}, + content=b"", + request=httpx.Request( + "PUT", + "https://s3.us-east-1.amazonaws.com/my-bucket/litellm-bedrock-files-anthropic.claude-3-5-sonnet-20240620-v1:0-abc123.jsonl", + ), + ) + + result = BedrockFilesConfig().transform_create_file_response( + model=None, + raw_response=raw_response, + logging_obj=MagicMock(), + litellm_params={"_file_content_size": 2048}, + ) + + assert result.bytes == 2048 + def test_file_content_signs_spaced_object_key_the_way_s3_does( self, monkeypatch: pytest.MonkeyPatch ) -> None: