From 1ac32992dec19bef4a35f9c403690085843ec04a Mon Sep 17 00:00:00 2001 From: Chesars Date: Fri, 23 Jan 2026 15:25:40 -0300 Subject: [PATCH] fix(oci): serialize imageUrl as object for OCI GenAI API OCI GenAI expects imageUrl to be an object with a 'url' property, not a plain string. This was causing 400 errors when sending images. Fixes #19589 --- litellm/llms/oci/chat/transformation.py | 3 +- litellm/types/llms/oci.py | 9 ++++- .../oci/chat/test_oci_chat_transformation.py | 37 ++++++++++++++++++- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/litellm/llms/oci/chat/transformation.py b/litellm/llms/oci/chat/transformation.py index 7af7be2094a..84f39ef2525 100644 --- a/litellm/llms/oci/chat/transformation.py +++ b/litellm/llms/oci/chat/transformation.py @@ -32,6 +32,7 @@ from litellm.types.llms.oci import ( OCICompletionResponse, OCIContentPartUnion, OCIImageContentPart, + OCIImageUrl, OCIMessage, OCIRoles, OCIServingMode, @@ -1129,7 +1130,7 @@ def adapt_messages_to_generic_oci_standard_content_message( image_url = image_url.get("url") if not isinstance(image_url, str): raise Exception("Prop `image_url` must be a string or an object with a `url` property") - new_content.append(OCIImageContentPart(imageUrl=image_url)) + new_content.append(OCIImageContentPart(imageUrl=OCIImageUrl(url=image_url))) return OCIMessage( role=open_ai_to_generic_oci_role_map[role], diff --git a/litellm/types/llms/oci.py b/litellm/types/llms/oci.py index b9a82cc8b73..9a654bc0f6c 100644 --- a/litellm/types/llms/oci.py +++ b/litellm/types/llms/oci.py @@ -35,11 +35,18 @@ class OCITextContentPart(OCIContentPart): text: str +class OCIImageUrl(BaseModel): + """ImageUrl object for OCI API. See: https://docs.oracle.com/en-us/iaas/tools/python/latest/api/generative_ai_inference/models/oci.generative_ai_inference.models.ImageUrl.html""" + + url: str + detail: Optional[Literal["AUTO", "HIGH", "LOW"]] = None + + class OCIImageContentPart(OCIContentPart): """Image content part for the OCI API.""" type: Literal["IMAGE"] = "IMAGE" - imageUrl: str + imageUrl: OCIImageUrl OCIContentPartUnion = Union[OCITextContentPart, OCIImageContentPart] diff --git a/tests/litellm/llms/oci/chat/test_oci_chat_transformation.py b/tests/litellm/llms/oci/chat/test_oci_chat_transformation.py index f706a025a09..0a6c59d1b44 100644 --- a/tests/litellm/llms/oci/chat/test_oci_chat_transformation.py +++ b/tests/litellm/llms/oci/chat/test_oci_chat_transformation.py @@ -203,6 +203,7 @@ class TestOCIImageUrlTransformation: """Tests for OCI image_url format handling in multimodal messages. Fixes: https://github.com/BerriAI/litellm/issues/18270 + Fixes: https://github.com/BerriAI/litellm/issues/19589 """ def test_image_url_as_string(self): @@ -224,7 +225,8 @@ class TestOCIImageUrlTransformation: assert len(result) == 1 assert result[0].role == "USER" assert len(result[0].content) == 2 - assert result[0].content[1].imageUrl == "https://example.com/image.png" + # imageUrl is now an OCIImageUrl object with a 'url' property + assert result[0].content[1].imageUrl.url == "https://example.com/image.png" def test_image_url_as_openai_object(self): """Test that image_url as OpenAI-style object {"url": "..."} works.""" @@ -245,7 +247,38 @@ class TestOCIImageUrlTransformation: assert len(result) == 1 assert result[0].role == "USER" assert len(result[0].content) == 2 - assert result[0].content[1].imageUrl == "https://example.com/image.png" + # imageUrl is now an OCIImageUrl object with a 'url' property + assert result[0].content[1].imageUrl.url == "https://example.com/image.png" + + def test_image_url_serializes_as_object(self): + """Test that imageUrl serializes as {"url": "..."} for OCI API. + + Fixes: https://github.com/BerriAI/litellm/issues/19589 + OCI expects imageUrl to be an object with a 'url' property, not a plain string. + """ + from litellm.llms.oci.chat.transformation import adapt_messages_to_generic_oci_standard + + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Describe this image."}, + {"type": "image_url", "image_url": {"url": "data:image/png;base64,ABC123"}}, + ], + } + ] + + result = adapt_messages_to_generic_oci_standard(messages) + image_part = result[0].content[1] + + # Serialize as OCI would receive it (with exclude_none=True) + serialized = image_part.model_dump(exclude_none=True) + + # Verify the structure matches OCI's expected format + assert serialized == { + "type": "IMAGE", + "imageUrl": {"url": "data:image/png;base64,ABC123"} + } def test_image_url_invalid_type_raises_error(self): """Test that invalid image_url type raises an error."""