You've already forked adk-python
mirror of
https://github.com/encounter/adk-python.git
synced 2026-03-30 10:57:20 -07:00
fix: Handle mixed tool and non-tool parts in LiteLLM content conversion
When converting a `types.Content` to LiteLLM messages, if the content contains both `function_response` parts and other types of parts (e.g., text, image), the function now generates a list of LiteLLM messages Close #4091 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 854240746
This commit is contained in:
committed by
Copybara-Service
parent
62fa4e513c
commit
fdc286a23c
@@ -461,7 +461,8 @@ async def _content_to_message_param(
|
||||
A litellm Message, a list of litellm Messages.
|
||||
"""
|
||||
|
||||
tool_messages = []
|
||||
tool_messages: list[Message] = []
|
||||
non_tool_parts: list[types.Part] = []
|
||||
for part in content.parts:
|
||||
if part.function_response:
|
||||
response = part.function_response.response
|
||||
@@ -477,9 +478,22 @@ async def _content_to_message_param(
|
||||
content=response_content,
|
||||
)
|
||||
)
|
||||
if tool_messages:
|
||||
else:
|
||||
non_tool_parts.append(part)
|
||||
|
||||
if tool_messages and not non_tool_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)
|
||||
|
||||
|
||||
@@ -1813,6 +1813,46 @@ async def test_content_to_message_param_multi_part_function_response():
|
||||
assert messages[1]["content"] == '{"value": 123}'
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_content_to_message_param_function_response_with_extra_parts():
|
||||
tool_part = types.Part.from_function_response(
|
||||
name="load_image",
|
||||
response={"status": "success"},
|
||||
)
|
||||
tool_part.function_response.id = "tool_call_1"
|
||||
|
||||
text_part = types.Part.from_text(text="[Image: img_123.png]")
|
||||
image_bytes = b"test_image_data"
|
||||
image_part = types.Part.from_bytes(data=image_bytes, mime_type="image/png")
|
||||
|
||||
content = types.Content(
|
||||
role="user",
|
||||
parts=[tool_part, text_part, image_part],
|
||||
)
|
||||
|
||||
messages = await _content_to_message_param(content)
|
||||
assert isinstance(messages, list)
|
||||
assert messages == [
|
||||
{
|
||||
"role": "tool",
|
||||
"tool_call_id": "tool_call_1",
|
||||
"content": '{"status": "success"}',
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "[Image: img_123.png]"},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": "data:image/png;base64,dGVzdF9pbWFnZV9kYXRh"
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_content_to_message_param_function_response_preserves_string():
|
||||
"""Tests that string responses are used directly without double-serialization.
|
||||
|
||||
Reference in New Issue
Block a user