Skip to content
Merged
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
7 changes: 6 additions & 1 deletion src/google/adk/agents/loop_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,15 @@ async def _run_async_impl(
times_looped = 0
while not self.max_iterations or times_looped < self.max_iterations:
for sub_agent in self.sub_agents:
should_exit = False
async for event in sub_agent.run_async(ctx):
yield event
if event.actions.escalate:
return
should_exit = True

if should_exit:
return

times_looped += 1
return

Expand Down
1 change: 1 addition & 0 deletions src/google/adk/tools/exit_loop_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ def exit_loop(tool_context: ToolContext):
Call this function only when you are instructed to do so.
"""
tool_context.actions.escalate = True
tool_context.actions.skip_summarization = True
17 changes: 15 additions & 2 deletions tests/unittests/agents/test_loop_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ async def _run_async_impl(
),
actions=EventActions(escalate=True),
)
yield Event(
author=self.name,
invocation_id=ctx.invocation_id,
content=types.Content(
parts=[types.Part(text=f'I have done my job after escalation!!')]
),
)


async def _create_parent_invocation_context(
Expand Down Expand Up @@ -115,17 +122,20 @@ async def test_run_async_with_escalate_action(request: pytest.FixtureRequest):
escalating_agent = _TestingAgentWithEscalateAction(
name=f'{request.function.__name__}_test_escalating_agent'
)
ignored_agent = _TestingAgent(
name=f'{request.function.__name__}_test_ignored_agent'
)
loop_agent = LoopAgent(
name=f'{request.function.__name__}_test_loop_agent',
sub_agents=[non_escalating_agent, escalating_agent],
sub_agents=[non_escalating_agent, escalating_agent, ignored_agent],
)
parent_ctx = await _create_parent_invocation_context(
request.function.__name__, loop_agent
)
events = [e async for e in loop_agent.run_async(parent_ctx)]

# Only two events are generated because the sub escalating_agent escalates.
assert len(events) == 2
assert len(events) == 3
assert events[0].author == non_escalating_agent.name
assert events[1].author == escalating_agent.name
assert events[0].content.parts[0].text == (
Expand All @@ -134,3 +144,6 @@ async def test_run_async_with_escalate_action(request: pytest.FixtureRequest):
assert events[1].content.parts[0].text == (
f'Hello, async {escalating_agent.name}!'
)
assert (
events[2].content.parts[0].text == 'I have done my job after escalation!!'
)
4 changes: 1 addition & 3 deletions tests/unittests/flows/llm_flows/test_agent_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,11 +303,9 @@ def test_auto_to_loop():
name='exit_loop', response={'result': None}
),
),
# root_agent summarizes.
('root_agent', 'response4'),
]

# root_agent should still be the current agent because sub_agent_1 is loop.
assert testing_utils.simplify_events(runner.run('test2')) == [
('root_agent', 'response5'),
('root_agent', 'response4'),
]