fix: Handle string function responses in LiteLLM conversion

When converting `types.Content` with a `function_response` to LiteLLM's `ChatCompletionToolMessage`, if the response is already a string, use it directly. Otherwise, serialize the response to JSON. This prevents double-serialization of string payloads

Close #3676

Co-authored-by: George Weale <gweale@google.com>
PiperOrigin-RevId: 840013822
This commit is contained in:
George Weale
2025-12-03 19:17:16 -08:00
committed by Copybara-Service
parent 0a07a667e9
commit 2b64715505
2 changed files with 44 additions and 1 deletions
+37
View File
@@ -1382,6 +1382,43 @@ 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_preserves_string():
"""Tests that string responses are used directly without double-serialization.
The google.genai FunctionResponse.response field is typed as dict, but
_content_to_message_param defensively handles string responses to avoid
double-serialization. This test verifies that behavior by mocking a
function_response with a string response attribute.
"""
response_payload = '{"type": "files", "count": 2}'
# Create a Part with a dict response, then mock the response to be a string
# to simulate edge cases where response might be set directly as a string
part = types.Part.from_function_response(
name="list_files",
response={"placeholder": "will be mocked"},
)
# Mock the response attribute to return a string
# Using Mock without spec_set to allow setting response to a string,
# which simulates the edge case we're testing
mock_function_response = Mock(spec=types.FunctionResponse)
mock_function_response.response = response_payload
mock_function_response.id = "tool_call_1"
part.function_response = mock_function_response
content = types.Content(
role="tool",
parts=[part],
)
message = await _content_to_message_param(content)
assert message["role"] == "tool"
assert message["tool_call_id"] == "tool_call_1"
assert message["content"] == response_payload
@pytest.mark.asyncio
async def test_content_to_message_param_assistant_message():
content = types.Content(