From b813e2dc92eb7bc3ebfbf9e2dcfacff9fb4337cd Mon Sep 17 00:00:00 2001 From: Srikanth-Saravanan <107021624+Srikanth-Saravanan@users.noreply.github.com> Date: Wed, 5 Aug 2026 04:20:32 +0530 Subject: [PATCH 1/2] added transfer_reason arg to transfer_to_agent tool --- src/google/adk/tools/transfer_to_agent_tool.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/google/adk/tools/transfer_to_agent_tool.py b/src/google/adk/tools/transfer_to_agent_tool.py index fb419dd575a..c88a51da1a2 100644 --- a/src/google/adk/tools/transfer_to_agent_tool.py +++ b/src/google/adk/tools/transfer_to_agent_tool.py @@ -27,7 +27,9 @@ # For most use cases, you should use TransferToAgentTool instead of this # function directly. TransferToAgentTool provides additional enum constraints # that prevent LLMs from hallucinating invalid agent names. -def transfer_to_agent(agent_name: str, tool_context: ToolContext) -> None: +def transfer_to_agent( + agent_name: str, transfer_reason: str, tool_context: ToolContext +) -> None: """Transfer the query to another agent. Use this tool to hand off control to another agent that is more suitable to @@ -35,6 +37,7 @@ def transfer_to_agent(agent_name: str, tool_context: ToolContext) -> None: Args: agent_name: the agent name to transfer to. + transfer_reason: the reason for transferring to the target agent. """ tool_context.actions.transfer_to_agent = agent_name From 387f4e66fbaa2bb2b189a5b9ff02b050c29b6d36 Mon Sep 17 00:00:00 2001 From: Srikanth-Saravanan <107021624+Srikanth-Saravanan@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:13:05 +0000 Subject: [PATCH 2/2] updated the test script for transfer_reason arg --- .../agents/test_resumable_llm_agent.py | 3 +- .../runners/test_pause_invocation.py | 3 +- .../runners/test_resume_invocation.py | 3 +- .../streaming/test_multi_agent_streaming.py | 8 +++-- .../tools/test_transfer_to_agent_tool.py | 32 +++++++++++++++---- .../unittests/workflow/test_agent_transfer.py | 3 +- .../workflow/test_llm_agent_as_node.py | 8 ++--- 7 files changed, 44 insertions(+), 16 deletions(-) diff --git a/tests/unittests/agents/test_resumable_llm_agent.py b/tests/unittests/agents/test_resumable_llm_agent.py index 35b02cd389a..3b5122d0b36 100644 --- a/tests/unittests/agents/test_resumable_llm_agent.py +++ b/tests/unittests/agents/test_resumable_llm_agent.py @@ -32,7 +32,8 @@ def transfer_call_part(agent_name: str) -> Part: return Part.from_function_call( - name="transfer_to_agent", args={"agent_name": agent_name} + name="transfer_to_agent", + args={"agent_name": agent_name, "transfer_reason": "test reason"}, ) diff --git a/tests/unittests/runners/test_pause_invocation.py b/tests/unittests/runners/test_pause_invocation.py index 3493674aa8e..a5bd4e65dd5 100644 --- a/tests/unittests/runners/test_pause_invocation.py +++ b/tests/unittests/runners/test_pause_invocation.py @@ -37,7 +37,8 @@ def _transfer_call_part(agent_name: str) -> Part: return Part.from_function_call( - name="transfer_to_agent", args={"agent_name": agent_name} + name="transfer_to_agent", + args={"agent_name": agent_name, "transfer_reason": "test reason"}, ) diff --git a/tests/unittests/runners/test_resume_invocation.py b/tests/unittests/runners/test_resume_invocation.py index c2d517232b5..b4578d89d53 100644 --- a/tests/unittests/runners/test_resume_invocation.py +++ b/tests/unittests/runners/test_resume_invocation.py @@ -28,7 +28,8 @@ def transfer_call_part(agent_name: str) -> Part: return Part.from_function_call( - name="transfer_to_agent", args={"agent_name": agent_name} + name="transfer_to_agent", + args={"agent_name": agent_name, "transfer_reason": "test reason"}, ) diff --git a/tests/unittests/streaming/test_multi_agent_streaming.py b/tests/unittests/streaming/test_multi_agent_streaming.py index fd8d60efbd0..424437e2869 100644 --- a/tests/unittests/streaming/test_multi_agent_streaming.py +++ b/tests/unittests/streaming/test_multi_agent_streaming.py @@ -35,7 +35,8 @@ def test_live_streaming_multi_agent_single_tool(): # Mock response for the root_agent to delegate the task to the roll_agent. # FIX: Use from_function_call to represent delegation to a sub-agent. delegation_to_roll_agent = types.Part.from_function_call( - name='transfer_to_agent', args={'agent_name': 'roll_agent'} + name='transfer_to_agent', + args={'agent_name': 'roll_agent', 'transfer_reason': 'test reason'}, ) root_response1 = LlmResponse( @@ -132,7 +133,10 @@ async def consume_responses(session: testing_utils.Session): # FIX: Check for the function call that represents delegation. if part.function_call.name == 'transfer_to_agent': delegation_found = True - assert part.function_call.args == {'agent_name': 'roll_agent'} + assert part.function_call.args == { + 'agent_name': 'roll_agent', + 'transfer_reason': 'test reason', + } # Check for the function call made by the roll_agent. if part.function_call.name == 'roll_die': diff --git a/tests/unittests/tools/test_transfer_to_agent_tool.py b/tests/unittests/tools/test_transfer_to_agent_tool.py index 100e767a7e0..d838a130af5 100644 --- a/tests/unittests/tools/test_transfer_to_agent_tool.py +++ b/tests/unittests/tools/test_transfer_to_agent_tool.py @@ -52,8 +52,14 @@ def test_transfer_to_agent_tool_enum_constraint(self): assert agent_name_schema.type == types.Type.STRING assert agent_name_schema.enum == agent_names - # Verify that agent_name is marked as required - assert decl.parameters.required == ['agent_name'] + # Verify that transfer_reason is a string parameter without enum constraint + assert 'transfer_reason' in decl.parameters.properties + transfer_reason_schema = decl.parameters.properties['transfer_reason'] + assert transfer_reason_schema.type == types.Type.STRING + assert transfer_reason_schema.enum is None + + # Verify that agent_name and transfer_reason are marked as required + assert decl.parameters.required == ['agent_name', 'transfer_reason'] def test_transfer_to_agent_tool_single_agent(self): """Test TransferToAgentTool with a single agent.""" @@ -105,9 +111,10 @@ def test_transfer_to_agent_tool_no_extra_parameters(self): decl = tool._get_declaration() assert decl is not None - # Should only have agent_name parameter (tool_context is ignored) - assert len(decl.parameters.properties) == 1 + # Should only have agent_name and transfer_reason (tool_context is ignored) + assert len(decl.parameters.properties) == 2 assert 'agent_name' in decl.parameters.properties + assert 'transfer_reason' in decl.parameters.properties assert 'tool_context' not in decl.parameters.properties @@ -200,7 +207,19 @@ def test_transfer_to_agent_tool_enum_constraint(self): agent_name_schema = decl.parameters_json_schema['properties']['agent_name'] assert agent_name_schema['type'] == 'string' assert agent_name_schema['enum'] == agent_names - assert decl.parameters_json_schema['required'] == ['agent_name'] + + # Verify that transfer_reason is a string parameter without enum constraint + assert 'transfer_reason' in decl.parameters_json_schema['properties'] + transfer_reason_schema = decl.parameters_json_schema['properties'][ + 'transfer_reason' + ] + assert transfer_reason_schema['type'] == 'string' + assert 'enum' not in transfer_reason_schema + + assert decl.parameters_json_schema['required'] == [ + 'agent_name', + 'transfer_reason', + ] def test_transfer_to_agent_tool_single_agent(self): """Test TransferToAgentTool with a single agent.""" @@ -251,6 +270,7 @@ def test_transfer_to_agent_tool_no_extra_parameters(self): decl = tool._get_declaration() assert decl is not None - assert len(decl.parameters_json_schema['properties']) == 1 + assert len(decl.parameters_json_schema['properties']) == 2 assert 'agent_name' in decl.parameters_json_schema['properties'] + assert 'transfer_reason' in decl.parameters_json_schema['properties'] assert 'tool_context' not in decl.parameters_json_schema['properties'] diff --git a/tests/unittests/workflow/test_agent_transfer.py b/tests/unittests/workflow/test_agent_transfer.py index 6832168b68e..37e94271072 100644 --- a/tests/unittests/workflow/test_agent_transfer.py +++ b/tests/unittests/workflow/test_agent_transfer.py @@ -34,7 +34,8 @@ def transfer_call_part(agent_name: str) -> Part: return Part.from_function_call( - name='transfer_to_agent', args={'agent_name': agent_name} + name='transfer_to_agent', + args={'agent_name': agent_name, 'transfer_reason': 'test reason'}, ) diff --git a/tests/unittests/workflow/test_llm_agent_as_node.py b/tests/unittests/workflow/test_llm_agent_as_node.py index 71cf7cee3b5..c56a0ddaddb 100644 --- a/tests/unittests/workflow/test_llm_agent_as_node.py +++ b/tests/unittests/workflow/test_llm_agent_as_node.py @@ -1217,19 +1217,19 @@ async def test_three_layer_llm_agent_transfer_round_trip( # Prepare the transfer function call parts fc_transfer_to_child = types.Part.from_function_call( name='transfer_to_agent', - args={'agent_name': 'child_agent'}, + args={'agent_name': 'child_agent', 'transfer_reason': 'test reason'}, ) fc_transfer_to_grandchild = types.Part.from_function_call( name='transfer_to_agent', - args={'agent_name': 'grandchild_agent'}, + args={'agent_name': 'grandchild_agent', 'transfer_reason': 'test reason'}, ) fc_transfer_to_child_parent = types.Part.from_function_call( name='transfer_to_agent', - args={'agent_name': 'child_agent'}, + args={'agent_name': 'child_agent', 'transfer_reason': 'test reason'}, ) fc_transfer_to_root = types.Part.from_function_call( name='transfer_to_agent', - args={'agent_name': 'root_agent'}, + args={'agent_name': 'root_agent', 'transfer_reason': 'test reason'}, ) # Mock models for 3 layers