diff --git a/litellm/llms/fireworks_ai/chat/transformation.py b/litellm/llms/fireworks_ai/chat/transformation.py index 8407e8ab69..6b654ebdfd 100644 --- a/litellm/llms/fireworks_ai/chat/transformation.py +++ b/litellm/llms/fireworks_ai/chat/transformation.py @@ -185,11 +185,16 @@ class FireworksAIConfig(OpenAIGPTConfig): ): # allow user to toggle this feature. return content if isinstance(content["image_url"], str): - content["image_url"] = f"{content['image_url']}#transform=inline" + # Skip base64 data URLs — appending #transform=inline corrupts the + # base64 payload and causes an "Incorrect padding" decode error on + # the Fireworks side. Data URLs are already inlined by definition. + # Lower-case before checking: URI schemes are case-insensitive (RFC 3986). + if not content["image_url"].lower().startswith("data:"): + content["image_url"] = f"{content['image_url']}#transform=inline" elif isinstance(content["image_url"], dict): - content["image_url"][ - "url" - ] = f"{content['image_url']['url']}#transform=inline" + url = content["image_url"]["url"] + if not url.lower().startswith("data:"): + content["image_url"]["url"] = f"{url}#transform=inline" return content def _transform_tools( diff --git a/tests/llm_translation/test_fireworks_ai_translation.py b/tests/llm_translation/test_fireworks_ai_translation.py index b9abbd501d..24c0d546e2 100644 --- a/tests/llm_translation/test_fireworks_ai_translation.py +++ b/tests/llm_translation/test_fireworks_ai_translation.py @@ -161,6 +161,24 @@ def test_document_inlining_example(disable_add_transform_inline_image_block): "vision-gpt", "http://example.com/image.png", ), + # data: URLs must never have #transform=inline appended — doing so + # corrupts the base64 payload (fixes #23583). + # URI schemes are case-insensitive (RFC 3986) so check all variants. + ( + {"image_url": "data:image/png;base64,iVBORw0KGgo="}, + "gpt-4", + "data:image/png;base64,iVBORw0KGgo=", + ), + ( + {"image_url": {"url": "data:image/jpeg;base64,/9j/4AAQ=="}}, + "gpt-4", + {"url": "data:image/jpeg;base64,/9j/4AAQ=="}, + ), + ( + {"image_url": "Data:image/png;base64,iVBORw0KGgo="}, + "gpt-4", + "Data:image/png;base64,iVBORw0KGgo=", + ), ], ) def test_transform_inline(content, model, expected_url): diff --git a/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py b/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py index 5d5aaa64c8..2b71b88356 100644 --- a/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py +++ b/tests/test_litellm/llms/fireworks_ai/chat/test_fireworks_ai_chat_transformation.py @@ -110,6 +110,35 @@ def test_get_supported_openai_params_reasoning_effort(): assert "reasoning_effort" not in unsupported_params +def test_add_transform_inline_image_block_skips_data_urls(): + """ + data: URLs must not have #transform=inline appended — doing so corrupts the + base64 payload and raises binascii.Error: Incorrect padding on the Fireworks side. + Regression test for https://github.com/BerriAI/litellm/issues/23583 + """ + config = FireworksAIConfig() + data_url = "data:image/jpeg;base64,/9j/4AAQSkZJRgAB" + + # str branch + str_content = {"type": "image_url", "image_url": data_url} + result = config._add_transform_inline_image_block( + str_content, model="non-vision-model", disable_add_transform_inline_image_block=False + ) + assert result["image_url"] == data_url, "data URL must not be modified (str branch)" + + # dict branch + dict_content = {"type": "image_url", "image_url": {"url": data_url}} + result = config._add_transform_inline_image_block( + dict_content, model="non-vision-model", disable_add_transform_inline_image_block=False + ) + assert result["image_url"]["url"] == data_url, "data URL must not be modified (dict branch)" + + # regular https URL should still get the suffix + https_content = {"type": "image_url", "image_url": "https://example.com/image.jpg"} + result = config._add_transform_inline_image_block( + https_content, model="non-vision-model", disable_add_transform_inline_image_block=False + ) + assert result["image_url"].endswith("#transform=inline"), "https URL should get #transform=inline" @pytest.mark.parametrize( "api_base, expected_url_prefix", [