BerriAI/litellm · error · ValueError
Voyage multimodal embeddings require a non-empty `image_url`
Error message
Voyage multimodal embeddings require a non-empty `image_url`. Got an image content block without a `url`.
What it means
When normalizing multimodal input, VoyageMultimodalEmbeddingConfig._normalize_content_item expects an image content block ({"type": "image_url", "image_url": {..."url": ...}} or {"type": "image_url", "image_url": "https://..."}) to carry a URL. If the url field is absent or None, this ValueError is raised client-side before any HTTP call. data:image/...;base64 URLs are converted to image_base64 blocks automatically.
Source
Thrown at litellm/llms/voyage/embedding/transformation_multimodal.py:108
or get_secret_str("VOYAGE_AI_API_KEY")
or get_secret_str("VOYAGE_AI_TOKEN")
)
if not api_key:
raise ValueError(
"Voyage API key is required for multimodal embeddings. "
"Set VOYAGE_API_KEY / VOYAGE_AI_API_KEY / VOYAGE_AI_TOKEN "
"or pass `api_key` explicitly."
)
return {"Authorization": f"Bearer {api_key}"}
def _normalize_content_item(self, item: dict[str, Any]) -> dict[str, Any]:
item_type: Final = item.get("type")
if item_type == "image_url":
image_url = item.get("image_url")
if isinstance(image_url, dict):
image_url = image_url.get("url")
if image_url is None:
raise ValueError(
"Voyage multimodal embeddings require a non-empty `image_url`. "
"Got an image content block without a `url`."
)
if isinstance(image_url, str) and image_url.startswith("data:image/"):
_, _, encoded = image_url.partition(",")
return {"type": "image_base64", "image_base64": encoded}
return {"type": "image_url", "image_url": image_url}
return item
def _normalize_input_item(self, item: Any) -> dict[str, Any]:
if isinstance(item, str):
return {"content": [{"type": "text", "text": item}]}
if isinstance(item, dict) and "content" in item:
content: Final = item.get("content") or []
return {
**item,
"content": [self._normalize_content_item(content_item) for content_item in content],
}View on GitHub (pinned to 77b7c6c40c)
Solutions
- Ensure every image content block has a url: {"type": "image_url", "image_url": {"url": "https://..."}} or {"type": "image_url", "image_url": "https://..."}.
- Filter or reject malformed blocks before calling litellm.embedding.
- For local files, convert to a data URL: data:image/png;base64,<b64> - it will be base64-encoded into the request.
- Add a schema check (pydantic model or manual guard) on the input array before sending.
Example fix
# before
inputs = [{"content": [{"type": "image_url", "image_url": {"url": None}}]}]
resp = litellm.embedding(model="voyage-3-multimodal", input=inputs)
# -> ValueError: ...require a non-empty `image_url`...
# after
inputs = [{"content": [{"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}}]}]
resp = litellm.embedding(model="voyage-3-multimodal", input=inputs) Defensive patterns
Strategy: validation
Validate before calling
def valid_voyage_multimodal_input(items: list) -> bool:
for item in items:
for block in item.get("content", []):
if block.get("type") == "image_url":
url = block.get("image_url")
url = url.get("url") if isinstance(url, dict) else url
if not url:
return False
return True
if not valid_voyage_multimodal_input(inputs):
raise ValueError("every image_url block needs a non-empty url")
resp = litellm.embedding(model="voyage-3-multimodal", input=inputs) Type guard
type ImageBlock = { type: "image_url"; image_url: string | { url: string } };
const hasValidImageUrl = (b: unknown): b is ImageBlock => {
if (typeof b !== "object" || b === null || (b as any).type !== "image_url") return false;
const u = (b as any).image_url;
const url = typeof u === "string" ? u : u?.url;
return typeof url === "string" && url.length > 0;
}; Try / catch
try:
resp = litellm.embedding(model="voyage-3-multimodal", input=inputs)
except ValueError as e:
if "non-empty `image_url`" in str(e):
# drop or repair malformed blocks, then retry
inputs = repair_or_drop_image_blocks(inputs)
resp = litellm.embedding(model="voyage-3-multimodal", input=inputs)
else:
raise Prevention
- Validate user-supplied content blocks against the OpenAI image_url shape before sending.
- Reject uploads without a resolved URL at the API boundary, with a 400 to the client.
- Convert local files to data: URLs deterministically in one helper.
When it happens
Trigger: Passing {"type": "image_url", "image_url": {}} or {"type": "image_url"} as a content item; building image blocks from an upstream field that was None/missing (e.g. an API response without a thumbnail); confusing the OpenAI shape where image_url is a dict containing "url" with a bare string here.
Common situations: Integrating user uploads where the URL key is sometimes absent; LLM-produced tool-call payloads with incomplete image blocks; schema drift between an internal Media type and OpenAI-style content parts.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
Related errors
- Voyage API key is required for multimodal embeddings. Set VO
- Invalid image URL: {content_image_url}
- extract_file_data does not accept bare str inputs. Pass byte
- Invalid data URL format: {data_url[:50]}...
- Bedrock batch embedding `input` must be a string (or a singl
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/5216c119d329e024.
Report an issue: GitHub.