BerriAI/litellm · error · ValueError
Invalid image_url type: {type(image_url).__name__}. Expected
Error message
Invalid image_url type: {type(image_url).__name__}. Expected str or dict with 'url' field. What it means
An image_url field that is neither str nor dict (int, list, None, bytes, file object) reaches the token counter, which only understands the two OpenAI schema shapes: a URL string or {'url':..., 'detail':...}. The actual Python type name is included in the message.
Source
Thrown at litellm/litellm_core_utils/token_counter.py:607
raise ValueError(f"Invalid detail value: {detail}. Expected 'low', 'high', or 'auto'.")
url: Final = image_url.get("url")
if not url:
raise ValueError("Missing required key 'url' in image_url dict.")
return calculate_img_tokens(
data=url,
mode=detail,
use_default_image_token_count=use_default_image_token_count,
)
elif isinstance(image_url, str):
if not image_url.strip():
raise ValueError("Empty image_url string is not valid.")
return calculate_img_tokens(
data=image_url,
mode="auto",
use_default_image_token_count=use_default_image_token_count,
)
else:
raise ValueError(f"Invalid image_url type: {type(image_url).__name__}. Expected str or dict with 'url' field.")
def _validate_anthropic_content(content: Mapping[str, Any]) -> type:
"""
Validate and determine which Anthropic TypedDict applies.
Returns the corresponding TypedDict class if recognized, otherwise raises.
"""
content_type: Final = content.get("type")
if not content_type:
raise ValueError("Anthropic content missing required field: 'type'")
mapping: Final = {
"tool_use": AnthropicMessagesToolUseParam,
"tool_result": AnthropicMessagesToolResultParam,
}
expected_cls: Final = mapping.get(content_type)View on GitHub (pinned to 6c2dcb801b)
Solutions
- Convert to one of the two supported shapes: a string URL/data-URI or a dict with 'url'.
- For local files, base64-encode into a data URI: "data:image/png;base64," + b64.
Example fix
# before
block = {"type": "image_url", "image_url": file.read()} # bytes
# after
import base64
b64 = base64.b64encode(file.read()).decode()
block = {"type": "image_url", "image_url": {"url": "data:image/png;base64," + b64}} Defensive patterns
Strategy: type-guard
Validate before calling
import base64
def to_image_block(img):
if isinstance(img, str):
return {"type": "image_url", "image_url": img}
if isinstance(img, (bytes, bytearray)):
b64 = base64.b64encode(img).decode()
return {"type": "image_url", "image_url": {"url": "data:image/png;base64," + b64}}
if isinstance(img, dict) and img.get("url"):
return {"type": "image_url", "image_url": img}
raise ValueError("unsupported image payload " + type(img).__name__) Type guard
def is_supported_image_url(value) -> bool:
return isinstance(value, str) or (isinstance(value, dict) and bool(value.get("url"))) Prevention
- Convert files/bytes to base64 data URIs at ingest; never pass raw bytes into message content.
- Keep one canonical image-block builder used by every call site.
When it happens
Trigger: image_url=None (block built but URL never set); image_url=[url] (over-wrapped in a list); raw bytes or file-like objects from an upload handler passed directly into message content.
Common situations: Upload pipelines passing file objects or bytes through; inconsistent wrapping layers (double-wrapping in lists/dicts); None defaults leaking from config.
Related errors
- Invalid detail value: {detail}. Expected 'low', 'high', or '
- Missing required key 'url' in image_url dict.
- Empty image_url string is not valid.
- limit must be an integer
- api base needs to be a string. api_base={api_base}
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/429ab15e34b314e2.
Report an issue: GitHub.