fix(bedrock): respect s3_region_name for batch file uploads (GovCloud fix)

This commit is contained in:
Ishaan Jaffer 2026-03-13 10:30:45 -07:00
parent 2b61f2a41b
commit 913ccf4983
2 changed files with 118 additions and 1 deletions

View file

@ -173,7 +173,12 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig):
"S3 bucket_name is required. Set 's3_bucket_name' in litellm_params or AWS_S3_BUCKET_NAME env var"
)
aws_region_name = self._get_aws_region_name(optional_params, model)
s3_region_name = litellm_params.get("s3_region_name") or optional_params.get(
"s3_region_name"
)
aws_region_name = s3_region_name or self._get_aws_region_name(
optional_params, model
)
file_data = data.get("file")
purpose = data.get("purpose")
@ -398,6 +403,14 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig):
data=create_file_data,
)
# If s3_region_name is set and aws_region_name is not, propagate it so
# SigV4 signing uses the correct region (e.g. GovCloud us-gov-west-1).
s3_region_name = litellm_params.get("s3_region_name") or optional_params.get(
"s3_region_name"
)
if s3_region_name and not optional_params.get("aws_region_name"):
optional_params = {**optional_params, "aws_region_name": s3_region_name}
# Sign the request and return a pre-signed request object
signed_headers, signed_body = self._sign_s3_request(
content=file_content,

View file

@ -272,6 +272,110 @@ class TestBedrockFilesTransformation:
assert "messages" in model_input
assert "max_tokens" in model_input
def test_get_complete_file_url_respects_s3_region_name(self):
"""
s3_region_name in litellm_params must be used when building the S3 URL.
Previously the code fell back to us-west-2 even when s3_region_name was set,
breaking GovCloud (us-gov-west-1) deployments.
"""
from unittest.mock import MagicMock, patch
from litellm.llms.bedrock.files.transformation import BedrockFilesConfig
config = BedrockFilesConfig()
jsonl_content = json.dumps(
{
"custom_id": "req-1",
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "bedrock/amazon.nova-pro-v1:0",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 10,
},
}
).encode()
create_file_data = {
"file": ("batch.jsonl", jsonl_content, "application/jsonl"),
"purpose": "batch",
}
litellm_params = {
"s3_bucket_name": "litellm-batch-352026",
"s3_region_name": "us-gov-west-1",
}
url = config.get_complete_file_url(
api_base=None,
api_key=None,
model="amazon.nova-pro-v1:0",
optional_params={},
litellm_params=litellm_params,
data=create_file_data,
)
assert "us-gov-west-1" in url, (
f"Expected us-gov-west-1 in URL but got: {url}"
)
assert "us-west-2" not in url, (
f"us-west-2 must not appear when s3_region_name is set, got: {url}"
)
assert "litellm-batch-352026" in url
def test_transform_create_file_request_injects_s3_region_for_signing(self):
"""
When s3_region_name is provided, transform_create_file_request must pass
that region to _sign_s3_request so SigV4 signatures use the correct region.
"""
from unittest.mock import MagicMock, patch
from litellm.llms.bedrock.files.transformation import BedrockFilesConfig
config = BedrockFilesConfig()
jsonl_content = json.dumps(
{
"custom_id": "req-1",
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "bedrock/amazon.nova-pro-v1:0",
"messages": [{"role": "user", "content": "Hello"}],
"max_tokens": 10,
},
}
).encode()
create_file_data = {
"file": ("batch.jsonl", jsonl_content, "application/jsonl"),
"purpose": "batch",
}
litellm_params = {
"s3_bucket_name": "litellm-batch-352026",
"s3_region_name": "us-gov-west-1",
}
captured_optional_params: dict = {}
def fake_sign(content, api_base, optional_params):
captured_optional_params.update(optional_params)
return {"Authorization": "fake"}, content
with patch.object(config, "_sign_s3_request", side_effect=fake_sign):
config.transform_create_file_request(
model="amazon.nova-pro-v1:0",
create_file_data=create_file_data,
optional_params={},
litellm_params=litellm_params,
)
assert captured_optional_params.get("aws_region_name") == "us-gov-west-1", (
"s3_region_name must be forwarded as aws_region_name for SigV4 signing"
)
def test_openai_passthrough_still_works(self):
"""
Regression test: ensure OpenAI-compatible models (e.g. gpt-oss)