swarms 7.7.2__py3-none-any.whl → 7.7.3__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.
Files changed (34) hide show
  1. swarms/prompts/ag_prompt.py +51 -19
  2. swarms/prompts/agent_system_prompts.py +13 -4
  3. swarms/prompts/multi_agent_collab_prompt.py +18 -0
  4. swarms/prompts/prompt.py +6 -10
  5. swarms/schemas/__init__.py +0 -3
  6. swarms/structs/__init__.py +2 -4
  7. swarms/structs/agent.py +201 -160
  8. swarms/structs/aop.py +8 -1
  9. swarms/structs/auto_swarm_builder.py +271 -210
  10. swarms/structs/conversation.py +22 -65
  11. swarms/structs/hiearchical_swarm.py +93 -122
  12. swarms/structs/ma_utils.py +96 -0
  13. swarms/structs/mixture_of_agents.py +20 -103
  14. swarms/structs/multi_agent_router.py +32 -95
  15. swarms/structs/output_types.py +3 -16
  16. swarms/structs/stopping_conditions.py +30 -0
  17. swarms/structs/swarm_router.py +56 -4
  18. swarms/structs/swarming_architectures.py +576 -185
  19. swarms/telemetry/main.py +1 -7
  20. swarms/tools/mcp_client.py +209 -53
  21. swarms/tools/mcp_integration.py +1 -53
  22. swarms/utils/generate_keys.py +64 -0
  23. swarms/utils/history_output_formatter.py +2 -0
  24. {swarms-7.7.2.dist-info → swarms-7.7.3.dist-info}/METADATA +98 -263
  25. {swarms-7.7.2.dist-info → swarms-7.7.3.dist-info}/RECORD +28 -32
  26. swarms/schemas/agent_input_schema.py +0 -149
  27. swarms/structs/agents_available.py +0 -87
  28. swarms/structs/graph_swarm.py +0 -612
  29. swarms/structs/queue_swarm.py +0 -193
  30. swarms/structs/swarm_builder.py +0 -395
  31. swarms/structs/swarm_output_type.py +0 -23
  32. {swarms-7.7.2.dist-info → swarms-7.7.3.dist-info}/LICENSE +0 -0
  33. {swarms-7.7.2.dist-info → swarms-7.7.3.dist-info}/WHEEL +0 -0
  34. {swarms-7.7.2.dist-info → swarms-7.7.3.dist-info}/entry_points.txt +0 -0
swarms/telemetry/main.py CHANGED
@@ -1,4 +1,3 @@
1
- import threading
2
1
  import asyncio
3
2
 
4
3
 
@@ -394,9 +393,4 @@ def _log_agent_data(data_dict: dict):
394
393
 
395
394
  def log_agent_data(data_dict: dict):
396
395
  """Log agent data"""
397
- process_thread = threading.Thread(
398
- target=_log_agent_data,
399
- args=(data_dict,),
400
- daemon=True,
401
- )
402
- process_thread.start()
396
+ pass
@@ -1,90 +1,246 @@
1
1
  import asyncio
2
- from typing import Literal, Dict, Any, Union
2
+ import json
3
+ from typing import List, Literal, Dict, Any, Union
3
4
  from fastmcp import Client
4
- from swarms.utils.any_to_str import any_to_str
5
5
  from swarms.utils.str_to_dict import str_to_dict
6
+ from loguru import logger
6
7
 
7
8
 
8
9
  def parse_agent_output(
9
10
  dictionary: Union[str, Dict[Any, Any]]
10
11
  ) -> tuple[str, Dict[Any, Any]]:
11
- if isinstance(dictionary, str):
12
- dictionary = str_to_dict(dictionary)
12
+ """
13
+ Parse agent output into tool name and parameters.
13
14
 
14
- elif not isinstance(dictionary, dict):
15
- raise ValueError("Invalid dictionary")
15
+ Args:
16
+ dictionary: Either a string or dictionary containing tool information.
17
+ If string, it will be converted to a dictionary.
18
+ Must contain a 'name' key for the tool name.
16
19
 
17
- # Handle OpenAI function call format
18
- if "function_call" in dictionary:
19
- name = dictionary["function_call"]["name"]
20
- # arguments is a JSON string, so we need to parse it
21
- params = str_to_dict(dictionary["function_call"]["arguments"])
22
- return name, params
20
+ Returns:
21
+ tuple[str, Dict[Any, Any]]: A tuple containing the tool name and its parameters.
23
22
 
24
- # Handle OpenAI tool calls format
25
- if "tool_calls" in dictionary:
26
- # Get the first tool call (or you could handle multiple if needed)
27
- tool_call = dictionary["tool_calls"][0]
28
- name = tool_call["function"]["name"]
29
- params = str_to_dict(tool_call["function"]["arguments"])
30
- return name, params
23
+ Raises:
24
+ ValueError: If the input is invalid or missing required 'name' key.
25
+ """
26
+ try:
27
+ if isinstance(dictionary, str):
28
+ dictionary = str_to_dict(dictionary)
31
29
 
32
- # Handle regular dictionary format
33
- if "name" in dictionary:
34
- name = dictionary["name"]
35
- params = dictionary.get("arguments", {})
36
- return name, params
30
+ elif not isinstance(dictionary, dict):
31
+ raise ValueError("Invalid dictionary")
37
32
 
38
- raise ValueError("Invalid function call format")
33
+ # Handle regular dictionary format
34
+ if "name" in dictionary:
35
+ name = dictionary["name"]
36
+ # Remove the name key and use remaining key-value pairs as parameters
37
+ params = dict(dictionary)
38
+ params.pop("name")
39
+ return name, params
40
+
41
+ raise ValueError("Invalid function call format")
42
+ except Exception as e:
43
+ raise ValueError(f"Error parsing agent output: {str(e)}")
44
+
45
+
46
+ async def _list_all(url: str):
47
+ """
48
+ Asynchronously list all tools available on a given MCP server.
49
+
50
+ Args:
51
+ url: The URL of the MCP server to query.
52
+
53
+ Returns:
54
+ List of available tools.
55
+
56
+ Raises:
57
+ ValueError: If there's an error connecting to or querying the server.
58
+ """
59
+ try:
60
+ async with Client(url) as client:
61
+ return await client.list_tools()
62
+ except Exception as e:
63
+ raise ValueError(f"Error listing tools: {str(e)}")
64
+
65
+
66
+ def list_all(url: str, output_type: Literal["str", "json"] = "json"):
67
+ """
68
+ Synchronously list all tools available on a given MCP server.
69
+
70
+ Args:
71
+ url: The URL of the MCP server to query.
72
+
73
+ Returns:
74
+ List of dictionaries containing tool information.
75
+
76
+ Raises:
77
+ ValueError: If there's an error connecting to or querying the server.
78
+ """
79
+ try:
80
+ out = asyncio.run(_list_all(url))
81
+
82
+ outputs = []
83
+ for tool in out:
84
+ outputs.append(tool.model_dump())
85
+
86
+ if output_type == "json":
87
+ return json.dumps(outputs, indent=4)
88
+ else:
89
+ return outputs
90
+ except Exception as e:
91
+ raise ValueError(f"Error in list_all: {str(e)}")
92
+
93
+
94
+ def list_tools_for_multiple_urls(
95
+ urls: List[str], output_type: Literal["str", "json"] = "json"
96
+ ):
97
+ """
98
+ List tools available across multiple MCP servers.
99
+
100
+ Args:
101
+ urls: List of MCP server URLs to query.
102
+ output_type: Format of the output, either "json" (string) or "str" (list).
103
+
104
+ Returns:
105
+ If output_type is "json": JSON string containing all tools with server URLs.
106
+ If output_type is "str": List of tools with server URLs.
107
+
108
+ Raises:
109
+ ValueError: If there's an error querying any of the servers.
110
+ """
111
+ try:
112
+ out = []
113
+ for url in urls:
114
+ tools = list_all(url)
115
+ # Add server URL to each tool's data
116
+ for tool in tools:
117
+ tool["server_url"] = url
118
+ out.append(tools)
119
+
120
+ if output_type == "json":
121
+ return json.dumps(out, indent=4)
122
+ else:
123
+ return out
124
+ except Exception as e:
125
+ raise ValueError(
126
+ f"Error listing tools for multiple URLs: {str(e)}"
127
+ )
39
128
 
40
129
 
41
130
  async def _execute_mcp_tool(
42
131
  url: str,
43
- method: Literal["stdio", "sse"] = "sse",
44
132
  parameters: Dict[Any, Any] = None,
45
- output_type: Literal["str", "dict"] = "str",
46
133
  *args,
47
134
  **kwargs,
48
135
  ) -> Dict[Any, Any]:
136
+ """
137
+ Asynchronously execute a tool on an MCP server.
49
138
 
50
- if "sse" or "stdio" not in url:
51
- raise ValueError("Invalid URL")
139
+ Args:
140
+ url: The URL of the MCP server.
141
+ parameters: Dictionary containing tool name and parameters.
142
+ *args: Additional positional arguments for the Client.
143
+ **kwargs: Additional keyword arguments for the Client.
52
144
 
53
- url = f"{url}/{method}"
145
+ Returns:
146
+ Dictionary containing the tool execution results.
54
147
 
55
- name, params = parse_agent_output(parameters)
148
+ Raises:
149
+ ValueError: If the URL is invalid or tool execution fails.
150
+ """
151
+ try:
152
+
153
+ name, params = parse_agent_output(parameters)
154
+
155
+ outputs = []
56
156
 
57
- if output_type == "str":
58
- async with Client(url, *args, **kwargs) as client:
59
- out = await client.call_tool(
60
- name=name,
61
- arguments=params,
62
- )
63
- return any_to_str(out)
64
- elif output_type == "dict":
65
157
  async with Client(url, *args, **kwargs) as client:
66
158
  out = await client.call_tool(
67
159
  name=name,
68
160
  arguments=params,
69
161
  )
70
- return out
71
- else:
72
- raise ValueError(f"Invalid output type: {output_type}")
162
+
163
+ for output in out:
164
+ outputs.append(output.model_dump())
165
+
166
+ # convert outputs to string
167
+ return json.dumps(outputs, indent=4)
168
+ except Exception as e:
169
+ raise ValueError(f"Error executing MCP tool: {str(e)}")
73
170
 
74
171
 
75
172
  def execute_mcp_tool(
76
173
  url: str,
77
- tool_name: str = None,
78
- method: Literal["stdio", "sse"] = "sse",
79
174
  parameters: Dict[Any, Any] = None,
80
- output_type: Literal["str", "dict"] = "str",
81
175
  ) -> Dict[Any, Any]:
82
- return asyncio.run(
83
- _execute_mcp_tool(
84
- url=url,
85
- tool_name=tool_name,
86
- method=method,
87
- parameters=parameters,
88
- output_type=output_type,
176
+ """
177
+ Synchronously execute a tool on an MCP server.
178
+
179
+ Args:
180
+ url: The URL of the MCP server.
181
+ parameters: Dictionary containing tool name and parameters.
182
+
183
+ Returns:
184
+ Dictionary containing the tool execution results.
185
+
186
+ Raises:
187
+ ValueError: If tool execution fails.
188
+ """
189
+ try:
190
+ logger.info(f"Executing MCP tool with URL: {url}")
191
+ logger.debug(f"Tool parameters: {parameters}")
192
+
193
+ result = asyncio.run(
194
+ _execute_mcp_tool(
195
+ url=url,
196
+ parameters=parameters,
197
+ )
198
+ )
199
+
200
+ logger.info("MCP tool execution completed successfully")
201
+ logger.debug(f"Tool execution result: {result}")
202
+ return result
203
+ except Exception as e:
204
+ logger.error(f"Error in execute_mcp_tool: {str(e)}")
205
+ raise ValueError(f"Error in execute_mcp_tool: {str(e)}")
206
+
207
+
208
+ def find_and_execute_tool(
209
+ urls: List[str], tool_name: str, parameters: Dict[Any, Any]
210
+ ) -> Dict[Any, Any]:
211
+ """
212
+ Find a tool across multiple servers and execute it with the given parameters.
213
+
214
+ Args:
215
+ urls: List of server URLs to search through.
216
+ tool_name: Name of the tool to find and execute.
217
+ parameters: Parameters to pass to the tool.
218
+
219
+ Returns:
220
+ Dict containing the tool execution results.
221
+
222
+ Raises:
223
+ ValueError: If tool is not found on any server or execution fails.
224
+ """
225
+ try:
226
+ # Search for tool across all servers
227
+ for url in urls:
228
+ try:
229
+ tools = list_all(url)
230
+ # Check if tool exists on this server
231
+ if any(tool["name"] == tool_name for tool in tools):
232
+ # Prepare parameters in correct format
233
+ tool_params = {"name": tool_name, **parameters}
234
+ # Execute tool on this server
235
+ return execute_mcp_tool(
236
+ url=url, parameters=tool_params
237
+ )
238
+ except Exception:
239
+ # Skip servers that fail and continue searching
240
+ continue
241
+
242
+ raise ValueError(
243
+ f"Tool '{tool_name}' not found on any provided servers"
89
244
  )
90
- )
245
+ except Exception as e:
246
+ raise ValueError(f"Error in find_and_execute_tool: {str(e)}")
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any, List
3
+ from typing import Any
4
4
 
5
5
 
6
6
  from loguru import logger
@@ -25,8 +25,6 @@ from mcp.client.sse import sse_client
25
25
  from mcp.types import CallToolResult, JSONRPCMessage
26
26
  from typing_extensions import NotRequired, TypedDict
27
27
 
28
- from swarms.utils.any_to_str import any_to_str
29
-
30
28
 
31
29
  class MCPServer(abc.ABC):
32
30
  """Base class for Model Context Protocol servers."""
@@ -340,53 +338,3 @@ class MCPServerSse(_MCPServerWithClientSession):
340
338
  def name(self) -> str:
341
339
  """A readable name for the server."""
342
340
  return self._name
343
-
344
-
345
- def mcp_flow_get_tool_schema(
346
- params: MCPServerSseParams,
347
- ) -> MCPServer:
348
- server = MCPServerSse(params, cache_tools_list=True)
349
-
350
- # Connect the server
351
- asyncio.run(server.connect())
352
-
353
- # Return the server
354
- output = asyncio.run(server.list_tools())
355
-
356
- # Cleanup the server
357
- asyncio.run(server.cleanup())
358
-
359
- return output.model_dump()
360
-
361
-
362
- def mcp_flow(
363
- params: MCPServerSseParams,
364
- function_call: dict[str, Any],
365
- ) -> MCPServer:
366
- server = MCPServerSse(params, cache_tools_list=True)
367
-
368
- # Connect the server
369
- asyncio.run(server.connect())
370
-
371
- # Return the server
372
- output = asyncio.run(server.call_tool(function_call))
373
-
374
- output = output.model_dump()
375
-
376
- # Cleanup the server
377
- asyncio.run(server.cleanup())
378
-
379
- return any_to_str(output)
380
-
381
-
382
- def batch_mcp_flow(
383
- params: List[MCPServerSseParams],
384
- function_call: List[dict[str, Any]] = [],
385
- ) -> MCPServer:
386
- output_list = []
387
-
388
- for param in params:
389
- output = mcp_flow(param, function_call)
390
- output_list.append(output)
391
-
392
- return output_list
@@ -0,0 +1,64 @@
1
+ import secrets
2
+ import string
3
+ import re
4
+
5
+
6
+ def generate_api_key(prefix: str = "sk-", length: int = 32) -> str:
7
+ """
8
+ Generate a secure API key with a custom prefix.
9
+
10
+ Args:
11
+ prefix (str): The prefix to add to the API key (default: "sk-")
12
+ length (int): The length of the random part of the key (default: 32)
13
+
14
+ Returns:
15
+ str: A secure API key in the format: prefix + random_string
16
+
17
+ Example:
18
+ >>> generate_api_key("sk-")
19
+ 'sk-abc123...'
20
+ """
21
+ # Validate prefix
22
+ if not isinstance(prefix, str):
23
+ raise ValueError("Prefix must be a string")
24
+
25
+ # Validate length
26
+ if length < 8:
27
+ raise ValueError("Length must be at least 8 characters")
28
+
29
+ # Generate random string using alphanumeric characters
30
+ alphabet = string.ascii_letters + string.digits
31
+ random_part = "".join(
32
+ secrets.choice(alphabet) for _ in range(length)
33
+ )
34
+
35
+ # Combine prefix and random part
36
+ api_key = f"{prefix}{random_part}"
37
+
38
+ return api_key
39
+
40
+
41
+ def validate_api_key(api_key: str, prefix: str = "sk-") -> bool:
42
+ """
43
+ Validate if an API key matches the expected format.
44
+
45
+ Args:
46
+ api_key (str): The API key to validate
47
+ prefix (str): The expected prefix (default: "sk-")
48
+
49
+ Returns:
50
+ bool: True if the API key is valid, False otherwise
51
+ """
52
+ if not isinstance(api_key, str):
53
+ return False
54
+
55
+ # Check if key starts with prefix
56
+ if not api_key.startswith(prefix):
57
+ return False
58
+
59
+ # Check if the rest of the key contains only alphanumeric characters
60
+ random_part = api_key[len(prefix) :]
61
+ if not re.match(r"^[a-zA-Z0-9]+$", random_part):
62
+ return False
63
+
64
+ return True
@@ -19,6 +19,8 @@ HistoryOutputType = Literal[
19
19
  "str-all-except-first",
20
20
  ]
21
21
 
22
+ output_type: HistoryOutputType
23
+
22
24
 
23
25
  def history_output_formatter(
24
26
  conversation: Conversation, type: HistoryOutputType = "list"