swarms 7.9.3__py3-none-any.whl → 7.9.4__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.
@@ -9,6 +9,7 @@ from swarms.utils.history_output_formatter import (
9
9
  history_output_formatter,
10
10
  )
11
11
  from swarms.utils.loguru_logger import initialize_logger
12
+ from swarms.utils.formatter import formatter
12
13
 
13
14
  logger = initialize_logger(log_folder="concurrent_workflow")
14
15
 
@@ -32,7 +33,7 @@ class ConcurrentWorkflow(BaseSwarm):
32
33
  return_str_on (bool): Flag indicating whether to return the output as a string. Defaults to False.
33
34
  auto_generate_prompts (bool): Flag indicating whether to auto-generate prompts for agents. Defaults to False.
34
35
  return_entire_history (bool): Flag indicating whether to return the entire conversation history. Defaults to False.
35
-
36
+ show_dashboard (bool): Flag indicating whether to show a real-time dashboard. Defaults to True.
36
37
 
37
38
  Raises:
38
39
  ValueError: If the list of agents is empty or if the description is empty.
@@ -46,6 +47,8 @@ class ConcurrentWorkflow(BaseSwarm):
46
47
  output_type (str): The type of output format.
47
48
  max_loops (int): The maximum number of loops for each agent.
48
49
  auto_generate_prompts (bool): Flag indicating whether to auto-generate prompts for agents.
50
+ show_dashboard (bool): Flag indicating whether to show a real-time dashboard.
51
+ agent_statuses (dict): Dictionary to track agent statuses.
49
52
  """
50
53
 
51
54
  def __init__(
@@ -58,6 +61,7 @@ class ConcurrentWorkflow(BaseSwarm):
58
61
  output_type: str = "dict-all-except-first",
59
62
  max_loops: int = 1,
60
63
  auto_generate_prompts: bool = False,
64
+ show_dashboard: bool = True,
61
65
  *args,
62
66
  **kwargs,
63
67
  ):
@@ -76,10 +80,24 @@ class ConcurrentWorkflow(BaseSwarm):
76
80
  self.max_loops = max_loops
77
81
  self.auto_generate_prompts = auto_generate_prompts
78
82
  self.output_type = output_type
83
+ self.show_dashboard = show_dashboard
84
+ self.agent_statuses = {
85
+ agent.agent_name: {"status": "pending", "output": ""}
86
+ for agent in agents
87
+ }
79
88
 
80
89
  self.reliability_check()
81
90
  self.conversation = Conversation()
82
91
 
92
+ if self.show_dashboard is True:
93
+ self.agents = self.fix_agents()
94
+
95
+ def fix_agents(self):
96
+ if self.show_dashboard is True:
97
+ for agent in self.agents:
98
+ agent.print_on = False
99
+ return self.agents
100
+
83
101
  def reliability_check(self):
84
102
  try:
85
103
  if self.agents is None:
@@ -115,7 +133,146 @@ class ConcurrentWorkflow(BaseSwarm):
115
133
  for agent in self.agents:
116
134
  agent.auto_generate_prompt = True
117
135
 
118
- def run(
136
+ def display_agent_dashboard(
137
+ self,
138
+ title: str = "🤖 Agent Dashboard",
139
+ is_final: bool = False,
140
+ ) -> None:
141
+ """
142
+ Displays the current status of all agents in a beautiful dashboard format.
143
+
144
+ Args:
145
+ title (str): The title of the dashboard.
146
+ is_final (bool): Flag indicating whether this is the final dashboard.
147
+ """
148
+ agents_data = [
149
+ {
150
+ "name": agent.agent_name,
151
+ "status": self.agent_statuses[agent.agent_name][
152
+ "status"
153
+ ],
154
+ "output": self.agent_statuses[agent.agent_name][
155
+ "output"
156
+ ],
157
+ }
158
+ for agent in self.agents
159
+ ]
160
+ formatter.print_agent_dashboard(agents_data, title, is_final)
161
+
162
+ def run_with_dashboard(
163
+ self,
164
+ task: str,
165
+ img: Optional[str] = None,
166
+ imgs: Optional[List[str]] = None,
167
+ ):
168
+ """
169
+ Executes all agents in the workflow concurrently on the given task.
170
+ Now includes real-time dashboard updates.
171
+ """
172
+ try:
173
+ self.conversation.add(role="User", content=task)
174
+
175
+ # Reset agent statuses
176
+ for agent in self.agents:
177
+ self.agent_statuses[agent.agent_name] = {
178
+ "status": "pending",
179
+ "output": "",
180
+ }
181
+
182
+ # Display initial dashboard if enabled
183
+ if self.show_dashboard:
184
+ self.display_agent_dashboard()
185
+
186
+ # Use 95% of available CPU cores for optimal performance
187
+ max_workers = int(os.cpu_count() * 0.95)
188
+
189
+ # Create a list to store all futures and their results
190
+ futures = []
191
+ results = []
192
+
193
+ def run_agent_with_status(agent, task, img, imgs):
194
+ try:
195
+ # Update status to running
196
+ self.agent_statuses[agent.agent_name][
197
+ "status"
198
+ ] = "running"
199
+ if self.show_dashboard:
200
+ self.display_agent_dashboard()
201
+
202
+ # Run the agent
203
+ output = agent.run(task=task, img=img, imgs=imgs)
204
+
205
+ # Update status to completed
206
+ self.agent_statuses[agent.agent_name][
207
+ "status"
208
+ ] = "completed"
209
+ self.agent_statuses[agent.agent_name][
210
+ "output"
211
+ ] = output
212
+ if self.show_dashboard:
213
+ self.display_agent_dashboard()
214
+
215
+ return output
216
+ except Exception as e:
217
+ # Update status to error
218
+ self.agent_statuses[agent.agent_name][
219
+ "status"
220
+ ] = "error"
221
+ self.agent_statuses[agent.agent_name][
222
+ "output"
223
+ ] = f"Error: {str(e)}"
224
+ if self.show_dashboard:
225
+ self.display_agent_dashboard()
226
+ raise
227
+
228
+ # Run agents concurrently using ThreadPoolExecutor
229
+ with concurrent.futures.ThreadPoolExecutor(
230
+ max_workers=max_workers
231
+ ) as executor:
232
+ # Submit all agent tasks
233
+ futures = [
234
+ executor.submit(
235
+ run_agent_with_status, agent, task, img, imgs
236
+ )
237
+ for agent in self.agents
238
+ ]
239
+
240
+ # Wait for all futures to complete
241
+ concurrent.futures.wait(futures)
242
+
243
+ # Process results in order of completion
244
+ for future, agent in zip(futures, self.agents):
245
+ try:
246
+ output = future.result()
247
+ results.append((agent.agent_name, output))
248
+ except Exception as e:
249
+ logger.error(
250
+ f"Agent {agent.agent_name} failed: {str(e)}"
251
+ )
252
+ results.append(
253
+ (agent.agent_name, f"Error: {str(e)}")
254
+ )
255
+
256
+ # Add all results to conversation
257
+ for agent_name, output in results:
258
+ self.conversation.add(role=agent_name, content=output)
259
+
260
+ # Display final dashboard if enabled
261
+ if self.show_dashboard:
262
+ self.display_agent_dashboard(
263
+ "🎉 Final Agent Dashboard", is_final=True
264
+ )
265
+
266
+ return history_output_formatter(
267
+ conversation=self.conversation,
268
+ type=self.output_type,
269
+ )
270
+ finally:
271
+ # Always clean up the dashboard display
272
+ if self.show_dashboard:
273
+ formatter.stop_dashboard()
274
+
275
+ def _run(
119
276
  self,
120
277
  task: str,
121
278
  img: Optional[str] = None,
@@ -167,6 +324,20 @@ class ConcurrentWorkflow(BaseSwarm):
167
324
  type=self.output_type,
168
325
  )
169
326
 
327
+ def run(
328
+ self,
329
+ task: str,
330
+ img: Optional[str] = None,
331
+ imgs: Optional[List[str]] = None,
332
+ ):
333
+ """
334
+ Executes all agents in the workflow concurrently on the given task.
335
+ """
336
+ if self.show_dashboard:
337
+ return self.run_with_dashboard(task, img, imgs)
338
+ else:
339
+ return self._run(task, img, imgs)
340
+
170
341
  def batch_run(
171
342
  self,
172
343
  tasks: List[str],
@@ -6,6 +6,7 @@ from loguru import logger
6
6
 
7
7
  from swarms.structs.agent import Agent
8
8
  from swarms.structs.conversation import Conversation
9
+ from swarms.structs.ma_utils import create_agent_map
9
10
  from swarms.utils.generate_keys import generate_api_key
10
11
  from swarms.utils.history_output_formatter import (
11
12
  history_output_formatter,
@@ -24,12 +25,6 @@ class AgentNotFoundError(InteractiveGroupChatError):
24
25
  pass
25
26
 
26
27
 
27
- class NoMentionedAgentsError(InteractiveGroupChatError):
28
- """Raised when no agents are mentioned in the task"""
29
-
30
- pass
31
-
32
-
33
28
  class InvalidTaskFormatError(InteractiveGroupChatError):
34
29
  """Raised when the task format is invalid"""
35
30
 
@@ -294,14 +289,7 @@ class InteractiveGroupChat:
294
289
  # Initialize conversation history
295
290
  self.conversation = Conversation(time_enabled=True)
296
291
 
297
- # Create a mapping of agent names to agents for easy lookup
298
- self.agent_map = {}
299
- for agent in agents:
300
- if isinstance(agent, Agent):
301
- self.agent_map[agent.agent_name] = agent
302
- elif callable(agent):
303
- # For callable functions, use the function name as the agent name
304
- self.agent_map[agent.__name__] = agent
292
+ self.agent_map = create_agent_map(self.agents)
305
293
 
306
294
  self._validate_initialization()
307
295
  self._setup_conversation_context()
@@ -398,7 +386,7 @@ class InteractiveGroupChat:
398
386
  Start an interactive terminal session for chatting with agents.
399
387
 
400
388
  This method creates a REPL (Read-Eval-Print Loop) that allows users to:
401
- - Chat with agents using @mentions
389
+ - Chat with agents using @mentions (optional)
402
390
  - See available agents and their descriptions
403
391
  - Exit the session using 'exit' or 'quit'
404
392
  - Get help using 'help' or '?'
@@ -426,7 +414,9 @@ class InteractiveGroupChat:
426
414
  print("- Type 'help' or '?' for help")
427
415
  print("- Type 'exit' or 'quit' to end the session")
428
416
  print("- Type 'speaker' to change speaker function")
429
- print("- Use @agent_name to mention agents")
417
+ print(
418
+ "- Use @agent_name to mention specific agents (optional)"
419
+ )
430
420
  print("\nStart chatting:")
431
421
 
432
422
  while True:
@@ -441,9 +431,11 @@ class InteractiveGroupChat:
441
431
 
442
432
  if user_input.lower() in ["help", "?"]:
443
433
  print("\nHelp:")
444
- print("1. Mention agents using @agent_name")
445
434
  print(
446
- "2. You can mention multiple agents in one task"
435
+ "1. You can mention specific agents using @agent_name (optional)"
436
+ )
437
+ print(
438
+ "2. If no agents are mentioned, they will be selected automatically"
447
439
  )
448
440
  print("3. Available agents:")
449
441
  for name in self.agent_map:
@@ -513,10 +505,6 @@ class InteractiveGroupChat:
513
505
  print("\nChat:")
514
506
  # print(response)
515
507
 
516
- except NoMentionedAgentsError:
517
- print(
518
- "\nError: Please mention at least one agent using @agent_name"
519
- )
520
508
  except AgentNotFoundError as e:
521
509
  print(f"\nError: {str(e)}")
522
510
  except Exception as e:
@@ -699,13 +687,13 @@ Remember: You are part of a team. Your response should reflect that you've read,
699
687
 
700
688
  def _extract_mentions(self, task: str) -> List[str]:
701
689
  """
702
- Extracts @mentions from the task.
690
+ Extracts @mentions from the task. If no mentions are found, returns all available agents.
703
691
 
704
692
  Args:
705
693
  task (str): The input task
706
694
 
707
695
  Returns:
708
- List[str]: List of mentioned agent names
696
+ List[str]: List of mentioned agent names or all agent names if no mentions
709
697
 
710
698
  Raises:
711
699
  InvalidtaskFormatError: If the task format is invalid
@@ -713,11 +701,17 @@ Remember: You are part of a team. Your response should reflect that you've read,
713
701
  try:
714
702
  # Find all @mentions using regex
715
703
  mentions = re.findall(r"@(\w+)", task)
716
- return [
704
+ valid_mentions = [
717
705
  mention
718
706
  for mention in mentions
719
707
  if mention in self.agent_map
720
708
  ]
709
+
710
+ # If no valid mentions found, return all available agents
711
+ if not valid_mentions:
712
+ return list(self.agent_map.keys())
713
+
714
+ return valid_mentions
721
715
  except Exception as e:
722
716
  logger.error(f"Error extracting mentions: {e}")
723
717
  raise InvalidTaskFormatError(f"Invalid task format: {e}")
@@ -810,6 +804,149 @@ Remember: You are part of a team. Your response should reflect that you've read,
810
804
  # Fallback to original order
811
805
  return mentioned_agents
812
806
 
807
+ def _process_dynamic_speakers(
808
+ self,
809
+ mentioned_agents: List[str],
810
+ img: Optional[str],
811
+ imgs: Optional[List[str]],
812
+ ) -> None:
813
+ """
814
+ Process responses using the dynamic speaker function.
815
+ """
816
+ # Get strategy from speaker state (default to sequential)
817
+ strategy = self.speaker_state.get("strategy", "sequential")
818
+
819
+ # Track which agents have spoken to ensure all get a chance
820
+ spoken_agents = set()
821
+ last_response = ""
822
+ max_iterations = (
823
+ len(mentioned_agents) * 3
824
+ ) # Allow more iterations for parallel
825
+ iteration = 0
826
+
827
+ while iteration < max_iterations and len(spoken_agents) < len(
828
+ mentioned_agents
829
+ ):
830
+ # Determine next speaker(s) using dynamic function
831
+ next_speakers = self.speaker_function(
832
+ mentioned_agents,
833
+ last_response,
834
+ strategy=strategy,
835
+ **self.speaker_state,
836
+ )
837
+
838
+ # Handle both single agent and multiple agents
839
+ if isinstance(next_speakers, str):
840
+ next_speakers = [next_speakers]
841
+
842
+ # Filter out invalid agents
843
+ valid_next_speakers = [
844
+ agent
845
+ for agent in next_speakers
846
+ if agent in mentioned_agents
847
+ ]
848
+
849
+ if not valid_next_speakers:
850
+ # If no valid mentions found, randomly select from unspoken agents
851
+ unspoken_agents = [
852
+ agent
853
+ for agent in mentioned_agents
854
+ if agent not in spoken_agents
855
+ ]
856
+ if unspoken_agents:
857
+ valid_next_speakers = [
858
+ random.choice(unspoken_agents)
859
+ ]
860
+ else:
861
+ # All agents have spoken, break the loop
862
+ break
863
+
864
+ # Process agents based on strategy
865
+ if strategy == "sequential":
866
+ self._process_sequential_speakers(
867
+ valid_next_speakers, spoken_agents, img, imgs
868
+ )
869
+ elif strategy == "parallel":
870
+ self._process_parallel_speakers(
871
+ valid_next_speakers, spoken_agents, img, imgs
872
+ )
873
+
874
+ iteration += 1
875
+
876
+ def _process_sequential_speakers(
877
+ self,
878
+ speakers: List[str],
879
+ spoken_agents: set,
880
+ img: Optional[str],
881
+ imgs: Optional[List[str]],
882
+ ) -> None:
883
+ """
884
+ Process speakers sequentially.
885
+ """
886
+ for next_speaker in speakers:
887
+ if next_speaker in spoken_agents:
888
+ continue # Skip if already spoken
889
+
890
+ response = self._get_agent_response(
891
+ next_speaker, img, imgs
892
+ )
893
+ if response:
894
+ spoken_agents.add(next_speaker)
895
+ break # Only process one agent in sequential mode
896
+
897
+ def _process_parallel_speakers(
898
+ self,
899
+ speakers: List[str],
900
+ spoken_agents: set,
901
+ img: Optional[str],
902
+ imgs: Optional[List[str]],
903
+ ) -> None:
904
+ """
905
+ Process speakers in parallel.
906
+ """
907
+ import concurrent.futures
908
+
909
+ # Get responses from all valid agents
910
+ responses = []
911
+ with concurrent.futures.ThreadPoolExecutor() as executor:
912
+ future_to_agent = {
913
+ executor.submit(
914
+ self._get_agent_response, agent, img, imgs
915
+ ): agent
916
+ for agent in speakers
917
+ if agent not in spoken_agents
918
+ }
919
+
920
+ for future in concurrent.futures.as_completed(
921
+ future_to_agent
922
+ ):
923
+ agent = future_to_agent[future]
924
+ try:
925
+ response = future.result()
926
+ if response:
927
+ responses.append(response)
928
+ spoken_agents.add(agent)
929
+ except Exception as e:
930
+ logger.error(
931
+ f"Error getting response from {agent}: {e}"
932
+ )
933
+
934
+ def _process_static_speakers(
935
+ self,
936
+ mentioned_agents: List[str],
937
+ img: Optional[str],
938
+ imgs: Optional[List[str]],
939
+ ) -> None:
940
+ """
941
+ Process responses using a static speaker function.
942
+ """
943
+ speaking_order = self._get_speaking_order(mentioned_agents)
944
+ logger.info(f"Speaking order determined: {speaking_order}")
945
+
946
+ # Get responses from mentioned agents in the determined order
947
+ for agent_name in speaking_order:
948
+ self._get_agent_response(agent_name, img, imgs)
949
+
813
950
  def run(
814
951
  self,
815
952
  task: str,
@@ -817,151 +954,33 @@ Remember: You are part of a team. Your response should reflect that you've read,
817
954
  imgs: Optional[List[str]] = None,
818
955
  ) -> str:
819
956
  """
820
- Process a task and get responses from mentioned agents.
821
- If interactive mode is enabled, this will be called by start_interactive_session().
822
- Otherwise, it can be called directly for single task processing.
957
+ Process a task and get responses from agents. If no agents are mentioned,
958
+ randomly selects agents to participate.
823
959
  """
824
960
  try:
825
- # Extract mentioned agents
826
- mentioned_agents = self._extract_mentions(task)
827
-
828
- if not mentioned_agents:
829
- raise NoMentionedAgentsError(
830
- "No valid agents mentioned in the task"
831
- )
961
+ # Extract mentioned agents (or all agents if none mentioned)
962
+ if "@" in task:
963
+ mentioned_agents = self._extract_mentions(task)
964
+ else:
965
+ pass
832
966
 
833
967
  # Add user task to conversation
834
968
  self.conversation.add(role="User", content=task)
835
969
 
836
- # Handle dynamic speaker function differently
970
+ # Process responses based on speaker function type
837
971
  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"
972
+ self._process_dynamic_speakers(
973
+ mentioned_agents, img, imgs
841
974
  )
842
-
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,
861
- )
862
-
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
975
  else:
944
- # For non-dynamic speaker functions, use the original logic
945
- speaking_order = self._get_speaking_order(
946
- mentioned_agents
976
+ self._process_static_speakers(
977
+ mentioned_agents, img, imgs
947
978
  )
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
956
- )
957
979
 
958
980
  return history_output_formatter(
959
981
  self.conversation, self.output_type
960
982
  )
961
983
 
962
- except InteractiveGroupChatError as e:
963
- logger.error(f"GroupChat error: {e}")
964
- raise
965
984
  except Exception as e:
966
985
  logger.error(f"Unexpected error: {e}")
967
986
  raise InteractiveGroupChatError(