diff --git a/src/google/adk/models/lite_llm.py b/src/google/adk/models/lite_llm.py index 2e4cf65f32..60c2287d01 100644 --- a/src/google/adk/models/lite_llm.py +++ b/src/google/adk/models/lite_llm.py @@ -922,9 +922,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 = ( @@ -965,7 +971,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 ) @@ -974,7 +980,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 6f4341f55f..a0322cb2b5 100644 --- a/tests/unittests/models/test_litellm.py +++ b/tests/unittests/models/test_litellm.py @@ -1928,6 +1928,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(