Skip to content

Commit bb16958

Browse files
committed
fix: guard peer agent mode access in agent transfer
_get_transfer_targets read peer_agent.mode directly while iterating the parent's sub_agents. mode only exists on LlmAgent, so a peer that is a plain BaseAgent subclass raised AttributeError and broke transfer_to_agent. Mirror the hasattr guard already used in the sub_agents branch. Closes #5863 Change-Id: Ia0bcfcae43df81f469867d9e1e7e356db9f34fb6
1 parent d029bce commit bb16958

2 files changed

Lines changed: 50 additions & 1 deletion

File tree

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,10 @@ def _get_transfer_targets(agent: LlmAgent) -> list[BaseAgent]:
193193
peer_agent
194194
for peer_agent in agent.parent_agent.sub_agents
195195
if peer_agent.name != agent.name
196-
and peer_agent.mode not in ('single_turn', 'task')
196+
and (
197+
not hasattr(peer_agent, 'mode')
198+
or peer_agent.mode not in ('single_turn', 'task')
199+
)
197200
])
198201

199202
return result

tests/unittests/flows/llm_flows/test_agent_transfer_system_instructions.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@
1919
implementation.
2020
"""
2121

22+
from typing import AsyncGenerator
23+
24+
from google.adk.agents.base_agent import BaseAgent
2225
from google.adk.agents.invocation_context import InvocationContext
2326
from google.adk.agents.llm_agent import Agent
2427
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
28+
from google.adk.events.event import Event
2529
from google.adk.flows.llm_flows import agent_transfer
2630
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
2731
from google.adk.models.llm_request import LlmRequest
@@ -34,6 +38,15 @@
3438
from ... import testing_utils
3539

3640

41+
class _NonLlmAgent(BaseAgent):
42+
"""A minimal BaseAgent subclass that, like any non-LlmAgent, has no `mode`."""
43+
44+
async def _run_async_impl(
45+
self, ctx: InvocationContext
46+
) -> AsyncGenerator[Event, None]:
47+
yield Event(author=self.name, invocation_id=ctx.invocation_id)
48+
49+
3750
async def create_test_invocation_context(agent: Agent) -> InvocationContext:
3851
"""Helper to create constructed InvocationContext."""
3952
session_service = InMemorySessionService()
@@ -296,3 +309,36 @@ async def test_agent_transfer_no_instructions_when_no_transfer_targets():
296309
instructions = llm_request.config.system_instruction or ''
297310
assert '**NOTE**:' not in instructions
298311
assert 'transfer_to_agent' not in instructions
312+
313+
314+
@pytest.mark.asyncio
315+
async def test_agent_transfer_with_non_llm_peer_agent():
316+
"""Peer agents that are not LlmAgents (no `mode`) must not break transfer."""
317+
mockModel = testing_utils.MockModel.create(responses=[])
318+
319+
non_llm_peer = _NonLlmAgent(
320+
name='non_llm_peer', description='A non-LlmAgent peer'
321+
)
322+
parent_agent = Agent(
323+
name='parent_agent',
324+
model=mockModel,
325+
sub_agents=[non_llm_peer],
326+
description='Parent agent',
327+
)
328+
main_agent = Agent(
329+
name='main_agent',
330+
model=mockModel,
331+
parent_agent=parent_agent,
332+
description='Main agent',
333+
)
334+
335+
invocation_context = await create_test_invocation_context(main_agent)
336+
llm_request = LlmRequest()
337+
338+
async for _ in agent_transfer.request_processor.run_async(
339+
invocation_context, llm_request
340+
):
341+
pass
342+
343+
instructions = llm_request.config.system_instruction
344+
assert 'non_llm_peer' in instructions

0 commit comments

Comments
 (0)