Describe the bug
EventActions does not capture all the action when there are multiple function calls in an Event.
For example, if an Event generated from LLM contains two functionCall
content:
parts:
0:
functionCall:
id: "adk-645f8032-712b-41a7-8e27-64c49f977522"
args:
query: "arg1"
name: "state_update_tool1"
1:
functionCall:
id: "adk-645f8032-712b-41a7-8e27-64c49f977522"
args:
query: "arg2"
name: "state_update_tool2"
and both of the tools will update he session state, respectively. then, only the last state update will be horned.
To Reproduce
the following unit test will fail with the current implementation
async def test_parallel_function_calls_with_state_change():
function_calls = [
types.Part.from_function_call(
name='update_session_state',
args={'key': 'test_key1', 'value': 'test_value1'},
),
types.Part.from_function_call(
name='update_session_state',
args={'key': 'test_key2', 'value': 'test_value2'},
),
types.Part.from_function_call(
name='transfer_to_agent', args={'agent_name': 'test_sub_agent'}
),
]
function_responses = [
types.Part.from_function_response(
name='update_session_state', response={'result': None}
),
types.Part.from_function_response(
name='update_session_state', response={'result': None}
),
types.Part.from_function_response(
name='transfer_to_agent', response={'result': None}
),
]
responses: list[types.Content] = [
function_calls,
'response1',
]
function_called = 0
mock_model = testing_utils.MockModel.create(responses=responses)
async def update_session_state(
key: str, value: str, tool_context: ToolContext
) -> None:
nonlocal function_called
function_called += 1
tool_context.state.update({key: value})
return
async def transfer_to_agent(
agent_name: str, tool_context: ToolContext
) -> None:
nonlocal function_called
function_called += 1
tool_context.actions.transfer_to_agent = agent_name
return
test_sub_agent = Agent(
name='test_sub_agent',
)
agent = Agent(
name='root_agent',
model=mock_model,
tools=[update_session_state, transfer_to_agent],
sub_agents=[test_sub_agent],
)
runner = testing_utils.TestInMemoryRunner(agent)
events = await runner.run_async_with_new_session('test')
# Notice that the following assertion only checks the "contents" part of the events.
# The "actions" part will be checked later.
assert testing_utils.simplify_events(events) == [
('root_agent', function_calls),
('root_agent', function_responses),
('test_sub_agent', 'response1'),
]
# Asserts the function calls.
assert function_called == 3
# Asserts the actions in response event.
response_event = events[1]
assert response_event.actions == EventActions(
state_delta={
'test_key1': 'test_value1',
'test_key2': 'test_value2',
},
transfer_to_agent='test_sub_agent',
)
Expected behavior
EventAction from multiple function calls will be properly merged.
Desktop (please complete the following information):
- OS: Linux
- Python version(python -V): 3.11.2
- ADK version(pip show google-adk): 1.6.1
Additional Information:
After carefully inspect the code, i found the root cause of the issue. When there are multiple functions calls in the same event, ADK tries to merge the EventAction updates resulting from all the function calls (see: https://github.com/google/adk-python/blob/main/src/google/adk/flows/llm_flows/functions.py#L537). However, the merging logic is wrong. it replaces the value instead of merging it when the type of the action field is dict. The handling of requested_auth_configs is correct though.
Also, it seems that #345 and #1119 both related to this.
I will create a PR that fix this properly that merging logic changes based on the data type.
For project owner: you can assign this issue to me.
Describe the bug
EventActions does not capture all the action when there are multiple function calls in an Event.
For example, if an Event generated from LLM contains two
functionCalland both of the tools will update he session state, respectively. then, only the last state update will be horned.
To Reproduce
the following unit test will fail with the current implementation
Expected behavior
EventAction from multiple function calls will be properly merged.
Desktop (please complete the following information):
Additional Information:
After carefully inspect the code, i found the root cause of the issue. When there are multiple functions calls in the same event, ADK tries to merge the EventAction updates resulting from all the function calls (see: https://github.com/google/adk-python/blob/main/src/google/adk/flows/llm_flows/functions.py#L537). However, the merging logic is wrong. it replaces the value instead of merging it when the type of the action field is
dict. The handling ofrequested_auth_configsis correct though.Also, it seems that #345 and #1119 both related to this.
I will create a PR that fix this properly that merging logic changes based on the data type.
For project owner: you can assign this issue to me.