Skip to content
Closed
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
164 changes: 89 additions & 75 deletions src/google/adk/models/lite_llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,19 +450,27 @@ async def _content_to_message_param(
) -> Union[Message, list[Message]]:
"""Converts a types.Content to a litellm Message or list of Messages.

Handles multipart function responses by returning a list of
ChatCompletionToolMessage objects if multiple function_response parts exist.
This function processes a `types.Content` object, which may contain multiple
parts, and converts them into a format suitable for LiteLLM. It handles
mixed content, such as tool responses alongside text or other media, by
generating a list of messages.

- `function_response` parts are converted into `tool` role messages.
- Other parts (text, images, etc.) are grouped and converted into a
single `user` or `assistant` message.

Args:
content: The content to convert.
provider: The LLM provider name (e.g., "openai", "azure").

Returns:
A litellm Message, a list of litellm Messages.
A single litellm Message, a list of litellm Messages, or an empty list if
the content is empty.
"""

tool_messages: list[Message] = []
non_tool_parts: list[types.Part] = []
tool_messages = []
other_parts = []

# 1. Separate function responses from other content (Text, Images, etc.)
for part in content.parts:
if part.function_response:
response = part.function_response.response
Expand All @@ -479,82 +487,88 @@ async def _content_to_message_param(
)
)
else:
non_tool_parts.append(part)
other_parts.append(part)

if tool_messages and not non_tool_parts:
# 2. If ONLY tools are present, return them immediately (Original behavior)
if tool_messages and not other_parts:
return tool_messages if len(tool_messages) > 1 else tool_messages[0]

if tool_messages and non_tool_parts:
follow_up = await _content_to_message_param(
types.Content(role=content.role, parts=non_tool_parts),
provider=provider,
)
follow_up_messages = (
follow_up if isinstance(follow_up, list) else [follow_up]
)
return tool_messages + follow_up_messages

# Handle user or assistant messages
role = _to_litellm_role(content.role)

if role == "user":
user_parts = [part for part in content.parts if not part.thought]
message_content = await _get_content(user_parts, provider=provider) or None
return ChatCompletionUserMessage(role="user", content=message_content)
else: # assistant/model
tool_calls = []
content_parts: list[types.Part] = []
reasoning_parts: list[types.Part] = []
for part in content.parts:
if part.function_call:
tool_calls.append(
ChatCompletionAssistantToolCall(
type="function",
id=part.function_call.id,
function=Function(
name=part.function_call.name,
arguments=_safe_json_serialize(part.function_call.args),
),
)
)
elif part.thought:
reasoning_parts.append(part)
else:
content_parts.append(part)
# 3. Handle user or assistant messages for any non-tool parts
extra_message = None
if other_parts:
role = _to_litellm_role(content.role)

if role == "user":
# Original logic for User messages
user_parts = [part for part in other_parts if not part.thought]
message_content = await _get_content(user_parts, provider=provider) or None
extra_message = ChatCompletionUserMessage(role="user", content=message_content)

else: # assistant/model
# Original logic for Assistant messages (Tool calls + Reasoning)
tool_calls = []
content_parts: list[types.Part] = []
reasoning_parts: list[types.Part] = []
for part in other_parts:
if part.function_call:
tool_calls.append(
ChatCompletionAssistantToolCall(
type="function",
id=part.function_call.id,
function=Function(
name=part.function_call.name,
arguments=_safe_json_serialize(part.function_call.args),
),
)
)
elif part.thought:
reasoning_parts.append(part)
else:
content_parts.append(part)

final_content = (
await _get_content(content_parts, provider=provider)
if content_parts
else None
)
if final_content and isinstance(final_content, list):
# when the content is a single text object, we can use it directly.
# this is needed for ollama_chat provider which fails if content is a list
final_content = (
final_content[0].get("text", "")
if final_content[0].get("type", None) == "text"
else final_content
await _get_content(content_parts, provider=provider)
if content_parts
else None
)

# Handle Ollama specific formatting from original code
if final_content and isinstance(final_content, list):
final_content = (
final_content[0].get("text", "")
if final_content[0].get("type", None) == "text"
else final_content
)

reasoning_texts = []
for part in reasoning_parts:
if part.text:
reasoning_texts.append(part.text)
elif (
part.inline_data
and part.inline_data.data
and part.inline_data.mime_type
and part.inline_data.mime_type.startswith("text/")
):
reasoning_texts.append(_decode_inline_text_data(part.inline_data.data))

reasoning_content = _NEW_LINE.join(text for text in reasoning_texts if text)
return ChatCompletionAssistantMessage(
role=role,
content=final_content,
tool_calls=tool_calls or None,
reasoning_content=reasoning_content or None,
)
# Handle reasoning/thoughts using original logic
reasoning_texts = []
for part in reasoning_parts:
if part.text:
reasoning_texts.append(part.text)
elif (
part.inline_data
and part.inline_data.data
and part.inline_data.mime_type
and part.inline_data.mime_type.startswith("text/")
):
reasoning_texts.append(_decode_inline_text_data(part.inline_data.data))

reasoning_content = _NEW_LINE.join(text for text in reasoning_texts if text)

extra_message = ChatCompletionAssistantMessage(
role=role,
content=final_content,
tool_calls=tool_calls or None,
reasoning_content=reasoning_content or None,
)

# 4. Final step: Combine tool results and the extra message (Original logic fix)
final_result = tool_messages + ([extra_message] if extra_message else [])

if not final_result:
return []

return final_result if len(final_result) > 1 else final_result[0]


def _ensure_tool_results(messages: List[Message]) -> List[Message]:
Expand Down