ValueError / TypeError
ValueError: Invalid image format or TypeError: unsupported operand type(s)
Stack trace
Traceback (most recent call last):
File "script.py", line 42, in <module>
response = model.generate_content(["Describe this image:", image_data])
File "google/generativeai/generative_ai.py", line 185, in generate_content
raise ValueError(f"Invalid image format. Expected PIL Image, bytes, or file path, got {type(image_data)}")
ValueError: Invalid image format. Expected PIL Image, bytes, or file path, got <class 'bytes'>
OR
TypeError: Object of type bytes is not JSON serializable
File "google/generativeai/types/content_types.py", line 72, in _process_image
raise TypeError(f"Image must be PIL.Image.Image or base64-encoded string, got {type(img)}")
TypeError: Image must be PIL.Image.Image or base64-encoded string, got <class 'bytes'> Why it happens
The Gemini API's multimodal input expects images in one of three formats: (1) a PIL Image object passed directly, (2) raw image bytes wrapped in a Part object with mime_type, or (3) a base64-encoded string with explicit mime type. Developers often pass raw bytes directly, base64 strings without mime types, or PIL objects without converting to bytes. The API's type checking fails silently or throws cryptic serialization errors because the image isn't wrapped in the correct Content/Part structure.
Detection
Log the type and format of every image variable before passing to generate_content(). Add assertions: `assert isinstance(img, Image.Image) or isinstance(img, bytes) or isinstance(img, str)`. Use a wrapper function that validates image format and mime type before API calls.
Causes & fixes
Passing raw bytes directly without wrapping in a Part object with mime_type
Use genai.Part(inline_data=genai.Blob(mime_type='image/jpeg', data=image_bytes)) to wrap bytes with mime type, or use model.generate_content([text, Part(...)]) format
Passing PIL Image object directly without converting to bytes first
Convert PIL Image to bytes: from io import BytesIO; buf = BytesIO(); pil_img.save(buf, format='JPEG'); image_bytes = buf.getvalue(); then wrap in Part(inline_data=Blob(...))
Using base64-encoded string without explicit mime_type or in wrong format
Pass base64 string as: genai.Part(inline_data=genai.Blob(mime_type='image/jpeg', data=base64.b64decode(base64_string)))
Incorrect mime type (e.g., image/jpg instead of image/jpeg, or missing entirely)
Use correct mime types: 'image/jpeg' (not 'image/jpg'), 'image/png', 'image/webp', 'image/heic'. Match to actual image format
Code: broken vs fixed
import google.generativeai as genai
import os
from PIL import Image
genai.configure(api_key=os.environ['GOOGLE_API_KEY'])
model = genai.GenerativeModel('gemini-2.0-flash')
# BROKEN: Passing raw PIL Image object directly — causes TypeError
img = Image.open('photo.jpg')
response = model.generate_content(['Describe this image:', img]) # ❌ FAILS
# BROKEN: Passing raw bytes without mime type — causes ValueError
with open('photo.jpg', 'rb') as f:
image_bytes = f.read()
response = model.generate_content(['Describe this image:', image_bytes]) # ❌ FAILS
# BROKEN: Base64 string without Part wrapper — causes serialization error
import base64
with open('photo.jpg', 'rb') as f:
b64_string = base64.b64encode(f.read()).decode('utf-8')
response = model.generate_content(['Describe this image:', b64_string]) # ❌ FAILS import google.generativeai as genai
import os
from PIL import Image
from io import BytesIO
import base64
genai.configure(api_key=os.environ['GOOGLE_API_KEY'])
model = genai.GenerativeModel('gemini-2.0-flash')
# FIXED: Convert PIL Image to bytes, wrap in Part with mime_type
img = Image.open('photo.jpg')
buf = BytesIO()
img.save(buf, format='JPEG')
image_bytes = buf.getvalue()
image_part = genai.Part(inline_data=genai.Blob(mime_type='image/jpeg', data=image_bytes))
response = model.generate_content(['Describe this image:', image_part])
print(f"Response: {response.text}")
# FIXED: Read raw bytes, wrap in Part with mime_type
with open('photo.jpg', 'rb') as f:
image_bytes = f.read()
image_part = genai.Part(inline_data=genai.Blob(mime_type='image/jpeg', data=image_bytes))
response = model.generate_content(['Describe this image:', image_part])
print(f"Response: {response.text}")
# FIXED: Base64 string decoded back to bytes, wrapped in Part
with open('photo.jpg', 'rb') as f:
b64_string = base64.b64encode(f.read()).decode('utf-8')
image_bytes = base64.b64decode(b64_string)
image_part = genai.Part(inline_data=genai.Blob(mime_type='image/jpeg', data=image_bytes))
response = model.generate_content(['Describe this image:', image_part])
print(f"Response: {response.text}") Workaround
Create a helper function that detects image type and auto-converts: def prepare_image(img_input): if isinstance(img_input, Image.Image): buf = BytesIO(); img_input.save(buf, format='JPEG'); data = buf.getvalue(); elif isinstance(img_input, str): data = base64.b64decode(img_input); else: data = img_input; return genai.Part(inline_data=genai.Blob(mime_type='image/jpeg', data=data)). This abstracts the format handling and prevents type errors.
Prevention
Always normalize images to a single internal format (bytes) immediately after loading, wrap in Part/Blob with explicit mime_type before touching the API, and validate image dimensions and file size (Gemini limits images to ~100MB). Use type hints and unit tests: test_image_formats() with PIL, raw bytes, and base64 inputs separately.