1
- import math
2
1
import time
3
2
from unittest .mock import MagicMock , Mock , patch
4
3
14
13
from strands .types .content import ContentBlock
15
14
16
15
17
- def create_mock_agent (
18
- name , response_text = "Default response" , metrics = None , agent_id = None , complete_after_calls = 1 , should_fail = False
19
- ):
16
+ def create_mock_agent (name , response_text = "Default response" , metrics = None , agent_id = None , should_fail = False ):
20
17
"""Create a mock Agent with specified properties."""
21
18
agent = Mock (spec = Agent )
22
19
agent .name = name
@@ -27,8 +24,6 @@ def create_mock_agent(
27
24
agent .tool_registry .registry = {}
28
25
agent .tool_registry .process_tools = Mock ()
29
26
agent ._call_count = 0
30
- agent ._complete_after = complete_after_calls
31
- agent ._swarm_ref = None # Will be set by the swarm
32
27
agent ._should_fail = should_fail
33
28
agent ._session_manager = None
34
29
agent .hooks = HookRegistry ()
@@ -46,11 +41,6 @@ def create_mock_result():
46
41
if agent ._should_fail :
47
42
raise Exception ("Simulated agent failure" )
48
43
49
- # After specified calls, complete the task
50
- if agent ._call_count >= agent ._complete_after and agent ._swarm_ref :
51
- # Directly call the completion handler
52
- agent ._swarm_ref ._handle_completion ()
53
-
54
44
return AgentResult (
55
45
message = {"role" : "assistant" , "content" : [{"text" : response_text }]},
56
46
stop_reason = "end_turn" ,
@@ -73,9 +63,9 @@ async def mock_invoke_async(*args, **kwargs):
73
63
def mock_agents ():
74
64
"""Create a set of mock agents for testing."""
75
65
return {
76
- "coordinator" : create_mock_agent ("coordinator" , "Coordinating task" , complete_after_calls = 1 ),
77
- "specialist" : create_mock_agent ("specialist" , "Specialized response" , complete_after_calls = 1 ),
78
- "reviewer" : create_mock_agent ("reviewer" , "Review complete" , complete_after_calls = 1 ),
66
+ "coordinator" : create_mock_agent ("coordinator" , "Coordinating task" ),
67
+ "specialist" : create_mock_agent ("specialist" , "Specialized response" ),
68
+ "reviewer" : create_mock_agent ("reviewer" , "Review complete" ),
79
69
}
80
70
81
71
@@ -91,10 +81,6 @@ def mock_swarm(mock_agents):
91
81
node_timeout = 10.0 ,
92
82
)
93
83
94
- # Set swarm reference on agents so they can call completion
95
- for agent in agents :
96
- agent ._swarm_ref = swarm
97
-
98
84
return swarm
99
85
100
86
@@ -273,10 +259,6 @@ def test_swarm_synchronous_execution(mock_strands_tracer, mock_use_span, mock_ag
273
259
node_timeout = 5.0 ,
274
260
)
275
261
276
- # Set swarm reference on agents so they can call completion
277
- for agent in agents :
278
- agent ._swarm_ref = swarm
279
-
280
262
# Test synchronous execution
281
263
result = swarm ("Test synchronous swarm execution" )
282
264
@@ -335,27 +317,25 @@ def test_swarm_builder_validation(mock_agents):
335
317
with pytest .raises (ValueError , match = "already has tools with names that conflict" ):
336
318
Swarm (nodes = [conflicting_agent ])
337
319
338
- # Test tool name conflicts - complete tool
339
- conflicting_complete_agent = create_mock_agent ("conflicting_complete" )
340
- conflicting_complete_agent .tool_registry .registry = {"complete_swarm_task" : Mock ()}
341
-
342
- with pytest .raises (ValueError , match = "already has tools with names that conflict" ):
343
- Swarm (nodes = [conflicting_complete_agent ])
344
-
345
320
346
321
def test_swarm_handoff_functionality ():
347
322
"""Test swarm handoff functionality."""
348
323
349
324
# Create an agent that will hand off to another agent
350
325
def create_handoff_agent (name , target_agent_name , response_text = "Handing off" ):
351
326
"""Create a mock agent that performs handoffs."""
352
- agent = create_mock_agent (name , response_text , complete_after_calls = math . inf ) # Never complete naturally
327
+ agent = create_mock_agent (name , response_text )
353
328
agent ._handoff_done = False # Track if handoff has been performed
354
329
355
330
def create_handoff_result ():
356
331
agent ._call_count += 1
357
332
# Perform handoff on first execution call (not setup calls)
358
- if not agent ._handoff_done and agent ._swarm_ref and hasattr (agent ._swarm_ref .state , "completion_status" ):
333
+ if (
334
+ not agent ._handoff_done
335
+ and hasattr (agent , "_swarm_ref" )
336
+ and agent ._swarm_ref
337
+ and hasattr (agent ._swarm_ref .state , "completion_status" )
338
+ ):
359
339
target_node = agent ._swarm_ref .nodes .get (target_agent_name )
360
340
if target_node :
361
341
agent ._swarm_ref ._handle_handoff (
@@ -382,9 +362,9 @@ async def mock_invoke_async(*args, **kwargs):
382
362
agent .invoke_async = MagicMock (side_effect = mock_invoke_async )
383
363
return agent
384
364
385
- # Create agents - first one hands off, second one completes
365
+ # Create agents - first one hands off, second one completes by not handing off
386
366
handoff_agent = create_handoff_agent ("handoff_agent" , "completion_agent" )
387
- completion_agent = create_mock_agent ("completion_agent" , "Task completed" , complete_after_calls = 1 )
367
+ completion_agent = create_mock_agent ("completion_agent" , "Task completed" )
388
368
389
369
# Create a swarm with reasonable limits
390
370
handoff_swarm = Swarm (nodes = [handoff_agent , completion_agent ], max_handoffs = 10 , max_iterations = 10 )
@@ -427,18 +407,13 @@ def test_swarm_tool_creation_and_execution():
427
407
assert error_result ["status" ] == "error"
428
408
assert "not found" in error_result ["content" ][0 ]["text" ]
429
409
430
- completion_tool = error_swarm ._create_complete_tool ()
431
- completion_result = completion_tool ()
432
- assert completion_result ["status" ] == "success"
433
-
434
410
435
411
def test_swarm_failure_handling (mock_strands_tracer , mock_use_span ):
436
412
"""Test swarm execution with agent failures."""
437
413
# Test execution with agent failures
438
414
failing_agent = create_mock_agent ("failing_agent" )
439
415
failing_agent ._should_fail = True # Set failure flag after creation
440
416
failing_swarm = Swarm (nodes = [failing_agent ], node_timeout = 1.0 )
441
- failing_agent ._swarm_ref = failing_swarm
442
417
443
418
# The swarm catches exceptions internally and sets status to FAILED
444
419
result = failing_swarm ("Test failure handling" )
@@ -451,12 +426,32 @@ def test_swarm_metrics_handling():
451
426
"""Test swarm metrics handling with missing metrics."""
452
427
no_metrics_agent = create_mock_agent ("no_metrics" , metrics = None )
453
428
no_metrics_swarm = Swarm (nodes = [no_metrics_agent ])
454
- no_metrics_agent ._swarm_ref = no_metrics_swarm
455
429
456
430
result = no_metrics_swarm ("Test no metrics" )
457
431
assert result .status == Status .COMPLETED
458
432
459
433
434
+ def test_swarm_auto_completion_without_handoff ():
435
+ """Test swarm auto-completion when no handoff occurs."""
436
+ # Create a simple agent that doesn't hand off
437
+ no_handoff_agent = create_mock_agent ("no_handoff_agent" , "Task completed without handoff" )
438
+
439
+ # Create a swarm with just this agent
440
+ auto_complete_swarm = Swarm (nodes = [no_handoff_agent ])
441
+
442
+ # Execute swarm - this should complete automatically since there's no handoff
443
+ result = auto_complete_swarm ("Test auto-completion without handoff" )
444
+
445
+ # Verify the swarm completed successfully
446
+ assert result .status == Status .COMPLETED
447
+ assert result .execution_count == 1
448
+ assert len (result .node_history ) == 1
449
+ assert result .node_history [0 ].node_id == "no_handoff_agent"
450
+
451
+ # Verify the agent was called
452
+ no_handoff_agent .invoke_async .assert_called ()
453
+
454
+
460
455
def test_swarm_validate_unsupported_features ():
461
456
"""Test Swarm validation for session persistence and callbacks."""
462
457
# Test with normal agent (should work)
0 commit comments