Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/google/adk/models/lite_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down Expand Up @@ -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
)
Expand All @@ -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 = {
Expand Down
20 changes: 20 additions & 0 deletions tests/unittests/models/test_litellm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading