fix(bedrock): respect s3_region_name for batch file uploads (#23569)

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

* fix: s3_region_name always wins over aws_region_name for S3 signing (Greptile feedback)
This commit is contained in:
Ishaan Jaff 2026-03-13 18:01:03 -07:00 • committed by GitHub
parent 2405e0d400
commit 961d1d1a6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 173 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,15 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig):
data=create_file_data,
)
# s3_region_name always wins for S3 operations (same priority as in
# get_complete_file_url above). Overwrite aws_region_name unconditionally
# so the SigV4 region matches the URL region, avoiding SignatureDoesNotMatch.
s3_region_name = litellm_params.get("s3_region_name") or optional_params.get(
"s3_region_name"
)
if s3_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,164 @@ 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 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 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_s3_region_name_wins_over_aws_region_name_for_signing(self):
"""
When both s3_region_name and aws_region_name are set to different values,
s3_region_name must win for signing (same as for the URL). Otherwise the
SigV4 signature would be computed against a different region than the URL,
causing SignatureDoesNotMatch from AWS.
"""
from unittest.mock import 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",
}
# aws_region_name set to something different — s3_region_name must still win
optional_params = {"aws_region_name": "us-east-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=optional_params,
litellm_params=litellm_params,
)
assert captured_optional_params.get("aws_region_name") == "us-gov-west-1", (
"s3_region_name must override aws_region_name for SigV4 signing"
)
def test_openai_passthrough_still_works(self):
"""
Regression test: ensure OpenAI-compatible models (e.g. gpt-oss)