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

  1. Normalize detail to one of low/high/auto (lowercase) before counting.
  2. 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

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


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/4048bf96d74f6822. Report an issue: GitHub.