Skip to content

fix(aws): preserve image format for Bedrock images#6328

Open
VectorPeak wants to merge 1 commit into
livekit:mainfrom
VectorPeak:codex/aws-bedrock-image-format
Open

fix(aws): preserve image format for Bedrock images#6328
VectorPeak wants to merge 1 commit into
livekit:mainfrom
VectorPeak:codex/aws-bedrock-image-format

Conversation

@VectorPeak

@VectorPeak VectorPeak commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Fixes AWS Bedrock chat-context formatting for non-JPEG ImageContent inputs by deriving the Bedrock image block format from the serialized image MIME type instead of always declaring jpeg.

What Problem This Solves

AWS Bedrock image blocks require the declared format to match the image bytes being sent.

LiveKit already preserves that information when it serializes an ImageContent data URL. For example, a PNG data URL is decoded into raw PNG bytes with mime_type="image/png":

SerializedImage(
    data_bytes=...,        # original PNG bytes
    mime_type="image/png",
)

The bug is in the AWS formatter. It receives the serialized image, but always declares the Bedrock image format as jpeg:

{
    "image": {
        "format": "jpeg",
        "source": {"bytes": img.data_bytes},
    }
}

That is correct only when the serialized image is actually JPEG. For PNG, GIF, or WebP data URLs, the formatter keeps the original non-JPEG bytes but labels them as JPEG in the Bedrock request.

So the problem is not that LiveKit loses or rewrites the image bytes. The problem is that the AWS request metadata no longer matches those bytes.

AWS Bedrock supports png, jpeg, gif, and webp as ImageBlock.format values, so the formatter should use the serialized MIME type to choose the matching Bedrock format.

Change

Map SerializedImage.mime_type to the corresponding Bedrock image format:

image/jpeg -> jpeg
image/png  -> png
image/gif  -> gif
image/webp -> webp

This keeps the existing JPEG behavior for VideoFrame inputs, because serialize_image() currently encodes frames as JPEG.

This also keeps the current Bedrock behavior for external image URLs unchanged: external URLs are still rejected because this formatter path only builds inline Bedrock image blocks.

Evidence

A PNG ImageContent can currently be serialized with the correct MIME type but formatted for AWS with the wrong Bedrock format:

from livekit.agents.llm import ChatContext, ImageContent

ctx = ChatContext.empty()
ctx.add_message(
    role="user",
    content=[ImageContent(image="data:image/png;base64,<png-bytes>")],
)

messages, _ = ctx.to_provider_format(format="aws")

Before this fix, the generated Bedrock message declares the image as JPEG:

{
    "image": {
        "format": "jpeg",
        "source": {"bytes": b"<original png bytes>"},
    }
}

After this fix, the declared format matches the serialized image bytes:

{
    "image": {
        "format": "png",
        "source": {"bytes": b"<original png bytes>"},
    }
}

The same mapping applies to JPEG, GIF, and WebP inputs.

AWS documents ImageBlock.format valid values as png | jpeg | gif | webp: https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ImageBlock.html

Possible call chain / impact

livekit.agents.llm.ChatContext
  -> ChatContext.to_provider_format(format="aws")
    -> livekit.agents.llm._provider_format.aws.to_chat_ctx(...)
      -> _build_image(...)
        -> llm.utils.serialize_image(...)
        -> Bedrock image block

The affected path is only AWS Bedrock chat-context conversion for inline image inputs.

This PR does not change:

  • image serialization itself
  • VideoFrame encoding behavior
  • external URL handling for AWS Bedrock
  • OpenAI, Anthropic, Google, or Mistral provider formatting
  • tool/function-call formatting

Sibling provider surfaces already preserve image MIME type differently: OpenAI and Mistral include the MIME type in data URLs, Anthropic passes media_type, and Google passes mime_type in image parts. AWS was the outlier because it dropped the MIME type and hardcoded the Bedrock format.

Validation

  • uv run --no-sync pytest tests/test_chat_ctx.py -q - passed, 27 passed / 1 skipped
  • uv run --no-sync pytest tests/test_chat_ctx.py -q -k aws_image_content - passed, 5 passed
  • uv run --no-sync ruff check livekit-agents/livekit/agents/llm/_provider_format/aws.py tests/test_chat_ctx.py - passed
  • uv run --no-sync ruff format --check livekit-agents/livekit/agents/llm/_provider_format/aws.py tests/test_chat_ctx.py - passed
  • git diff --check - passed

@VectorPeak VectorPeak requested a review from a team as a code owner July 6, 2026 12:59

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant