diff --git a/src/google/adk/models/lite_llm.py b/src/google/adk/models/lite_llm.py index f160c5f927..f545e3ddd7 100644 --- a/src/google/adk/models/lite_llm.py +++ b/src/google/adk/models/lite_llm.py @@ -983,9 +983,15 @@ async def _content_to_message_param( """ _ensure_litellm_imported() + # A types.Content may have parts=None (e.g. types.Content(role="user")) or an + # empty list (e.g. from _append_fallback_user_content_if_missing). Normalize to + # an iterable so the loops below do not raise, matching the google_llm adapter + # which skips contents without parts. + parts = content.parts or [] + tool_messages: list[Message] = [] non_tool_parts: list[types.Part] = [] - for part in content.parts: + for part in parts: if part.function_response: response = part.function_response.response response_content = ( @@ -1026,7 +1032,7 @@ async def _content_to_message_param( role = _to_litellm_role(content.role) if role == "user": - user_parts = [part for part in content.parts if not part.thought] + user_parts = [part for part in parts if not part.thought] message_content = ( await _get_content(user_parts, provider=provider, model=model) or None ) @@ -1035,7 +1041,7 @@ async def _content_to_message_param( tool_calls = [] content_parts: list[types.Part] = [] reasoning_parts: list[types.Part] = [] - for part in content.parts: + for part in parts: if part.function_call: tool_call_id = part.function_call.id or "" tool_call_dict: ChatCompletionAssistantToolCall = { diff --git a/tests/unittests/models/test_litellm.py b/tests/unittests/models/test_litellm.py index 247f3c4a30..0033979cfb 100644 --- a/tests/unittests/models/test_litellm.py +++ b/tests/unittests/models/test_litellm.py @@ -1929,6 +1929,26 @@ async def test_content_to_message_param_user_message(): assert message["content"] == "Test prompt" +@pytest.mark.asyncio +@pytest.mark.parametrize("parts", [None, []]) +async def test_content_to_message_param_user_message_without_parts(parts): + # types.Content(role="user") defaults parts to None, so a content with no + # parts must not raise (mirrors the google_llm adapter which skips it). + content = types.Content(role="user", parts=parts) + message = await _content_to_message_param(content) + assert message["role"] == "user" + assert message["content"] is None + + +@pytest.mark.asyncio +@pytest.mark.parametrize("parts", [None, []]) +async def test_content_to_message_param_assistant_message_without_parts(parts): + content = types.Content(role="assistant", parts=parts) + message = await _content_to_message_param(content) + assert message["role"] == "assistant" + assert message["content"] is None + + @pytest.mark.asyncio @pytest.mark.parametrize("file_uri,mime_type", FILE_URI_TEST_CASES) async def test_content_to_message_param_user_message_with_file_uri(