swarms 7.9.0__py3-none-any.whl → 7.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -121,6 +121,71 @@ def priority_speaker(
121
121
  return available_agents[-1] # Fallback
122
122
 
123
123
 
124
+ def random_dynamic_speaker(
125
+ agents: List[str],
126
+ response: str = "",
127
+ strategy: str = "parallel",
128
+ **kwargs,
129
+ ) -> Union[str, List[str]]:
130
+ """
131
+ Random dynamic speaker function that selects agents based on @mentions in responses.
132
+
133
+ This function works in two phases:
134
+ 1. If no response is provided (first call), randomly selects an agent
135
+ 2. If a response is provided, extracts @mentions and returns agent(s) based on strategy
136
+
137
+ Args:
138
+ agents: List of available agent names
139
+ response: The response from the previous agent (may contain @mentions)
140
+ strategy: How to handle multiple mentions - "sequential" or "parallel"
141
+ **kwargs: Additional arguments (ignored)
142
+
143
+ Returns:
144
+ For sequential strategy: str (single agent name)
145
+ For parallel strategy: List[str] (list of agent names)
146
+ """
147
+ if not agents:
148
+ raise ValueError(
149
+ "No agents provided for random dynamic selection"
150
+ )
151
+
152
+ # If no response provided, randomly select first agent
153
+ if not response:
154
+ return random.choice(agents)
155
+
156
+ # Extract @mentions from the response
157
+ mentions = re.findall(r"@(\w+)", response)
158
+
159
+ # Filter mentions to only include valid agents
160
+ valid_mentions = [
161
+ mention for mention in mentions if mention in agents
162
+ ]
163
+
164
+ if not valid_mentions:
165
+ # If no valid mentions, randomly select from all agents
166
+ return random.choice(agents)
167
+
168
+ # Handle multiple mentions based on strategy
169
+ if strategy == "sequential":
170
+ # Return the first mentioned agent for sequential execution
171
+ return valid_mentions[0]
172
+ elif strategy == "parallel":
173
+ # Return all mentioned agents for parallel execution
174
+ return valid_mentions
175
+ else:
176
+ raise ValueError(
177
+ f"Invalid strategy: {strategy}. Must be 'sequential' or 'parallel'"
178
+ )
179
+
180
+
181
+ speaker_functions = {
182
+ "round-robin-speaker": round_robin_speaker,
183
+ "random-speaker": random_speaker,
184
+ "priority-speaker": priority_speaker,
185
+ "random-dynamic-speaker": random_dynamic_speaker,
186
+ }
187
+
188
+
124
189
  class InteractiveGroupChat:
125
190
  """
126
191
  An interactive group chat system that enables conversations with multiple agents using @mentions.
@@ -145,11 +210,38 @@ class InteractiveGroupChat:
145
210
  max_loops (int, optional): Maximum conversation turns. Defaults to 1.
146
211
  output_type (str, optional): Type of output format. Defaults to "string".
147
212
  interactive (bool, optional): Whether to enable interactive terminal mode. Defaults to False.
148
- speaker_function (Callable, optional): Function to determine speaking order. Defaults to round_robin_speaker.
213
+ speaker_function (Union[str, Callable], optional): Function to determine speaking order. Can be:
214
+ - A string name: "round-robin-speaker", "random-speaker", "priority-speaker", "random-dynamic-speaker"
215
+ - A custom callable function
216
+ - None (defaults to round_robin_speaker)
149
217
  speaker_state (dict, optional): Initial state for speaker function. Defaults to empty dict.
150
218
 
151
219
  Raises:
152
220
  ValueError: If invalid initialization parameters are provided
221
+ InvalidSpeakerFunctionError: If the speaker function is invalid
222
+
223
+ Examples:
224
+ # Initialize with string-based speaker function
225
+ group_chat = InteractiveGroupChat(
226
+ agents=[agent1, agent2, agent3],
227
+ speaker_function="random-speaker"
228
+ )
229
+
230
+ # Initialize with priority speaker function
231
+ group_chat = InteractiveGroupChat(
232
+ agents=[agent1, agent2, agent3],
233
+ speaker_function="priority-speaker",
234
+ speaker_state={"priorities": {"agent1": 3, "agent2": 2, "agent3": 1}}
235
+ )
236
+
237
+ # Initialize with dynamic speaker function (agents mention each other)
238
+ group_chat = InteractiveGroupChat(
239
+ agents=[agent1, agent2, agent3],
240
+ speaker_function="random-dynamic-speaker"
241
+ )
242
+
243
+ # Change speaker function during runtime
244
+ group_chat.set_speaker_function("round-robin-speaker")
153
245
  """
154
246
 
155
247
  def __init__(
@@ -161,7 +253,7 @@ class InteractiveGroupChat:
161
253
  max_loops: int = 1,
162
254
  output_type: str = "string",
163
255
  interactive: bool = False,
164
- speaker_function: Optional[Callable] = None,
256
+ speaker_function: Optional[Union[str, Callable]] = None,
165
257
  speaker_state: Optional[dict] = None,
166
258
  ):
167
259
  self.id = id
@@ -173,9 +265,27 @@ class InteractiveGroupChat:
173
265
  self.interactive = interactive
174
266
 
175
267
  # Speaker function configuration
176
- self.speaker_function = (
177
- speaker_function or round_robin_speaker
178
- )
268
+ if speaker_function is None:
269
+ self.speaker_function = round_robin_speaker
270
+ elif isinstance(speaker_function, str):
271
+ if speaker_function not in speaker_functions:
272
+ available_functions = ", ".join(
273
+ speaker_functions.keys()
274
+ )
275
+ raise InvalidSpeakerFunctionError(
276
+ f"Invalid speaker function: '{speaker_function}'. "
277
+ f"Available functions: {available_functions}"
278
+ )
279
+ self.speaker_function = speaker_functions[
280
+ speaker_function
281
+ ]
282
+ elif callable(speaker_function):
283
+ self.speaker_function = speaker_function
284
+ else:
285
+ raise InvalidSpeakerFunctionError(
286
+ "Speaker function must be either a string, callable, or None"
287
+ )
288
+
179
289
  self.speaker_state = speaker_state or {"current_index": 0}
180
290
 
181
291
  # Validate speaker function
@@ -197,6 +307,230 @@ class InteractiveGroupChat:
197
307
  self._setup_conversation_context()
198
308
  self._update_agent_prompts()
199
309
 
310
+ def set_speaker_function(
311
+ self,
312
+ speaker_function: Union[str, Callable],
313
+ speaker_state: Optional[dict] = None,
314
+ ) -> None:
315
+ """
316
+ Set the speaker function using either a string name or a custom callable.
317
+
318
+ Args:
319
+ speaker_function: Either a string name of a predefined function or a custom callable
320
+ String options:
321
+ - "round-robin-speaker": Cycles through agents in order
322
+ - "random-speaker": Selects agents randomly
323
+ - "priority-speaker": Selects based on priority weights
324
+ - "random-dynamic-speaker": Randomly selects first agent, then follows @mentions in responses
325
+ Callable: Custom function that takes (agents: List[str], **kwargs) -> str
326
+ speaker_state: Optional state for the speaker function
327
+
328
+ Raises:
329
+ InvalidSpeakerFunctionError: If the speaker function is invalid
330
+ """
331
+ if isinstance(speaker_function, str):
332
+ # Handle string-based speaker function
333
+ if speaker_function not in speaker_functions:
334
+ available_functions = ", ".join(
335
+ speaker_functions.keys()
336
+ )
337
+ raise InvalidSpeakerFunctionError(
338
+ f"Invalid speaker function: '{speaker_function}'. "
339
+ f"Available functions: {available_functions}"
340
+ )
341
+ self.speaker_function = speaker_functions[
342
+ speaker_function
343
+ ]
344
+ logger.info(
345
+ f"Speaker function set to: {speaker_function}"
346
+ )
347
+ elif callable(speaker_function):
348
+ # Handle callable speaker function
349
+ self.speaker_function = speaker_function
350
+ logger.info(
351
+ f"Custom speaker function set to: {speaker_function.__name__}"
352
+ )
353
+ else:
354
+ raise InvalidSpeakerFunctionError(
355
+ "Speaker function must be either a string or a callable"
356
+ )
357
+
358
+ # Update speaker state if provided
359
+ if speaker_state:
360
+ self.speaker_state.update(speaker_state)
361
+
362
+ # Validate the speaker function
363
+ self._validate_speaker_function()
364
+
365
+ def set_priorities(self, priorities: dict) -> None:
366
+ """
367
+ Set agent priorities for priority-based speaking order.
368
+
369
+ Args:
370
+ priorities: Dictionary mapping agent names to priority weights
371
+ """
372
+ self.speaker_state["priorities"] = priorities
373
+ logger.info(f"Agent priorities set: {priorities}")
374
+
375
+ def get_available_speaker_functions(self) -> List[str]:
376
+ """
377
+ Get a list of available speaker function names.
378
+
379
+ Returns:
380
+ List[str]: List of available speaker function names
381
+ """
382
+ return list(speaker_functions.keys())
383
+
384
+ def get_current_speaker_function(self) -> str:
385
+ """
386
+ Get the name of the current speaker function.
387
+
388
+ Returns:
389
+ str: Name of the current speaker function, or "custom" if it's a custom function
390
+ """
391
+ for name, func in speaker_functions.items():
392
+ if self.speaker_function == func:
393
+ return name
394
+ return "custom"
395
+
396
+ def start_interactive_session(self):
397
+ """
398
+ Start an interactive terminal session for chatting with agents.
399
+
400
+ This method creates a REPL (Read-Eval-Print Loop) that allows users to:
401
+ - Chat with agents using @mentions
402
+ - See available agents and their descriptions
403
+ - Exit the session using 'exit' or 'quit'
404
+ - Get help using 'help' or '?'
405
+ """
406
+ if not self.interactive:
407
+ raise InteractiveGroupChatError(
408
+ "Interactive mode is not enabled. Initialize with interactive=True"
409
+ )
410
+
411
+ print(f"\nWelcome to {self.name}!")
412
+ print(f"Description: {self.description}")
413
+ print(
414
+ f"Current speaker function: {self.get_current_speaker_function()}"
415
+ )
416
+ print("\nAvailable agents:")
417
+ for name, agent in self.agent_map.items():
418
+ if isinstance(agent, Agent):
419
+ print(
420
+ f"- @{name}: {agent.system_prompt.splitlines()[0]}"
421
+ )
422
+ else:
423
+ print(f"- @{name}: Custom callable function")
424
+
425
+ print("\nCommands:")
426
+ print("- Type 'help' or '?' for help")
427
+ print("- Type 'exit' or 'quit' to end the session")
428
+ print("- Type 'speaker' to change speaker function")
429
+ print("- Use @agent_name to mention agents")
430
+ print("\nStart chatting:")
431
+
432
+ while True:
433
+ try:
434
+ # Get user input
435
+ user_input = input("\nYou: ").strip()
436
+
437
+ # Handle special commands
438
+ if user_input.lower() in ["exit", "quit"]:
439
+ print("Goodbye!")
440
+ break
441
+
442
+ if user_input.lower() in ["help", "?"]:
443
+ print("\nHelp:")
444
+ print("1. Mention agents using @agent_name")
445
+ print(
446
+ "2. You can mention multiple agents in one task"
447
+ )
448
+ print("3. Available agents:")
449
+ for name in self.agent_map:
450
+ print(f" - @{name}")
451
+ print(
452
+ "4. Type 'speaker' to change speaker function"
453
+ )
454
+ print(
455
+ "5. Type 'exit' or 'quit' to end the session"
456
+ )
457
+ continue
458
+
459
+ if user_input.lower() == "speaker":
460
+ print(
461
+ f"\nCurrent speaker function: {self.get_current_speaker_function()}"
462
+ )
463
+ print("Available speaker functions:")
464
+ for i, func_name in enumerate(
465
+ self.get_available_speaker_functions(), 1
466
+ ):
467
+ print(f" {i}. {func_name}")
468
+
469
+ try:
470
+ choice = input(
471
+ "\nEnter the number or name of the speaker function: "
472
+ ).strip()
473
+
474
+ # Try to parse as number first
475
+ try:
476
+ func_index = int(choice) - 1
477
+ if (
478
+ 0
479
+ <= func_index
480
+ < len(
481
+ self.get_available_speaker_functions()
482
+ )
483
+ ):
484
+ selected_func = self.get_available_speaker_functions()[
485
+ func_index
486
+ ]
487
+ else:
488
+ print(
489
+ "Invalid number. Please try again."
490
+ )
491
+ continue
492
+ except ValueError:
493
+ # Try to parse as name
494
+ selected_func = choice
495
+
496
+ self.set_speaker_function(selected_func)
497
+ print(
498
+ f"Speaker function changed to: {self.get_current_speaker_function()}"
499
+ )
500
+
501
+ except InvalidSpeakerFunctionError as e:
502
+ print(f"Error: {e}")
503
+ except Exception as e:
504
+ print(f"An error occurred: {e}")
505
+ continue
506
+
507
+ if not user_input:
508
+ continue
509
+
510
+ # Process the task and get responses
511
+ try:
512
+ self.run(user_input)
513
+ print("\nChat:")
514
+ # print(response)
515
+
516
+ except NoMentionedAgentsError:
517
+ print(
518
+ "\nError: Please mention at least one agent using @agent_name"
519
+ )
520
+ except AgentNotFoundError as e:
521
+ print(f"\nError: {str(e)}")
522
+ except Exception as e:
523
+ print(f"\nAn error occurred: {str(e)}")
524
+
525
+ except KeyboardInterrupt:
526
+ print("\nSession terminated by user. Goodbye!")
527
+ break
528
+ except Exception as e:
529
+ print(f"\nAn unexpected error occurred: {str(e)}")
530
+ print(
531
+ "The session will continue. You can type 'exit' to end it."
532
+ )
533
+
200
534
  def _validate_speaker_function(self) -> None:
201
535
  """
202
536
  Validates the speaker function.
@@ -287,6 +621,7 @@ IMPORTANT: You are part of a collaborative group chat where you can interact wit
287
621
  2. ACKNOWLEDGE: Reference and acknowledge what other agents have said
288
622
  3. BUILD UPON: Add your perspective while building upon their insights
289
623
  4. MENTION: Use @agent_name to call on other agents when needed
624
+ 5. COMPLETE: Acknowledge when your part is done and what still needs to be done
290
625
 
291
626
  HOW TO MENTION OTHER AGENTS:
292
627
  - Use @agent_name to mention another agent in your response
@@ -325,24 +660,33 @@ COLLABORATION GUIDELINES:
325
660
  - ASK CLARIFYING QUESTIONS if you need more information from other agents
326
661
  - DELEGATE appropriately: "Let me ask @expert_agent to verify this" or "@specialist, can you elaborate on this point?"
327
662
 
663
+ TASK COMPLETION GUIDELINES:
664
+ - ACKNOWLEDGE when you are done with your part of the task
665
+ - CLEARLY STATE what still needs to be done before the overall task is finished
666
+ - If you mention other agents, explain what specific input you need from them
667
+ - Use phrases like "I have completed [specific part]" or "The task still requires [specific actions]"
668
+ - Provide a clear status update: "My analysis is complete. The task now needs @writer to create content and @reviewer to validate the approach."
669
+
328
670
  RESPONSE STRUCTURE:
329
671
  1. ACKNOWLEDGE: "I've reviewed the responses from @agent1 and @agent2..."
330
672
  2. BUILD: "Building on @agent1's analysis of the data..."
331
673
  3. CONTRIBUTE: "From my perspective, I would add..."
332
674
  4. COLLABORATE: "To get a complete picture, let me ask @agent3 to..."
333
- 5. SYNTHESIZE: "Combining our insights, the key findings are..."
675
+ 5. COMPLETE: "I have completed [my part]. The task still requires [specific next steps]"
676
+ 6. SYNTHESIZE: "Combining our insights, the key findings are..."
334
677
 
335
678
  EXAMPLES OF GOOD COLLABORATION:
336
- - "I've reviewed @analyst's data analysis and @researcher's market insights. The data shows strong growth potential, and I agree with @researcher that we should focus on emerging markets. Let me add that from a content perspective, we should @writer to create targeted messaging for these markets."
337
- - "Building on @researcher's findings about customer behavior, I can see that @analyst's data supports this trend. To get a complete understanding, let me ask @writer to help us craft messaging that addresses these specific customer needs."
679
+ - "I've reviewed @analyst's data analysis and @researcher's market insights. The data shows strong growth potential, and I agree with @researcher that we should focus on emerging markets. Let me add that from a content perspective, we should @writer to create targeted messaging for these markets. I have completed my market analysis. The task now requires @writer to develop content and @reviewer to validate our approach."
680
+ - "Building on @researcher's findings about customer behavior, I can see that @analyst's data supports this trend. To get a complete understanding, let me ask @writer to help us craft messaging that addresses these specific customer needs. My data analysis is complete. The task still needs @writer to create messaging and @reviewer to approve the final strategy."
338
681
 
339
682
  AVOID:
340
683
  - Ignoring other agents' responses
341
684
  - Repeating what others have already said
342
685
  - Making assumptions without consulting relevant experts
343
686
  - Responding in isolation without considering the group's collective knowledge
687
+ - Not acknowledging task completion status
344
688
 
345
- Remember: You are part of a team. Your response should reflect that you've read, understood, and built upon the contributions of others.
689
+ Remember: You are part of a team. Your response should reflect that you've read, understood, and are building upon the contributions of others, and clearly communicate your task completion status.
346
690
  """
347
691
 
348
692
  # Update the agent's system prompt
@@ -438,6 +782,12 @@ Remember: You are part of a team. Your response should reflect that you've read,
438
782
  )
439
783
  return sorted_agents
440
784
 
785
+ elif self.speaker_function == random_dynamic_speaker:
786
+ # For dynamic speaker, we need to handle it differently
787
+ # The dynamic speaker will be called during the run method
788
+ # For now, just return the original order
789
+ return mentioned_agents
790
+
441
791
  else:
442
792
  # Custom speaker function
443
793
  # For custom functions, we'll use the first agent returned
@@ -460,120 +810,12 @@ Remember: You are part of a team. Your response should reflect that you've read,
460
810
  # Fallback to original order
461
811
  return mentioned_agents
462
812
 
463
- def set_speaker_function(
813
+ def run(
464
814
  self,
465
- speaker_function: Callable,
466
- speaker_state: Optional[dict] = None,
467
- ) -> None:
468
- """
469
- Set a custom speaker function and optional state.
470
-
471
- Args:
472
- speaker_function: Function that determines speaking order
473
- speaker_state: Optional state for the speaker function
474
- """
475
- self.speaker_function = speaker_function
476
- if speaker_state:
477
- self.speaker_state.update(speaker_state)
478
- self._validate_speaker_function()
479
- logger.info(
480
- f"Speaker function updated to: {speaker_function.__name__}"
481
- )
482
-
483
- def set_priorities(self, priorities: dict) -> None:
484
- """
485
- Set agent priorities for priority-based speaking order.
486
-
487
- Args:
488
- priorities: Dictionary mapping agent names to priority weights
489
- """
490
- self.speaker_state["priorities"] = priorities
491
- logger.info(f"Agent priorities set: {priorities}")
492
-
493
- def start_interactive_session(self):
494
- """
495
- Start an interactive terminal session for chatting with agents.
496
-
497
- This method creates a REPL (Read-Eval-Print Loop) that allows users to:
498
- - Chat with agents using @mentions
499
- - See available agents and their descriptions
500
- - Exit the session using 'exit' or 'quit'
501
- - Get help using 'help' or '?'
502
- """
503
- if not self.interactive:
504
- raise InteractiveGroupChatError(
505
- "Interactive mode is not enabled. Initialize with interactive=True"
506
- )
507
-
508
- print(f"\nWelcome to {self.name}!")
509
- print(f"Description: {self.description}")
510
- print("\nAvailable agents:")
511
- for name, agent in self.agent_map.items():
512
- if isinstance(agent, Agent):
513
- print(
514
- f"- @{name}: {agent.system_prompt.splitlines()[0]}"
515
- )
516
- else:
517
- print(f"- @{name}: Custom callable function")
518
-
519
- print("\nCommands:")
520
- print("- Type 'help' or '?' for help")
521
- print("- Type 'exit' or 'quit' to end the session")
522
- print("- Use @agent_name to mention agents")
523
- print("\nStart chatting:")
524
-
525
- while True:
526
- try:
527
- # Get user input
528
- user_input = input("\nYou: ").strip()
529
-
530
- # Handle special commands
531
- if user_input.lower() in ["exit", "quit"]:
532
- print("Goodbye!")
533
- break
534
-
535
- if user_input.lower() in ["help", "?"]:
536
- print("\nHelp:")
537
- print("1. Mention agents using @agent_name")
538
- print(
539
- "2. You can mention multiple agents in one task"
540
- )
541
- print("3. Available agents:")
542
- for name in self.agent_map:
543
- print(f" - @{name}")
544
- print(
545
- "4. Type 'exit' or 'quit' to end the session"
546
- )
547
- continue
548
-
549
- if not user_input:
550
- continue
551
-
552
- # Process the task and get responses
553
- try:
554
- self.run(user_input)
555
- print("\nChat:")
556
- # print(response)
557
-
558
- except NoMentionedAgentsError:
559
- print(
560
- "\nError: Please mention at least one agent using @agent_name"
561
- )
562
- except AgentNotFoundError as e:
563
- print(f"\nError: {str(e)}")
564
- except Exception as e:
565
- print(f"\nAn error occurred: {str(e)}")
566
-
567
- except KeyboardInterrupt:
568
- print("\nSession terminated by user. Goodbye!")
569
- break
570
- except Exception as e:
571
- print(f"\nAn unexpected error occurred: {str(e)}")
572
- print(
573
- "The session will continue. You can type 'exit' to end it."
574
- )
575
-
576
- def run(self, task: str) -> str:
815
+ task: str,
816
+ img: Optional[str] = None,
817
+ imgs: Optional[List[str]] = None,
818
+ ) -> str:
577
819
  """
578
820
  Process a task and get responses from mentioned agents.
579
821
  If interactive mode is enabled, this will be called by start_interactive_session().
@@ -591,31 +833,171 @@ Remember: You are part of a team. Your response should reflect that you've read,
591
833
  # Add user task to conversation
592
834
  self.conversation.add(role="User", content=task)
593
835
 
594
- # Determine speaking order using speaker function
595
- speaking_order = self._get_speaking_order(
596
- mentioned_agents
597
- )
598
- logger.info(
599
- f"Speaking order determined: {speaking_order}"
600
- )
836
+ # Handle dynamic speaker function differently
837
+ if self.speaker_function == random_dynamic_speaker:
838
+ # Get strategy from speaker state (default to sequential)
839
+ strategy = self.speaker_state.get(
840
+ "strategy", "sequential"
841
+ )
601
842
 
602
- # Get responses from mentioned agents in the determined order
603
- for agent_name in speaking_order:
604
- agent = self.agent_map.get(agent_name)
605
- if not agent:
606
- raise AgentNotFoundError(
607
- f"Agent '{agent_name}' not found"
843
+ # For dynamic speaker, we'll determine the next speaker after each response
844
+ # Track which agents have spoken to ensure all get a chance
845
+ spoken_agents = set()
846
+ last_response = ""
847
+ max_iterations = (
848
+ len(mentioned_agents) * 3
849
+ ) # Allow more iterations for parallel
850
+ iteration = 0
851
+
852
+ while iteration < max_iterations and len(
853
+ spoken_agents
854
+ ) < len(mentioned_agents):
855
+ # Determine next speaker(s) using dynamic function
856
+ next_speakers = self.speaker_function(
857
+ mentioned_agents, # Use all mentioned agents, not remaining_agents
858
+ last_response,
859
+ strategy=strategy,
860
+ **self.speaker_state,
608
861
  )
609
862
 
610
- try:
611
- # Get the complete conversation history
612
- context = (
613
- self.conversation.return_history_as_string()
863
+ # Handle both single agent and multiple agents
864
+ if isinstance(next_speakers, str):
865
+ next_speakers = [next_speakers]
866
+
867
+ # Filter out invalid agents
868
+ valid_next_speakers = [
869
+ agent
870
+ for agent in next_speakers
871
+ if agent in mentioned_agents
872
+ ]
873
+
874
+ if not valid_next_speakers:
875
+ # If no valid mentions found, randomly select from unspoken agents
876
+ unspoken_agents = [
877
+ agent
878
+ for agent in mentioned_agents
879
+ if agent not in spoken_agents
880
+ ]
881
+ if unspoken_agents:
882
+ valid_next_speakers = [
883
+ random.choice(unspoken_agents)
884
+ ]
885
+ else:
886
+ # All agents have spoken, break the loop
887
+ break
888
+
889
+ # Process agents based on strategy
890
+ if strategy == "sequential":
891
+ # Process one agent at a time
892
+ for next_speaker in valid_next_speakers:
893
+ if next_speaker in spoken_agents:
894
+ continue # Skip if already spoken
895
+
896
+ response = self._get_agent_response(
897
+ next_speaker, img, imgs
898
+ )
899
+ if response:
900
+ last_response = response
901
+ spoken_agents.add(next_speaker)
902
+ break # Only process one agent in sequential mode
903
+
904
+ elif strategy == "parallel":
905
+ # Process all mentioned agents in parallel
906
+ import concurrent.futures
907
+
908
+ # Get responses from all valid agents
909
+ responses = []
910
+ with concurrent.futures.ThreadPoolExecutor() as executor:
911
+ future_to_agent = {
912
+ executor.submit(
913
+ self._get_agent_response,
914
+ agent,
915
+ img,
916
+ imgs,
917
+ ): agent
918
+ for agent in valid_next_speakers
919
+ if agent not in spoken_agents
920
+ }
921
+
922
+ for (
923
+ future
924
+ ) in concurrent.futures.as_completed(
925
+ future_to_agent
926
+ ):
927
+ agent = future_to_agent[future]
928
+ try:
929
+ response = future.result()
930
+ if response:
931
+ responses.append(response)
932
+ spoken_agents.add(agent)
933
+ except Exception as e:
934
+ logger.error(
935
+ f"Error getting response from {agent}: {e}"
936
+ )
937
+
938
+ # Combine responses for next iteration
939
+ if responses:
940
+ last_response = "\n\n".join(responses)
941
+
942
+ iteration += 1
943
+ else:
944
+ # For non-dynamic speaker functions, use the original logic
945
+ speaking_order = self._get_speaking_order(
946
+ mentioned_agents
947
+ )
948
+ logger.info(
949
+ f"Speaking order determined: {speaking_order}"
950
+ )
951
+
952
+ # Get responses from mentioned agents in the determined order
953
+ for agent_name in speaking_order:
954
+ response = self._get_agent_response(
955
+ agent_name, img, imgs
614
956
  )
615
957
 
616
- # Get response from agent
617
- if isinstance(agent, Agent):
618
- collaborative_task = f"""{context}
958
+ return history_output_formatter(
959
+ self.conversation, self.output_type
960
+ )
961
+
962
+ except InteractiveGroupChatError as e:
963
+ logger.error(f"GroupChat error: {e}")
964
+ raise
965
+ except Exception as e:
966
+ logger.error(f"Unexpected error: {e}")
967
+ raise InteractiveGroupChatError(
968
+ f"Unexpected error occurred: {str(e)}"
969
+ )
970
+
971
+ def _get_agent_response(
972
+ self,
973
+ agent_name: str,
974
+ img: Optional[str] = None,
975
+ imgs: Optional[List[str]] = None,
976
+ ) -> Optional[str]:
977
+ """
978
+ Get response from a specific agent.
979
+
980
+ Args:
981
+ agent_name: Name of the agent to get response from
982
+ img: Optional image for the task
983
+ imgs: Optional list of images for the task
984
+
985
+ Returns:
986
+ The agent's response or None if error
987
+ """
988
+ agent = self.agent_map.get(agent_name)
989
+ if not agent:
990
+ raise AgentNotFoundError(
991
+ f"Agent '{agent_name}' not found"
992
+ )
993
+
994
+ try:
995
+ # Get the complete conversation history
996
+ context = self.conversation.return_history_as_string()
997
+
998
+ # Get response from agent
999
+ if isinstance(agent, Agent):
1000
+ collaborative_task = f"""{context}
619
1001
 
620
1002
  COLLABORATIVE TASK: Please respond to the latest task as {agent_name}.
621
1003
 
@@ -626,38 +1008,56 @@ IMPORTANT INSTRUCTIONS:
626
1008
  4. If you need input from other agents, mention them using @agent_name
627
1009
  5. Provide your unique expertise while showing you understand the group's collective knowledge
628
1010
 
629
- Remember: You are part of a collaborative team. Your response should demonstrate that you've read, understood, and are building upon the contributions of others."""
1011
+ TASK COMPLETION GUIDELINES:
1012
+ - Acknowledge when you are done with your part of the task
1013
+ - Clearly state what still needs to be done before the overall task is finished
1014
+ - If you mention other agents, explain what specific input you need from them
1015
+ - Use phrases like "I have completed [specific part]" or "The task still requires [specific actions]"
630
1016
 
631
- response = agent.run(task=collaborative_task)
632
- else:
633
- # For callable functions
634
- response = agent(context)
1017
+ Remember: You are part of a collaborative team. Your response should demonstrate that you've read, understood, and are building upon the contributions of others."""
635
1018
 
636
- # Add response to conversation
637
- if response and not response.isspace():
638
- self.conversation.add(
639
- role=agent_name, content=response
640
- )
641
- logger.info(f"Agent {agent_name} responded")
1019
+ response = agent.run(
1020
+ task=collaborative_task,
1021
+ img=img,
1022
+ imgs=imgs,
1023
+ )
1024
+ else:
1025
+ # For callable functions
1026
+ response = agent(context)
642
1027
 
643
- except Exception as e:
644
- logger.error(
645
- f"Error getting response from {agent_name}: {e}"
646
- )
647
- self.conversation.add(
648
- role=agent_name,
649
- content=f"Error: Unable to generate response - {str(e)}",
650
- )
1028
+ # Add response to conversation
1029
+ if response and not response.isspace():
1030
+ self.conversation.add(
1031
+ role=agent_name, content=response
1032
+ )
1033
+ logger.info(f"Agent {agent_name} responded")
1034
+ return response
651
1035
 
652
- return history_output_formatter(
653
- self.conversation, self.output_type
1036
+ except Exception as e:
1037
+ logger.error(
1038
+ f"Error getting response from {agent_name}: {e}"
654
1039
  )
1040
+ self.conversation.add(
1041
+ role=agent_name,
1042
+ content=f"Error: Unable to generate response - {str(e)}",
1043
+ )
1044
+ return f"Error: Unable to generate response - {str(e)}"
655
1045
 
656
- except InteractiveGroupChatError as e:
657
- logger.error(f"GroupChat error: {e}")
658
- raise
659
- except Exception as e:
660
- logger.error(f"Unexpected error: {e}")
661
- raise InteractiveGroupChatError(
662
- f"Unexpected error occurred: {str(e)}"
1046
+ return None
1047
+
1048
+ def set_dynamic_strategy(self, strategy: str) -> None:
1049
+ """
1050
+ Set the strategy for the random-dynamic-speaker function.
1051
+
1052
+ Args:
1053
+ strategy: Either "sequential" or "parallel"
1054
+ - "sequential": Process one agent at a time based on @mentions
1055
+ - "parallel": Process all mentioned agents simultaneously
1056
+ """
1057
+ if strategy not in ["sequential", "parallel"]:
1058
+ raise ValueError(
1059
+ "Strategy must be either 'sequential' or 'parallel'"
663
1060
  )
1061
+
1062
+ self.speaker_state["strategy"] = strategy
1063
+ logger.info(f"Dynamic speaker strategy set to: {strategy}")