swarms 7.9.0__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.
@@ -83,7 +83,13 @@ from swarms.structs.swarming_architectures import (
83
83
  staircase_swarm,
84
84
  star_swarm,
85
85
  )
86
- from swarms.structs.interactive_groupchat import InteractiveGroupChat
86
+ from swarms.structs.interactive_groupchat import (
87
+ InteractiveGroupChat,
88
+ round_robin_speaker,
89
+ random_speaker,
90
+ priority_speaker,
91
+ random_dynamic_speaker,
92
+ )
87
93
 
88
94
  __all__ = [
89
95
  "Agent",
@@ -156,4 +162,8 @@ __all__ = [
156
162
  "find_agent_by_name",
157
163
  "run_agent",
158
164
  "InteractiveGroupChat",
165
+ "round_robin_speaker",
166
+ "random_speaker",
167
+ "priority_speaker",
168
+ "random_dynamic_speaker",
159
169
  ]
swarms/structs/agent.py CHANGED
@@ -5,6 +5,7 @@ import os
5
5
  import random
6
6
  import threading
7
7
  import time
8
+ import traceback
8
9
  import uuid
9
10
  from concurrent.futures import ThreadPoolExecutor
10
11
  from datetime import datetime
@@ -85,6 +86,7 @@ from swarms.utils.index import (
85
86
  )
86
87
  from swarms.schemas.conversation_schema import ConversationSchema
87
88
  from swarms.utils.output_types import OutputType
89
+ from swarms.utils.retry_func import retry_function
88
90
 
89
91
 
90
92
  def stop_when_repeats(response: str) -> bool:
@@ -153,6 +155,12 @@ class AgentLLMInitializationError(AgentError):
153
155
  pass
154
156
 
155
157
 
158
+ class AgentToolExecutionError(AgentError):
159
+ """Exception raised when the agent fails to execute a tool. Check the tool's configuration and availability."""
160
+
161
+ pass
162
+
163
+
156
164
  # [FEAT][AGENT]
157
165
  class Agent:
158
166
  """
@@ -425,6 +433,7 @@ class Agent:
425
433
  tool_call_summary: bool = True,
426
434
  output_raw_json_from_tool_call: bool = False,
427
435
  summarize_multiple_images: bool = False,
436
+ tool_retry_attempts: int = 3,
428
437
  *args,
429
438
  **kwargs,
430
439
  ):
@@ -564,6 +573,7 @@ class Agent:
564
573
  output_raw_json_from_tool_call
565
574
  )
566
575
  self.summarize_multiple_images = summarize_multiple_images
576
+ self.tool_retry_attempts = tool_retry_attempts
567
577
 
568
578
  # self.short_memory = self.short_memory_init()
569
579
 
@@ -791,10 +801,11 @@ class Agent:
791
801
  or exists(self.mcp_urls)
792
802
  or exists(self.mcp_config)
793
803
  ):
794
- self.pretty_print(
795
- f"✨ [SYSTEM] Successfully integrated {len(tools)} MCP tools into agent: {self.agent_name} | Status: ONLINE | Time: {time.strftime('%H:%M:%S')} ✨",
796
- loop_count=0,
797
- )
804
+ if self.print_on is True:
805
+ self.pretty_print(
806
+ f"✨ [SYSTEM] Successfully integrated {len(tools)} MCP tools into agent: {self.agent_name} | Status: ONLINE | Time: {time.strftime('%H:%M:%S')} ✨",
807
+ loop_count=0,
808
+ )
798
809
 
799
810
  return tools
800
811
  except AgentMCPConnectionError as e:
@@ -1015,8 +1026,8 @@ class Agent:
1015
1026
  # Print the request
1016
1027
  if print_task is True:
1017
1028
  formatter.print_panel(
1018
- f"\n User: {task}",
1019
- f"Task Request for {self.agent_name}",
1029
+ content=f"\n User: {task}",
1030
+ title=f"Task Request for {self.agent_name}",
1020
1031
  )
1021
1032
 
1022
1033
  while (
@@ -1077,6 +1088,8 @@ class Agent:
1077
1088
  **kwargs,
1078
1089
  )
1079
1090
 
1091
+ # If streaming is enabled, then don't print the response
1092
+
1080
1093
  # Parse the response from the agent with the output type
1081
1094
  if exists(self.tools_list_dictionary):
1082
1095
  if isinstance(response, BaseModel):
@@ -1091,26 +1104,24 @@ class Agent:
1091
1104
  )
1092
1105
 
1093
1106
  # Print
1094
- self.pretty_print(response, loop_count)
1107
+ if self.print_on is True:
1108
+ if isinstance(response, list):
1109
+ self.pretty_print(
1110
+ f"Structured Output - Attempting Function Call Execution [{time.strftime('%H:%M:%S')}] \n\n {format_data_structure(response)} ",
1111
+ loop_count,
1112
+ )
1113
+ elif self.streaming_on is True:
1114
+ pass
1115
+ else:
1116
+ self.pretty_print(
1117
+ response, loop_count
1118
+ )
1095
1119
 
1096
1120
  # Check and execute callable tools
1097
1121
  if exists(self.tools):
1098
- if (
1099
- self.output_raw_json_from_tool_call
1100
- is True
1101
- ):
1102
- response = response
1103
- else:
1104
- # Only execute tools if response is not None
1105
- if response is not None:
1106
- self.execute_tools(
1107
- response=response,
1108
- loop_count=loop_count,
1109
- )
1110
- else:
1111
- logger.warning(
1112
- f"LLM returned None response in loop {loop_count}, skipping tool execution"
1113
- )
1122
+ self.tool_execution_retry(
1123
+ response, loop_count
1124
+ )
1114
1125
 
1115
1126
  # Handle MCP tools
1116
1127
  if (
@@ -1141,8 +1152,12 @@ class Agent:
1141
1152
  self.save()
1142
1153
 
1143
1154
  logger.error(
1144
- f"Attempt {attempt+1}: Error generating"
1145
- f" response: {e}"
1155
+ f"Attempt {attempt+1}/{self.max_retries}: Error generating response in loop {loop_count} for agent '{self.agent_name}': {str(e)} | "
1156
+ f"Error type: {type(e).__name__}, Error details: {e.__dict__ if hasattr(e, '__dict__') else 'No additional details'} | "
1157
+ f"Current task: '{task}', Agent state: max_loops={self.max_loops}, "
1158
+ f"model={getattr(self.llm, 'model_name', 'unknown')}, "
1159
+ f"temperature={getattr(self.llm, 'temperature', 'unknown')}"
1160
+ f"{f' | Traceback: {e.__traceback__}' if hasattr(e, '__traceback__') else ''}"
1146
1161
  )
1147
1162
  attempt += 1
1148
1163
 
@@ -1164,13 +1179,19 @@ class Agent:
1164
1179
  self.stopping_condition is not None
1165
1180
  and self._check_stopping_condition(response)
1166
1181
  ):
1167
- logger.info("Stopping condition met.")
1182
+ logger.info(
1183
+ f"Agent '{self.agent_name}' stopping condition met. "
1184
+ f"Loop: {loop_count}, Response length: {len(str(response)) if response else 0}"
1185
+ )
1168
1186
  break
1169
1187
  elif (
1170
1188
  self.stopping_func is not None
1171
1189
  and self.stopping_func(response)
1172
1190
  ):
1173
- logger.info("Stopping function met.")
1191
+ logger.info(
1192
+ f"Agent '{self.agent_name}' stopping function condition met. "
1193
+ f"Loop: {loop_count}, Response length: {len(str(response)) if response else 0}"
1194
+ )
1174
1195
  break
1175
1196
 
1176
1197
  if self.interactive:
@@ -1217,14 +1238,27 @@ class Agent:
1217
1238
  self._handle_run_error(error)
1218
1239
 
1219
1240
  def __handle_run_error(self, error: any):
1241
+ import traceback
1242
+
1220
1243
  log_agent_data(self.to_dict())
1221
1244
 
1222
1245
  if self.autosave is True:
1223
1246
  self.save()
1224
1247
 
1225
- logger.info(
1226
- f"Error detected running your agent {self.agent_name} \n Error {error} \n Optimize your input parameters and or add an issue on the swarms github and contact our team on discord for support ;) "
1248
+ # Get detailed error information
1249
+ error_type = type(error).__name__
1250
+ error_message = str(error)
1251
+ traceback_info = traceback.format_exc()
1252
+
1253
+ logger.error(
1254
+ f"Error detected running your agent {self.agent_name}\n"
1255
+ f"Error Type: {error_type}\n"
1256
+ f"Error Message: {error_message}\n"
1257
+ f"Traceback:\n{traceback_info}\n"
1258
+ f"Agent State: {self.to_dict()}\n"
1259
+ f"Optimize your input parameters and or add an issue on the swarms github and contact our team on discord for support ;)"
1227
1260
  )
1261
+
1228
1262
  raise error
1229
1263
 
1230
1264
  def _handle_run_error(self, error: any):
@@ -2790,19 +2824,23 @@ class Agent:
2790
2824
  return self.role
2791
2825
 
2792
2826
  def pretty_print(self, response: str, loop_count: int):
2793
- if self.print_on is False:
2794
- if self.streaming_on is True:
2795
- # Skip printing here since real streaming is handled in call_llm
2796
- # This avoids double printing when streaming_on=True
2797
- pass
2798
- elif self.print_on is False:
2799
- pass
2800
- else:
2801
- # logger.info(f"Response: {response}")
2802
- formatter.print_panel(
2803
- f"{self.agent_name}: {response}",
2804
- f"Agent Name {self.agent_name} [Max Loops: {loop_count} ]",
2805
- )
2827
+ # if self.print_on is False:
2828
+ # if self.streaming_on is True:
2829
+ # # Skip printing here since real streaming is handled in call_llm
2830
+ # # This avoids double printing when streaming_on=True
2831
+ # pass
2832
+ # elif self.print_on is False:
2833
+ # pass
2834
+ # else:
2835
+ # # logger.info(f"Response: {response}")
2836
+ # formatter.print_panel(
2837
+ # response,
2838
+ # f"Agent Name {self.agent_name} [Max Loops: {loop_count} ]",
2839
+ # )
2840
+ formatter.print_panel(
2841
+ response,
2842
+ f"Agent Name {self.agent_name} [Max Loops: {loop_count} ]",
2843
+ )
2806
2844
 
2807
2845
  def parse_llm_output(self, response: Any):
2808
2846
  """Parse and standardize the output from the LLM.
@@ -2915,10 +2953,10 @@ class Agent:
2915
2953
  # execute_tool_call_simple returns a string directly, not an object with content attribute
2916
2954
  text_content = f"MCP Tool Response: \n\n {json.dumps(tool_response, indent=2)}"
2917
2955
 
2918
- if self.print_on is False:
2956
+ if self.print_on is True:
2919
2957
  formatter.print_panel(
2920
- text_content,
2921
- "MCP Tool Response: 🛠️",
2958
+ content=text_content,
2959
+ title="MCP Tool Response: 🛠️",
2922
2960
  style="green",
2923
2961
  )
2924
2962
 
@@ -2942,7 +2980,8 @@ class Agent:
2942
2980
  # Fallback: provide a default summary
2943
2981
  summary = "I successfully executed the MCP tool and retrieved the information above."
2944
2982
 
2945
- self.pretty_print(summary, loop_count=current_loop)
2983
+ if self.print_on is True:
2984
+ self.pretty_print(summary, loop_count=current_loop)
2946
2985
 
2947
2986
  # Add to the memory
2948
2987
  self.short_memory.add(
@@ -2974,21 +3013,30 @@ class Agent:
2974
3013
  )
2975
3014
  return
2976
3015
 
2977
- output = (
2978
- self.tool_struct.execute_function_calls_from_api_response(
3016
+ try:
3017
+ output = self.tool_struct.execute_function_calls_from_api_response(
2979
3018
  response
2980
3019
  )
2981
- )
3020
+ except Exception as e:
3021
+ # Retry the tool call
3022
+ output = self.tool_struct.execute_function_calls_from_api_response(
3023
+ response
3024
+ )
3025
+
3026
+ if output is None:
3027
+ logger.error(f"Error executing tools: {e}")
3028
+ raise e
2982
3029
 
2983
3030
  self.short_memory.add(
2984
3031
  role="Tool Executor",
2985
3032
  content=format_data_structure(output),
2986
3033
  )
2987
3034
 
2988
- self.pretty_print(
2989
- f"{format_data_structure(output)}",
2990
- loop_count,
2991
- )
3035
+ if self.print_on is True:
3036
+ self.pretty_print(
3037
+ f"Tool Executed Successfully [{time.strftime('%H:%M:%S')}]",
3038
+ loop_count,
3039
+ )
2992
3040
 
2993
3041
  # Now run the LLM again without tools - create a temporary LLM instance
2994
3042
  # instead of modifying the cached one
@@ -3012,10 +3060,11 @@ class Agent:
3012
3060
  content=tool_response,
3013
3061
  )
3014
3062
 
3015
- self.pretty_print(
3016
- f"{tool_response}",
3017
- loop_count,
3018
- )
3063
+ if self.print_on is True:
3064
+ self.pretty_print(
3065
+ tool_response,
3066
+ loop_count,
3067
+ )
3019
3068
 
3020
3069
  def list_output_types(self):
3021
3070
  return OutputType
@@ -3150,3 +3199,53 @@ class Agent:
3150
3199
  raise Exception(
3151
3200
  f"Failed to find correct answer '{correct_answer}' after {max_attempts} attempts"
3152
3201
  )
3202
+
3203
+ def tool_execution_retry(self, response: any, loop_count: int):
3204
+ """
3205
+ Execute tools with retry logic for handling failures.
3206
+
3207
+ This method attempts to execute tools based on the LLM response. If the response
3208
+ is None, it logs a warning and skips execution. If an exception occurs during
3209
+ tool execution, it logs the error with full traceback and retries the operation
3210
+ using the configured retry attempts.
3211
+
3212
+ Args:
3213
+ response (any): The response from the LLM that may contain tool calls to execute.
3214
+ Can be None if the LLM failed to provide a valid response.
3215
+ loop_count (int): The current iteration loop number for logging and debugging purposes.
3216
+
3217
+ Returns:
3218
+ None
3219
+
3220
+ Raises:
3221
+ Exception: Re-raises any exception that occurs during tool execution after
3222
+ all retry attempts have been exhausted.
3223
+
3224
+ Note:
3225
+ - Uses self.tool_retry_attempts for the maximum number of retry attempts
3226
+ - Logs detailed error information including agent name and loop count
3227
+ - Skips execution gracefully if response is None
3228
+ """
3229
+ try:
3230
+ if response is not None:
3231
+ self.execute_tools(
3232
+ response=response,
3233
+ loop_count=loop_count,
3234
+ )
3235
+ else:
3236
+ logger.warning(
3237
+ f"Agent '{self.agent_name}' received None response from LLM in loop {loop_count}. "
3238
+ f"This may indicate an issue with the model or prompt. Skipping tool execution."
3239
+ )
3240
+ except Exception as e:
3241
+ logger.error(
3242
+ f"Agent '{self.agent_name}' encountered error during tool execution in loop {loop_count}: {str(e)}. "
3243
+ f"Full traceback: {traceback.format_exc()}. "
3244
+ f"Attempting to retry tool execution with 3 attempts"
3245
+ )
3246
+ retry_function(
3247
+ self.execute_tools,
3248
+ response=response,
3249
+ loop_count=loop_count,
3250
+ max_retries=self.tool_retry_attempts,
3251
+ )
@@ -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
- *args,
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(role, content)
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