From dcf5a2433f7d72e36699da74f1329310bb937b4d Mon Sep 17 00:00:00 2001 From: "Eric (GabiDevFamily)" <271972409+santino18727-debug@users.noreply.github.com> Date: Sat, 13 Jun 2026 17:45:24 +0200 Subject: [PATCH] fix: download and base64-encode remote image URLs for ollama_chat Ollama does not accept image URLs, so extract_images_from_message must send pure base64. Remote http(s) image_url values were forwarded verbatim, making Ollama fail with "illegal base64 data at input byte 5" (the ":" in "https:"). Download and base64-encode them, mirroring the data:-URL handling fixed in #18465. --- .../prompt_templates/common_utils.py | 26 +++++++++++++---- .../test_extract_base64_image.py | 28 +++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/common_utils.py b/litellm/litellm_core_utils/prompt_templates/common_utils.py index fe34731759f..3706fed449d 100644 --- a/litellm/litellm_core_utils/prompt_templates/common_utils.py +++ b/litellm/litellm_core_utils/prompt_templates/common_utils.py @@ -1555,13 +1555,29 @@ def _extract_base64_data(image_url: str) -> str: return image_url +def _extract_ollama_image_data(image_url: str) -> str: + """ + Return pure base64 image data for providers like Ollama that do not accept URLs. + + Remote http(s) URLs are downloaded and base64-encoded; data URLs have their base64 + payload extracted; anything else is returned unchanged. + """ + if image_url.startswith(("http://", "https://")): + from litellm.litellm_core_utils.prompt_templates.image_handling import ( + convert_url_to_base64, + ) + + image_url = convert_url_to_base64(image_url) + return _extract_base64_data(image_url) + + def extract_images_from_message(message: AllMessageValues) -> List[str]: """ Extract images from a message. - For data URLs (e.g., "data:image/png;base64,iVBOR..."), only the base64 - data portion is extracted. This is required for providers like Ollama - that expect pure base64 data rather than full data URLs. + For data URLs (e.g., "data:image/png;base64,iVBOR..."), only the base64 data portion is + extracted, and remote http(s) URLs are downloaded and base64-encoded. This is required for + providers like Ollama that expect pure base64 data rather than URLs or full data URLs. """ images = [] message_content = message.get("content") @@ -1570,9 +1586,9 @@ def extract_images_from_message(message: AllMessageValues) -> List[str]: image_url = m.get("image_url") if image_url: if isinstance(image_url, str): - images.append(_extract_base64_data(image_url)) + images.append(_extract_ollama_image_data(image_url)) elif isinstance(image_url, dict) and "url" in image_url: - images.append(_extract_base64_data(image_url["url"])) + images.append(_extract_ollama_image_data(image_url["url"])) return images diff --git a/tests/test_litellm/litellm_core_utils/test_extract_base64_image.py b/tests/test_litellm/litellm_core_utils/test_extract_base64_image.py index 2d9901a7878..6958cf1f21a 100644 --- a/tests/test_litellm/litellm_core_utils/test_extract_base64_image.py +++ b/tests/test_litellm/litellm_core_utils/test_extract_base64_image.py @@ -7,6 +7,8 @@ which fixes the Ollama error "illegal base64 data at input byte 4". Related issue: https://github.com/BerriAI/litellm/issues/18338 """ +from unittest.mock import patch + import pytest from litellm.litellm_core_utils.prompt_templates.common_utils import ( @@ -91,8 +93,12 @@ class TestExtractImagesFromMessage: result = extract_images_from_message(message) assert result == ["iVBORw0KGgo"] - def test_extract_from_message_with_regular_url(self): - """Test that regular URLs are preserved""" + def test_extract_from_message_with_remote_url_downloads_and_encodes(self): + """Remote http(s) URLs must be downloaded and base64-encoded (issue #30313). + + Ollama does not accept URLs, so forwarding one verbatim made it fail with + "illegal base64 data at input byte 5". + """ message = { "role": "user", "content": [ @@ -102,8 +108,14 @@ class TestExtractImagesFromMessage: } ], } - result = extract_images_from_message(message) - assert result == ["https://example.com/image.png"] + with patch( + "litellm.litellm_core_utils.prompt_templates.image_handling.convert_url_to_base64", + return_value="data:image/png;base64,ZmV0Y2hlZA==", + ) as mock_convert: + result = extract_images_from_message(message) + + mock_convert.assert_called_once_with("https://example.com/image.png") + assert result == ["ZmV0Y2hlZA=="] def test_extract_multiple_images(self): """Test extracting multiple images from a single message""" @@ -124,11 +136,15 @@ class TestExtractImagesFromMessage: }, ], } - result = extract_images_from_message(message) + with patch( + "litellm.litellm_core_utils.prompt_templates.image_handling.convert_url_to_base64", + return_value="data:image/png;base64,image3base64", + ): + result = extract_images_from_message(message) assert result == [ "image1base64", "image2base64", - "https://example.com/image3.png", + "image3base64", ] def test_empty_content(self):