Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/google/adk/tools/transfer_to_agent_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@
# 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
answer the user's query according to the agent's description.

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

Expand Down
3 changes: 2 additions & 1 deletion tests/unittests/agents/test_resumable_llm_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
)


Expand Down
3 changes: 2 additions & 1 deletion tests/unittests/runners/test_pause_invocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
)


Expand Down
3 changes: 2 additions & 1 deletion tests/unittests/runners/test_resume_invocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
)


Expand Down
8 changes: 6 additions & 2 deletions tests/unittests/streaming/test_multi_agent_streaming.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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':
Expand Down
32 changes: 26 additions & 6 deletions tests/unittests/tools/test_transfer_to_agent_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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']
3 changes: 2 additions & 1 deletion tests/unittests/workflow/test_agent_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'},
)


Expand Down
8 changes: 4 additions & 4 deletions tests/unittests/workflow/test_llm_agent_as_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down