litellm - ✅(Solved) Fix bug: Anthropic rejects image when declared media_type mismatches actual bytes (e.g. PNG header on JPEG data) [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
BerriAI/litellm#24900Fetched 2026-04-08 02:23:55
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Author
Participants
Timeline (top)
cross-referenced ×1labeled ×1referenced ×1

When an image is passed with a data:image/png;base64,... data URI but the actual bytes are JPEG (or any other mismatch), the Anthropic API returns a 400 error:

``` Bad Request: {"error":{"message":"{"type":"error","error":{"type":"invalid_request_error", "message":"messages.92.content.0.tool_result.content.1.image.source.base64: The image was specified using the image/png media type, but the image appears to be a image/jpeg image"}}"}} ```

Error Message

def convert_to_anthropic_image_obj(openai_image_url: str, format: Optional[str]) -> GenericImageParsingChunk: ... media_type, base64_data = openai_image_url.split("data:")[1].split(";base64,") if format: media_type = format else: media_type = media_type.replace("\/", "/")

# Anthropic rejects requests where the declared media_type mismatches the actual
# image bytes. Auto-detect from magic bytes and override the declared type when needed.
try:
    from litellm.litellm_core_utils.token_counter import get_image_type
    image_bytes = base64.b64decode(base64_data[:128])
    detected = get_image_type(image_bytes)
    if detected is not None:
        detected_mime = "image/{}".format(detected)
        if detected_mime != media_type:
            verbose_logger.debug(
                "convert_to_anthropic_image_obj: declared media_type=%s does not match "
                "detected type=%s; using detected type.",
                media_type, detected_mime,
            )
            media_type = detected_mime
except Exception:
    pass  # fall back to declared media_type if detection fails
...

Root Cause

In `litellm/litellm_core_utils/prompt_templates/factory.py`, the function `convert_to_anthropic_image_obj()` extracts the `media_type` directly from the data URI prefix and passes it to Anthropic without any validation:

# line ~894
media_type, base64_data = openai_image_url.split("data:")[1].split(";base64,")
# media_type is trusted as-is — no byte-level verification

This is a common real-world scenario: client SDKs or tools may mislabel images (e.g. send a JPEG with a image/png content type). Anthropic's API strictly validates the declared type against the actual bytes and rejects mismatches.

Fix Action

Fixed

PR fix notes

PR #24901: fix(anthropic): auto-detect image media_type from magic bytes to prevent API rejection

Description (problem / solution / changelog)

Summary

Fixes #24900

Anthropic API strictly validates that the declared media_type in a base64 image source matches the actual image bytes. When they mismatch (e.g. a client sends a JPEG with a data:image/png;base64,... URI, or passes an Anthropic-native message with a wrong media_type), Anthropic returns a 400:

invalid_request_error: messages.92.content.0.tool_result.content.1.image.source.base64:
The image was specified using the image/png media type, but the image appears to be a image/jpeg image

Root Cause

There are two separate code paths that need fixing:

Path 1 — /chat/completions (OpenAI format)

convert_to_anthropic_image_obj() in factory.py extracted media_type directly from the data URI prefix and passed it through without byte-level verification:

# Before: trusted declared type blindly
media_type, base64_data = openai_image_url.split("data:")[1].split(";base64,")

Path 2 — /v1/messages (Anthropic native format)

When clients use the Anthropic-native /v1/messages API, images arrive already in Anthropic format:

{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "..."}}

These messages bypass factory.py entirely — anthropic_messages_handler() forwards them directly to Anthropic without any media_type validation.

Fix

Fix 1 — factory.py (convert_to_anthropic_image_obj)

After extracting the declared media_type, decode the first 128 bytes and run them through the existing get_image_type() magic-byte detector. Override the declared type if there's a mismatch:

try:
    from litellm.litellm_core_utils.token_counter import get_image_type
    image_bytes = base64.b64decode(base64_data[:128])
    detected = get_image_type(image_bytes)
    if detected is not None:
        detected_mime = "image/{}".format(detected)
        if detected_mime != media_type:
            media_type = detected_mime
except Exception:
    pass

Fix 2 — messages/handler.py (anthropic_messages_handler)

Add _fix_image_media_types_in_messages() called before the request is sent. It walks all content blocks (including nested tool_result content) and applies the same magic-byte correction:

def _fix_image_media_types_in_messages(messages):
    # Walk all blocks including tool_result nested content
    # Detect from magic bytes, correct media_type if mismatched
    ...

def anthropic_messages_handler(...):
    messages = _fix_image_media_types_in_messages(messages)
    ...

Why This Approach

  • Zero overhead for correct images: only 128 bytes decoded for magic-byte check
  • Reuses existing infrastructure: get_image_type() already used by BedrockImageProcessor and get_image_dimensions()
  • Graceful degradation: detection wrapped in try/except; any failure preserves original behavior
  • No breaking changes: only changes behavior when declared and actual types mismatch
  • Covers both API formats: /chat/completions (OpenAI) and /v1/messages (Anthropic native)

Changed files

  • litellm/litellm_core_utils/prompt_templates/factory.py (modified, +20/-0)
  • litellm/llms/anthropic/experimental_pass_through/messages/handler.py (modified, +63/-0)

Code Example

# line ~894
media_type, base64_data = openai_image_url.split("data:")[1].split(";base64,")
# media_type is trusted as-is — no byte-level verification

---

def convert_to_anthropic_image_obj(openai_image_url: str, format: Optional[str]) -> GenericImageParsingChunk:
    ...
    media_type, base64_data = openai_image_url.split("data:")[1].split(";base64,")
    if format:
        media_type = format
    else:
        media_type = media_type.replace("\\/", "/")

    # Anthropic rejects requests where the declared media_type mismatches the actual
    # image bytes. Auto-detect from magic bytes and override the declared type when needed.
    try:
        from litellm.litellm_core_utils.token_counter import get_image_type
        image_bytes = base64.b64decode(base64_data[:128])
        detected = get_image_type(image_bytes)
        if detected is not None:
            detected_mime = "image/{}".format(detected)
            if detected_mime != media_type:
                verbose_logger.debug(
                    "convert_to_anthropic_image_obj: declared media_type=%s does not match "
                    "detected type=%s; using detected type.",
                    media_type, detected_mime,
                )
                media_type = detected_mime
    except Exception:
        pass  # fall back to declared media_type if detection fails
    ...
RAW_BUFFERClick to expand / collapse

Description

When an image is passed with a data:image/png;base64,... data URI but the actual bytes are JPEG (or any other mismatch), the Anthropic API returns a 400 error:

``` Bad Request: {"error":{"message":"{"type":"error","error":{"type":"invalid_request_error", "message":"messages.92.content.0.tool_result.content.1.image.source.base64: The image was specified using the image/png media type, but the image appears to be a image/jpeg image"}}"}} ```

Root Cause

In `litellm/litellm_core_utils/prompt_templates/factory.py`, the function `convert_to_anthropic_image_obj()` extracts the `media_type` directly from the data URI prefix and passes it to Anthropic without any validation:

# line ~894
media_type, base64_data = openai_image_url.split("data:")[1].split(";base64,")
# media_type is trusted as-is — no byte-level verification

This is a common real-world scenario: client SDKs or tools may mislabel images (e.g. send a JPEG with a image/png content type). Anthropic's API strictly validates the declared type against the actual bytes and rejects mismatches.

Affected Path

The error occurs in tool_result content blocks (message index 92 in the example), but the same convert_to_anthropic_image_obj() function is used for all Anthropic image inputs.

Existing Infrastructure (Unused)

LiteLLM already has everything needed to fix this:

  • get_image_type() in litellm/litellm_core_utils/token_counter.py — detects image format from magic bytes
  • infer_content_type_from_url_and_content() in prompt_templates/common_utils.py — already uses get_image_type() for Bedrock
  • BedrockImageProcessor already uses byte-level detection

The fix just needs to wire get_image_type() into convert_to_anthropic_image_obj().

Proposed Fix

After extracting the declared media_type, decode the first ~128 bytes of base64 and run magic-byte detection. Override the declared type if there's a mismatch:

def convert_to_anthropic_image_obj(openai_image_url: str, format: Optional[str]) -> GenericImageParsingChunk:
    ...
    media_type, base64_data = openai_image_url.split("data:")[1].split(";base64,")
    if format:
        media_type = format
    else:
        media_type = media_type.replace("\\/", "/")

    # Anthropic rejects requests where the declared media_type mismatches the actual
    # image bytes. Auto-detect from magic bytes and override the declared type when needed.
    try:
        from litellm.litellm_core_utils.token_counter import get_image_type
        image_bytes = base64.b64decode(base64_data[:128])
        detected = get_image_type(image_bytes)
        if detected is not None:
            detected_mime = "image/{}".format(detected)
            if detected_mime != media_type:
                verbose_logger.debug(
                    "convert_to_anthropic_image_obj: declared media_type=%s does not match "
                    "detected type=%s; using detected type.",
                    media_type, detected_mime,
                )
                media_type = detected_mime
    except Exception:
        pass  # fall back to declared media_type if detection fails
    ...

The fix is zero-overhead for correctly-labeled images (no decode needed beyond 128 bytes), and gracefully falls back to the declared type if detection fails.

LiteLLM Version

Reproduced on latest main (commit ~2026-03-30).

Provider

Anthropic (direct + via proxy)

Model

claude-sonnet-4-6 (also affects other Claude models)

extent analysis

TL;DR

Update the convert_to_anthropic_image_obj function to validate the image type by detecting the magic bytes and override the declared type if there's a mismatch.

Guidance

  • Verify that the get_image_type function from litellm/litellm_core_utils/token_counter.py correctly detects the image format from magic bytes.
  • Update the convert_to_anthropic_image_obj function to use get_image_type for byte-level verification and override the declared media type if a mismatch is detected.
  • Test the updated function with various image types and formats to ensure it correctly handles different scenarios.
  • Consider adding additional logging or error handling to handle cases where image type detection fails.

Example

The proposed fix provides an example of how to update the convert_to_anthropic_image_obj function to include magic byte detection:

image_bytes = base64.b64decode(base64_data[:128])
detected = get_image_type(image_bytes)
if detected is not None:
    detected_mime = "image/{}".format(detected)
    if detected_mime != media_type:
        # override the declared media type
        media_type = detected_mime

Notes

The fix assumes that the get_image_type function is reliable and accurate in detecting image formats. If this function is not reliable, additional error handling or alternative detection methods may be necessary.

Recommendation

Apply the proposed workaround by updating the convert_to_anthropic_image_obj function to include magic byte detection and override the declared media type if a mismatch is detected. This will ensure that the Anthropic API receives accurate image type information and reduce the likelihood of 400 errors due to media type mismatches.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING