swarms 7.7.0__py3-none-any.whl → 7.7.2__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/structs/aop.py ADDED
@@ -0,0 +1,557 @@
1
+ import asyncio
2
+ import inspect
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from functools import wraps
5
+ from typing import Any, Callable, Literal, Optional
6
+
7
+ from fastmcp import FastMCP, Client
8
+ from loguru import logger
9
+ from swarms.utils.any_to_str import any_to_str
10
+
11
+
12
+ class AOP:
13
+ """
14
+ Agent-Orchestration Protocol (AOP) class for managing tools, agents, and swarms.
15
+
16
+ This class provides decorators and methods for registering and running various components
17
+ in a Swarms environment. It handles logging, metadata management, and execution control.
18
+
19
+ Attributes:
20
+ name (str): The name of the AOP instance
21
+ description (str): A description of the AOP instance
22
+ mcp (FastMCP): The underlying FastMCP instance for managing components
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ name: Optional[str] = None,
28
+ description: Optional[str] = None,
29
+ url: Optional[str] = "http://localhost:8000/sse",
30
+ *args,
31
+ **kwargs,
32
+ ):
33
+ """
34
+ Initialize the AOP instance.
35
+
36
+ Args:
37
+ name (str): The name of the AOP instance
38
+ description (str): A description of the AOP instance
39
+ url (str): The URL of the MCP instance
40
+ *args: Additional positional arguments passed to FastMCP
41
+ **kwargs: Additional keyword arguments passed to FastMCP
42
+ """
43
+ logger.info(f"[AOP] Initializing AOP instance: {name}")
44
+ self.name = name
45
+ self.description = description
46
+ self.url = url
47
+
48
+ self.tools = {}
49
+ self.swarms = {}
50
+
51
+ self.mcp = FastMCP(name=name, *args, **kwargs)
52
+
53
+ logger.success(
54
+ f"[AOP] Successfully initialized AOP instance: {name}"
55
+ )
56
+
57
+ def tool(
58
+ self,
59
+ name: Optional[str] = None,
60
+ description: Optional[str] = None,
61
+ ):
62
+ """
63
+ Decorator to register an MCP tool with optional metadata.
64
+
65
+ This decorator registers a function as a tool in the MCP system. It handles
66
+ logging, metadata management, and execution tracking.
67
+
68
+ Args:
69
+ name (Optional[str]): Custom name for the tool. If None, uses function name
70
+ description (Optional[str]): Custom description. If None, uses function docstring
71
+
72
+ Returns:
73
+ Callable: A decorator function that registers the tool
74
+ """
75
+ logger.debug(
76
+ f"[AOP] Creating tool decorator with name={name}, description={description}"
77
+ )
78
+
79
+ def decorator(func: Callable):
80
+ tool_name = name or func.__name__
81
+ tool_description = description or (
82
+ inspect.getdoc(func) or ""
83
+ )
84
+
85
+ logger.debug(
86
+ f"[AOP] Registering tool: {tool_name} - {tool_description}"
87
+ )
88
+
89
+ self.tools[tool_name] = {
90
+ "name": tool_name,
91
+ "description": tool_description,
92
+ "function": func,
93
+ }
94
+
95
+ @self.mcp.tool(
96
+ name=f"tool_{tool_name}", description=tool_description
97
+ )
98
+ @wraps(func)
99
+ async def wrapper(*args, **kwargs) -> Any:
100
+ logger.info(
101
+ f"[TOOL:{tool_name}] ➤ called with args={args}, kwargs={kwargs}"
102
+ )
103
+ try:
104
+ result = await func(*args, **kwargs)
105
+ logger.success(f"[TOOL:{tool_name}] ✅ completed")
106
+ return result
107
+ except Exception as e:
108
+ logger.error(
109
+ f"[TOOL:{tool_name}] ❌ failed with error: {str(e)}"
110
+ )
111
+ raise
112
+
113
+ return wrapper
114
+
115
+ return decorator
116
+
117
+ def agent(
118
+ self,
119
+ name: Optional[str] = None,
120
+ description: Optional[str] = None,
121
+ ):
122
+ """
123
+ Decorator to define an agent entry point.
124
+
125
+ This decorator registers a function as an agent in the MCP system. It handles
126
+ logging, metadata management, and execution tracking for agent operations.
127
+
128
+ Args:
129
+ name (Optional[str]): Custom name for the agent. If None, uses 'agent_' + function name
130
+ description (Optional[str]): Custom description. If None, uses function docstring
131
+
132
+ Returns:
133
+ Callable: A decorator function that registers the agent
134
+ """
135
+ logger.debug(
136
+ f"[AOP] Creating agent decorator with name={name}, description={description}"
137
+ )
138
+
139
+ def decorator(func: Callable):
140
+ agent_name = name or f"agent_{func.__name__}"
141
+ agent_description = description or (
142
+ inspect.getdoc(func) or ""
143
+ )
144
+
145
+ @self.mcp.tool(
146
+ name=agent_name, description=agent_description
147
+ )
148
+ @wraps(func)
149
+ async def wrapper(*args, **kwargs):
150
+ logger.info(f"[AGENT:{agent_name}] 👤 Starting")
151
+ try:
152
+ result = await func(*args, **kwargs)
153
+ logger.success(
154
+ f"[AGENT:{agent_name}] ✅ Finished"
155
+ )
156
+ return result
157
+ except Exception as e:
158
+ logger.error(
159
+ f"[AGENT:{agent_name}] ❌ failed with error: {str(e)}"
160
+ )
161
+ raise
162
+
163
+ wrapper._is_agent = True
164
+ wrapper._agent_name = agent_name
165
+ wrapper._agent_description = agent_description
166
+ return wrapper
167
+
168
+ return decorator
169
+
170
+ def swarm(
171
+ self,
172
+ name: Optional[str] = None,
173
+ description: Optional[str] = None,
174
+ ):
175
+ """
176
+ Decorator to define a swarm controller.
177
+
178
+ This decorator registers a function as a swarm controller in the MCP system.
179
+ It handles logging, metadata management, and execution tracking for swarm operations.
180
+
181
+ Args:
182
+ name (Optional[str]): Custom name for the swarm. If None, uses 'swarm_' + function name
183
+ description (Optional[str]): Custom description. If None, uses function docstring
184
+
185
+ Returns:
186
+ Callable: A decorator function that registers the swarm
187
+ """
188
+ logger.debug(
189
+ f"[AOP] Creating swarm decorator with name={name}, description={description}"
190
+ )
191
+
192
+ def decorator(func: Callable):
193
+ swarm_name = name or f"swarm_{func.__name__}"
194
+ swarm_description = description or (
195
+ inspect.getdoc(func) or ""
196
+ )
197
+
198
+ @self.mcp.tool(
199
+ name=swarm_name, description=swarm_description
200
+ )
201
+ @wraps(func)
202
+ async def wrapper(*args, **kwargs):
203
+ logger.info(
204
+ f"[SWARM:{swarm_name}] 🐝 Spawning swarm..."
205
+ )
206
+ try:
207
+ result = await func(*args, **kwargs)
208
+ logger.success(
209
+ f"[SWARM:{swarm_name}] 🐝 Completed"
210
+ )
211
+ return result
212
+ except Exception as e:
213
+ logger.error(
214
+ f"[SWARM:{swarm_name}] ❌ failed with error: {str(e)}"
215
+ )
216
+ raise
217
+
218
+ wrapper._is_swarm = True
219
+ wrapper._swarm_name = swarm_name
220
+ wrapper._swarm_description = swarm_description
221
+ return wrapper
222
+
223
+ return decorator
224
+
225
+ def run(self, method: Literal["stdio", "sse"], *args, **kwargs):
226
+ """
227
+ Run the MCP with the specified method.
228
+
229
+ Args:
230
+ method (Literal['stdio', 'sse']): The execution method to use
231
+ *args: Additional positional arguments for the run method
232
+ **kwargs: Additional keyword arguments for the run method
233
+
234
+ Returns:
235
+ Any: The result of the MCP run operation
236
+ """
237
+ logger.info(f"[AOP] Running MCP with method: {method}")
238
+ try:
239
+ result = self.mcp.run(method, *args, **kwargs)
240
+ logger.success(
241
+ f"[AOP] Successfully ran MCP with method: {method}"
242
+ )
243
+ return result
244
+ except Exception as e:
245
+ logger.error(
246
+ f"[AOP] Failed to run MCP with method {method}: {str(e)}"
247
+ )
248
+ raise
249
+
250
+ def run_stdio(self, *args, **kwargs):
251
+ """
252
+ Run the MCP using standard I/O method.
253
+
254
+ Args:
255
+ *args: Additional positional arguments for the run method
256
+ **kwargs: Additional keyword arguments for the run method
257
+
258
+ Returns:
259
+ Any: The result of the MCP run operation
260
+ """
261
+ logger.info("[AOP] Running MCP with stdio method")
262
+ return self.run("stdio", *args, **kwargs)
263
+
264
+ def run_sse(self, *args, **kwargs):
265
+ """
266
+ Run the MCP using Server-Sent Events method.
267
+
268
+ Args:
269
+ *args: Additional positional arguments for the run method
270
+ **kwargs: Additional keyword arguments for the run method
271
+
272
+ Returns:
273
+ Any: The result of the MCP run operation
274
+ """
275
+ logger.info("[AOP] Running MCP with SSE method")
276
+ return self.run("sse", *args, **kwargs)
277
+
278
+ def list_available(
279
+ self, output_type: Literal["str", "list"] = "str"
280
+ ):
281
+ """
282
+ List all available tools in the MCP.
283
+
284
+ Returns:
285
+ list: A list of all registered tools
286
+ """
287
+ if output_type == "str":
288
+ return any_to_str(self.mcp.list_tools())
289
+ elif output_type == "list":
290
+ return self.mcp.list_tools()
291
+ else:
292
+ raise ValueError(f"Invalid output type: {output_type}")
293
+
294
+ async def check_utility_exists(
295
+ self, url: str, name: str, *args, **kwargs
296
+ ):
297
+ async with Client(url, *args, **kwargs) as client:
298
+ if any(tool.name == name for tool in client.list_tools()):
299
+ return True
300
+ else:
301
+ return False
302
+
303
+ async def _call_tool(
304
+ self, url: str, name: str, arguments: dict, *args, **kwargs
305
+ ):
306
+ try:
307
+ async with Client(url, *args, **kwargs) as client:
308
+ result = await client.call_tool(name, arguments)
309
+ logger.info(
310
+ f"Client connected: {client.is_connected()}"
311
+ )
312
+ return result
313
+ except Exception as e:
314
+ logger.error(f"Error calling tool: {e}")
315
+ return None
316
+
317
+ def call_tool(
318
+ self,
319
+ url: str,
320
+ name: str,
321
+ arguments: dict,
322
+ *args,
323
+ **kwargs,
324
+ ):
325
+ return asyncio.run(
326
+ self._call_tool(url, name, arguments, *args, **kwargs)
327
+ )
328
+
329
+ def call_tool_or_agent(
330
+ self,
331
+ url: str,
332
+ name: str,
333
+ arguments: dict,
334
+ output_type: Literal["str", "list"] = "str",
335
+ *args,
336
+ **kwargs,
337
+ ):
338
+ """
339
+ Execute a tool or agent by name.
340
+
341
+ Args:
342
+ name (str): The name of the tool or agent to execute
343
+ arguments (dict): The arguments to pass to the tool or agent
344
+ """
345
+ if output_type == "str":
346
+ return any_to_str(
347
+ self.call_tool(
348
+ url=url, name=name, arguments=arguments
349
+ )
350
+ )
351
+ elif output_type == "list":
352
+ return self.call_tool(
353
+ url=url, name=name, arguments=arguments
354
+ )
355
+ else:
356
+ raise ValueError(f"Invalid output type: {output_type}")
357
+
358
+ def call_tool_or_agent_batched(
359
+ self,
360
+ url: str,
361
+ names: list[str],
362
+ arguments: list[dict],
363
+ output_type: Literal["str", "list"] = "str",
364
+ *args,
365
+ **kwargs,
366
+ ):
367
+ """
368
+ Execute a list of tools or agents by name.
369
+
370
+ Args:
371
+ names (list[str]): The names of the tools or agents to execute
372
+ """
373
+ if output_type == "str":
374
+ return [
375
+ any_to_str(
376
+ self.call_tool_or_agent(
377
+ url=url,
378
+ name=name,
379
+ arguments=argument,
380
+ *args,
381
+ **kwargs,
382
+ )
383
+ )
384
+ for name, argument in zip(names, arguments)
385
+ ]
386
+ elif output_type == "list":
387
+ return [
388
+ self.call_tool_or_agent(
389
+ url=url,
390
+ name=name,
391
+ arguments=argument,
392
+ *args,
393
+ **kwargs,
394
+ )
395
+ for name, argument in zip(names, arguments)
396
+ ]
397
+ else:
398
+ raise ValueError(f"Invalid output type: {output_type}")
399
+
400
+ def call_tool_or_agent_concurrently(
401
+ self,
402
+ url: str,
403
+ names: list[str],
404
+ arguments: list[dict],
405
+ output_type: Literal["str", "list"] = "str",
406
+ *args,
407
+ **kwargs,
408
+ ):
409
+ """
410
+ Execute a list of tools or agents by name concurrently.
411
+
412
+ Args:
413
+ names (list[str]): The names of the tools or agents to execute
414
+ arguments (list[dict]): The arguments to pass to the tools or agents
415
+ """
416
+ outputs = []
417
+ with ThreadPoolExecutor(max_workers=len(names)) as executor:
418
+ futures = [
419
+ executor.submit(
420
+ self.call_tool_or_agent,
421
+ url=url,
422
+ name=name,
423
+ arguments=argument,
424
+ *args,
425
+ **kwargs,
426
+ )
427
+ for name, argument in zip(names, arguments)
428
+ ]
429
+ for future in as_completed(futures):
430
+ outputs.append(future.result())
431
+
432
+ if output_type == "str":
433
+ return any_to_str(outputs)
434
+ elif output_type == "list":
435
+ return outputs
436
+ else:
437
+ raise ValueError(f"Invalid output type: {output_type}")
438
+
439
+ def call_swarm(
440
+ self,
441
+ url: str,
442
+ name: str,
443
+ arguments: dict,
444
+ output_type: Literal["str", "list"] = "str",
445
+ *args,
446
+ **kwargs,
447
+ ):
448
+ """
449
+ Execute a swarm by name.
450
+
451
+ Args:
452
+ name (str): The name of the swarm to execute
453
+ """
454
+ if output_type == "str":
455
+ return any_to_str(
456
+ asyncio.run(
457
+ self._call_tool(
458
+ url=url,
459
+ name=name,
460
+ arguments=arguments,
461
+ )
462
+ )
463
+ )
464
+ elif output_type == "list":
465
+ return asyncio.run(
466
+ self._call_tool(
467
+ url=url,
468
+ name=name,
469
+ arguments=arguments,
470
+ )
471
+ )
472
+ else:
473
+ raise ValueError(f"Invalid output type: {output_type}")
474
+
475
+ def list_agents(
476
+ self, output_type: Literal["str", "list"] = "str"
477
+ ):
478
+ """
479
+ List all available agents in the MCP.
480
+
481
+ Returns:
482
+ list: A list of all registered agents
483
+ """
484
+
485
+ out = self.list_all()
486
+ agents = []
487
+ for item in out:
488
+ if "agent" in item["name"]:
489
+ agents.append(item)
490
+ return agents
491
+
492
+ def list_swarms(
493
+ self, output_type: Literal["str", "list"] = "str"
494
+ ):
495
+ """
496
+ List all available swarms in the MCP.
497
+
498
+ Returns:
499
+ list: A list of all registered swarms
500
+ """
501
+ out = self.list_all()
502
+ agents = []
503
+ for item in out:
504
+ if "swarm" in item["name"]:
505
+ agents.append(item)
506
+ return agents
507
+
508
+ async def _list_all(self):
509
+ async with Client(self.url) as client:
510
+ return await client.list_tools()
511
+
512
+ def list_all(self):
513
+ out = asyncio.run(self._list_all())
514
+
515
+ outputs = []
516
+ for tool in out:
517
+ outputs.append(tool.model_dump())
518
+
519
+ return outputs
520
+
521
+ def list_tool_parameters(self, name: str):
522
+ out = self.list_all()
523
+
524
+ # Find the tool by name
525
+ for tool in out:
526
+ if tool["name"] == name:
527
+ return tool
528
+ return None
529
+
530
+ def search_if_tool_exists(self, name: str):
531
+ out = self.list_all()
532
+ for tool in out:
533
+ if tool["name"] == name:
534
+ return True
535
+ return False
536
+
537
+ def search(
538
+ self,
539
+ type: Literal["tool", "agent", "swarm"],
540
+ name: str,
541
+ output_type: Literal["str", "list"] = "str",
542
+ ):
543
+ """
544
+ Search for a tool, agent, or swarm by name.
545
+
546
+ Args:
547
+ type (Literal["tool", "agent", "swarm"]): The type of the item to search for
548
+ name (str): The name of the item to search for
549
+
550
+ Returns:
551
+ dict: The item if found, otherwise None
552
+ """
553
+ all_items = self.list_all()
554
+ for item in all_items:
555
+ if item["name"] == name:
556
+ return item
557
+ return None
@@ -105,7 +105,7 @@ class Conversation(BaseStructure):
105
105
  if tokenizer is not None:
106
106
  self.truncate_memory_with_tokenizer()
107
107
 
108
- def add(
108
+ def _add(
109
109
  self,
110
110
  role: str,
111
111
  content: Union[str, dict, list],
@@ -118,8 +118,6 @@ class Conversation(BaseStructure):
118
118
  role (str): The role of the speaker (e.g., 'User', 'System').
119
119
  content (Union[str, dict, list]): The content of the message to be added.
120
120
  """
121
- now = datetime.datetime.now()
122
- now.strftime("%Y-%m-%d %H:%M:%S")
123
121
 
124
122
  # Base message with role
125
123
  message = {
@@ -131,7 +129,7 @@ class Conversation(BaseStructure):
131
129
  message["content"] = content
132
130
  elif self.time_enabled:
133
131
  message["content"] = (
134
- f"Time: {now.strftime('%Y-%m-%d %H:%M:%S')} \n {content}"
132
+ f"Time: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} \n {content}"
135
133
  )
136
134
  else:
137
135
  message["content"] = content
@@ -159,9 +157,21 @@ class Conversation(BaseStructure):
159
157
  True # Make thread terminate when main program exits
160
158
  )
161
159
  token_thread.start()
162
- elif self.autosave:
163
- # If token counting is disabled but autosave is enabled, save immediately
164
- self.save_as_json(self.save_filepath)
160
+
161
+ def add(self, role: str, content: Union[str, dict, list]):
162
+ """Add a message to the conversation history.
163
+
164
+ Args:
165
+ role (str): The role of the speaker (e.g., 'User', 'System').
166
+ content (Union[str, dict, list]): The content of the message to be added.
167
+ """
168
+ process_thread = threading.Thread(
169
+ target=self._add,
170
+ args=(role, content),
171
+ daemon=True,
172
+ )
173
+ process_thread.start()
174
+ # process_thread.join()
165
175
 
166
176
  def delete(self, index: str):
167
177
  """Delete a message from the conversation history.
@@ -524,6 +534,27 @@ class Conversation(BaseStructure):
524
534
  # print(output)
525
535
  return output
526
536
 
537
+ def return_all_except_first(self):
538
+ """Return all messages except the first one.
539
+
540
+ Returns:
541
+ list: List of messages except the first one.
542
+ """
543
+ return self.conversation_history[2:]
544
+
545
+ def return_all_except_first_string(self):
546
+ """Return all messages except the first one as a string.
547
+
548
+ Returns:
549
+ str: All messages except the first one as a string.
550
+ """
551
+ return "\n".join(
552
+ [
553
+ f"{msg['content']}"
554
+ for msg in self.conversation_history[2:]
555
+ ]
556
+ )
557
+
527
558
 
528
559
  # # Example usage
529
560
  # # conversation = Conversation()
@@ -13,7 +13,7 @@ from swarms.structs.groupchat import GroupChat
13
13
  from swarms.structs.hiearchical_swarm import HierarchicalSwarm
14
14
  from swarms.structs.majority_voting import MajorityVoting
15
15
  from swarms.structs.mixture_of_agents import MixtureOfAgents
16
- from swarms.structs.multi_agent_orchestrator import MultiAgentRouter
16
+ from swarms.structs.multi_agent_router import MultiAgentRouter
17
17
  from swarms.structs.rearrange import AgentRearrange
18
18
  from swarms.structs.sequential_workflow import SequentialWorkflow
19
19
  from swarms.structs.spreadsheet_swarm import SpreadSheetSwarm