BerriAI/litellm · error · ValueError
Invalid image URL
Error message
Invalid image URL: {content_image_url} What it means
While converting multimodal chat content to Responses API input, an image_url content block yielded no usable URL: image_url was neither a plain string nor a dict containing a 'url' key (or the url value was None). The Responses API requires input_image.image_url to be an actual URL string, so the bridge rejects the block up front with the offending value echoed in the message.
Solutions
- Ensure every image block is {"type":"image_url","image_url":{"url":"https://..." or "data:image/png;base64,..."}}
- Validate image blocks in a preflight loop and drop or reject blocks without a non-empty url string
- When converting from other formats, map the source's URL/base64 field into the url key explicitly
- Add optional detail only as image_url.detail — never in place of url
Example fix
// before
messages=[{"role":"user","content":[
{"type": "image_url", "image_url": {}}, # ValueError: Invalid image URL: {}
{"type": "text", "text": "what is this?"}]}]
// after
messages=[{"role":"user","content":[
{"type": "image_url", "image_url": {"url": "https://example.com/cat.png", "detail": "auto"}},
{"type": "text", "text": "what is this?"}]}] Defensive patterns
Strategy: validation
Validate before calling
def image_blocks_are_valid(messages) -> bool:
for msg in messages or []:
content = msg.get("content")
if not isinstance(content, list):
continue
for block in content:
if isinstance(block, dict) and block.get("type") == "image_url":
iu = block.get("image_url")
url = iu if isinstance(iu, str) else (iu or {}).get("url") if isinstance(iu, dict) else None
if not isinstance(url, str) or not url:
return False
return True Type guard
def is_valid_image_block(block: dict) -> bool:
iu = block.get("image_url")
return isinstance(iu, str) or (isinstance(iu, dict) and isinstance(iu.get("url"), str) and bool(iu["url"])) Try / catch
try:
resp = litellm.completion(model=m, messages=messages)
except ValueError as e:
if "Invalid image URL" in str(e):
cleaned = [sanitize_image_blocks(msg) for msg in messages] # drop/repair bad blocks
resp = litellm.completion(model=m, messages=cleaned)
else:
raise Prevention
- Preflight multimodal messages: every image_url block needs a non-empty url (https:// or data:image/...;base64,)
- Map foreign image schemas (Anthropic source blocks) explicitly onto image_url.url
- Reject empty image uploads at the API edge before they enter stored conversation history
When it happens
Trigger: messages with {"type":"image_url","image_url":{}} (empty object); image_url key missing or None; url misspelled or nested one level wrong (e.g. passing {"image_url": {"image_url": {...}}}); image blocks produced by another SDK's schema and forwarded unconverted; content where the URL failed to serialize.
Common situations: Multimodal agent loops replaying stored conversations where the URL field was dropped; converting Anthropic-style image blocks (source/base64) to OpenAI format incompletely; frontend forms submitting empty image fields; base64 data URLs built with a typo in the key.
Related errors
- Prop ` ` is not a string
- Invalid first message. Should always start with…
- kwarg `messages` must be an array of messages that follow…
- Message must be a dictionary
- Message must have a 'content' field
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/42717e927be0f77d.
Report an issue: GitHub.
Appendix: source
Thrown at litellm/completion_extras/litellm_responses_transformation/transformation.py:834
self, content: "ChatCompletionImageObject", role: str
) -> "ResponseInputImageParam":
from openai.types.responses import ResponseInputImageParam
content_image_url: Final = content.get("image_url")
actual_image_url: str | None = None
detail: Literal["low", "high", "auto"] | None = None
if isinstance(content_image_url, str):
actual_image_url = content_image_url
elif isinstance(content_image_url, dict):
actual_image_url = content_image_url.get("url")
detail = cast(
Literal["low", "high", "auto"] | None,
content_image_url.get("detail"),
)
if actual_image_url is None:
raise ValueError(f"Invalid image URL: {content_image_url}")
image_param: Final = ResponseInputImageParam(image_url=actual_image_url, detail="auto", type="input_image")
if detail:
image_param["detail"] = detail
return image_param
def _convert_content_to_responses_format(
self,
content: str
| list[object]
| Iterable[
Union["OpenAIMessageContentListBlock", "ChatCompletionThinkingBlock", "ChatCompletionRedactedThinkingBlock"]
]
| None,
role: str,
) -> list[dict[str, object]]:View on GitHub (pinned to 77b7c6c40c)