diff --git a/litellm/llms/bedrock/files/transformation.py b/litellm/llms/bedrock/files/transformation.py index edd86960542..694447bca26 100644 --- a/litellm/llms/bedrock/files/transformation.py +++ b/litellm/llms/bedrock/files/transformation.py @@ -600,12 +600,11 @@ class BedrockFilesConfig(BaseAWSLLM, BaseFilesConfig): ## 1. If jsonl, check if there's a model name file_content: Final = self._get_content_from_openai_file(extracted_file_data_content) - # Split into lines and parse each line as JSON. A file that is not JSONL gets the generic name below; the - # upload's own conversion is what rejects a malformed batch file. - with suppress(json.JSONDecodeError): - openai_jsonl_content: Final = [json.loads(line) for line in file_content.splitlines() if line.strip()] - if len(openai_jsonl_content) > 0: - return self._get_s3_object_name_from_batch_jsonl(openai_jsonl_content) + # Reject a malformed line here rather than falling back: the upload only converts (and so re-checks) files + # whose MIME type is on the batch allowlist, so for any other MIME type this is the only check. + openai_jsonl_content: Final = _parse_batch_jsonl(file_content) + if len(openai_jsonl_content) > 0: + return self._get_s3_object_name_from_batch_jsonl(openai_jsonl_content) ## 2. If not jsonl, store under a server-generated managed object name filename: Final = extracted_file_data.get("filename") diff --git a/tests/unit/llms/bedrock/files/test_bedrock_files_transformation.py b/tests/unit/llms/bedrock/files/test_bedrock_files_transformation.py index 503c3724cae..b5d22ca5d61 100644 --- a/tests/unit/llms/bedrock/files/test_bedrock_files_transformation.py +++ b/tests/unit/llms/bedrock/files/test_bedrock_files_transformation.py @@ -4387,10 +4387,25 @@ class TestMalformedBatchJsonl: assert exc_info.value.status_code == 400 assert "line 3" in exc_info.value.message - def test_object_name_inference_still_falls_back_for_a_non_json_file(self): - from litellm.llms.bedrock.files.transformation import BedrockFilesConfig + @pytest.mark.parametrize("content_type", ["application/jsonl", "application/x-jsonl"]) + def test_create_file_rejects_a_malformed_line_before_uploading(self, content_type: str): + """application/x-jsonl is not on the batch MIME allowlist, so the upload skips conversion for it; the line + check must still run so the broken file is rejected instead of landing in S3 unchanged.""" + from litellm.llms.bedrock.common_utils import BedrockError - object_name = BedrockFilesConfig().get_object_name( - {"content": b"just some text, not jsonl", "filename": "notes.txt"}, purpose="batch" - ) - assert object_name.endswith("notes.txt") + with respx.mock as router: + any_request: Final = router.route() + with pytest.raises(BedrockError) as exc_info: + litellm.create_file( + file=("batch.jsonl", _MALFORMED_BATCH_JSONL, content_type), + purpose="batch", + custom_llm_provider="bedrock", + model="bedrock/amazon.nova-pro-v1:0", + s3_bucket_name="litellm-batch-bucket", + aws_region_name="us-west-2", + aws_access_key_id="test-key-id", + aws_secret_access_key="test-secret", + ) + assert exc_info.value.status_code == 400 + assert "line 3" in exc_info.value.message + assert not any_request.called