Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion litellm/llms/custom_httpx/llm_http_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3015,7 +3015,8 @@ def create_file(

# Store the upload URL in litellm_params for the transformation method
litellm_params_with_url = dict(litellm_params)
litellm_params_with_url["upload_url"] = api_base
if "upload_url" not in litellm_params:
litellm_params_with_url["upload_url"] = api_base

return provider_config.transform_create_file_response(
model=None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
import base64
from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest

import litellm
from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler
from litellm.types.llms.openai import HttpxBinaryResponseContent
from litellm.types.utils import SpecialEnums

Expand Down Expand Up @@ -108,3 +110,64 @@ async def test_litellm_afile_content_bedrock_provider_with_unified_file_id(self)
assert call_kwargs["_is_async"] is True
# The handler extracts S3 URI from the unified file ID
assert call_kwargs["file_content_request"]["file_id"] == encoded_file_id

def test_sync_create_file_preserves_provider_upload_url(self):
"""
Test that sync create_file does not overwrite upload_url set by the
provider's transform_create_file_request.

Regression test for https://github.com/BerriAI/litellm/issues/21546
where the sync path unconditionally overwrote litellm_params["upload_url"]
with api_base, causing the returned file_id to point to the wrong S3 key.
"""
handler = BaseLLMHTTPHandler()

# The provider sets upload_url during transform_create_file_request
# (this is what Bedrock does at bedrock/files/transformation.py:370)
provider_upload_url = "https://s3.us-east-1.amazonaws.com/bucket/correct-uuid-key.jsonl"
# api_base holds a stale URL from the first get_complete_file_url call
stale_api_base = "https://s3.us-east-1.amazonaws.com/bucket/stale-uuid-key.jsonl"

litellm_params = {"upload_url": provider_upload_url}

mock_provider = MagicMock()
# transform_create_file_request returns a pre-signed dict (Bedrock path)
mock_provider.transform_create_file_request.return_value = {
"method": "PUT",
"url": provider_upload_url,
"headers": {"Authorization": "AWS4-HMAC-SHA256 ..."},
"data": b"file-content",
}
mock_provider.validate_environment.return_value = {}
mock_provider.get_complete_file_url.return_value = stale_api_base

mock_response = httpx.Response(
status_code=200,
headers={"ETag": '"abc123"', "Content-Length": "100"},
request=httpx.Request(method="PUT", url=provider_upload_url),
)
mock_provider.transform_create_file_response.return_value = MagicMock()

mock_client = MagicMock()
mock_client.put.return_value = mock_response

handler.create_file(
create_file_data={"file": b"data", "purpose": "batch"},
litellm_params=litellm_params,
provider_config=mock_provider,
api_base=stale_api_base,
api_key="fake-key",
headers={},
logging_obj=MagicMock(),
client=mock_client,
timeout=30,
_is_async=False,
)

# The key assertion: transform_create_file_response must receive
# the provider's upload_url, NOT the stale api_base
call_kwargs = mock_provider.transform_create_file_response.call_args.kwargs
assert call_kwargs["litellm_params"]["upload_url"] == provider_upload_url, (
"upload_url was overwritten with stale api_base; "
"provider's upload_url should be preserved"
)
Loading