swarms 7.7.1__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.
- swarms/prompts/ag_prompt.py +51 -19
- swarms/prompts/agent_system_prompts.py +13 -4
- swarms/prompts/multi_agent_collab_prompt.py +18 -0
- swarms/prompts/prompt.py +6 -10
- swarms/schemas/__init__.py +0 -3
- swarms/structs/__init__.py +3 -8
- swarms/structs/agent.py +211 -163
- swarms/structs/aop.py +8 -1
- swarms/structs/auto_swarm_builder.py +271 -210
- swarms/structs/conversation.py +23 -56
- swarms/structs/hiearchical_swarm.py +93 -122
- swarms/structs/ma_utils.py +96 -0
- swarms/structs/mixture_of_agents.py +20 -103
- swarms/structs/{multi_agent_orchestrator.py → multi_agent_router.py} +32 -95
- swarms/structs/output_types.py +3 -16
- swarms/structs/stopping_conditions.py +30 -0
- swarms/structs/swarm_router.py +57 -5
- swarms/structs/swarming_architectures.py +576 -185
- swarms/telemetry/main.py +6 -2
- swarms/tools/mcp_client.py +209 -53
- swarms/tools/mcp_integration.py +1 -53
- swarms/utils/formatter.py +15 -1
- swarms/utils/generate_keys.py +64 -0
- swarms/utils/history_output_formatter.py +2 -0
- {swarms-7.7.1.dist-info → swarms-7.7.3.dist-info}/METADATA +98 -263
- {swarms-7.7.1.dist-info → swarms-7.7.3.dist-info}/RECORD +29 -38
- swarms/schemas/agent_input_schema.py +0 -149
- swarms/structs/agents_available.py +0 -87
- swarms/structs/async_workflow.py +0 -818
- swarms/structs/graph_swarm.py +0 -612
- swarms/structs/octotools.py +0 -844
- swarms/structs/pulsar_swarm.py +0 -469
- swarms/structs/queue_swarm.py +0 -193
- swarms/structs/swarm_builder.py +0 -395
- swarms/structs/swarm_load_balancer.py +0 -344
- swarms/structs/swarm_output_type.py +0 -23
- swarms/structs/talk_hier.py +0 -729
- {swarms-7.7.1.dist-info → swarms-7.7.3.dist-info}/LICENSE +0 -0
- {swarms-7.7.1.dist-info → swarms-7.7.3.dist-info}/WHEEL +0 -0
- {swarms-7.7.1.dist-info → swarms-7.7.3.dist-info}/entry_points.txt +0 -0
swarms/telemetry/main.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# Add these imports at the top
|
2
1
|
import asyncio
|
3
2
|
|
4
3
|
|
@@ -351,7 +350,7 @@ async def log_agent_data_async(data_dict: dict):
|
|
351
350
|
return None
|
352
351
|
|
353
352
|
|
354
|
-
def
|
353
|
+
def _log_agent_data(data_dict: dict):
|
355
354
|
"""
|
356
355
|
Enhanced log_agent_data with both sync and async capabilities
|
357
356
|
"""
|
@@ -390,3 +389,8 @@ def log_agent_data(data_dict: dict):
|
|
390
389
|
return response.json()
|
391
390
|
except Exception:
|
392
391
|
return None
|
392
|
+
|
393
|
+
|
394
|
+
def log_agent_data(data_dict: dict):
|
395
|
+
"""Log agent data"""
|
396
|
+
pass
|
swarms/tools/mcp_client.py
CHANGED
@@ -1,90 +1,246 @@
|
|
1
1
|
import asyncio
|
2
|
-
|
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
|
-
|
12
|
-
|
12
|
+
"""
|
13
|
+
Parse agent output into tool name and parameters.
|
13
14
|
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
33
|
-
|
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
|
-
|
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
|
-
|
51
|
-
|
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
|
-
|
145
|
+
Returns:
|
146
|
+
Dictionary containing the tool execution results.
|
54
147
|
|
55
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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)}")
|
swarms/tools/mcp_integration.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import Any
|
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
|
swarms/utils/formatter.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import threading
|
1
2
|
import time
|
2
3
|
from typing import Any, Callable, Dict, List
|
3
4
|
|
@@ -20,7 +21,7 @@ class Formatter:
|
|
20
21
|
"""
|
21
22
|
self.console = Console()
|
22
23
|
|
23
|
-
def
|
24
|
+
def _print_panel(
|
24
25
|
self, content: str, title: str = "", style: str = "bold blue"
|
25
26
|
) -> None:
|
26
27
|
"""
|
@@ -48,6 +49,19 @@ class Formatter:
|
|
48
49
|
)
|
49
50
|
self.console.print(panel)
|
50
51
|
|
52
|
+
def print_panel(
|
53
|
+
self,
|
54
|
+
content: str,
|
55
|
+
title: str = "",
|
56
|
+
style: str = "bold blue",
|
57
|
+
) -> None:
|
58
|
+
process_thread = threading.Thread(
|
59
|
+
target=self._print_panel,
|
60
|
+
args=(content, title, style),
|
61
|
+
daemon=True,
|
62
|
+
)
|
63
|
+
process_thread.start()
|
64
|
+
|
51
65
|
def print_table(
|
52
66
|
self, title: str, data: Dict[str, List[str]]
|
53
67
|
) -> None:
|
@@ -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
|