swarms 7.8.9__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.
- swarms/cli/onboarding_process.py +1 -3
- swarms/prompts/collaborative_prompts.py +177 -0
- swarms/structs/__init__.py +11 -1
- swarms/structs/agent.py +488 -127
- swarms/structs/concurrent_workflow.py +70 -196
- swarms/structs/conversation.py +103 -25
- swarms/structs/interactive_groupchat.py +815 -108
- swarms/structs/ma_utils.py +25 -6
- swarms/structs/mixture_of_agents.py +88 -113
- swarms/structs/swarm_router.py +155 -195
- swarms/telemetry/__init__.py +4 -18
- swarms/telemetry/log_executions.py +43 -0
- swarms/telemetry/main.py +53 -217
- swarms/tools/base_tool.py +8 -3
- swarms/utils/formatter.py +130 -13
- swarms/utils/litellm_wrapper.py +7 -1
- swarms/utils/retry_func.py +66 -0
- swarms-7.9.1.dist-info/METADATA +626 -0
- {swarms-7.8.9.dist-info → swarms-7.9.1.dist-info}/RECORD +22 -19
- swarms-7.8.9.dist-info/METADATA +0 -2119
- {swarms-7.8.9.dist-info → swarms-7.9.1.dist-info}/LICENSE +0 -0
- {swarms-7.8.9.dist-info → swarms-7.9.1.dist-info}/WHEEL +0 -0
- {swarms-7.8.9.dist-info → swarms-7.9.1.dist-info}/entry_points.txt +0 -0
@@ -1,13 +1,10 @@
|
|
1
|
+
import concurrent.futures
|
1
2
|
import os
|
2
|
-
import
|
3
|
-
from concurrent.futures import ThreadPoolExecutor
|
4
|
-
from functools import lru_cache
|
5
|
-
from typing import Any, Callable, Dict, List, Optional, Union
|
3
|
+
from typing import Callable, List, Optional, Union
|
6
4
|
|
7
5
|
from swarms.structs.agent import Agent
|
8
6
|
from swarms.structs.base_swarm import BaseSwarm
|
9
7
|
from swarms.structs.conversation import Conversation
|
10
|
-
from swarms.utils.formatter import formatter
|
11
8
|
from swarms.utils.history_output_formatter import (
|
12
9
|
history_output_formatter,
|
13
10
|
)
|
@@ -35,9 +32,7 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
35
32
|
return_str_on (bool): Flag indicating whether to return the output as a string. Defaults to False.
|
36
33
|
auto_generate_prompts (bool): Flag indicating whether to auto-generate prompts for agents. Defaults to False.
|
37
34
|
return_entire_history (bool): Flag indicating whether to return the entire conversation history. Defaults to False.
|
38
|
-
|
39
|
-
max_retries (int): The maximum number of retry attempts. Defaults to 3.
|
40
|
-
retry_delay (float): The delay between retry attempts in seconds. Defaults to 1.0.
|
35
|
+
|
41
36
|
|
42
37
|
Raises:
|
43
38
|
ValueError: If the list of agents is empty or if the description is empty.
|
@@ -50,13 +45,7 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
50
45
|
auto_save (bool): Flag indicating whether to automatically save the metadata.
|
51
46
|
output_type (str): The type of output format.
|
52
47
|
max_loops (int): The maximum number of loops for each agent.
|
53
|
-
return_str_on (bool): Flag indicating whether to return the output as a string.
|
54
48
|
auto_generate_prompts (bool): Flag indicating whether to auto-generate prompts for agents.
|
55
|
-
return_entire_history (bool): Flag indicating whether to return the entire conversation history.
|
56
|
-
cache_size (int): The size of the cache.
|
57
|
-
max_retries (int): The maximum number of retry attempts.
|
58
|
-
retry_delay (float): The delay between retry attempts in seconds.
|
59
|
-
_cache (dict): The cache for storing agent outputs.
|
60
49
|
"""
|
61
50
|
|
62
51
|
def __init__(
|
@@ -68,12 +57,7 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
68
57
|
auto_save: bool = True,
|
69
58
|
output_type: str = "dict-all-except-first",
|
70
59
|
max_loops: int = 1,
|
71
|
-
return_str_on: bool = False,
|
72
60
|
auto_generate_prompts: bool = False,
|
73
|
-
return_entire_history: bool = False,
|
74
|
-
cache_size: int = 100,
|
75
|
-
max_retries: int = 3,
|
76
|
-
retry_delay: float = 1.0,
|
77
61
|
*args,
|
78
62
|
**kwargs,
|
79
63
|
):
|
@@ -90,63 +74,31 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
90
74
|
self.metadata_output_path = metadata_output_path
|
91
75
|
self.auto_save = auto_save
|
92
76
|
self.max_loops = max_loops
|
93
|
-
self.return_str_on = return_str_on
|
94
77
|
self.auto_generate_prompts = auto_generate_prompts
|
95
|
-
self.max_workers = os.cpu_count()
|
96
78
|
self.output_type = output_type
|
97
|
-
self.return_entire_history = return_entire_history
|
98
|
-
self.tasks = [] # Initialize tasks list
|
99
|
-
self.cache_size = cache_size
|
100
|
-
self.max_retries = max_retries
|
101
|
-
self.retry_delay = retry_delay
|
102
|
-
self._cache = {}
|
103
79
|
|
104
80
|
self.reliability_check()
|
105
81
|
self.conversation = Conversation()
|
106
82
|
|
107
83
|
def reliability_check(self):
|
108
84
|
try:
|
109
|
-
|
110
|
-
content=f"\n 🏷️ Name: {self.name}\n 📝 Description: {self.description}\n 🤖 Agents: {len(self.agents)}\n 🔄 Max Loops: {self.max_loops}\n ",
|
111
|
-
title="⚙️ Concurrent Workflow Settings",
|
112
|
-
style="bold blue",
|
113
|
-
)
|
114
|
-
formatter.print_panel(
|
115
|
-
content="🔍 Starting reliability checks",
|
116
|
-
title="🔒 Reliability Checks",
|
117
|
-
style="bold blue",
|
118
|
-
)
|
119
|
-
|
120
|
-
if self.name is None:
|
121
|
-
logger.error("❌ A name is required for the swarm")
|
85
|
+
if self.agents is None:
|
122
86
|
raise ValueError(
|
123
|
-
"
|
87
|
+
"ConcurrentWorkflow: No agents provided"
|
124
88
|
)
|
125
89
|
|
126
|
-
if
|
127
|
-
logger.error(
|
128
|
-
"❌ The list of agents must not be empty."
|
129
|
-
)
|
90
|
+
if len(self.agents) == 0:
|
130
91
|
raise ValueError(
|
131
|
-
"
|
92
|
+
"ConcurrentWorkflow: No agents provided"
|
132
93
|
)
|
133
94
|
|
134
|
-
if
|
135
|
-
logger.
|
136
|
-
|
137
|
-
|
138
|
-
formatter.print_panel(
|
139
|
-
content="✅ Reliability checks completed successfully",
|
140
|
-
title="🎉 Reliability Checks",
|
141
|
-
style="bold green",
|
142
|
-
)
|
143
|
-
|
144
|
-
except ValueError as e:
|
145
|
-
logger.error(f"❌ Reliability check failed: {e}")
|
146
|
-
raise
|
95
|
+
if len(self.agents) == 1:
|
96
|
+
logger.warning(
|
97
|
+
"ConcurrentWorkflow: Only one agent provided. With ConcurrentWorkflow, you should use at least 2+ agents."
|
98
|
+
)
|
147
99
|
except Exception as e:
|
148
100
|
logger.error(
|
149
|
-
f"
|
101
|
+
f"ConcurrentWorkflow: Reliability check failed: {e}"
|
150
102
|
)
|
151
103
|
raise
|
152
104
|
|
@@ -163,162 +115,84 @@ class ConcurrentWorkflow(BaseSwarm):
|
|
163
115
|
for agent in self.agents:
|
164
116
|
agent.auto_generate_prompt = True
|
165
117
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
"""Validate input task"""
|
173
|
-
if not isinstance(task, str):
|
174
|
-
raise ValueError("Task must be a string")
|
175
|
-
if not task.strip():
|
176
|
-
raise ValueError("Task cannot be empty")
|
177
|
-
return True
|
178
|
-
|
179
|
-
def _run_with_retry(
|
180
|
-
self, agent: Agent, task: str, img: str = None
|
181
|
-
) -> Any:
|
182
|
-
"""Run agent with retry mechanism"""
|
183
|
-
for attempt in range(self.max_retries):
|
184
|
-
try:
|
185
|
-
output = agent.run(task=task, img=img)
|
186
|
-
self.conversation.add(agent.agent_name, output)
|
187
|
-
return output
|
188
|
-
except Exception as e:
|
189
|
-
if attempt == self.max_retries - 1:
|
190
|
-
logger.error(
|
191
|
-
f"Error running agent {agent.agent_name} after {self.max_retries} attempts: {e}"
|
192
|
-
)
|
193
|
-
raise
|
194
|
-
logger.warning(
|
195
|
-
f"Attempt {attempt + 1} failed for agent {agent.agent_name}: {e}"
|
196
|
-
)
|
197
|
-
time.sleep(
|
198
|
-
self.retry_delay * (attempt + 1)
|
199
|
-
) # Exponential backoff
|
200
|
-
|
201
|
-
def _process_agent(
|
202
|
-
self, agent: Agent, task: str, img: str = None
|
203
|
-
) -> Any:
|
118
|
+
def run(
|
119
|
+
self,
|
120
|
+
task: str,
|
121
|
+
img: Optional[str] = None,
|
122
|
+
imgs: Optional[List[str]] = None,
|
123
|
+
):
|
204
124
|
"""
|
205
|
-
|
125
|
+
Executes all agents in the workflow concurrently on the given task.
|
206
126
|
|
207
127
|
Args:
|
208
|
-
|
209
|
-
|
210
|
-
|
128
|
+
task (str): The task to be executed by all agents.
|
129
|
+
img (Optional[str]): Optional image path for agents that support image input.
|
130
|
+
imgs (Optional[List[str]]): Optional list of image paths for agents that support multiple image inputs.
|
211
131
|
|
212
132
|
Returns:
|
213
|
-
The
|
214
|
-
"""
|
215
|
-
try:
|
216
|
-
# Fast path - check cache first
|
217
|
-
cache_key = f"{task}_{agent.agent_name}"
|
218
|
-
if cache_key in self._cache:
|
219
|
-
output = self._cache[cache_key]
|
220
|
-
else:
|
221
|
-
# Slow path - run agent and update cache
|
222
|
-
output = self._run_with_retry(agent, task, img)
|
223
|
-
|
224
|
-
if len(self._cache) >= self.cache_size:
|
225
|
-
self._cache.pop(next(iter(self._cache)))
|
133
|
+
The formatted output based on the configured output_type.
|
226
134
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
logger.error(
|
232
|
-
f"Error running agent {agent.agent_name}: {e}"
|
233
|
-
)
|
234
|
-
raise
|
235
|
-
|
236
|
-
def _run(
|
237
|
-
self, task: str, img: str = None, *args, **kwargs
|
238
|
-
) -> Union[Dict[str, Any], str]:
|
239
|
-
"""
|
240
|
-
Enhanced run method with parallel execution.
|
135
|
+
Example:
|
136
|
+
>>> workflow = ConcurrentWorkflow(agents=[agent1, agent2])
|
137
|
+
>>> result = workflow.run("Analyze this financial data")
|
138
|
+
>>> print(result)
|
241
139
|
"""
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
140
|
+
self.conversation.add(role="User", content=task)
|
141
|
+
|
142
|
+
# Use 95% of available CPU cores for optimal performance
|
143
|
+
max_workers = int(os.cpu_count() * 0.95)
|
144
|
+
|
145
|
+
# Run agents concurrently using ThreadPoolExecutor
|
146
|
+
with concurrent.futures.ThreadPoolExecutor(
|
147
|
+
max_workers=max_workers
|
148
|
+
) as executor:
|
149
|
+
# Submit all agent tasks and store with their index
|
150
|
+
future_to_agent = {
|
151
|
+
executor.submit(
|
152
|
+
agent.run, task=task, img=img, imgs=imgs
|
153
|
+
): agent
|
154
|
+
for agent in self.agents
|
155
|
+
}
|
156
|
+
|
157
|
+
# Collect results and add to conversation in completion order
|
158
|
+
for future in concurrent.futures.as_completed(
|
159
|
+
future_to_agent
|
160
|
+
):
|
161
|
+
agent = future_to_agent[future]
|
162
|
+
output = future.result()
|
163
|
+
self.conversation.add(role=agent.name, content=output)
|
264
164
|
|
265
165
|
return history_output_formatter(
|
266
|
-
self.conversation,
|
267
|
-
|
166
|
+
conversation=self.conversation,
|
167
|
+
output_type=self.output_type,
|
268
168
|
)
|
269
169
|
|
270
|
-
def
|
170
|
+
def batch_run(
|
271
171
|
self,
|
272
|
-
|
172
|
+
tasks: List[str],
|
273
173
|
img: Optional[str] = None,
|
274
|
-
|
275
|
-
|
276
|
-
) -> Any:
|
174
|
+
imgs: Optional[List[str]] = None,
|
175
|
+
):
|
277
176
|
"""
|
278
|
-
Executes the
|
177
|
+
Executes the workflow on multiple tasks sequentially.
|
279
178
|
|
280
179
|
Args:
|
281
|
-
|
282
|
-
img (Optional[str]
|
283
|
-
|
284
|
-
**kwargs: Additional keyword arguments to be passed to the execution method.
|
180
|
+
tasks (List[str]): List of tasks to be executed by all agents.
|
181
|
+
img (Optional[str]): Optional image path for agents that support image input.
|
182
|
+
imgs (Optional[List[str]]): Optional list of image paths for agents that support multiple image inputs.
|
285
183
|
|
286
184
|
Returns:
|
287
|
-
|
288
|
-
|
289
|
-
Raises:
|
290
|
-
ValueError: If task validation fails.
|
291
|
-
Exception: If any other error occurs during execution.
|
292
|
-
"""
|
293
|
-
if task is not None:
|
294
|
-
self.tasks.append(task)
|
295
|
-
|
296
|
-
try:
|
297
|
-
outputs = self._run(task, img, *args, **kwargs)
|
298
|
-
return outputs
|
299
|
-
except Exception as e:
|
300
|
-
logger.error(f"An error occurred during execution: {e}")
|
301
|
-
raise e
|
185
|
+
List of results, one for each task.
|
302
186
|
|
303
|
-
|
304
|
-
|
305
|
-
|
187
|
+
Example:
|
188
|
+
>>> workflow = ConcurrentWorkflow(agents=[agent1, agent2])
|
189
|
+
>>> tasks = ["Task 1", "Task 2", "Task 3"]
|
190
|
+
>>> results = workflow.batch_run(tasks)
|
191
|
+
>>> print(len(results)) # 3
|
306
192
|
"""
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
return [self.run(task) for task in tasks]
|
311
|
-
|
312
|
-
def clear_cache(self):
|
313
|
-
"""Clear the task cache"""
|
314
|
-
self._cache.clear()
|
315
|
-
|
316
|
-
def get_cache_stats(self) -> Dict[str, int]:
|
317
|
-
"""Get cache statistics"""
|
318
|
-
return {
|
319
|
-
"cache_size": len(self._cache),
|
320
|
-
"max_cache_size": self.cache_size,
|
321
|
-
}
|
193
|
+
return [
|
194
|
+
self.run(task=task, img=img, imgs=imgs) for task in tasks
|
195
|
+
]
|
322
196
|
|
323
197
|
|
324
198
|
# if __name__ == "__main__":
|
swarms/structs/conversation.py
CHANGED
@@ -221,27 +221,6 @@ class Conversation(BaseStructure):
|
|
221
221
|
):
|
222
222
|
super().__init__()
|
223
223
|
|
224
|
-
# Support both 'provider' and 'backend' parameters for backwards compatibility
|
225
|
-
# 'backend' takes precedence if both are provided
|
226
|
-
self.backend = backend or provider
|
227
|
-
self.backend_instance = None
|
228
|
-
|
229
|
-
# Validate backend
|
230
|
-
valid_backends = [
|
231
|
-
"in-memory",
|
232
|
-
"mem0",
|
233
|
-
"supabase",
|
234
|
-
"redis",
|
235
|
-
"sqlite",
|
236
|
-
"duckdb",
|
237
|
-
"pulsar",
|
238
|
-
]
|
239
|
-
if self.backend not in valid_backends:
|
240
|
-
raise ValueError(
|
241
|
-
f"Invalid backend: '{self.backend}'. "
|
242
|
-
f"Valid backends are: {', '.join(valid_backends)}"
|
243
|
-
)
|
244
|
-
|
245
224
|
# Initialize all attributes first
|
246
225
|
self.id = id
|
247
226
|
self.name = name or id
|
@@ -275,6 +254,27 @@ class Conversation(BaseStructure):
|
|
275
254
|
self.provider = provider # Keep for backwards compatibility
|
276
255
|
self.conversations_dir = conversations_dir
|
277
256
|
|
257
|
+
# Support both 'provider' and 'backend' parameters for backwards compatibility
|
258
|
+
# 'backend' takes precedence if both are provided
|
259
|
+
self.backend = backend or provider
|
260
|
+
self.backend_instance = None
|
261
|
+
|
262
|
+
# Validate backend
|
263
|
+
valid_backends = [
|
264
|
+
"in-memory",
|
265
|
+
"mem0",
|
266
|
+
"supabase",
|
267
|
+
"redis",
|
268
|
+
"sqlite",
|
269
|
+
"duckdb",
|
270
|
+
"pulsar",
|
271
|
+
]
|
272
|
+
if self.backend not in valid_backends:
|
273
|
+
raise ValueError(
|
274
|
+
f"Invalid backend: '{self.backend}'. "
|
275
|
+
f"Valid backends are: {', '.join(valid_backends)}"
|
276
|
+
)
|
277
|
+
|
278
278
|
# Initialize backend if using persistent storage
|
279
279
|
if self.backend in [
|
280
280
|
"supabase",
|
@@ -484,8 +484,7 @@ class Conversation(BaseStructure):
|
|
484
484
|
self,
|
485
485
|
role: str,
|
486
486
|
content: Union[str, dict, list, Any],
|
487
|
-
|
488
|
-
**kwargs,
|
487
|
+
category: Optional[str] = None,
|
489
488
|
):
|
490
489
|
"""Add a message to the conversation history.
|
491
490
|
|
@@ -505,6 +504,9 @@ class Conversation(BaseStructure):
|
|
505
504
|
if self.message_id_on:
|
506
505
|
message["message_id"] = str(uuid.uuid4())
|
507
506
|
|
507
|
+
if category:
|
508
|
+
message["category"] = category
|
509
|
+
|
508
510
|
# Add message to conversation history
|
509
511
|
self.conversation_history.append(message)
|
510
512
|
|
@@ -520,6 +522,79 @@ class Conversation(BaseStructure):
|
|
520
522
|
f"Failed to autosave conversation: {str(e)}"
|
521
523
|
)
|
522
524
|
|
525
|
+
def export_and_count_categories(
|
526
|
+
self, tokenizer_model_name: Optional[str] = "gpt-4.1-mini"
|
527
|
+
) -> Dict[str, int]:
|
528
|
+
"""Export all messages with category 'input' and 'output' and count their tokens.
|
529
|
+
|
530
|
+
This method searches through the conversation history and:
|
531
|
+
1. Extracts all messages marked with category 'input' or 'output'
|
532
|
+
2. Concatenates the content of each category
|
533
|
+
3. Counts tokens for each category using the specified tokenizer model
|
534
|
+
|
535
|
+
Args:
|
536
|
+
tokenizer_model_name (str): Name of the model to use for tokenization
|
537
|
+
|
538
|
+
Returns:
|
539
|
+
Dict[str, int]: A dictionary containing:
|
540
|
+
- input_tokens: Number of tokens in input messages
|
541
|
+
- output_tokens: Number of tokens in output messages
|
542
|
+
- total_tokens: Total tokens across both categories
|
543
|
+
"""
|
544
|
+
try:
|
545
|
+
# Extract input and output messages
|
546
|
+
input_messages = []
|
547
|
+
output_messages = []
|
548
|
+
|
549
|
+
for message in self.conversation_history:
|
550
|
+
# Get message content and ensure it's a string
|
551
|
+
content = message.get("content", "")
|
552
|
+
if not isinstance(content, str):
|
553
|
+
content = str(content)
|
554
|
+
|
555
|
+
# Sort messages by category
|
556
|
+
category = message.get("category", "")
|
557
|
+
if category == "input":
|
558
|
+
input_messages.append(content)
|
559
|
+
elif category == "output":
|
560
|
+
output_messages.append(content)
|
561
|
+
|
562
|
+
# Join messages with spaces
|
563
|
+
all_input_text = " ".join(input_messages)
|
564
|
+
all_output_text = " ".join(output_messages)
|
565
|
+
|
566
|
+
print(all_input_text)
|
567
|
+
print(all_output_text)
|
568
|
+
|
569
|
+
# Count tokens only if there is text
|
570
|
+
input_tokens = (
|
571
|
+
count_tokens(all_input_text, tokenizer_model_name)
|
572
|
+
if all_input_text.strip()
|
573
|
+
else 0
|
574
|
+
)
|
575
|
+
output_tokens = (
|
576
|
+
count_tokens(all_output_text, tokenizer_model_name)
|
577
|
+
if all_output_text.strip()
|
578
|
+
else 0
|
579
|
+
)
|
580
|
+
total_tokens = input_tokens + output_tokens
|
581
|
+
|
582
|
+
return {
|
583
|
+
"input_tokens": input_tokens,
|
584
|
+
"output_tokens": output_tokens,
|
585
|
+
"total_tokens": total_tokens,
|
586
|
+
}
|
587
|
+
|
588
|
+
except Exception as e:
|
589
|
+
logger.error(
|
590
|
+
f"Error in export_and_count_categories: {str(e)}"
|
591
|
+
)
|
592
|
+
return {
|
593
|
+
"input_tokens": 0,
|
594
|
+
"output_tokens": 0,
|
595
|
+
"total_tokens": 0,
|
596
|
+
}
|
597
|
+
|
523
598
|
def add_mem0(
|
524
599
|
self,
|
525
600
|
role: str,
|
@@ -546,8 +621,9 @@ class Conversation(BaseStructure):
|
|
546
621
|
def add(
|
547
622
|
self,
|
548
623
|
role: str,
|
549
|
-
content: Union[str, dict, list],
|
624
|
+
content: Union[str, dict, list, Any],
|
550
625
|
metadata: Optional[dict] = None,
|
626
|
+
category: Optional[str] = None,
|
551
627
|
):
|
552
628
|
"""Add a message to the conversation history."""
|
553
629
|
# If using a persistent backend, delegate to it
|
@@ -562,7 +638,9 @@ class Conversation(BaseStructure):
|
|
562
638
|
)
|
563
639
|
return self.add_in_memory(role, content)
|
564
640
|
elif self.provider == "in-memory":
|
565
|
-
return self.add_in_memory(
|
641
|
+
return self.add_in_memory(
|
642
|
+
role=role, content=content, category=category
|
643
|
+
)
|
566
644
|
elif self.provider == "mem0":
|
567
645
|
return self.add_mem0(
|
568
646
|
role=role, content=content, metadata=metadata
|