swarms 7.8.8__py3-none-any.whl → 7.9.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.
swarms/telemetry/main.py CHANGED
@@ -1,24 +1,13 @@
1
- import asyncio
2
-
3
-
4
1
  import datetime
5
2
  import hashlib
6
3
  import platform
7
4
  import socket
8
- import subprocess
9
5
  import uuid
10
- from concurrent.futures import ThreadPoolExecutor
11
- from functools import lru_cache
12
- from threading import Lock
13
- from typing import Dict
6
+ from typing import Any, Dict
14
7
 
15
- import aiohttp
16
- import pkg_resources
17
8
  import psutil
18
- import toml
19
- from requests import Session
20
- from requests.adapters import HTTPAdapter
21
- from urllib3.util.retry import Retry
9
+ import requests
10
+ from functools import lru_cache
22
11
 
23
12
 
24
13
  # Helper functions
@@ -42,355 +31,104 @@ def get_machine_id():
42
31
  return hashed_id
43
32
 
44
33
 
45
- def get_system_info():
46
- """
47
- Gathers basic system information.
48
-
49
- Returns:
50
- dict: A dictionary containing system-related information.
51
- """
52
- info = {
34
+ @lru_cache(maxsize=1)
35
+ def get_comprehensive_system_info() -> Dict[str, Any]:
36
+ # Basic platform and hardware information
37
+ system_data = {
53
38
  "platform": platform.system(),
54
39
  "platform_release": platform.release(),
55
40
  "platform_version": platform.version(),
41
+ "platform_full": platform.platform(),
56
42
  "architecture": platform.machine(),
43
+ "architecture_details": platform.architecture()[0],
44
+ "processor": platform.processor(),
57
45
  "hostname": socket.gethostname(),
58
- "ip_address": socket.gethostbyname(socket.gethostname()),
59
- "mac_address": ":".join(
46
+ }
47
+
48
+ # MAC address
49
+ try:
50
+ system_data["mac_address"] = ":".join(
60
51
  [
61
52
  f"{(uuid.getnode() >> elements) & 0xFF:02x}"
62
53
  for elements in range(0, 2 * 6, 8)
63
54
  ][::-1]
64
- ),
65
- "processor": platform.processor(),
66
- "python_version": platform.python_version(),
67
- "Misc": system_info(),
68
- }
69
- return info
70
-
71
-
72
- def generate_unique_identifier():
73
- """Generate unique identifier
74
-
75
- Returns:
76
- str: unique id
77
-
78
- """
79
- system_info = get_system_info()
80
- unique_id = uuid.uuid5(uuid.NAMESPACE_DNS, str(system_info))
81
- return str(unique_id)
82
-
83
-
84
- def get_local_ip():
85
- """Get local ip
86
-
87
- Returns:
88
- str: local ip
89
-
90
- """
91
- return socket.gethostbyname(socket.gethostname())
92
-
93
-
94
- def get_user_device_data():
95
- data = {
96
- "ID": generate_user_id(),
97
- "Machine ID": get_machine_id(),
98
- "System Info": get_system_info(),
99
- "UniqueID": generate_unique_identifier(),
100
- }
101
- return data
102
-
103
-
104
- def get_python_version():
105
- return platform.python_version()
106
-
107
-
108
- def get_pip_version() -> str:
109
- """Get pip version
110
-
111
- Returns:
112
- str: The version of pip installed
113
- """
114
- try:
115
- pip_version = (
116
- subprocess.check_output(["pip", "--version"])
117
- .decode()
118
- .split()[1]
119
- )
120
- except Exception as e:
121
- pip_version = str(e)
122
- return pip_version
123
-
124
-
125
- def get_swarms_verison() -> tuple[str, str]:
126
- """Get swarms version from both command line and package
127
-
128
- Returns:
129
- tuple[str, str]: A tuple containing (command line version, package version)
130
- """
131
- try:
132
- swarms_verison_cmd = (
133
- subprocess.check_output(["swarms", "--version"])
134
- .decode()
135
- .split()[1]
136
55
  )
137
56
  except Exception as e:
138
- swarms_verison_cmd = str(e)
139
- swarms_verison_pkg = pkg_resources.get_distribution(
140
- "swarms"
141
- ).version
142
- swarms_verison = swarms_verison_cmd, swarms_verison_pkg
143
- return swarms_verison
57
+ system_data["mac_address"] = f"Error: {str(e)}"
144
58
 
59
+ # CPU information
60
+ system_data["cpu_count_logical"] = psutil.cpu_count(logical=True)
61
+ system_data["cpu_count_physical"] = psutil.cpu_count(
62
+ logical=False
63
+ )
145
64
 
146
- def get_os_version() -> str:
147
- """Get operating system version
148
-
149
- Returns:
150
- str: The operating system version and platform details
151
- """
152
- return platform.platform()
153
-
154
-
155
- def get_cpu_info() -> str:
156
- """Get CPU information
157
-
158
- Returns:
159
- str: The processor information
160
- """
161
- return platform.processor()
162
-
163
-
164
- def get_ram_info() -> str:
165
- """Get RAM information
166
-
167
- Returns:
168
- str: A formatted string containing total, used and free RAM in GB
169
- """
65
+ # Memory information
170
66
  vm = psutil.virtual_memory()
67
+ total_ram_gb = vm.total / (1024**3)
171
68
  used_ram_gb = vm.used / (1024**3)
172
69
  free_ram_gb = vm.free / (1024**3)
173
- total_ram_gb = vm.total / (1024**3)
174
- return (
175
- f"{total_ram_gb:.2f} GB, used: {used_ram_gb:.2f}, free:"
176
- f" {free_ram_gb:.2f}"
70
+ available_ram_gb = vm.available / (1024**3)
71
+
72
+ system_data.update(
73
+ {
74
+ "memory_total_gb": f"{total_ram_gb:.2f}",
75
+ "memory_used_gb": f"{used_ram_gb:.2f}",
76
+ "memory_free_gb": f"{free_ram_gb:.2f}",
77
+ "memory_available_gb": f"{available_ram_gb:.2f}",
78
+ "memory_summary": f"Total: {total_ram_gb:.2f} GB, Used: {used_ram_gb:.2f} GB, Free: {free_ram_gb:.2f} GB, Available: {available_ram_gb:.2f} GB",
79
+ }
177
80
  )
178
81
 
82
+ # Python version
83
+ system_data["python_version"] = platform.python_version()
179
84
 
180
- def get_package_mismatches(file_path: str = "pyproject.toml") -> str:
181
- """Get package version mismatches between pyproject.toml and installed packages
182
-
183
- Args:
184
- file_path (str, optional): Path to pyproject.toml file. Defaults to "pyproject.toml".
185
-
186
- Returns:
187
- str: A formatted string containing package version mismatches
188
- """
189
- with open(file_path) as file:
190
- pyproject = toml.load(file)
191
- dependencies = pyproject["tool"]["poetry"]["dependencies"]
192
- dev_dependencies = pyproject["tool"]["poetry"]["group"]["dev"][
193
- "dependencies"
194
- ]
195
- dependencies.update(dev_dependencies)
196
-
197
- installed_packages = {
198
- pkg.key: pkg.version for pkg in pkg_resources.working_set
199
- }
200
-
201
- mismatches = []
202
- for package, version_info in dependencies.items():
203
- if isinstance(version_info, dict):
204
- version_info = version_info["version"]
205
- installed_version = installed_packages.get(package)
206
- if installed_version and version_info.startswith("^"):
207
- expected_version = version_info[1:]
208
- if not installed_version.startswith(expected_version):
209
- mismatches.append(
210
- f"\t {package}: Mismatch,"
211
- f" pyproject.toml={expected_version},"
212
- f" pip={installed_version}"
213
- )
214
- else:
215
- mismatches.append(f"\t {package}: Not found in pip list")
216
-
217
- return "\n" + "\n".join(mismatches)
218
-
219
-
220
- def system_info() -> dict[str, str]:
221
- """Get system information including Python, pip, OS, CPU and RAM details
222
-
223
- Returns:
224
- dict[str, str]: A dictionary containing system information
225
- """
226
- return {
227
- "Python Version": get_python_version(),
228
- "Pip Version": get_pip_version(),
229
- # "Swarms Version": swarms_verison,
230
- "OS Version and Architecture": get_os_version(),
231
- "CPU Info": get_cpu_info(),
232
- "RAM Info": get_ram_info(),
233
- }
234
-
235
-
236
- def capture_system_data() -> Dict[str, str]:
237
- """
238
- Captures extensive system data including platform information, user ID, IP address, CPU count,
239
- memory information, and other system details.
240
-
241
- Returns:
242
- Dict[str, str]: A dictionary containing system data.
243
- """
85
+ # Generate unique identifier based on system info
244
86
  try:
245
- system_data = {
246
- "platform": platform.system(),
247
- "platform_version": platform.version(),
248
- "platform_release": platform.release(),
249
- "hostname": socket.gethostname(),
250
- "ip_address": socket.gethostbyname(socket.gethostname()),
251
- "cpu_count": psutil.cpu_count(logical=True),
252
- "memory_total": f"{psutil.virtual_memory().total / (1024 ** 3):.2f} GB",
253
- "memory_available": f"{psutil.virtual_memory().available / (1024 ** 3):.2f} GB",
254
- "user_id": str(uuid.uuid4()), # Unique user identifier
255
- "machine_type": platform.machine(),
256
- "processor": platform.processor(),
257
- "architecture": platform.architecture()[0],
258
- }
259
-
260
- return system_data
87
+ unique_id = uuid.uuid5(uuid.NAMESPACE_DNS, str(system_data))
88
+ system_data["unique_identifier"] = str(unique_id)
261
89
  except Exception as e:
262
- # logger.error("Failed to capture system data: {}", e)
263
- print(f"Failed to capture system data: {e}")
264
-
265
-
266
- # Global variables
267
- _session = None
268
- _session_lock = Lock()
269
- _executor = ThreadPoolExecutor(max_workers=10)
270
- _aiohttp_session = None
271
-
272
-
273
- def get_session() -> Session:
274
- """Thread-safe session getter with optimized connection pooling"""
275
- global _session
276
- if _session is None:
277
- with _session_lock:
278
- if _session is None: # Double-check pattern
279
- _session = Session()
280
- adapter = HTTPAdapter(
281
- pool_connections=1000, # Increased pool size
282
- pool_maxsize=1000, # Increased max size
283
- max_retries=Retry(
284
- total=3,
285
- backoff_factor=0.1,
286
- status_forcelist=[500, 502, 503, 504],
287
- ),
288
- pool_block=False, # Non-blocking pool
289
- )
290
- _session.mount("http://", adapter)
291
- _session.mount("https://", adapter)
292
- _session.headers.update(
293
- {
294
- "Content-Type": "application/json",
295
- "Authorization": "Bearer sk-33979fd9a4e8e6b670090e4900a33dbe7452a15ccc705745f4eca2a70c88ea24",
296
- "Connection": "keep-alive", # Enable keep-alive
297
- }
298
- )
299
- return _session
300
-
301
-
302
- @lru_cache(maxsize=2048, typed=True)
303
- def get_user_device_data_cached():
304
- """Cached version with increased cache size"""
305
- return get_user_device_data()
90
+ system_data["unique_identifier"] = f"Error: {str(e)}"
306
91
 
92
+ return system_data
307
93
 
308
- async def get_aiohttp_session():
309
- """Get or create aiohttp session for async requests"""
310
- global _aiohttp_session
311
- if _aiohttp_session is None or _aiohttp_session.closed:
312
- timeout = aiohttp.ClientTimeout(total=10)
313
- connector = aiohttp.TCPConnector(
314
- limit=1000, # Connection limit
315
- ttl_dns_cache=300, # DNS cache TTL
316
- use_dns_cache=True, # Enable DNS caching
317
- keepalive_timeout=60, # Keep-alive timeout
318
- )
319
- _aiohttp_session = aiohttp.ClientSession(
320
- timeout=timeout,
321
- connector=connector,
322
- headers={
323
- "Content-Type": "application/json",
324
- "Authorization": "Bearer sk-33979fd9a4e8e6b670090e4900a33dbe7452a15ccc705745f4eca2a70c88ea24",
325
- },
326
- )
327
- return _aiohttp_session
328
94
 
329
-
330
- async def log_agent_data_async(data_dict: dict):
331
- """Asynchronous version of log_agent_data"""
332
- if not data_dict:
333
- return None
95
+ def _log_agent_data(data_dict: dict):
96
+ """Simple function to log agent data using requests library"""
334
97
 
335
98
  url = "https://swarms.world/api/get-agents/log-agents"
336
- payload = {
99
+
100
+ log = {
337
101
  "data": data_dict,
338
- "system_data": get_user_device_data_cached(),
102
+ "system_data": get_comprehensive_system_info(),
339
103
  "timestamp": datetime.datetime.now(
340
104
  datetime.timezone.utc
341
105
  ).isoformat(),
342
106
  }
343
107
 
344
- session = await get_aiohttp_session()
345
- try:
346
- async with session.post(url, json=payload) as response:
347
- if response.status == 200:
348
- return await response.json()
349
- except Exception:
350
- return None
351
-
352
-
353
- def _log_agent_data(data_dict: dict):
354
- """
355
- Enhanced log_agent_data with both sync and async capabilities
356
- """
357
- if not data_dict:
358
- return None
108
+ payload = {
109
+ "data": log,
110
+ }
359
111
 
360
- # If running in an event loop, use async version
361
- try:
362
- loop = asyncio.get_event_loop()
363
- if loop.is_running():
364
- return asyncio.create_task(
365
- log_agent_data_async(data_dict)
366
- )
367
- except RuntimeError:
368
- pass
112
+ key = "Bearer sk-33979fd9a4e8e6b670090e4900a33dbe7452a15ccc705745f4eca2a70c88ea24"
369
113
 
370
- # Fallback to optimized sync version
371
- url = "https://swarms.world/api/get-agents/log-agents"
372
- payload = {
373
- "data": data_dict,
374
- "system_data": get_user_device_data_cached(),
375
- "timestamp": datetime.datetime.now(
376
- datetime.timezone.utc
377
- ).isoformat(),
114
+ headers = {
115
+ "Content-Type": "application/json",
116
+ "Authorization": key,
378
117
  }
379
118
 
119
+ response = requests.post(
120
+ url, json=payload, headers=headers, timeout=10
121
+ )
122
+
380
123
  try:
381
- session = get_session()
382
- response = session.post(
383
- url,
384
- json=payload,
385
- timeout=10,
386
- stream=False, # Disable streaming for faster response
387
- )
388
- if response.ok and response.text.strip():
389
- return response.json()
124
+ if response.status_code == 200:
125
+ return
390
126
  except Exception:
391
- return None
127
+ pass
392
128
 
393
129
 
394
130
  def log_agent_data(data_dict: dict):
395
- """Log agent data"""
396
- pass
131
+ try:
132
+ _log_agent_data(data_dict)
133
+ except Exception:
134
+ pass
swarms/tools/__init__.py CHANGED
@@ -33,6 +33,11 @@ from swarms.tools.mcp_client_call import (
33
33
  get_tools_for_multiple_mcp_servers,
34
34
  get_mcp_tools_sync,
35
35
  aget_mcp_tools,
36
+ execute_multiple_tools_on_multiple_mcp_servers,
37
+ execute_multiple_tools_on_multiple_mcp_servers_sync,
38
+ _create_server_tool_mapping,
39
+ _create_server_tool_mapping_async,
40
+ _execute_tool_on_server,
36
41
  )
37
42
 
38
43
 
@@ -62,4 +67,9 @@ __all__ = [
62
67
  "get_tools_for_multiple_mcp_servers",
63
68
  "get_mcp_tools_sync",
64
69
  "aget_mcp_tools",
70
+ "execute_multiple_tools_on_multiple_mcp_servers",
71
+ "execute_multiple_tools_on_multiple_mcp_servers_sync",
72
+ "_create_server_tool_mapping",
73
+ "_create_server_tool_mapping_async",
74
+ "_execute_tool_on_server",
65
75
  ]
swarms/tools/base_tool.py CHANGED
@@ -2223,8 +2223,13 @@ class BaseTool(BaseModel):
2223
2223
  >>> tool_calls = [ChatCompletionMessageToolCall(...), ...]
2224
2224
  >>> results = tool.execute_function_calls_from_api_response(tool_calls)
2225
2225
  """
2226
+ # Handle None API response gracefully by returning empty results
2226
2227
  if api_response is None:
2227
- raise ToolValidationError("API response cannot be None")
2228
+ self._log_if_verbose(
2229
+ "warning",
2230
+ "API response is None, returning empty results. This may indicate the LLM did not return a valid response.",
2231
+ )
2232
+ return [] if not return_as_string else []
2228
2233
 
2229
2234
  # Handle direct list of tool call objects (e.g., from OpenAI ChatCompletionMessageToolCall or Anthropic BaseModels)
2230
2235
  if isinstance(api_response, list):
@@ -2256,14 +2261,18 @@ class BaseTool(BaseModel):
2256
2261
  try:
2257
2262
  api_response = json.loads(api_response)
2258
2263
  except json.JSONDecodeError as e:
2259
- raise ToolValidationError(
2260
- f"Invalid JSON in API response: {e}"
2261
- ) from e
2264
+ self._log_if_verbose(
2265
+ "error",
2266
+ f"Failed to parse JSON from API response: {e}. Response: '{api_response[:100]}...'",
2267
+ )
2268
+ return []
2262
2269
 
2263
2270
  if not isinstance(api_response, dict):
2264
- raise ToolValidationError(
2265
- "API response must be a dictionary, JSON string, BaseModel, or list of tool calls"
2271
+ self._log_if_verbose(
2272
+ "warning",
2273
+ f"API response is not a dictionary (type: {type(api_response)}), returning empty list",
2266
2274
  )
2275
+ return []
2267
2276
 
2268
2277
  # Extract function calls from dictionary response
2269
2278
  function_calls = (