{"record":{"id":"5216c119d329e024","repo":"BerriAI/litellm","slug":"voyage-multimodal-embeddings-require-a-non-empty","errorCode":null,"errorMessage":"Voyage multimodal embeddings require a non-empty `image_url`. Got an image content block without a `url`.","messagePattern":"Voyage multimodal embeddings require a non-empty `image_url`\\. Got an image content block without a `url`\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/llms/voyage/embedding/transformation_multimodal.py","lineNumber":108,"sourceCode":"                or get_secret_str(\"VOYAGE_AI_API_KEY\")\n                or get_secret_str(\"VOYAGE_AI_TOKEN\")\n            )\n        if not api_key:\n            raise ValueError(\n                \"Voyage API key is required for multimodal embeddings. \"\n                \"Set VOYAGE_API_KEY / VOYAGE_AI_API_KEY / VOYAGE_AI_TOKEN \"\n                \"or pass `api_key` explicitly.\"\n            )\n        return {\"Authorization\": f\"Bearer {api_key}\"}\n\n    def _normalize_content_item(self, item: dict[str, Any]) -> dict[str, Any]:\n        item_type: Final = item.get(\"type\")\n        if item_type == \"image_url\":\n            image_url = item.get(\"image_url\")\n            if isinstance(image_url, dict):\n                image_url = image_url.get(\"url\")\n            if image_url is None:\n                raise ValueError(\n                    \"Voyage multimodal embeddings require a non-empty `image_url`. \"\n                    \"Got an image content block without a `url`.\"\n                )\n            if isinstance(image_url, str) and image_url.startswith(\"data:image/\"):\n                _, _, encoded = image_url.partition(\",\")\n                return {\"type\": \"image_base64\", \"image_base64\": encoded}\n            return {\"type\": \"image_url\", \"image_url\": image_url}\n        return item\n\n    def _normalize_input_item(self, item: Any) -> dict[str, Any]:\n        if isinstance(item, str):\n            return {\"content\": [{\"type\": \"text\", \"text\": item}]}\n        if isinstance(item, dict) and \"content\" in item:\n            content: Final = item.get(\"content\") or []\n            return {\n                **item,\n                \"content\": [self._normalize_content_item(content_item) for content_item in content],\n            }","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/voyage/embedding/transformation_multimodal.py#L90-L126","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\ninputs = [{\"content\": [{\"type\": \"image_url\", \"image_url\": {\"url\": None}}]}]\nresp = litellm.embedding(model=\"voyage-3-multimodal\", input=inputs)\n# -> ValueError: ...require a non-empty `image_url`...\n\n# after\ninputs = [{\"content\": [{\"type\": \"image_url\", \"image_url\": {\"url\": \"https://example.com/cat.jpg\"}}]}]\nresp = litellm.embedding(model=\"voyage-3-multimodal\", input=inputs)","handlingStrategy":"validation","validationCode":"def valid_voyage_multimodal_input(items: list) -> bool:\n    for item in items:\n        for block in item.get(\"content\", []):\n            if block.get(\"type\") == \"image_url\":\n                url = block.get(\"image_url\")\n                url = url.get(\"url\") if isinstance(url, dict) else url\n                if not url:\n                    return False\n    return True\n\nif not valid_voyage_multimodal_input(inputs):\n    raise ValueError(\"every image_url block needs a non-empty url\")\nresp = litellm.embedding(model=\"voyage-3-multimodal\", input=inputs)","typeGuard":"type ImageBlock = { type: \"image_url\"; image_url: string | { url: string } };\n\nconst hasValidImageUrl = (b: unknown): b is ImageBlock => {\n  if (typeof b !== \"object\" || b === null || (b as any).type !== \"image_url\") return false;\n  const u = (b as any).image_url;\n  const url = typeof u === \"string\" ? u : u?.url;\n  return typeof url === \"string\" && url.length > 0;\n};","tryCatchPattern":"try:\n    resp = litellm.embedding(model=\"voyage-3-multimodal\", input=inputs)\nexcept ValueError as e:\n    if \"non-empty `image_url`\" in str(e):\n        # drop or repair malformed blocks, then retry\n        inputs = repair_or_drop_image_blocks(inputs)\n        resp = litellm.embedding(model=\"voyage-3-multimodal\", input=inputs)\n    else:\n        raise","preventionTips":["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."],"tags":["voyage","multimodal","image-url","input-validation","embedding","litellm"],"backgroundTag":"missing-required-field","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}