Skip to content

Commit 7a7083c

Browse files
GWealecopybara-github
authored andcommitted
fix: accumulate list values when merging parallel tool call state_delta
Parallel tool calls writing list values to the same state_delta key were silently dropped because deep_merge_dicts only recursed into dict values; lists hit the overwrite branch. Closes #5190 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 905229706
1 parent 66bfedc commit 7a7083c

2 files changed

Lines changed: 63 additions & 1 deletion

File tree

src/google/adk/flows/llm_flows/functions.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1134,10 +1134,12 @@ def __build_response_event(
11341134

11351135

11361136
def deep_merge_dicts(d1: dict, d2: dict) -> dict:
1137-
"""Recursively merges d2 into d1."""
1137+
"""Recursively merges d2 into d1; concatenates list values at the same key."""
11381138
for key, value in d2.items():
11391139
if key in d1 and isinstance(d1[key], dict) and isinstance(value, dict):
11401140
d1[key] = deep_merge_dicts(d1[key], value)
1141+
elif key in d1 and isinstance(d1[key], list) and isinstance(value, list):
1142+
d1[key] = d1[key] + value
11411143
else:
11421144
d1[key] = value
11431145
return d1

tests/unittests/flows/llm_flows/test_functions_simple.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from google.adk.events.event import Event
2121
from google.adk.events.event_actions import EventActions
2222
from google.adk.events.ui_widget import UiWidget
23+
from google.adk.flows.llm_flows.functions import deep_merge_dicts
2324
from google.adk.flows.llm_flows.functions import find_matching_function_call
2425
from google.adk.flows.llm_flows.functions import handle_function_calls_async
2526
from google.adk.flows.llm_flows.functions import handle_function_calls_live
@@ -1236,3 +1237,62 @@ async def mock_run(*args, **kwargs):
12361237
# Verify the image was converted to a blob
12371238
assert len(response_part.parts) == 1
12381239
assert response_part.parts[0].inline_data is not None
1240+
1241+
1242+
def test_deep_merge_dicts_concatenates_lists_at_same_key():
1243+
d1 = {'items': ['a', 'b']}
1244+
d2 = {'items': ['c', 'd']}
1245+
assert deep_merge_dicts(d1, d2) == {'items': ['a', 'b', 'c', 'd']}
1246+
1247+
1248+
def test_deep_merge_dicts_concatenates_nested_lists():
1249+
d1 = {'state_delta': {'items': ['a']}}
1250+
d2 = {'state_delta': {'items': ['b']}}
1251+
assert deep_merge_dicts(d1, d2) == {'state_delta': {'items': ['a', 'b']}}
1252+
1253+
1254+
def test_deep_merge_dicts_overwrites_when_types_differ():
1255+
d1 = {'x': ['a']}
1256+
d2 = {'x': 'b'}
1257+
assert deep_merge_dicts(d1, d2) == {'x': 'b'}
1258+
1259+
d1 = {'x': 'a'}
1260+
d2 = {'x': ['b']}
1261+
assert deep_merge_dicts(d1, d2) == {'x': ['b']}
1262+
1263+
1264+
def test_deep_merge_dicts_overwrites_scalars():
1265+
d1 = {'x': 1, 'y': 'old'}
1266+
d2 = {'x': 2, 'y': 'new'}
1267+
assert deep_merge_dicts(d1, d2) == {'x': 2, 'y': 'new'}
1268+
1269+
1270+
def test_merge_parallel_function_response_events_concatenates_state_delta_lists():
1271+
"""Regression test for https://github.com/google/adk-python/issues/5190."""
1272+
function_response1 = types.FunctionResponse(
1273+
id='func_1', name='append_item', response={'result': 'ok'}
1274+
)
1275+
function_response2 = types.FunctionResponse(
1276+
id='func_2', name='append_item', response={'result': 'ok'}
1277+
)
1278+
1279+
event1 = Event(
1280+
invocation_id='inv_1',
1281+
author='test_agent',
1282+
content=types.Content(
1283+
role='user', parts=[types.Part(function_response=function_response1)]
1284+
),
1285+
actions=EventActions(state_delta={'items': ['a']}),
1286+
)
1287+
event2 = Event(
1288+
invocation_id='inv_1',
1289+
author='test_agent',
1290+
content=types.Content(
1291+
role='user', parts=[types.Part(function_response=function_response2)]
1292+
),
1293+
actions=EventActions(state_delta={'items': ['b']}),
1294+
)
1295+
1296+
merged_event = merge_parallel_function_response_events([event1, event2])
1297+
1298+
assert merged_event.actions.state_delta == {'items': ['a', 'b']}

0 commit comments

Comments
 (0)