swarms 7.9.9__py3-none-any.whl → 8.0.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.
@@ -0,0 +1,253 @@
1
+ from swarms.structs.agent import Agent
2
+ from typing import List
3
+ from swarms.structs.conversation import Conversation
4
+ import uuid
5
+ import random
6
+ from loguru import logger
7
+ from typing import Optional
8
+
9
+
10
+ class QASwarm:
11
+ """
12
+ A Question and Answer swarm system where random agents ask questions to speaker agents.
13
+
14
+ This system allows for dynamic Q&A sessions where:
15
+ - Multiple agents can act as questioners
16
+ - One or multiple agents can act as speakers/responders
17
+ - Questions are asked randomly by different agents
18
+ - The conversation is tracked and managed
19
+ - Agents are showcased to each other with detailed information
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ name: str = "QandA",
25
+ description: str = "Question and Answer Swarm System",
26
+ agents: List[Agent] = None,
27
+ speaker_agents: List[Agent] = None,
28
+ id: str = str(uuid.uuid4()),
29
+ max_loops: int = 5,
30
+ show_dashboard: bool = True,
31
+ speaker_agent: Agent = None,
32
+ showcase_agents: bool = True,
33
+ **kwargs,
34
+ ):
35
+ self.id = id
36
+ self.name = name
37
+ self.description = description
38
+ self.max_loops = max_loops
39
+ self.show_dashboard = show_dashboard
40
+ self.agents = agents or []
41
+ self.speaker_agents = speaker_agents or []
42
+ self.kwargs = kwargs
43
+ self.speaker_agent = speaker_agent
44
+ self.showcase_agents = showcase_agents
45
+
46
+ self.conversation = Conversation()
47
+
48
+ # Validate setup
49
+ self._validate_setup()
50
+
51
+ def _validate_setup(self):
52
+ """Validate that the Q&A system is properly configured."""
53
+ if not self.agents:
54
+ logger.warning(
55
+ "No questioner agents provided. Add agents using add_agent() method."
56
+ )
57
+
58
+ if not self.speaker_agents and not self.speaker_agent:
59
+ logger.warning(
60
+ "No speaker agents provided. Add speaker agents using add_speaker_agent() method."
61
+ )
62
+
63
+ if (
64
+ not self.agents
65
+ and not self.speaker_agents
66
+ and not self.speaker_agent
67
+ ):
68
+ raise ValueError(
69
+ "At least one agent (questioner or speaker) must be provided."
70
+ )
71
+
72
+ def add_agent(self, agent: Agent):
73
+ """Add a questioner agent to the swarm."""
74
+ self.agents.append(agent)
75
+ logger.info(f"Added questioner agent: {agent.agent_name}")
76
+
77
+ def add_speaker_agent(self, agent: Agent):
78
+ """Add a speaker agent to the swarm."""
79
+ if self.speaker_agents is None:
80
+ self.speaker_agents = []
81
+ self.speaker_agents.append(agent)
82
+ logger.info(f"Added speaker agent: {agent.agent_name}")
83
+
84
+ def get_agent_info(self, agent: Agent) -> dict:
85
+ """Extract key information about an agent for showcasing."""
86
+ info = {
87
+ "name": getattr(agent, "agent_name", "Unknown Agent"),
88
+ "description": getattr(
89
+ agent, "agent_description", "No description available"
90
+ ),
91
+ "role": getattr(agent, "role", "worker"),
92
+ }
93
+
94
+ # Get system prompt preview (first 50 characters)
95
+ system_prompt = getattr(agent, "system_prompt", "")
96
+ if system_prompt:
97
+ info["system_prompt_preview"] = (
98
+ system_prompt[:50] + "..."
99
+ if len(system_prompt) > 50
100
+ else system_prompt
101
+ )
102
+ else:
103
+ info["system_prompt_preview"] = (
104
+ "No system prompt available"
105
+ )
106
+
107
+ return info
108
+
109
+ def showcase_speaker_to_questioner(
110
+ self, questioner: Agent, speaker: Agent
111
+ ) -> str:
112
+ """Create a showcase prompt introducing the speaker agent to the questioner."""
113
+ speaker_info = self.get_agent_info(speaker)
114
+
115
+ showcase_prompt = f"""
116
+ You are about to ask a question to a specialized agent. Here's what you need to know about them:
117
+
118
+ **Speaker Agent Information:**
119
+ - **Name**: {speaker_info['name']}
120
+ - **Role**: {speaker_info['role']}
121
+ - **Description**: {speaker_info['description']}
122
+ - **System Prompt Preview**: {speaker_info['system_prompt_preview']}
123
+
124
+ Please craft a thoughtful, relevant question that takes into account this agent's expertise and background.
125
+ Your question should be specific and demonstrate that you understand their role and capabilities.
126
+ """
127
+ return showcase_prompt
128
+
129
+ def showcase_questioner_to_speaker(
130
+ self, speaker: Agent, questioner: Agent
131
+ ) -> str:
132
+ """Create a showcase prompt introducing the questioner agent to the speaker."""
133
+ questioner_info = self.get_agent_info(questioner)
134
+
135
+ showcase_prompt = f"""
136
+ You are about to answer a question from another agent. Here's what you need to know about them:
137
+
138
+ **Questioner Agent Information:**
139
+ - **Name**: {questioner_info['name']}
140
+ - **Role**: {questioner_info['role']}
141
+ - **Description**: {questioner_info['description']}
142
+ - **System Prompt Preview**: {questioner_info['system_prompt_preview']}
143
+
144
+ Please provide a comprehensive answer that demonstrates your expertise and addresses their question thoroughly.
145
+ Consider their background and role when formulating your response.
146
+ """
147
+ return showcase_prompt
148
+
149
+ def random_select_agent(self, agents: List[Agent]) -> Agent:
150
+ """Randomly select an agent from the list."""
151
+ if not agents:
152
+ raise ValueError("No agents available for selection")
153
+ return random.choice(agents)
154
+
155
+ def get_current_speaker(self) -> Agent:
156
+ """Get the current speaker agent (either from speaker_agents list or single speaker_agent)."""
157
+ if self.speaker_agent:
158
+ return self.speaker_agent
159
+ elif self.speaker_agents:
160
+ return self.random_select_agent(self.speaker_agents)
161
+ else:
162
+ raise ValueError("No speaker agent available")
163
+
164
+ def run(
165
+ self, task: str, img: Optional[str] = None, *args, **kwargs
166
+ ):
167
+ """Run the Q&A session with agent showcasing."""
168
+ self.conversation.add(role="user", content=task)
169
+
170
+ # Get current speaker
171
+ current_speaker = self.get_current_speaker()
172
+
173
+ # Select a random questioner
174
+ questioner = self.random_select_agent(self.agents)
175
+
176
+ # Showcase agents to each other if enabled
177
+ if self.showcase_agents:
178
+ # Showcase speaker to questioner
179
+ speaker_showcase = self.showcase_speaker_to_questioner(
180
+ questioner, current_speaker
181
+ )
182
+ questioner_task = f"{speaker_showcase}\n\nNow ask a question about: {task}"
183
+
184
+ # Showcase questioner to speaker
185
+ questioner_showcase = self.showcase_questioner_to_speaker(
186
+ current_speaker, questioner
187
+ )
188
+ else:
189
+ questioner_task = f"Ask a question about {task} to {current_speaker.agent_name}"
190
+
191
+ # Generate question
192
+ question = questioner.run(
193
+ task=questioner_task,
194
+ img=img,
195
+ *args,
196
+ **kwargs,
197
+ )
198
+
199
+ self.conversation.add(
200
+ role=questioner.agent_name, content=question
201
+ )
202
+
203
+ # Prepare answer task with showcasing if enabled
204
+ if self.showcase_agents:
205
+ answer_task = f"{questioner_showcase}\n\nAnswer this question from {questioner.agent_name}: {question}"
206
+ else:
207
+ answer_task = f"Answer the question '{question}' from {questioner.agent_name}"
208
+
209
+ # Generate answer
210
+ answer = current_speaker.run(
211
+ task=answer_task,
212
+ img=img,
213
+ *args,
214
+ **kwargs,
215
+ )
216
+
217
+ self.conversation.add(
218
+ role=current_speaker.agent_name, content=answer
219
+ )
220
+
221
+ return answer
222
+
223
+ def run_multi_round(
224
+ self,
225
+ task: str,
226
+ rounds: int = 3,
227
+ img: Optional[str] = None,
228
+ *args,
229
+ **kwargs,
230
+ ):
231
+ """Run multiple rounds of Q&A with different questioners."""
232
+ results = []
233
+
234
+ for round_num in range(rounds):
235
+ logger.info(
236
+ f"Starting Q&A round {round_num + 1}/{rounds}"
237
+ )
238
+
239
+ round_result = self.run(task, img, *args, **kwargs)
240
+ results.append(
241
+ {"round": round_num + 1, "result": round_result}
242
+ )
243
+
244
+ return results
245
+
246
+ def get_conversation_history(self):
247
+ """Get the conversation history."""
248
+ return self.conversation.get_history()
249
+
250
+ def clear_conversation(self):
251
+ """Clear the conversation history."""
252
+ self.conversation = Conversation()
253
+ logger.info("Conversation history cleared")
@@ -28,6 +28,7 @@ from swarms.structs.malt import MALT
28
28
  from swarms.structs.deep_research_swarm import DeepResearchSwarm
29
29
  from swarms.structs.council_judge import CouncilAsAJudge
30
30
  from swarms.structs.interactive_groupchat import InteractiveGroupChat
31
+ from swarms.structs.heavy_swarm import HeavySwarm
31
32
  from swarms.structs.ma_utils import list_all_agents
32
33
  from swarms.utils.generate_keys import generate_api_key
33
34
 
@@ -49,6 +50,7 @@ SwarmType = Literal[
49
50
  "DeepResearchSwarm",
50
51
  "CouncilAsAJudge",
51
52
  "InteractiveGroupChat",
53
+ "HeavySwarm",
52
54
  ]
53
55
 
54
56
 
@@ -183,6 +185,10 @@ class SwarmRouter:
183
185
  conversation: Any = None,
184
186
  agents_config: Optional[Dict[Any, Any]] = None,
185
187
  speaker_function: str = None,
188
+ heavy_swarm_loops_per_agent: int = 1,
189
+ heavy_swarm_question_agent_model_name: str = "gpt-4.1",
190
+ heavy_swarm_worker_model_name: str = "claude-3-5-sonnet-20240620",
191
+ telemetry_enabled: bool = False,
186
192
  *args,
187
193
  **kwargs,
188
194
  ):
@@ -210,6 +216,14 @@ class SwarmRouter:
210
216
  self.conversation = conversation
211
217
  self.agents_config = agents_config
212
218
  self.speaker_function = speaker_function
219
+ self.heavy_swarm_loops_per_agent = heavy_swarm_loops_per_agent
220
+ self.heavy_swarm_question_agent_model_name = (
221
+ heavy_swarm_question_agent_model_name
222
+ )
223
+ self.heavy_swarm_worker_model_name = (
224
+ heavy_swarm_worker_model_name
225
+ )
226
+ self.telemetry_enabled = telemetry_enabled
213
227
 
214
228
  # Reliability check
215
229
  self.reliability_check()
@@ -234,6 +248,12 @@ class SwarmRouter:
234
248
  if self.rules is not None:
235
249
  self.handle_rules()
236
250
 
251
+ if self.multi_agent_collab_prompt is True:
252
+ self.update_system_prompt_for_agent_in_swarm()
253
+
254
+ if self.list_all_agents is True:
255
+ self.list_agents_to_eachother()
256
+
237
257
  def activate_shared_memory(self):
238
258
  logger.info("Activating shared memory with all agents ")
239
259
 
@@ -283,6 +303,10 @@ class SwarmRouter:
283
303
  Handles special case for CouncilAsAJudge which may not require agents.
284
304
  """
285
305
 
306
+ logger.info(
307
+ f"Initializing SwarmRouter: {self.name} Reliability Check..."
308
+ )
309
+
286
310
  # Check swarm type first since it affects other validations
287
311
  if self.swarm_type is None:
288
312
  raise ValueError(
@@ -300,6 +324,10 @@ class SwarmRouter:
300
324
 
301
325
  self.setup()
302
326
 
327
+ logger.info(
328
+ f"Reliability check for parameters and configurations are complete. SwarmRouter: {self.name} is ready to run!"
329
+ )
330
+
303
331
  def _create_swarm(self, task: str = None, *args, **kwargs):
304
332
  """
305
333
  Dynamically create and return the specified swarm type or automatically match the best swarm type for a given task.
@@ -321,6 +349,18 @@ class SwarmRouter:
321
349
 
322
350
  self._create_swarm(self.swarm_type)
323
351
 
352
+ elif self.swarm_type == "HeavySwarm":
353
+ return HeavySwarm(
354
+ name=self.name,
355
+ description=self.description,
356
+ agents=self.agents,
357
+ max_loops=self.max_loops,
358
+ output_type=self.output_type,
359
+ loops_per_agent=self.heavy_swarm_loops_per_agent,
360
+ question_agent_model_name=self.heavy_swarm_question_agent_model_name,
361
+ worker_model_name=self.heavy_swarm_worker_model_name,
362
+ )
363
+
324
364
  elif self.swarm_type == "AgentRearrange":
325
365
  return AgentRearrange(
326
366
  name=self.name,
@@ -478,6 +518,24 @@ class SwarmRouter:
478
518
 
479
519
  return agent_config
480
520
 
521
+ def list_agents_to_eachother(self):
522
+ if self.swarm_type == "SequentialWorkflow":
523
+ self.conversation = (
524
+ self.swarm.agent_rearrange.conversation
525
+ )
526
+ else:
527
+ self.conversation = self.swarm.conversation
528
+
529
+ if self.list_all_agents is True:
530
+ list_all_agents(
531
+ agents=self.agents,
532
+ conversation=self.swarm.conversation,
533
+ name=self.name,
534
+ description=self.description,
535
+ add_collaboration_prompt=True,
536
+ add_to_conversation=True,
537
+ )
538
+
481
539
  def _run(
482
540
  self,
483
541
  task: str,
@@ -503,31 +561,12 @@ class SwarmRouter:
503
561
  """
504
562
  self.swarm = self._create_swarm(task, *args, **kwargs)
505
563
 
506
- if self.swarm_type == "SequentialWorkflow":
507
- self.conversation = (
508
- self.swarm.agent_rearrange.conversation
509
- )
510
- else:
511
- self.conversation = self.swarm.conversation
512
-
513
- if self.list_all_agents is True:
514
- list_all_agents(
515
- agents=self.agents,
516
- conversation=self.swarm.conversation,
517
- name=self.name,
518
- description=self.description,
519
- add_collaboration_prompt=True,
520
- add_to_conversation=True,
521
- )
522
-
523
- if self.multi_agent_collab_prompt is True:
524
- self.update_system_prompt_for_agent_in_swarm()
525
-
526
564
  log_execution(
527
565
  swarm_id=self.id,
528
566
  status="start",
529
567
  swarm_config=self.to_dict(),
530
568
  swarm_architecture="swarm_router",
569
+ enabled_on=self.telemetry_enabled,
531
570
  )
532
571
 
533
572
  try:
@@ -548,12 +587,13 @@ class SwarmRouter:
548
587
  status="completion",
549
588
  swarm_config=self.to_dict(),
550
589
  swarm_architecture="swarm_router",
590
+ enabled_on=self.telemetry_enabled,
551
591
  )
552
592
 
553
593
  return result
554
594
  except Exception as e:
555
595
  raise RuntimeError(
556
- f"SwarmRouter: Error executing task on swarm: {str(e)} Traceback: {traceback.format_exc()}"
596
+ f"SwarmRouter: Error executing task on swarm: {str(e)} Traceback: {traceback.format_exc()}. Try reconfiguring the SwarmRouter Settings and or make sure the individual agents are configured correctly."
557
597
  )
558
598
 
559
599
  def run(
@@ -1,5 +1,250 @@
1
1
  from typing import Optional
2
2
  from swarms.telemetry.main import log_agent_data
3
+ import functools
4
+ import inspect
5
+ import time
6
+ from datetime import datetime
7
+
8
+
9
+ def log_function_execution(
10
+ swarm_id: Optional[str] = None,
11
+ swarm_architecture: Optional[str] = None,
12
+ enabled_on: Optional[bool] = True,
13
+ ):
14
+ """
15
+ Decorator to log function execution details including parameters and outputs.
16
+
17
+ This decorator automatically captures and logs:
18
+ - Function name
19
+ - Function parameters (args and kwargs)
20
+ - Function output/return value
21
+ - Execution timestamp
22
+ - Execution duration
23
+ - Execution status (success/error)
24
+
25
+ Args:
26
+ swarm_id (str, optional): Unique identifier for the swarm instance
27
+ swarm_architecture (str, optional): Name of the swarm architecture
28
+ enabled_on (bool, optional): Whether logging is enabled. Defaults to True.
29
+
30
+ Returns:
31
+ Decorated function that logs execution details
32
+
33
+ Example:
34
+ >>> @log_function_execution(swarm_id="my-swarm", swarm_architecture="sequential")
35
+ ... def process_data(data, threshold=0.5):
36
+ ... return {"processed": len(data), "threshold": threshold}
37
+ ...
38
+ >>> result = process_data([1, 2, 3], threshold=0.8)
39
+ """
40
+
41
+ def decorator(func):
42
+ @functools.wraps(func)
43
+ def wrapper(*args, **kwargs):
44
+ if not enabled_on:
45
+ return func(*args, **kwargs)
46
+
47
+ # Capture function details
48
+ function_name = func.__name__
49
+ function_module = func.__module__
50
+ start_time = time.time()
51
+ timestamp = datetime.now().isoformat()
52
+
53
+ # Capture function parameters
54
+ sig = inspect.signature(func)
55
+ bound_args = sig.bind(*args, **kwargs)
56
+ bound_args.apply_defaults()
57
+
58
+ # Convert parameters to serializable format
59
+ parameters = {}
60
+ for (
61
+ param_name,
62
+ param_value,
63
+ ) in bound_args.arguments.items():
64
+ try:
65
+ # Handle special method parameters
66
+ if param_name == "self":
67
+ # For instance methods, log class name and instance info
68
+ parameters[param_name] = {
69
+ "class_name": param_value.__class__.__name__,
70
+ "class_module": param_value.__class__.__module__,
71
+ "instance_id": hex(id(param_value)),
72
+ "type": "instance",
73
+ }
74
+ elif param_name == "cls":
75
+ # For class methods, log class information
76
+ parameters[param_name] = {
77
+ "class_name": param_value.__name__,
78
+ "class_module": param_value.__module__,
79
+ "type": "class",
80
+ }
81
+ elif isinstance(
82
+ param_value,
83
+ (str, int, float, bool, type(None)),
84
+ ):
85
+ parameters[param_name] = param_value
86
+ elif isinstance(param_value, (list, dict, tuple)):
87
+ parameters[param_name] = str(param_value)[
88
+ :500
89
+ ] # Truncate large objects
90
+ elif hasattr(param_value, "__class__"):
91
+ # Handle other object instances
92
+ parameters[param_name] = {
93
+ "class_name": param_value.__class__.__name__,
94
+ "class_module": param_value.__class__.__module__,
95
+ "instance_id": hex(id(param_value)),
96
+ "type": "object_instance",
97
+ }
98
+ else:
99
+ parameters[param_name] = str(
100
+ type(param_value)
101
+ )
102
+ except Exception:
103
+ parameters[param_name] = "<non-serializable>"
104
+
105
+ # Determine if this is a method call and add context
106
+ method_context = _get_method_context(
107
+ func, bound_args.arguments
108
+ )
109
+
110
+ execution_data = {
111
+ "function_name": function_name,
112
+ "function_module": function_module,
113
+ "swarm_id": swarm_id,
114
+ "swarm_architecture": swarm_architecture,
115
+ "timestamp": timestamp,
116
+ "parameters": parameters,
117
+ "status": "start",
118
+ **method_context,
119
+ }
120
+
121
+ try:
122
+ # Log function start
123
+ log_agent_data(data_dict=execution_data)
124
+
125
+ # Execute the function
126
+ result = func(*args, **kwargs)
127
+
128
+ # Calculate execution time
129
+ end_time = time.time()
130
+ execution_time = end_time - start_time
131
+
132
+ # Log successful execution
133
+ success_data = {
134
+ **execution_data,
135
+ "status": "success",
136
+ "execution_time_seconds": execution_time,
137
+ "output": _serialize_output(result),
138
+ }
139
+ log_agent_data(data_dict=success_data)
140
+
141
+ return result
142
+
143
+ except Exception as e:
144
+ # Calculate execution time even for errors
145
+ end_time = time.time()
146
+ execution_time = end_time - start_time
147
+
148
+ # Log error execution
149
+ error_data = {
150
+ **execution_data,
151
+ "status": "error",
152
+ "execution_time_seconds": execution_time,
153
+ "error_message": str(e),
154
+ "error_type": type(e).__name__,
155
+ }
156
+
157
+ try:
158
+ log_agent_data(data_dict=error_data)
159
+ except Exception:
160
+ pass # Silent fail on logging errors
161
+
162
+ # Re-raise the original exception
163
+ raise
164
+
165
+ return wrapper
166
+
167
+ return decorator
168
+
169
+
170
+ def _get_method_context(func, arguments):
171
+ """
172
+ Helper function to extract method context information.
173
+
174
+ Args:
175
+ func: The function/method being called
176
+ arguments: The bound arguments dictionary
177
+
178
+ Returns:
179
+ Dictionary with method context information
180
+ """
181
+ context = {}
182
+
183
+ try:
184
+ # Check if this is a method call
185
+ if "self" in arguments:
186
+ # Instance method
187
+ self_obj = arguments["self"]
188
+ context.update(
189
+ {
190
+ "method_type": "instance_method",
191
+ "class_name": self_obj.__class__.__name__,
192
+ "class_module": self_obj.__class__.__module__,
193
+ "instance_id": hex(id(self_obj)),
194
+ }
195
+ )
196
+ elif "cls" in arguments:
197
+ # Class method
198
+ cls_obj = arguments["cls"]
199
+ context.update(
200
+ {
201
+ "method_type": "class_method",
202
+ "class_name": cls_obj.__name__,
203
+ "class_module": cls_obj.__module__,
204
+ }
205
+ )
206
+ else:
207
+ # Regular function or static method
208
+ context.update({"method_type": "function"})
209
+
210
+ # Try to get qualname for additional context
211
+ if hasattr(func, "__qualname__"):
212
+ context["qualified_name"] = func.__qualname__
213
+
214
+ except Exception:
215
+ # If anything fails, just mark as unknown
216
+ context = {"method_type": "unknown"}
217
+
218
+ return context
219
+
220
+
221
+ def _serialize_output(output):
222
+ """
223
+ Helper function to serialize function output for logging.
224
+
225
+ Args:
226
+ output: The function return value to serialize
227
+
228
+ Returns:
229
+ Serializable representation of the output
230
+ """
231
+ try:
232
+ if output is None:
233
+ return None
234
+ elif isinstance(output, (str, int, float, bool)):
235
+ return output
236
+ elif isinstance(output, (list, dict, tuple)):
237
+ # Truncate large outputs to prevent log bloat
238
+ output_str = str(output)
239
+ return (
240
+ output_str[:1000] + "..."
241
+ if len(output_str) > 1000
242
+ else output_str
243
+ )
244
+ else:
245
+ return str(type(output))
246
+ except Exception:
247
+ return "<non-serializable-output>"
3
248
 
4
249
 
5
250
  def log_execution(
@@ -7,6 +252,7 @@ def log_execution(
7
252
  status: Optional[str] = None,
8
253
  swarm_config: Optional[dict] = None,
9
254
  swarm_architecture: Optional[str] = None,
255
+ enabled_on: Optional[bool] = False,
10
256
  ):
11
257
  """
12
258
  Log execution data for a swarm router instance.
@@ -31,13 +277,16 @@ def log_execution(
31
277
  ... )
32
278
  """
33
279
  try:
34
- log_agent_data(
35
- data_dict={
36
- "swarm_router_id": swarm_id,
37
- "status": status,
38
- "swarm_router_config": swarm_config,
39
- "swarm_architecture": swarm_architecture,
40
- }
41
- )
280
+ if enabled_on is None:
281
+ log_agent_data(
282
+ data_dict={
283
+ "swarm_router_id": swarm_id,
284
+ "status": status,
285
+ "swarm_router_config": swarm_config,
286
+ "swarm_architecture": swarm_architecture,
287
+ }
288
+ )
289
+ else:
290
+ pass
42
291
  except Exception:
43
292
  pass