diff --git a/litellm/llms/sambanova/chat.py b/litellm/llms/sambanova/chat.py index 0e7dadf7062..c849d52e744 100644 --- a/litellm/llms/sambanova/chat.py +++ b/litellm/llms/sambanova/chat.py @@ -8,12 +8,31 @@ from collections.abc import Coroutine from typing import Any, Final, Literal, overload from litellm.litellm_core_utils.prompt_templates.common_utils import ( - handle_messages_with_content_list_to_str_conversion, + convert_content_list_to_str, ) from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig from litellm.types.llms.openai import AllMessageValues +def _flatten_text_only_messages( + messages: list[AllMessageValues], +) -> list[AllMessageValues]: + """ + Flatten a message's content list to a plain string only when it contains + text alone. A content list that carries any non-text part (e.g. image_url) + is left as a list so that part still reaches the SambaNova API, which + accepts content lists for vision-capable models. Flattening those would + drop the image and silently return a text-only answer. + """ + for message in messages: + content = message.get("content") + if isinstance(content, list) and all(isinstance(part, dict) and part.get("type") == "text" for part in content): + texts = convert_content_list_to_str(message=message) + if texts: + message["content"] = texts + return messages + + class SambanovaConfig(OpenAIGPTConfig): """ Reference: https://docs.sambanova.ai/cloud/api-reference/ @@ -117,14 +136,15 @@ class SambanovaConfig(OpenAIGPTConfig): """ Transform messages to handle content list conversion. - SambaNova API doesn't support content as a list - only string content. - This converts content lists like [{"type": "text", "text": "..."}] to strings. + Text-only content lists like [{"type": "text", "text": "..."}] are + flattened to a plain string. Content lists that carry non-text parts + (e.g. image_url) are left intact so those parts still reach the + SambaNova API, which accepts content lists for vision-capable models. """ async def _async_transform(): - return handle_messages_with_content_list_to_str_conversion(messages) + return _flatten_text_only_messages(messages) if is_async: return _async_transform() - messages = handle_messages_with_content_list_to_str_conversion(messages) - return messages + return _flatten_text_only_messages(messages) diff --git a/tests/llm_translation/test_sambanova_chat_transformation.py b/tests/llm_translation/test_sambanova_chat_transformation.py index c2938d530c5..8c58ec6d0e8 100644 --- a/tests/llm_translation/test_sambanova_chat_transformation.py +++ b/tests/llm_translation/test_sambanova_chat_transformation.py @@ -2,7 +2,6 @@ Unit tests for SambaNova chat message transformation """ -import pytest from litellm.llms.sambanova.chat import SambanovaConfig @@ -102,3 +101,34 @@ class TestSambanovaContentListHandling: assert transformed_messages[1]["content"] == "What is the weather?" assert transformed_messages[2]["content"] == "I need your location." assert transformed_messages[3]["content"] == "I'm in San Francisco" + + def test_content_list_with_image_url_is_preserved(self): + """ + A content list carrying a non-text part (image_url) must stay a list so + the image reaches the SambaNova API, instead of being flattened to a + text-only string that silently drops the image. + """ + config = SambanovaConfig() + + image_part = { + "type": "image_url", + "image_url": {"url": "data:image/png;base64,aGVsbG8="}, + } + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "What colors are in this image?"}, + image_part, + ], + } + ] + + transformed_messages = config._transform_messages( + messages=messages, model="sambanova/gpt-oss-120b", is_async=False + ) + + content = transformed_messages[0]["content"] + assert isinstance(content, list) + assert image_part in content + assert any(part.get("type") == "image_url" for part in content)