swarms 7.8.9__py3-none-any.whl → 7.9.0__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.
@@ -1,5 +1,6 @@
1
1
  import re
2
- from typing import Callable, List, Union
2
+ import random
3
+ from typing import Callable, List, Union, Optional
3
4
 
4
5
  from loguru import logger
5
6
 
@@ -35,6 +36,91 @@ class InvalidTaskFormatError(InteractiveGroupChatError):
35
36
  pass
36
37
 
37
38
 
39
+ class InvalidSpeakerFunctionError(InteractiveGroupChatError):
40
+ """Raised when an invalid speaker function is provided"""
41
+
42
+ pass
43
+
44
+
45
+ # Built-in speaker functions
46
+ def round_robin_speaker(
47
+ agents: List[str], current_index: int = 0
48
+ ) -> str:
49
+ """
50
+ Round robin speaker function that cycles through agents in order.
51
+
52
+ Args:
53
+ agents: List of agent names
54
+ current_index: Current position in the cycle
55
+
56
+ Returns:
57
+ Next agent name in the round robin sequence
58
+ """
59
+ if not agents:
60
+ raise ValueError("No agents provided for round robin")
61
+ return agents[current_index % len(agents)]
62
+
63
+
64
+ def random_speaker(agents: List[str], **kwargs) -> str:
65
+ """
66
+ Random speaker function that selects agents randomly.
67
+
68
+ Args:
69
+ agents: List of agent names
70
+ **kwargs: Additional arguments (ignored)
71
+
72
+ Returns:
73
+ Randomly selected agent name
74
+ """
75
+ if not agents:
76
+ raise ValueError("No agents provided for random selection")
77
+ return random.choice(agents)
78
+
79
+
80
+ def priority_speaker(
81
+ agents: List[str], priorities: dict, **kwargs
82
+ ) -> str:
83
+ """
84
+ Priority-based speaker function that selects agents based on priority weights.
85
+
86
+ Args:
87
+ agents: List of agent names
88
+ priorities: Dictionary mapping agent names to priority weights
89
+ **kwargs: Additional arguments (ignored)
90
+
91
+ Returns:
92
+ Selected agent name based on priority weights
93
+ """
94
+ if not agents:
95
+ raise ValueError("No agents provided for priority selection")
96
+
97
+ # Filter agents that exist in the priorities dict
98
+ available_agents = [
99
+ agent for agent in agents if agent in priorities
100
+ ]
101
+ if not available_agents:
102
+ # Fallback to random if no priorities match
103
+ return random.choice(agents)
104
+
105
+ # Calculate total weight
106
+ total_weight = sum(
107
+ priorities[agent] for agent in available_agents
108
+ )
109
+ if total_weight == 0:
110
+ return random.choice(available_agents)
111
+
112
+ # Select based on weighted probability
113
+ rand_val = random.uniform(0, total_weight)
114
+ current_weight = 0
115
+
116
+ for agent in available_agents:
117
+ current_weight += priorities[agent]
118
+ if rand_val <= current_weight:
119
+ return agent
120
+
121
+ return available_agents[-1] # Fallback
122
+
123
+
38
124
  class InteractiveGroupChat:
39
125
  """
40
126
  An interactive group chat system that enables conversations with multiple agents using @mentions.
@@ -49,6 +135,8 @@ class InteractiveGroupChat:
49
135
  max_loops (int): Maximum number of conversation turns
50
136
  conversation (Conversation): Stores the chat history
51
137
  agent_map (Dict[str, Union[Agent, Callable]]): Mapping of agent names to their instances
138
+ speaker_function (Callable): Function to determine speaking order
139
+ speaker_state (dict): State for speaker functions that need it
52
140
 
53
141
  Args:
54
142
  name (str, optional): Name of the group chat. Defaults to "InteractiveGroupChat".
@@ -57,6 +145,8 @@ class InteractiveGroupChat:
57
145
  max_loops (int, optional): Maximum conversation turns. Defaults to 1.
58
146
  output_type (str, optional): Type of output format. Defaults to "string".
59
147
  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.
149
+ speaker_state (dict, optional): Initial state for speaker function. Defaults to empty dict.
60
150
 
61
151
  Raises:
62
152
  ValueError: If invalid initialization parameters are provided
@@ -71,6 +161,8 @@ class InteractiveGroupChat:
71
161
  max_loops: int = 1,
72
162
  output_type: str = "string",
73
163
  interactive: bool = False,
164
+ speaker_function: Optional[Callable] = None,
165
+ speaker_state: Optional[dict] = None,
74
166
  ):
75
167
  self.id = id
76
168
  self.name = name
@@ -80,6 +172,15 @@ class InteractiveGroupChat:
80
172
  self.output_type = output_type
81
173
  self.interactive = interactive
82
174
 
175
+ # Speaker function configuration
176
+ self.speaker_function = (
177
+ speaker_function or round_robin_speaker
178
+ )
179
+ self.speaker_state = speaker_state or {"current_index": 0}
180
+
181
+ # Validate speaker function
182
+ self._validate_speaker_function()
183
+
83
184
  # Initialize conversation history
84
185
  self.conversation = Conversation(time_enabled=True)
85
186
 
@@ -96,6 +197,32 @@ class InteractiveGroupChat:
96
197
  self._setup_conversation_context()
97
198
  self._update_agent_prompts()
98
199
 
200
+ def _validate_speaker_function(self) -> None:
201
+ """
202
+ Validates the speaker function.
203
+
204
+ Raises:
205
+ InvalidSpeakerFunctionError: If the speaker function is invalid
206
+ """
207
+ if not callable(self.speaker_function):
208
+ raise InvalidSpeakerFunctionError(
209
+ "Speaker function must be callable"
210
+ )
211
+
212
+ # Test the speaker function with a dummy list
213
+ try:
214
+ test_result = self.speaker_function(
215
+ ["test_agent"], **self.speaker_state
216
+ )
217
+ if not isinstance(test_result, str):
218
+ raise InvalidSpeakerFunctionError(
219
+ "Speaker function must return a string"
220
+ )
221
+ except Exception as e:
222
+ raise InvalidSpeakerFunctionError(
223
+ f"Speaker function validation failed: {e}"
224
+ )
225
+
99
226
  def _validate_initialization(self) -> None:
100
227
  """
101
228
  Validates the group chat configuration.
@@ -150,6 +277,26 @@ class InteractiveGroupChat:
150
277
  }
151
278
  )
152
279
 
280
+ # Create the enhanced prompt that teaches agents how to use @mentions
281
+ mention_instruction = """
282
+
283
+ IMPORTANT: You are part of a collaborative group chat where you can interact with other agents using @mentions.
284
+
285
+ -COLLABORATIVE RESPONSE PROTOCOL:
286
+ 1. FIRST: Read and understand all previous responses from other agents
287
+ 2. ACKNOWLEDGE: Reference and acknowledge what other agents have said
288
+ 3. BUILD UPON: Add your perspective while building upon their insights
289
+ 4. MENTION: Use @agent_name to call on other agents when needed
290
+
291
+ HOW TO MENTION OTHER AGENTS:
292
+ - Use @agent_name to mention another agent in your response
293
+ - You can mention multiple agents: @agent1 @agent2
294
+ - When you mention an agent, they will be notified and can respond
295
+ - Example: "I think @analyst should review this data" or "Let's ask @researcher to investigate this further"
296
+
297
+ AVAILABLE AGENTS TO MENTION:
298
+ """
299
+
153
300
  group_context = (
154
301
  f"\n\nYou are part of a group chat named '{self.name}' with the following description: {self.description}\n"
155
302
  f"Other participants in this chat:\n"
@@ -163,11 +310,40 @@ class InteractiveGroupChat:
163
310
  for info in agent_info
164
311
  if info["name"] != agent.agent_name
165
312
  ]
166
- agent_context = group_context
313
+ agent_context = group_context + mention_instruction
167
314
  for other in other_agents:
168
- agent_context += (
169
- f"- {other['name']}: {other['description']}\n"
170
- )
315
+ agent_context += f"- @{other['name']}: {other['description']}\n"
316
+
317
+ # Add final instruction
318
+ agent_context += """
319
+
320
+ COLLABORATION GUIDELINES:
321
+ - ALWAYS read the full conversation history before responding
322
+ - ACKNOWLEDGE other agents' contributions: "Building on @analyst's data insights..." or "I agree with @researcher's findings that..."
323
+ - BUILD UPON previous responses rather than repeating information
324
+ - SYNTHESIZE multiple perspectives when possible
325
+ - ASK CLARIFYING QUESTIONS if you need more information from other agents
326
+ - DELEGATE appropriately: "Let me ask @expert_agent to verify this" or "@specialist, can you elaborate on this point?"
327
+
328
+ RESPONSE STRUCTURE:
329
+ 1. ACKNOWLEDGE: "I've reviewed the responses from @agent1 and @agent2..."
330
+ 2. BUILD: "Building on @agent1's analysis of the data..."
331
+ 3. CONTRIBUTE: "From my perspective, I would add..."
332
+ 4. COLLABORATE: "To get a complete picture, let me ask @agent3 to..."
333
+ 5. SYNTHESIZE: "Combining our insights, the key findings are..."
334
+
335
+ 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."
338
+
339
+ AVOID:
340
+ - Ignoring other agents' responses
341
+ - Repeating what others have already said
342
+ - Making assumptions without consulting relevant experts
343
+ - Responding in isolation without considering the group's collective knowledge
344
+
345
+ Remember: You are part of a team. Your response should reflect that you've read, understood, and built upon the contributions of others.
346
+ """
171
347
 
172
348
  # Update the agent's system prompt
173
349
  agent.system_prompt = (
@@ -202,6 +378,118 @@ class InteractiveGroupChat:
202
378
  logger.error(f"Error extracting mentions: {e}")
203
379
  raise InvalidTaskFormatError(f"Invalid task format: {e}")
204
380
 
381
+ def _get_speaking_order(
382
+ self, mentioned_agents: List[str]
383
+ ) -> List[str]:
384
+ """
385
+ Determines the speaking order using the configured speaker function.
386
+
387
+ Args:
388
+ mentioned_agents: List of agent names that were mentioned
389
+
390
+ Returns:
391
+ List of agent names in the order they should speak
392
+ """
393
+ if not mentioned_agents:
394
+ return []
395
+
396
+ # Use the speaker function to determine order
397
+ try:
398
+ if self.speaker_function == round_robin_speaker:
399
+ # For round robin, we need to maintain state
400
+ current_index = self.speaker_state.get(
401
+ "current_index", 0
402
+ )
403
+ ordered_agents = []
404
+
405
+ # Create the order starting from current index
406
+ for i in range(len(mentioned_agents)):
407
+ agent = round_robin_speaker(
408
+ mentioned_agents, current_index + i
409
+ )
410
+ ordered_agents.append(agent)
411
+
412
+ # Update state for next round
413
+ self.speaker_state["current_index"] = (
414
+ current_index + len(mentioned_agents)
415
+ ) % len(mentioned_agents)
416
+ return ordered_agents
417
+
418
+ elif self.speaker_function == random_speaker:
419
+ # For random, shuffle the list
420
+ shuffled = mentioned_agents.copy()
421
+ random.shuffle(shuffled)
422
+ return shuffled
423
+
424
+ elif self.speaker_function == priority_speaker:
425
+ # For priority, we need priorities in speaker_state
426
+ priorities = self.speaker_state.get("priorities", {})
427
+ if not priorities:
428
+ # Fallback to random if no priorities set
429
+ shuffled = mentioned_agents.copy()
430
+ random.shuffle(shuffled)
431
+ return shuffled
432
+
433
+ # Sort by priority (higher priority first)
434
+ sorted_agents = sorted(
435
+ mentioned_agents,
436
+ key=lambda x: priorities.get(x, 0),
437
+ reverse=True,
438
+ )
439
+ return sorted_agents
440
+
441
+ else:
442
+ # Custom speaker function
443
+ # For custom functions, we'll use the first agent returned
444
+ # and then process the rest in original order
445
+ first_speaker = self.speaker_function(
446
+ mentioned_agents, **self.speaker_state
447
+ )
448
+ if first_speaker in mentioned_agents:
449
+ remaining = [
450
+ agent
451
+ for agent in mentioned_agents
452
+ if agent != first_speaker
453
+ ]
454
+ return [first_speaker] + remaining
455
+ else:
456
+ return mentioned_agents
457
+
458
+ except Exception as e:
459
+ logger.error(f"Error in speaker function: {e}")
460
+ # Fallback to original order
461
+ return mentioned_agents
462
+
463
+ def set_speaker_function(
464
+ 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
+
205
493
  def start_interactive_session(self):
206
494
  """
207
495
  Start an interactive terminal session for chatting with agents.
@@ -263,9 +551,9 @@ class InteractiveGroupChat:
263
551
 
264
552
  # Process the task and get responses
265
553
  try:
266
- response = self.run(user_input)
554
+ self.run(user_input)
267
555
  print("\nChat:")
268
- print(response)
556
+ # print(response)
269
557
 
270
558
  except NoMentionedAgentsError:
271
559
  print(
@@ -303,8 +591,16 @@ class InteractiveGroupChat:
303
591
  # Add user task to conversation
304
592
  self.conversation.add(role="User", content=task)
305
593
 
306
- # Get responses from mentioned agents
307
- for agent_name in mentioned_agents:
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
+ )
601
+
602
+ # Get responses from mentioned agents in the determined order
603
+ for agent_name in speaking_order:
308
604
  agent = self.agent_map.get(agent_name)
309
605
  if not agent:
310
606
  raise AgentNotFoundError(
@@ -319,9 +615,20 @@ class InteractiveGroupChat:
319
615
 
320
616
  # Get response from agent
321
617
  if isinstance(agent, Agent):
322
- response = agent.run(
323
- task=f"{context}\nPlease respond to the latest task as {agent_name}."
324
- )
618
+ collaborative_task = f"""{context}
619
+
620
+ COLLABORATIVE TASK: Please respond to the latest task as {agent_name}.
621
+
622
+ IMPORTANT INSTRUCTIONS:
623
+ 1. Read the ENTIRE conversation history above
624
+ 2. Acknowledge what other agents have said before adding your perspective
625
+ 3. Build upon their insights rather than repeating information
626
+ 4. If you need input from other agents, mention them using @agent_name
627
+ 5. Provide your unique expertise while showing you understand the group's collective knowledge
628
+
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."""
630
+
631
+ response = agent.run(task=collaborative_task)
325
632
  else:
326
633
  # For callable functions
327
634
  response = agent(context)
@@ -1,12 +1,17 @@
1
1
  from typing import List, Any, Optional, Union, Callable
2
2
  import random
3
+ from swarms.prompts.collaborative_prompts import (
4
+ get_multi_agent_collaboration_prompt_one,
5
+ )
3
6
 
4
7
 
5
8
  def list_all_agents(
6
9
  agents: List[Union[Callable, Any]],
7
10
  conversation: Optional[Any] = None,
8
- name: str = "",
9
- add_to_conversation: bool = False,
11
+ name: Optional[str] = None,
12
+ description: Optional[str] = None,
13
+ add_to_conversation: Optional[bool] = False,
14
+ add_collaboration_prompt: Optional[bool] = True,
10
15
  ) -> str:
11
16
  """Lists all agents in a swarm and optionally adds them to a conversation.
12
17
 
@@ -27,6 +32,7 @@ def list_all_agents(
27
32
  >>> conversation = Conversation()
28
33
  >>> agent_info = list_all_agents(agents, conversation, "MySwarm")
29
34
  >>> print(agent_info)
35
+ Swarm: MySwarm
30
36
  Total Agents: 2
31
37
 
32
38
  Agent: Agent1
@@ -39,8 +45,15 @@ def list_all_agents(
39
45
  # Compile information about all agents
40
46
  total_agents = len(agents)
41
47
 
42
- all_agents = f"Total Agents: {total_agents}\n\n" + "\n\n".join(
43
- f"Agent: {agent.agent_name} \n\n Description: {agent.description or (agent.system_prompt[:50] + '...' if len(agent.system_prompt) > 50 else agent.system_prompt)}"
48
+ all_agents = f"Team Name: {name}\n" if name else ""
49
+ all_agents += (
50
+ f"Team Description: {description}\n" if description else ""
51
+ )
52
+ all_agents += f"Total Agents: {total_agents}\n\n"
53
+ all_agents += "| Agent | Description |\n"
54
+ all_agents += "|-------|-------------|\n"
55
+ all_agents += "\n".join(
56
+ f"| {agent.agent_name} | {agent.description or (agent.system_prompt[:50] + '...' if len(agent.system_prompt) > 50 else agent.system_prompt)} |"
44
57
  for agent in agents
45
58
  )
46
59
 
@@ -48,10 +61,15 @@ def list_all_agents(
48
61
  # Add the agent information to the conversation
49
62
  conversation.add(
50
63
  role="System",
51
- content=f"All Agents Available in the Swarm {name}:\n\n{all_agents}",
64
+ content=all_agents,
52
65
  )
53
66
 
54
- return all_agents
67
+ if add_collaboration_prompt:
68
+ return get_multi_agent_collaboration_prompt_one(
69
+ agents_in_swarm=all_agents
70
+ )
71
+ else:
72
+ return all_agents
55
73
 
56
74
 
57
75
  models = [
@@ -68,6 +86,7 @@ models = [
68
86
  "o4-mini",
69
87
  "o3",
70
88
  "gpt-4.1",
89
+ "groq/llama-3.1-8b-instant",
71
90
  "gpt-4.1-nano",
72
91
  ]
73
92