fix: avoid pre-serializing dict values in Interactions API to prevent double-escaping

PiperOrigin-RevId: 916037238
This commit is contained in:
Google Team Member
2026-05-15 09:16:07 -07:00
committed by Copybara-Service
parent 115124cdf4
commit 85f397d20f
2 changed files with 39 additions and 12 deletions
+5 -5
View File
@@ -111,12 +111,12 @@ def convert_part_to_interaction_content(part: types.Part) -> Optional[dict]:
).decode('utf-8')
return result
elif part.function_response is not None:
# Convert the function response to a string for the interactions API
# The interactions API expects result to be either a string or items list
# Pass the function response through to the interactions API.
# Dict and list values are passed directly — the Interactions API handles
# JSON serialization internally. Pre-serializing with json.dumps() would
# cause double-escaping.
result = part.function_response.response
if isinstance(result, dict):
result = json.dumps(result)
elif not isinstance(result, str):
if not isinstance(result, (dict, str, list)):
result = str(result)
logger.debug(
'Converting function_response: name=%s, call_id=%s',
@@ -280,11 +280,9 @@ class TestConvertPartToInteractionContent:
assert result['type'] == 'function_result'
assert result['call_id'] == 'call_123'
assert result['name'] == 'get_weather'
# Dict should be JSON serialized
assert json.loads(result['result']) == {
'temperature': 20,
'condition': 'sunny',
}
# Dict should be passed through directly (not JSON-serialized).
assert result['result'] == {'temperature': 20, 'condition': 'sunny'}
assert isinstance(result['result'], dict)
def test_function_response_simple(self):
"""Test converting a function response Part with simple response."""
@@ -299,8 +297,37 @@ class TestConvertPartToInteractionContent:
assert result['type'] == 'function_result'
assert result['call_id'] == 'call_123'
assert result['name'] == 'check_weather'
# Dict should be JSON serialized
assert json.loads(result['result']) == {'message': 'Weather is sunny'}
# Dict should be passed through directly (not JSON-serialized).
assert result['result'] == {'message': 'Weather is sunny'}
def test_function_response_dict_not_double_serialized(self):
"""Regression test: avoid double-serializing bash tool outputs.
Bash tool responses contain JSON structures (stdout/stderr). When these
dict responses were json.dumps()'d before being sent to the Interactions
API, the API's own serialization would escape the already-escaped content,
producing unreadable output like:
{"result":"\\\"{\\\\\\\"error\\\\\\\":\\\\\\\"...\\\\\\\"}\\\""
"""
bash_response = {
'stdout': '{"name": "test", "version": "1.0"}\n',
'stderr': '',
}
part = types.Part(
function_response=types.FunctionResponse(
id='call_bash',
name='bash',
response=bash_response,
)
)
result = interactions_utils.convert_part_to_interaction_content(part)
# The result value must be the dict itself, NOT a JSON string.
assert isinstance(result['result'], dict)
assert result['result'] == bash_response
# Verify there's no double-escaping: if result were a JSON string,
# serializing it again would add backslashes before the internal quotes.
wire_json = json.dumps(result)
assert '\\\\' not in wire_json
def test_inline_data_image(self):
"""Test converting an inline image Part."""