Repository: BerriAI/litellm

Issue:
[Bug]: Unable to process .txt and .mp4 files with Amazon Nova Lite/Pro models
### What happened?

Hi @krrishdholakia!

I’m having an issue when trying to send `.txt` and .`mp4` files to the `awsbedrock/amazon.nova-lite-v1:0` and `awsbedrock/amazon.nova-pro-v1:0` models. According to the [Amazon Nova documentation](https://docs.aws.amazon.com/nova/latest/userguide/modalities.html), both extensions (.txt and .mp4) should be supported by these models. However, Litellm responds with an error that only image formats are supported ([`'png', 'jpeg', 'gif', 'webp'`]). 

The docs also note that for `.txt` and `.pdf` files, the input method should be Bytes (Still, for `.pdf` I can successfully send it using `"data:application/pdf;base64,{encoded_file}"` to Amazon Nova Pro without any issues).

Below is the body of my request when sending a `.txt` file to the `v1/chat/completions` endpoint:
```
{
  "max_tokens": 2048,
  "temperature": 0.1,
  "model": "awsbedrock/amazon.nova-pro-v1:0",
  "messages": [
    {
      "role": "system",
      "content": "You are a helpful assistant."
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "What is this about?"
        },
        {
          "type": "image_url",
          "image_url": {
            "url": "data:text/plain;base64,{base64file}"
          }
        }
      ]
    }
  ],
  "stream": true,
  "stream_options": {
    "include_usage": true
  }
}

```

And here’s the corresponding error message:

`{"error":{"message":"litellm.APIConnectionError: Unsupported image format: plain. Supported formats: ['png', 'jpeg', 'gif', 'webp']\nTraceback (most recent call last):\n File \"/usr/local/lib/python3.13/site-packages/litellm/main.py\", line 2479, in completion\n response = bedrock_converse_chat_completion.completion(\n model=model,\n ...<13 lines>...\n api_base=api_base,\n )\n File \"/usr/local/lib/python3.13/site-packages/litellm/llms/bedrock/chat/converse_handler.py\", line 288, in completion\n _data = litellm.AmazonConverseConfig()._transform_request(\n model=model,\n ...<2 lines>...\n litellm_params=litellm_params,\n )\n File \"/usr/local/lib/python3.13/site-packages/litellm/llms/bedrock/chat/converse_transformation.py\", line 362, in _transform_request\n bedrock_messages: List[MessageBlock] = _bedrock_converse_messages_pt(\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^\n messages=messages,\n ^^^^^^^^^^^^^^^^^^\n ...<2 lines>...\n user_continue_message=litellm_params.pop(\"user_continue_message\", None),\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n )\n ^\n File \"/usr/local/lib/python3.13/site-packages/litellm/litellm_core_utils/prompt_templates/factory.py\", line 2747, in _bedrock_converse_messages_pt\n _part = _process_bedrock_converse_image_block( # type: ignore\n image_url=image_url\n )\n File \"/usr/local/lib/python3.13/site-packages/litellm/litellm_core_utils/prompt_templates/factory.py\", line 2231, in _process_bedrock_converse_image_block\n raise ValueError(\n ...<3 lines>...\n )\nValueError: Unsupported image format: plain. Supported formats: ['png', 'jpeg', 'gif', 'webp']\n\nReceived Model Group=awsbedrock/amazon.nova-lite-v1:0\nAvailable Model Group Fallbacks=None","type":null,"param":null,"code":"500"}}`

I run into a similar error when sending an `.mp4` file with the following body parameter:

```
...
{
  "type": "image_url",
  "image_url": {
    "url": "data:video/mp4;base64,{base64file}"
  }
}
...
```

This results in the error:

`"Unsupported image format: mp4. Supported formats: ['png', 'jpeg', 'gif', 'webp']"
`

Meanwhile, sending other file formats like `.pdf,` `.png,` `.jpg`, and `.gif` via `data:...base64` works fine.

I’m not certain whether this is a bug or if it should be categorized as a “Help” question, so I’d love some guidance on:

- Whether there’s an alternative approach for uploading .txt and .mp4 files (e.g., different param or field in the request).
- If this is indeed a bug, any insight on how to fix or work around it.
- How to send files for all the file extensions that the Amazon documentation states are supported by these models.

Any help or clarification would be greatly appreciated—thank you in advance!

### Relevant log output

```shell
"Unsupported image format: plain. Supported formats: ['png', 'jpeg', 'gif', 'webp']"
```


### Are you a ML Ops Team?

No

### What LiteLLM version are you on ?

v1.58.0

### Twitter / LinkedIn details

_No response_

Discussion:
@mvrodrig: Hello @krrishdholakia , is this issue supposed to be solved in version [v1.58.2](https://github.com/BerriAI/litellm/releases/tag/v1.58.2)?@krrishdholakia: Yes
<img width="587" alt="Image" src="https://github.com/user-attachments/assets/dd5d4f26-4b6a-4a8f-8543-22c54d5f0ab8" />@krrishdholakia: Hi @mvrodrig just pushed a 2nd pr on this. Do you still see any issues passing data to nova models? @mvrodrig: HI @krrishdholakia ,  

Thanks for asking. I was just about to open a new issue since I'm using version 59.9 and still experiencing the same problem, particularly with .txt and .mp4 files.  

Which version should fix this issue?@mvrodrig: Hi @krrishdholakia , this is still not working with version v1.61.7. @krrishdholakia: Hi @mvrodrig I can see .txt should already be working on your current version. 

Can you share how you're running your test? @mvrodrig: 
```
import requests
import json
import base64


url = "http://localhost:4000/chat/completions"
headers = {
    'Content-Type': 'application/json'
}


def encode_file(file_path):
    with open(file_path, 'rb') as file:
        return base64.b64encode(file.read()).decode('utf-8')

file_path = r'C:\Users\maria***\file-samples\SampleVideo_1280x720_1mb.mp4'
encoded_file = encode_file(file_path)


payload = {
    "model": "awsbedrock/amazon.nova-pro-v1:0",
    "messages": [
        {
            "role": "user",
            "content": {
                "type": "text",
                "text": "What's this file about?"
            }
        },
        {
            "type": "image_url",
            "image_url": f"data:video/mp4;base64,{encoded_file}"
        }
    ]
}

response = requests.post(url, headers=headers, json=payload)


print("Status Code:", response.status_code)
print("\nResponse:")
print(json.dumps(response.json(), indent=2))
```

Error:

```
Status Code: 500

Response:
{
  "error": {
    "message": "litellm.APIConnectionError: Invalid message={'type': 'image_url', 'image_url': 'data:video/mp4;base64,AAAAIGZ0eXBpc...'} at index 1. Please ensure all messages are valid OpenAI chat completion messages.\n. Received Model Group=awsbedrock/amazon.nova-pro-v1:0\nAvailable Model Group Fallbacks=None",
    "type": null,
    "param": null,
    "code": "500"
  }
}
```@krrishdholakia: @mvrodrig Your message is invalid. Notice how the 2nd block does not have a role.@mvrodrig: Could you provide the correct way of sending the message? I'm using the cURL sample provided [here](https://docs.litellm.ai/docs/providers/bedrock#base64) in your doc (PROXY tab).@krrishdholakia: Hi @mvrodrig our example is correct. 

Please see the image url is inside the content field, of a user message.

```
image_content = [
    {"type": "text", "text": "What's this file about?"},
    {
        "type": "image_url",
        "image_url": base64_url, # OR {"url": base64_url}
    },
]


messages=[{"role": "user", "content": image_content}],
```

Your code has the image url as a separate dictionary item outside of the user message content. 

```
message = [
        {
            "role": "user",
            "content": {
                "type": "text",
                "text": "What's this file about?"
            }
        },
        {
            "type": "image_url",
            "image_url": f"data:video/mp4;base64,{encoded_file}"
        }
    ]
```@mvrodrig: Great, thanks for the clarification. I confirm it works for .txt, but still not working for .mp4
Payload:

```
payload = {
    "model": "awsbedrock/amazon.nova-pro-v1:0",
    "messages":  [
        {
            "role": "user",
            "content": [{
                "type": "text",
                "text": "What's this file about?"
            },
            {
                "type": "image_url",
                "image_url": f"data:video/mp4;base64,{encoded_file}"
            }
            ]
        }
    ]
}
```

Error:
```
Status Code: 500

Response:
{
  "error": {
    "message": "litellm.APIConnectionError: Unsupported image format: mp4. Supported formats: ['png', 'jpeg', 'gif', 'webp']\nTraceback (most recent call last):\n  File \"/usr/lib/python3.13/site-packages/litellm/main.py\", line 467, in acompletion\n    response = await init_response\n               ^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.13/site-packages/litellm/llms/bedrock/chat/converse_handler.py\", line 186, in async_completion\n    request_data = await litellm.AmazonConverseConfig()._async_transform_request(\n                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n    ...<4 lines>...\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/llms/bedrock/chat/converse_transformation.py\", line 463, in _async_transform_request\n    await BedrockConverseMessagesProcessor._bedrock_converse_messages_pt_async(\n    ...<4 lines>...\n    )\n  File \"/usr/lib/python3.13/site-packages/litellm/litellm_core_utils/prompt_templates/factory.py\", line 2816, in _bedrock_converse_messages_pt_async\n    _part = await BedrockImageProcessor.process_image_async(  # type: ignore\n            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n        image_url=image_url\n        ^^^^^^^^^^^^^^^^^^^\n    )\n    ^\n  File \"/usr/lib/python3.13/site-packages/litellm/litellm_core_utils/prompt_templates/factory.py\", line 2325, in process_image_async\n    image_format = cls._validate_format(mime_type, image_format)\n  File \"/usr/lib/python3.13/site-packages/litellm/litellm_core_utils/prompt_templates/factory.py\", line 2265, in _validate_format\n    raise ValueError(\n        f\"Unsupported image format: {image_format}. Supported formats: {supported_image_formats}\"\n    )\nValueError: Unsupported image format: mp4. Supported formats: ['png', 'jpeg', 'gif', 'webp']\n. Received Model Group=awsbedrock/amazon.nova-pro-v1:0\nAvailable Model Group Fallbacks=None",
    "type": null,
    "param": null,
    "code": "500"
  }
}
```@krrishdholakia: Please confirm you're on the latest version (v1.61.7) @mvrodrig @mvrodrig: @krrishdholakia Yes, I am. 

![Image](https://github.com/user-attachments/assets/6d58840c-9fba-4fe9-b252-5249a63fcd93)


Solution patch:
diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py
index d05e64954..772f80777 100644
--- a/litellm/litellm_core_utils/prompt_templates/factory.py
+++ b/litellm/litellm_core_utils/prompt_templates/factory.py
@@ -2185,12 +2185,7 @@ def get_image_details(image_url) -> Tuple[str, str]:
         # Convert the image content to base64 bytes
         base64_bytes = base64.b64encode(response.content).decode("utf-8")
 
-        # Get mime-type
-        mime_type = content_type.split("/")[
-            1
-        ]  # Extract mime-type from content-type header
-
-        return base64_bytes, mime_type
+        return base64_bytes, content_type
 
     except Exception as e:
         raise e
@@ -2216,50 +2211,37 @@ def _process_bedrock_converse_image_block(
             mime_type = "image/jpeg"
             image_format = "jpeg"
         _blob = BedrockSourceBlock(bytes=img_without_base_64)
-        supported_image_formats = (
-            litellm.AmazonConverseConfig().get_supported_image_types()
-        )
-        supported_document_types = (
-            litellm.AmazonConverseConfig().get_supported_document_types()
-        )
-        if image_format in supported_image_formats:
-            return BedrockContentBlock(image=BedrockImageBlock(source=_blob, format=image_format))  # type: ignore
-        elif image_format in supported_document_types:
-            return BedrockContentBlock(document=BedrockDocumentBlock(source=_blob, format=image_format, name="DocumentPDFmessages_{}".format(str(uuid.uuid4()))))  # type: ignore
-        else:
-            # Handle the case when the image format is not supported
-            raise ValueError(
-                "Unsupported image format: {}. Supported formats: {}".format(
-                    image_format, supported_image_formats
-                )
-            )
+
     elif "https:/" in image_url:
         # Case 2: Images with direct links
-        image_bytes, image_format = get_image_details(image_url)
+        image_bytes, mime_type = get_image_details(image_url)
+        image_format = mime_type.split("/")[1]
         _blob = BedrockSourceBlock(bytes=image_bytes)
-        supported_image_formats = (
-            litellm.AmazonConverseConfig().get_supported_image_types()
-        )
-        supported_document_types = (
-            litellm.AmazonConverseConfig().get_supported_document_types()
-        )
-        if image_format in supported_image_formats:
-            return BedrockContentBlock(image=BedrockImageBlock(source=_blob, format=image_format))  # type: ignore
-        elif image_format in supported_document_types:
-            return BedrockContentBlock(document=BedrockDocumentBlock(source=_blob, format=image_format, name="DocumentPDFmessages_{}".format(str(uuid.uuid4()))))  # type: ignore
-        else:
-            # Handle the case when the image format is not supported
-            raise ValueError(
-                "Unsupported image format: {}. Supported formats: {}".format(
-                    image_format, supported_image_formats
-                )
-            )
     else:
         raise ValueError(
             "Unsupported image type. Expected either image url or base64 encoded string - \
                 e.g. 'data:image/jpeg;base64,<base64-encoded-string>'"
         )
 
+    supported_image_formats = litellm.AmazonConverseConfig().get_supported_image_types()
+
+    document_types = ["application", "text"]
+    is_document = any(
+        mime_type.startswith(document_type) for document_type in document_types
+    )
+
+    if image_format in supported_image_formats:
+        return BedrockContentBlock(image=BedrockImageBlock(source=_blob, format=image_format))  # type: ignore
+    elif is_document:
+        return BedrockContentBlock(document=BedrockDocumentBlock(source=_blob, format=image_format, name="DocumentPDFmessages_{}".format(str(uuid.uuid4()))))  # type: ignore
+    else:
+        # Handle the case when the image format is not supported
+        raise ValueError(
+            "Unsupported image format: {}. Supported formats: {}".format(
+                image_format, supported_image_formats
+            )
+        )
+
 
 def _convert_to_bedrock_tool_call_invoke(
     tool_calls: list,
diff --git a/litellm/proxy/_new_secret_config.yaml b/litellm/proxy/_new_secret_config.yaml
index 5d86034cb..749272690 100644
--- a/litellm/proxy/_new_secret_config.yaml
+++ b/litellm/proxy/_new_secret_config.yaml
@@ -4,4 +4,4 @@ model_list:
       model: "azure/gpt-4o"
       api_key: os.environ/AZURE_API_KEY
       api_base: os.environ/AZURE_API_BASE
-      
\ No newline at end of file
+     
\ No newline at end of file
diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py
index 04d48d8f0..575b408cd 100644
--- a/litellm/proxy/_types.py
+++ b/litellm/proxy/_types.py
@@ -657,6 +657,7 @@ class GenerateKeyResponse(KeyRequestBase):
     user_id: Optional[str] = None
     token_id: Optional[str] = None
     litellm_budget_table: Optional[Any] = None
+    token: Optional[str] = None
 
     @model_validator(mode="before")
     @classmethod
diff --git a/litellm/proxy/hooks/key_management_event_hooks.py b/litellm/proxy/hooks/key_management_event_hooks.py
index 224348876..2030cb2a4 100644
--- a/litellm/proxy/hooks/key_management_event_hooks.py
+++ b/litellm/proxy/hooks/key_management_event_hooks.py
@@ -10,12 +10,14 @@ import litellm
 from litellm._logging import verbose_proxy_logger
 from litellm.proxy._types import (
     GenerateKeyRequest,
+    GenerateKeyResponse,
     KeyRequest,
     LiteLLM_AuditLogs,
     LiteLLM_VerificationToken,
     LitellmTableNames,
     ProxyErrorTypes,
     ProxyException,
+    RegenerateKeyRequest,
     UpdateKeyRequest,
     UserAPIKeyAuth,
     WebhookEvent,
@@ -30,7 +32,7 @@ class KeyManagementEventHooks:
     @staticmethod
     async def async_key_generated_hook(
         data: GenerateKeyRequest,
-        response: dict,
+        response: GenerateKeyResponse,
         user_api_key_dict: UserAPIKeyAuth,
         litellm_changed_by: Optional[str] = None,
     ):
@@ -48,11 +50,13 @@ class KeyManagementEventHooks:
         from litellm.proxy.proxy_server import litellm_proxy_admin_name
 
         if data.send_invite_email is True:
-            await KeyManagementEventHooks._send_key_created_email(response)
+            await KeyManagementEventHooks._send_key_created_email(
+                response.model_dump(exclude_none=True)
+            )
 
         # Enterprise Feature - Audit Logging. Enable with litellm.store_audit_logs = True
         if litellm.store_audit_logs is True:
-            _updated_values = json.dumps(response, default=str)
+            _updated_values = response.model_dump_json(exclude_none=True)
             asyncio.create_task(
                 create_audit_log_for_update(
                     request_data=LiteLLM_AuditLogs(
@@ -63,7 +67,7 @@ class KeyManagementEventHooks:
                         or litellm_proxy_admin_name,
                         changed_by_api_key=user_api_key_dict.api_key,
                         table_name=LitellmTableNames.KEY_TABLE_NAME,
-                        object_id=response.get("token_id", ""),
+                        object_id=response.token_id or "",
                         action="created",
                         updated_values=_updated_values,
                         before_value=None,
@@ -72,8 +76,8 @@ class KeyManagementEventHooks:
             )
         # store the generated key in the secret manager
         await KeyManagementEventHooks._store_virtual_key_in_secret_manager(
-            secret_name=data.key_alias or f"virtual-key-{uuid.uuid4()}",
-            secret_token=response.get("token", ""),
+            secret_name=data.key_alias or f"virtual-key-{response.token_id}",
+            secret_token=response.key,
         )
 
     @staticmethod
@@ -119,7 +123,25 @@ class KeyManagementEventHooks:
                     )
                 )
             )
-        pass
+
+    @staticmethod
+    async def async_key_rotated_hook(
+        data: Optional[RegenerateKeyRequest],
+        existing_key_row: Any,
+        response: GenerateKeyResponse,
+        user_api_key_dict: UserAPIKeyAuth,
+        litellm_changed_by: Optional[str] = None,
+    ):
+        # store the generated key in the secret manager
+        if data is not None and response.token_id is not None:
+            initial_secret_name = (
+                existing_key_row.key_alias or f"virtual-key-{existing_key_row.token}"
+            )
+            await KeyManagementEventHooks._rotate_virtual_key_in_secret_manager(
+                current_secret_name=initial_secret_name,
+                new_secret_name=data.key_alias or f"virtual-key-{response.token_id}",
+                new_secret_value=response.key,
+            )
 
     @staticmethod
     async def async_key_deleted_hook(
@@ -207,6 +229,35 @@ class KeyManagementEventHooks:
                         secret_value=secret_token,
                     )
 
+    @staticmethod
+    async def _rotate_virtual_key_in_secret_manager(
+        current_secret_name: str, new_secret_name: str, new_secret_value: str
+    ):
+        """
+        Update a virtual key in the secret manager
+
+        Args:
+            secret_name: Name of the virtual key
+            secret_token: Value of the virtual key (example: sk-1234)
+        """
+        if litellm._key_management_settings is not None:
+            if litellm._key_management_settings.store_virtual_keys is True:
+                from litellm.secret_managers.base_secret_manager import (
+                    BaseSecretManager,
+                )
+
+                # store the key in the secret manager
+                if isinstance(litellm.secret_manager_client, BaseSecretManager):
+                    await litellm.secret_manager_client.async_rotate_secret(
+                        current_secret_name=KeyManagementEventHooks._get_secret_name(
+                            current_secret_name
+                        ),
+                        new_secret_name=KeyManagementEventHooks._get_secret_name(
+                            new_secret_name
+                        ),
+                        new_secret_value=new_secret_value,
+                    )
+
     @staticmethod
     def _get_secret_name(secret_name: str) -> str:
         if litellm._key_management_settings.prefix_for_stored_virtual_keys.endswith(
diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py
index 0f2872faf..a8a39c8be 100644
--- a/litellm/proxy/management_endpoints/key_management_endpoints.py
+++ b/litellm/proxy/management_endpoints/key_management_endpoints.py
@@ -523,6 +523,8 @@ async def generate_key_fn(  # noqa: PLR0915
             data.soft_budget
         )  # include the user-input soft budget in the response
 
+        response = GenerateKeyResponse(**response)
+
         asyncio.create_task(
             KeyManagementEventHooks.async_key_generated_hook(
                 data=data,
@@ -532,7 +534,7 @@ async def generate_key_fn(  # noqa: PLR0915
             )
         )
 
-        return GenerateKeyResponse(**response)
+        return response
     except Exception as e:
         verbose_proxy_logger.exception(
             "litellm.proxy.proxy_server.generate_key_fn(): Exception occured - {}".format(
@@ -1517,7 +1519,7 @@ async def regenerate_key_fn(
             updated_token_dict = dict(updated_token)
 
         updated_token_dict["key"] = new_token
-        updated_token_dict.pop("token")
+        updated_token_dict["token_id"] = updated_token_dict.pop("token")
 
         ### 3. remove existing key entry from cache
         ######################################################################
@@ -1535,9 +1537,21 @@ async def regenerate_key_fn(
                 proxy_logging_obj=proxy_logging_obj,
             )
 
-        return GenerateKeyResponse(
+        response = GenerateKeyResponse(
             **updated_token_dict,
         )
+
+        asyncio.create_task(
+            KeyManagementEventHooks.async_key_rotated_hook(
+                data=data,
+                existing_key_row=_key_in_db,
+                response=response,
+                user_api_key_dict=user_api_key_dict,
+                litellm_changed_by=litellm_changed_by,
+            )
+        )
+
+        return response
     except Exception as e:
         raise handle_exception_on_proxy(e)
 
diff --git a/litellm/secret_managers/base_secret_manager.py b/litellm/secret_managers/base_secret_manager.py
index 66d9ad05a..0b5bf74db 100644
--- a/litellm/secret_managers/base_secret_manager.py
+++ b/litellm/secret_managers/base_secret_manager.py
@@ -3,6 +3,8 @@ from typing import Any, Dict, Optional, Union
 
 import httpx
 
+from litellm import verbose_logger
+
 
 class BaseSecretManager(ABC):
     """
@@ -93,3 +95,82 @@ class BaseSecretManager(ABC):
             dict: Response from the secret manager containing deletion details
         """
         pass
+
+    async def async_rotate_secret(
+        self,
+        current_secret_name: str,
+        new_secret_name: str,
+        new_secret_value: str,
+        optional_params: Optional[dict] = None,
+        timeout: Optional[Union[float, httpx.Timeout]] = None,
+    ) -> dict:
+        """
+        Async function to rotate a secret by creating a new one and deleting the old one.
+        This allows for both value and name changes during rotation.
+
+        Args:
+            current_secret_name: Current name of the secret
+            new_secret_name: New name for the secret
+            new_secret_value: New value for the secret
+            optional_params: Additional AWS parameters
+            timeout: Request timeout
+
+        Returns:
+            dict: Response containing the new secret details
+
+        Raises:
+            ValueError: If the secret doesn't exist or if there's an HTTP error
+        """
+        try:
+            # First verify the old secret exists
+            old_secret = await self.async_read_secret(
+                secret_name=current_secret_name,
+                optional_params=optional_params,
+                timeout=timeout,
+            )
+
+            if old_secret is None:
+                raise ValueError(f"Current secret {current_secret_name} not found")
+
+            # Create new secret with new name and value
+            create_response = await self.async_write_secret(
+                secret_name=new_secret_name,
+                secret_value=new_secret_value,
+                description=f"Rotated from {current_secret_name}",
+                optional_params=optional_params,
+                timeout=timeout,
+            )
+
+            # Verify new secret was created successfully
+            new_secret = await self.async_read_secret(
+                secret_name=new_secret_name,
+                optional_params=optional_params,
+                timeout=timeout,
+            )
+
+            if new_secret is None:
+                raise ValueError(f"Failed to verify new secret {new_secret_name}")
+
+            # If everything is successful, delete the old secret
+            await self.async_delete_secret(
+                secret_name=current_secret_name,
+                recovery_window_in_days=7,  # Keep for recovery if needed
+                optional_params=optional_params,
+                timeout=timeout,
+            )
+
+            return create_response
+
+        except httpx.HTTPStatusError as err:
+            verbose_logger.exception(
+                "Error rotating secret in AWS Secrets Manager: %s",
+                str(err.response.text),
+            )
+            raise ValueError(f"HTTP error occurred: {err.response.text}")
+        except httpx.TimeoutException:
+            raise ValueError("Timeout error occurred")
+        except Exception as e:
+            verbose_logger.exception(
+                "Error rotating secret in AWS Secrets Manager: %s", str(e)
+            )
+            raise
diff --git a/litellm/secret_managers/hashicorp_secret_manager.py b/litellm/secret_managers/hashicorp_secret_manager.py
index 8819f2be6..bbfbf3e65 100644
--- a/litellm/secret_managers/hashicorp_secret_manager.py
+++ b/litellm/secret_managers/hashicorp_secret_manager.py
@@ -222,6 +222,16 @@ class HashicorpSecretManager(BaseSecretManager):
             verbose_logger.exception(f"Error writing secret to Hashicorp Vault: {e}")
             return {"status": "error", "message": str(e)}
 
+    async def async_rotate_secret(
+        self,
+        current_secret_name: str,
+        new_secret_name: str,
+        new_secret_value: str,
+        optional_params: Dict | None = None,
+        timeout: float | httpx.Timeout | None = None,
+    ) -> Dict:
+        raise NotImplementedError("Hashicorp does not support secret rotation")
+
     async def async_delete_secret(
         self,
         secret_name: str,


Test patch:
diff --git a/tests/llm_translation/test_bedrock_completion.py b/tests/llm_translation/test_bedrock_completion.py
index 493560afa..0bd31dd5d 100644
--- a/tests/llm_translation/test_bedrock_completion.py
+++ b/tests/llm_translation/test_bedrock_completion.py
@@ -2379,3 +2379,15 @@ class TestBedrockEmbedding(BaseLLMEmbeddingTest):
         transformed_request[
             "inputImage"
         ] == "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkBAMAAACCzIhnAAAAG1BMVEURAAD///+ln5/h39/Dv79qX18uHx+If39MPz9oMSdmAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABB0lEQVRYhe2SzWrEIBCAh2A0jxEs4j6GLDS9hqWmV5Flt0cJS+lRwv742DXpEjY1kOZW6HwHFZnPmVEBEARBEARB/jd0KYA/bcUYbPrRLh6amXHJ/K+ypMoyUaGthILzw0l+xI0jsO7ZcmCcm4ILd+QuVYgpHOmDmz6jBeJImdcUCmeBqQpuqRIbVmQsLCrAalrGpfoEqEogqbLTWuXCPCo+Ki1XGqgQ+jVVuhB8bOaHkvmYuzm/b0KYLWwoK58oFqi6XfxQ4Uz7d6WeKpna6ytUs5e8betMcqAv5YPC5EZB2Lm9FIn0/VP6R58+/GEY1X1egVoZ/3bt/EqF6malgSAIgiDIH+QL41409QMY0LMAAAAASUVORK5CYII="
+
+
+def test_process_bedrock_converse_image_block():
+    from litellm.litellm_core_utils.prompt_templates.factory import (
+        _process_bedrock_converse_image_block,
+    )
+
+    block = _process_bedrock_converse_image_block(
+        image_url="data:text/plain;base64,base64file"
+    )
+
+    assert block["document"] is not None
diff --git a/tests/llm_translation/test_prompt_factory.py b/tests/llm_translation/test_prompt_factory.py
index ca4a4bd35..e6fc69d7d 100644
--- a/tests/llm_translation/test_prompt_factory.py
+++ b/tests/llm_translation/test_prompt_factory.py
@@ -327,7 +327,9 @@ def test_bedrock_parallel_tool_calling_pt(provider):
     """
     Make sure parallel tool call blocks are merged correctly - https://github.com/BerriAI/litellm/issues/5277
     """
-    from litellm.litellm_core_utils.prompt_templates.factory import _bedrock_converse_messages_pt
+    from litellm.litellm_core_utils.prompt_templates.factory import (
+        _bedrock_converse_messages_pt,
+    )
     from litellm.types.utils import ChatCompletionMessageToolCall, Function, Message
 
     messages = [
@@ -682,7 +684,9 @@ def test_alternating_roles_e2e():
 
 
 def test_just_system_message():
-    from litellm.litellm_core_utils.prompt_templates.factory import _bedrock_converse_messages_pt
+    from litellm.litellm_core_utils.prompt_templates.factory import (
+        _bedrock_converse_messages_pt,
+    )
 
     with pytest.raises(litellm.BadRequestError) as e:
         _bedrock_converse_messages_pt(