BerriAI/litellm · error · ValueError
Invalid detail value: {detail}. Expected 'low', 'high', or '
Error message
Invalid detail value: {detail}. Expected 'low', 'high', or 'auto'. What it means
For image content blocks passed as dicts, the 'detail' field must be exactly 'low', 'high', or 'auto' (default 'auto'). Any other value - including casing variants like 'High' and typos like 'medium' - is rejected before token math, because image token cost depends directly on the detail level.
Source
Thrown at litellm/litellm_core_utils/token_counter.py:589
use_default_image_token_count: bool,
) -> int:
"""
Count tokens for an image_url content block.
Args:
image_url: The image URL data - can be a string URL or dict with 'url' and 'detail'
use_default_image_token_count: Whether to use default image token counts
Returns:
int: Number of tokens for the image
Raises:
ValueError: If image_url is invalid type or detail value is invalid
"""
if isinstance(image_url, dict):
detail: Final = image_url.get("detail", "auto")
if detail not in ["low", "high", "auto"]:
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.")View on GitHub (pinned to 6c2dcb801b)
Solutions
- Normalize detail to one of low/high/auto (lowercase) before counting.
- Validate at your API boundary so bad values never reach litellm.
Example fix
# before
block = {"type": "image_url", "image_url": {"url": u, "detail": "High"}}
# after
detail = block["image_url"].get("detail", "auto").lower()
if detail not in ("low", "high", "auto"):
detail = "auto"
block["image_url"]["detail"] = detail Defensive patterns
Strategy: validation
Validate before calling
for block in content:
iu = block.get("image_url") if isinstance(block, dict) else None
if isinstance(iu, dict):
d = str(iu.get("detail", "auto")).lower()
iu["detail"] = d if d in ("low", "high", "auto") else "auto" Type guard
def has_valid_detail(image_url: dict) -> bool:
return image_url.get("detail", "auto") in ("low", "high", "auto") Prevention
- Lowercase and whitelist detail at the API boundary.
- Never forward unvalidated user-supplied detail values into token counting.
When it happens
Trigger: An image_url dict with detail='FULL' (wrong casing), 'medium', None, or a user-supplied detail value forwarded without validation, passed to token_counter or cost calculation.
Common situations: Passing user-supplied request bodies straight into token counting; API gateways forwarding non-normalized detail values; copy-paste from provider docs that use different casing.
Related errors
- text and messages cannot both be set
- tools or tool_choice cannot be set if using text
- Missing required key 'url' in image_url dict.
- Empty image_url string is not valid.
- Invalid image_url type: {type(image_url).__name__}. Expected
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/4048bf96d74f6822.
Report an issue: GitHub.