fix: request body for image embedding request

This commit is contained in:
Sameer Kankute 2025-12-22 16:55:04 +05:30
parent 46438684ca
commit cbc1ac9508
3 changed files with 287 additions and 3 deletions

View file

@ -172,6 +172,125 @@ print(f"Results available at: {output_s3_uri}")
**Note:** The actual embedding results are stored in S3. When the job is completed, download the results from the S3 location specified in `status.metadata['output_file_id']`. The results will be in JSON/JSONL format containing the embedding vectors.
## Amazon Nova Multimodal Embeddings
Amazon Nova supports multimodal embeddings for text, images, video, and audio. It offers flexible embedding dimensions and purposes optimized for different use cases.
### Supported Features
- **Modalities**: Text, Image, Video, Audio
- **Dimensions**: 256, 384, 1024, 3072 (default: 3072)
- **Embedding Purposes**:
- `GENERIC_INDEX` (default)
- `GENERIC_RETRIEVAL`
- `TEXT_RETRIEVAL`
- `IMAGE_RETRIEVAL`
- `VIDEO_RETRIEVAL`
- `AUDIO_RETRIEVAL`
- `CLASSIFICATION`
- `CLUSTERING`
### Text Embedding
```python
from litellm import embedding
response = embedding(
model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0",
input=["Hello, world!"],
aws_region_name="us-east-1",
dimensions=1024, # Optional: 256, 384, 1024, or 3072
)
print(response.data[0].embedding)
```
### Image Embedding with Base64
Amazon Nova accepts images in base64 format using the standard data URL format:
```python
import base64
from litellm import embedding
# Method 1: Load image from file
with open("image.jpg", "rb") as image_file:
image_data = base64.b64encode(image_file.read()).decode('utf-8')
# Create data URL with proper format
image_base64 = f"data:image/jpeg;base64,{image_data}"
response = embedding(
model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0",
input=[image_base64],
aws_region_name="us-east-1",
dimensions=1024,
)
print(f"Image embedding: {response.data[0].embedding[:10]}...") # First 10 dimensions
```
#### Supported Image Formats
Nova supports the following image formats:
- JPEG: `data:image/jpeg;base64,...`
- PNG: `data:image/png;base64,...`
- GIF: `data:image/gif;base64,...`
- WebP: `data:image/webp;base64,...`
#### Complete Example with Error Handling
```python
import base64
from litellm import embedding
def get_image_embedding(image_path, dimensions=1024):
"""
Get embedding for an image file.
Args:
image_path: Path to the image file
dimensions: Embedding dimension (256, 384, 1024, or 3072)
Returns:
List of embedding values
"""
try:
# Determine image format from file extension
if image_path.lower().endswith('.png'):
mime_type = "image/png"
elif image_path.lower().endswith(('.jpg', '.jpeg')):
mime_type = "image/jpeg"
elif image_path.lower().endswith('.gif'):
mime_type = "image/gif"
elif image_path.lower().endswith('.webp'):
mime_type = "image/webp"
else:
raise ValueError(f"Unsupported image format: {image_path}")
# Read and encode image
with open(image_path, "rb") as image_file:
image_data = base64.b64encode(image_file.read()).decode('utf-8')
image_base64 = f"data:{mime_type};base64,{image_data}"
# Get embedding
response = embedding(
model="bedrock/amazon.nova-2-multimodal-embeddings-v1:0",
input=[image_base64],
aws_region_name="us-east-1",
dimensions=dimensions,
)
return response.data[0].embedding
except Exception as e:
print(f"Error getting image embedding: {e}")
raise
# Example usage
image_embedding = get_image_embedding("photo.jpg", dimensions=1024)
print(f"Got embedding with {len(image_embedding)} dimensions")
```
### Error Handling
#### Common Errors

View file

@ -46,6 +46,39 @@ class AmazonNovaEmbeddingConfig:
elif k in self.get_supported_openai_params():
optional_params[k] = v
return optional_params
def _parse_data_url(self, data_url: str) -> tuple:
"""
Parse a data URL to extract the media type and base64 data.
Args:
data_url: Data URL in format: data:image/jpeg;base64,/9j/4AAQ...
Returns:
tuple: (media_type, base64_data)
media_type: e.g., "image/jpeg", "video/mp4", "audio/mpeg"
base64_data: The base64-encoded data without the prefix
"""
if not data_url.startswith("data:"):
raise ValueError(f"Invalid data URL format: {data_url[:50]}...")
# Split by comma to separate metadata from data
# Format: data:image/jpeg;base64,<base64_data>
if "," not in data_url:
raise ValueError(f"Invalid data URL format (missing comma): {data_url[:50]}...")
metadata, base64_data = data_url.split(",", 1)
# Extract media type from metadata
# Remove 'data:' prefix and ';base64' suffix
metadata = metadata[5:] # Remove 'data:'
if ";" in metadata:
media_type = metadata.split(";")[0]
else:
media_type = metadata
return media_type, base64_data
def _transform_request(
self,
@ -99,15 +132,58 @@ class AmazonNovaEmbeddingConfig:
if "embeddingDimension" not in embedding_params:
embedding_params["embeddingDimension"] = 3072
# For text input, add basic text structure if user hasn't provided text/image/video/audio
# For text/media input, add basic structure if user hasn't provided text/image/video/audio
if "text" not in embedding_params and "image" not in embedding_params and "video" not in embedding_params and "audio" not in embedding_params:
# Default to text if no modality specified
if input.startswith("s3://"):
# Check if input is a data URL (e.g., data:image/jpeg;base64,...)
if input.startswith("data:"):
# Parse the data URL to extract media type and base64 data
media_type, base64_data = self._parse_data_url(input)
if media_type.startswith("image/"):
# Extract image format from MIME type (e.g., image/jpeg -> jpeg)
image_format = media_type.split("/")[1].lower()
# Nova API expects specific formats
if image_format == "jpg":
image_format = "jpeg"
embedding_params["image"] = {
"format": image_format,
"source": {
"bytes": base64_data
}
}
elif media_type.startswith("video/"):
# Handle video data URLs
video_format = media_type.split("/")[1].lower()
embedding_params["video"] = {
"format": video_format,
"source": {
"bytes": base64_data
}
}
elif media_type.startswith("audio/"):
# Handle audio data URLs
audio_format = media_type.split("/")[1].lower()
embedding_params["audio"] = {
"format": audio_format,
"source": {
"bytes": base64_data
}
}
else:
# Fallback to text for unknown types
embedding_params["text"] = {
"value": input,
"truncationMode": "END"
}
elif input.startswith("s3://"):
# S3 URL - default to text for now, user should specify modality
embedding_params["text"] = {
"source": {"s3Location": {"uri": input}},
"truncationMode": "END" # Required by Nova API
}
else:
# Plain text input
embedding_params["text"] = {
"value": input,
"truncationMode": "END" # Required by Nova API

View file

@ -216,6 +216,95 @@ class TestNovaTransformationRequest:
params = request["singleEmbeddingParams"]
assert params["embeddingDimension"] == 3072
def test_data_url_image_parsing(self):
"""Test that data URL images are properly parsed and transformed."""
config = AmazonNovaEmbeddingConfig()
# Test with JPEG image data URL
jpeg_data_url = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD"
request = config._transform_request(
input=jpeg_data_url,
inference_params={"dimensions": 1024},
async_invoke_route=False,
)
params = request["singleEmbeddingParams"]
assert "image" in params
assert params["image"]["format"] == "jpeg"
assert "source" in params["image"]
assert params["image"]["source"]["bytes"] == "/9j/4AAQSkZJRgABAQAASABIAAD"
assert params["embeddingDimension"] == 1024
assert params["embeddingPurpose"] == "GENERIC_INDEX"
def test_data_url_png_image_parsing(self):
"""Test that data URL PNG images are properly parsed."""
config = AmazonNovaEmbeddingConfig()
# Test with PNG image data URL
png_data_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ"
request = config._transform_request(
input=png_data_url,
inference_params={},
async_invoke_route=False,
)
params = request["singleEmbeddingParams"]
assert "image" in params
assert params["image"]["format"] == "png"
assert params["image"]["source"]["bytes"] == "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ"
def test_data_url_jpg_format_conversion(self):
"""Test that jpg format is converted to jpeg."""
config = AmazonNovaEmbeddingConfig()
# Test with jpg (should be converted to jpeg)
jpg_data_url = "data:image/jpg;base64,/9j/4AAQSkZJRg"
request = config._transform_request(
input=jpg_data_url,
inference_params={},
async_invoke_route=False,
)
params = request["singleEmbeddingParams"]
assert params["image"]["format"] == "jpeg" # Should be converted from jpg to jpeg
def test_data_url_video_parsing(self):
"""Test that data URL videos are properly parsed."""
config = AmazonNovaEmbeddingConfig()
video_data_url = "data:video/mp4;base64,AAAAIGZ0eXBpc29t"
request = config._transform_request(
input=video_data_url,
inference_params={},
async_invoke_route=False,
)
params = request["singleEmbeddingParams"]
assert "video" in params
assert params["video"]["format"] == "mp4"
assert params["video"]["source"]["bytes"] == "AAAAIGZ0eXBpc29t"
def test_data_url_audio_parsing(self):
"""Test that data URL audio files are properly parsed."""
config = AmazonNovaEmbeddingConfig()
audio_data_url = "data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAA"
request = config._transform_request(
input=audio_data_url,
inference_params={},
async_invoke_route=False,
)
params = request["singleEmbeddingParams"]
assert "audio" in params
assert params["audio"]["format"] == "mp3"
assert params["audio"]["source"]["bytes"] == "SUQzBAAAAAAAI1RTU0UAAAA"
class TestNovaTransformationResponse: