swarms 7.7.7__py3-none-any.whl → 7.7.9__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.
@@ -1,20 +1,39 @@
1
1
  import datetime
2
+ import hashlib
2
3
  import json
3
- from typing import Any, List, Optional, Union, Dict
4
+ import os
4
5
  import threading
5
- import hashlib
6
+ import uuid
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ Dict,
11
+ List,
12
+ Optional,
13
+ Union,
14
+ Literal,
15
+ )
6
16
 
7
17
  import yaml
18
+
8
19
  from swarms.structs.base_structure import BaseStructure
9
- from typing import TYPE_CHECKING
10
20
  from swarms.utils.any_to_str import any_to_str
11
21
  from swarms.utils.formatter import formatter
12
22
  from swarms.utils.litellm_tokenizer import count_tokens
13
23
 
14
24
  if TYPE_CHECKING:
15
- from swarms.structs.agent import (
16
- Agent,
17
- ) # Only imported during type checking
25
+ from swarms.structs.agent import Agent
26
+
27
+ from loguru import logger
28
+
29
+
30
+ def generate_conversation_id():
31
+ """Generate a unique conversation ID."""
32
+ return str(uuid.uuid4())
33
+
34
+
35
+ # Define available providers
36
+ providers = Literal["mem0", "in-memory"]
18
37
 
19
38
 
20
39
  class Conversation(BaseStructure):
@@ -41,10 +60,13 @@ class Conversation(BaseStructure):
41
60
  cache_enabled (bool): Flag to enable prompt caching.
42
61
  cache_stats (dict): Statistics about cache usage.
43
62
  cache_lock (threading.Lock): Lock for thread-safe cache operations.
63
+ conversations_dir (str): Directory to store cached conversations.
44
64
  """
45
65
 
46
66
  def __init__(
47
67
  self,
68
+ id: str = generate_conversation_id(),
69
+ name: str = None,
48
70
  system_prompt: Optional[str] = None,
49
71
  time_enabled: bool = False,
50
72
  autosave: bool = False,
@@ -59,29 +81,16 @@ class Conversation(BaseStructure):
59
81
  save_as_json_bool: bool = False,
60
82
  token_count: bool = True,
61
83
  cache_enabled: bool = True,
84
+ conversations_dir: Optional[str] = None,
85
+ provider: providers = "in-memory",
62
86
  *args,
63
87
  **kwargs,
64
88
  ):
65
- """
66
- Initializes the Conversation object with the provided parameters.
67
-
68
- Args:
69
- system_prompt (Optional[str]): The system prompt for the conversation.
70
- time_enabled (bool): Flag to enable time tracking for messages.
71
- autosave (bool): Flag to enable automatic saving of conversation history.
72
- save_filepath (str): File path for saving the conversation history.
73
- tokenizer (Any): Tokenizer for counting tokens in messages.
74
- context_length (int): Maximum number of tokens allowed in the conversation history.
75
- rules (str): Rules for the conversation.
76
- custom_rules_prompt (str): Custom prompt for rules.
77
- user (str): The user identifier for messages.
78
- auto_save (bool): Flag to enable auto-saving of conversation history.
79
- save_as_yaml (bool): Flag to save conversation history as YAML.
80
- save_as_json_bool (bool): Flag to save conversation history as JSON.
81
- token_count (bool): Flag to enable token counting for messages.
82
- cache_enabled (bool): Flag to enable prompt caching.
83
- """
84
89
  super().__init__()
90
+
91
+ # Initialize all attributes first
92
+ self.id = id
93
+ self.name = name or id
85
94
  self.system_prompt = system_prompt
86
95
  self.time_enabled = time_enabled
87
96
  self.autosave = autosave
@@ -97,6 +106,7 @@ class Conversation(BaseStructure):
97
106
  self.save_as_json_bool = save_as_json_bool
98
107
  self.token_count = token_count
99
108
  self.cache_enabled = cache_enabled
109
+ self.provider = provider
100
110
  self.cache_stats = {
101
111
  "hits": 0,
102
112
  "misses": 0,
@@ -104,20 +114,70 @@ class Conversation(BaseStructure):
104
114
  "total_tokens": 0,
105
115
  }
106
116
  self.cache_lock = threading.Lock()
117
+ self.conversations_dir = conversations_dir
118
+
119
+ self.setup()
120
+
121
+ def setup(self):
122
+ # Set up conversations directory
123
+ self.conversations_dir = (
124
+ self.conversations_dir
125
+ or os.path.join(
126
+ os.path.expanduser("~"), ".swarms", "conversations"
127
+ )
128
+ )
129
+ os.makedirs(self.conversations_dir, exist_ok=True)
130
+
131
+ # Try to load existing conversation if it exists
132
+ conversation_file = os.path.join(
133
+ self.conversations_dir, f"{self.name}.json"
134
+ )
135
+ if os.path.exists(conversation_file):
136
+ with open(conversation_file, "r") as f:
137
+ saved_data = json.load(f)
138
+ # Update attributes from saved data
139
+ for key, value in saved_data.get(
140
+ "metadata", {}
141
+ ).items():
142
+ if hasattr(self, key):
143
+ setattr(self, key, value)
144
+ self.conversation_history = saved_data.get(
145
+ "history", []
146
+ )
147
+ else:
148
+ # If system prompt is not None, add it to the conversation history
149
+ if self.system_prompt is not None:
150
+ self.add("System", self.system_prompt)
107
151
 
108
- # If system prompt is not None, add it to the conversation history
109
- if self.system_prompt is not None:
110
- self.add("System", self.system_prompt)
152
+ if self.rules is not None:
153
+ self.add(self.user or "User", self.rules)
111
154
 
112
- if self.rules is not None:
113
- self.add("User", rules)
155
+ if self.custom_rules_prompt is not None:
156
+ self.add(
157
+ self.user or "User", self.custom_rules_prompt
158
+ )
114
159
 
115
- if custom_rules_prompt is not None:
116
- self.add(user or "User", custom_rules_prompt)
160
+ # If tokenizer then truncate
161
+ if self.tokenizer is not None:
162
+ self.truncate_memory_with_tokenizer()
117
163
 
118
- # If tokenizer then truncate
119
- if tokenizer is not None:
120
- self.truncate_memory_with_tokenizer()
164
+ def mem0_provider(self):
165
+ try:
166
+ from mem0 import AsyncMemory
167
+ except ImportError:
168
+ logger.warning(
169
+ "mem0ai is not installed. Please install it to use the Conversation class."
170
+ )
171
+ return None
172
+
173
+ try:
174
+ memory = AsyncMemory()
175
+ return memory
176
+ except Exception as e:
177
+ logger.error(
178
+ f"Failed to initialize AsyncMemory: {str(e)}"
179
+ )
180
+ return None
121
181
 
122
182
  def _generate_cache_key(
123
183
  self, content: Union[str, dict, list]
@@ -174,7 +234,46 @@ class Conversation(BaseStructure):
174
234
  self.cache_stats["cached_tokens"] += token_count
175
235
  self.cache_stats["total_tokens"] += token_count
176
236
 
177
- def add(
237
+ def _save_to_cache(self):
238
+ """Save the current conversation state to the cache directory."""
239
+ if not self.conversations_dir:
240
+ return
241
+
242
+ conversation_file = os.path.join(
243
+ self.conversations_dir, f"{self.name}.json"
244
+ )
245
+
246
+ # Prepare metadata
247
+ metadata = {
248
+ "id": self.id,
249
+ "name": self.name,
250
+ "system_prompt": self.system_prompt,
251
+ "time_enabled": self.time_enabled,
252
+ "autosave": self.autosave,
253
+ "save_filepath": self.save_filepath,
254
+ "context_length": self.context_length,
255
+ "rules": self.rules,
256
+ "custom_rules_prompt": self.custom_rules_prompt,
257
+ "user": self.user,
258
+ "auto_save": self.auto_save,
259
+ "save_as_yaml": self.save_as_yaml,
260
+ "save_as_json_bool": self.save_as_json_bool,
261
+ "token_count": self.token_count,
262
+ "cache_enabled": self.cache_enabled,
263
+ }
264
+
265
+ # Prepare data to save
266
+ save_data = {
267
+ "metadata": metadata,
268
+ "history": self.conversation_history,
269
+ "cache_stats": self.cache_stats,
270
+ }
271
+
272
+ # Save to file
273
+ with open(conversation_file, "w") as f:
274
+ json.dump(save_data, f, indent=4)
275
+
276
+ def add_in_memory(
178
277
  self,
179
278
  role: str,
180
279
  content: Union[str, dict, list],
@@ -210,7 +309,7 @@ class Conversation(BaseStructure):
210
309
  else:
211
310
  message["cached"] = False
212
311
 
213
- # Add the message to history immediately without waiting for token count
312
+ # Add message to appropriate backend
214
313
  self.conversation_history.append(message)
215
314
 
216
315
  if self.token_count is True and not message.get(
@@ -218,6 +317,41 @@ class Conversation(BaseStructure):
218
317
  ):
219
318
  self._count_tokens(content, message)
220
319
 
320
+ # Save to cache after adding message
321
+ self._save_to_cache()
322
+
323
+ def add_mem0(
324
+ self,
325
+ role: str,
326
+ content: Union[str, dict, list],
327
+ metadata: Optional[dict] = None,
328
+ ):
329
+ """Add a message to the conversation history using the Mem0 provider."""
330
+ if self.provider == "mem0":
331
+ memory = self.mem0_provider()
332
+ memory.add(
333
+ messages=content,
334
+ agent_id=role,
335
+ run_id=self.id,
336
+ metadata=metadata,
337
+ )
338
+
339
+ def add(
340
+ self,
341
+ role: str,
342
+ content: Union[str, dict, list],
343
+ metadata: Optional[dict] = None,
344
+ ):
345
+ """Add a message to the conversation history."""
346
+ if self.provider == "in-memory":
347
+ self.add_in_memory(role, content)
348
+ elif self.provider == "mem0":
349
+ self.add_mem0(
350
+ role=role, content=content, metadata=metadata
351
+ )
352
+ else:
353
+ raise ValueError(f"Invalid provider: {self.provider}")
354
+
221
355
  def add_multiple_messages(
222
356
  self, roles: List[str], contents: List[Union[str, dict, list]]
223
357
  ):
@@ -256,6 +390,7 @@ class Conversation(BaseStructure):
256
390
  index (str): Index of the message to delete.
257
391
  """
258
392
  self.conversation_history.pop(index)
393
+ self._save_to_cache()
259
394
 
260
395
  def update(self, index: str, role, content):
261
396
  """Update a message in the conversation history.
@@ -269,6 +404,7 @@ class Conversation(BaseStructure):
269
404
  "role": role,
270
405
  "content": content,
271
406
  }
407
+ self._save_to_cache()
272
408
 
273
409
  def query(self, index: str):
274
410
  """Query a message in the conversation history.
@@ -450,6 +586,7 @@ class Conversation(BaseStructure):
450
586
  def clear(self):
451
587
  """Clear the conversation history."""
452
588
  self.conversation_history = []
589
+ self._save_to_cache()
453
590
 
454
591
  def to_json(self):
455
592
  """Convert the conversation history to a JSON string.
@@ -508,7 +645,13 @@ class Conversation(BaseStructure):
508
645
  Returns:
509
646
  str: The last message formatted as 'role: content'.
510
647
  """
511
- return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}"
648
+ if self.provider == "mem0":
649
+ memory = self.mem0_provider()
650
+ return memory.get_all(run_id=self.id)
651
+ elif self.provider == "in-memory":
652
+ return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}"
653
+ else:
654
+ raise ValueError(f"Invalid provider: {self.provider}")
512
655
 
513
656
  def return_messages_as_list(self):
514
657
  """Return the conversation messages as a list of formatted strings.
@@ -629,6 +772,53 @@ class Conversation(BaseStructure):
629
772
  ),
630
773
  }
631
774
 
775
+ @classmethod
776
+ def load_conversation(
777
+ cls, name: str, conversations_dir: Optional[str] = None
778
+ ) -> "Conversation":
779
+ """Load a conversation from the cache by name.
780
+
781
+ Args:
782
+ name (str): Name of the conversation to load
783
+ conversations_dir (Optional[str]): Directory containing cached conversations
784
+
785
+ Returns:
786
+ Conversation: The loaded conversation object
787
+ """
788
+ return cls(name=name, conversations_dir=conversations_dir)
789
+
790
+ @classmethod
791
+ def list_cached_conversations(
792
+ cls, conversations_dir: Optional[str] = None
793
+ ) -> List[str]:
794
+ """List all cached conversations.
795
+
796
+ Args:
797
+ conversations_dir (Optional[str]): Directory containing cached conversations
798
+
799
+ Returns:
800
+ List[str]: List of conversation names (without .json extension)
801
+ """
802
+ if conversations_dir is None:
803
+ conversations_dir = os.path.join(
804
+ os.path.expanduser("~"), ".swarms", "conversations"
805
+ )
806
+
807
+ if not os.path.exists(conversations_dir):
808
+ return []
809
+
810
+ conversations = []
811
+ for file in os.listdir(conversations_dir):
812
+ if file.endswith(".json"):
813
+ conversations.append(
814
+ file[:-5]
815
+ ) # Remove .json extension
816
+ return conversations
817
+
818
+ def clear_memory(self):
819
+ """Clear the memory of the conversation."""
820
+ self.conversation_history = []
821
+
632
822
 
633
823
  # # Example usage
634
824
  # # conversation = Conversation()