diff --git a/litellm/utils.py b/litellm/utils.py index 796547cb042..6be1af4271a 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -7941,6 +7941,16 @@ def validate_and_fix_openai_messages(messages: List): if message.get("tool_calls"): message["tool_calls"] = jsonify_tools(tools=message["tool_calls"]) + content = message.get("content") + if isinstance(content, list): + normalized_content = [] + for item in content: + if isinstance(item, str): + normalized_content.append({"type": "text", "text": item}) + else: + normalized_content.append(item) + message["content"] = normalized_content + convert_msg_to_dict = cast(AllMessageValues, convert_to_dict(message)) cleaned_message = cleanup_none_field_in_message(message=convert_msg_to_dict) new_messages.append(cleaned_message) diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index bc60375f906..209831783b8 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -27,6 +27,7 @@ from litellm.utils import ( get_llm_provider, get_optional_params_image_gen, is_cached_message, + validate_and_fix_openai_messages, ) # Adds the parent directory to the system path @@ -3277,6 +3278,37 @@ class TestIsCachedMessage: assert is_cached_message(message) is False +def test_normalize_array_of_strings_in_content(): + """String items in list content become OpenAI multimodal text parts; dict parts unchanged.""" + only_strings = validate_and_fix_openai_messages( + [ + { + "role": "user", + "content": ["what is the capital of France?"], + } + ] + ) + assert only_strings[0]["content"] == [ + {"type": "text", "text": "what is the capital of France?"} + ] + + mixed = validate_and_fix_openai_messages( + [ + { + "role": "user", + "content": [ + "some text", + {"type": "image_url", "image_url": {"url": "https://example.com/x.png"}}, + ], + } + ] + ) + assert mixed[0]["content"] == [ + {"type": "text", "text": "some text"}, + {"type": "image_url", "image_url": {"url": "https://example.com/x.png"}}, + ] + + @pytest.mark.asyncio class TestProxyLoggingBudgetAlerts: """Test budget_alerts method in ProxyLogging class.""" @@ -4107,3 +4139,18 @@ class TestValidateAndFixThinkingParam: validate_and_fix_thinking_param(thinking=thinking) assert "budgetTokens" in thinking assert "budget_tokens" not in thinking + + +def test_normalize_array_of_strings_in_content(): + from litellm.utils import validate_and_fix_openai_messages + + messages = [ + { + "role": "user", + "content": ["what is the capital of France?"], + } + ] + result = validate_and_fix_openai_messages(messages=messages) + assert result[0]["content"] == [ + {"type": "text", "text": "what is the capital of France?"}, + ] \ No newline at end of file