swarms 7.8.4__py3-none-any.whl → 7.8.8__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 (58) hide show
  1. swarms/agents/ape_agent.py +5 -22
  2. swarms/agents/consistency_agent.py +1 -1
  3. swarms/agents/i_agent.py +1 -1
  4. swarms/agents/reasoning_agents.py +99 -3
  5. swarms/agents/reasoning_duo.py +1 -1
  6. swarms/cli/main.py +1 -1
  7. swarms/communication/__init__.py +1 -0
  8. swarms/communication/duckdb_wrap.py +32 -2
  9. swarms/communication/pulsar_struct.py +45 -19
  10. swarms/communication/redis_wrap.py +56 -11
  11. swarms/communication/supabase_wrap.py +1659 -0
  12. swarms/prompts/prompt.py +0 -3
  13. swarms/schemas/agent_completion_response.py +71 -0
  14. swarms/schemas/agent_rag_schema.py +7 -0
  15. swarms/schemas/conversation_schema.py +9 -0
  16. swarms/schemas/llm_agent_schema.py +99 -81
  17. swarms/schemas/swarms_api_schemas.py +164 -0
  18. swarms/structs/__init__.py +14 -11
  19. swarms/structs/agent.py +219 -199
  20. swarms/structs/agent_rag_handler.py +685 -0
  21. swarms/structs/base_swarm.py +2 -1
  22. swarms/structs/conversation.py +608 -87
  23. swarms/structs/csv_to_agent.py +153 -100
  24. swarms/structs/deep_research_swarm.py +197 -193
  25. swarms/structs/dynamic_conversational_swarm.py +18 -7
  26. swarms/structs/hiearchical_swarm.py +1 -1
  27. swarms/structs/hybrid_hiearchical_peer_swarm.py +2 -18
  28. swarms/structs/image_batch_processor.py +261 -0
  29. swarms/structs/interactive_groupchat.py +356 -0
  30. swarms/structs/ma_blocks.py +75 -0
  31. swarms/structs/majority_voting.py +1 -1
  32. swarms/structs/mixture_of_agents.py +1 -1
  33. swarms/structs/multi_agent_router.py +3 -2
  34. swarms/structs/rearrange.py +3 -3
  35. swarms/structs/sequential_workflow.py +3 -3
  36. swarms/structs/swarm_matcher.py +500 -411
  37. swarms/structs/swarm_router.py +15 -97
  38. swarms/structs/swarming_architectures.py +1 -1
  39. swarms/tools/mcp_client_call.py +3 -0
  40. swarms/utils/__init__.py +10 -2
  41. swarms/utils/check_all_model_max_tokens.py +43 -0
  42. swarms/utils/generate_keys.py +0 -27
  43. swarms/utils/history_output_formatter.py +5 -20
  44. swarms/utils/litellm_wrapper.py +208 -60
  45. swarms/utils/output_types.py +24 -0
  46. swarms/utils/vllm_wrapper.py +5 -6
  47. swarms/utils/xml_utils.py +37 -2
  48. {swarms-7.8.4.dist-info → swarms-7.8.8.dist-info}/METADATA +31 -55
  49. {swarms-7.8.4.dist-info → swarms-7.8.8.dist-info}/RECORD +53 -48
  50. swarms/structs/multi_agent_collab.py +0 -242
  51. swarms/structs/output_types.py +0 -6
  52. swarms/utils/markdown_message.py +0 -21
  53. swarms/utils/visualizer.py +0 -510
  54. swarms/utils/wrapper_clusterop.py +0 -127
  55. /swarms/{tools → schemas}/tool_schema_base_model.py +0 -0
  56. {swarms-7.8.4.dist-info → swarms-7.8.8.dist-info}/LICENSE +0 -0
  57. {swarms-7.8.4.dist-info → swarms-7.8.8.dist-info}/WHEEL +0 -0
  58. {swarms-7.8.4.dist-info → swarms-7.8.8.dist-info}/entry_points.txt +0 -0
@@ -12,6 +12,7 @@ from typing import (
12
12
  Optional,
13
13
  Union,
14
14
  Literal,
15
+ Any,
15
16
  )
16
17
 
17
18
  import yaml
@@ -52,7 +53,95 @@ def get_conversation_dir():
52
53
 
53
54
 
54
55
  # Define available providers
55
- providers = Literal["mem0", "in-memory"]
56
+ providers = Literal[
57
+ "mem0",
58
+ "in-memory",
59
+ "supabase",
60
+ "redis",
61
+ "sqlite",
62
+ "duckdb",
63
+ "pulsar",
64
+ ]
65
+
66
+
67
+ def _create_backend_conversation(backend: str, **kwargs):
68
+ """
69
+ Create a backend conversation instance based on the specified backend type.
70
+
71
+ This function uses lazy loading to import backend dependencies only when needed.
72
+ Each backend class handles its own dependency management and error messages.
73
+
74
+ Args:
75
+ backend (str): The backend type to create
76
+ **kwargs: Arguments to pass to the backend constructor
77
+
78
+ Returns:
79
+ Backend conversation instance
80
+
81
+ Raises:
82
+ ImportError: If required packages for the backend are not installed (raised by lazy loading)
83
+ ValueError: If backend is not supported
84
+ """
85
+ try:
86
+ if backend == "supabase":
87
+ from swarms.communication.supabase_wrap import (
88
+ SupabaseConversation,
89
+ )
90
+
91
+ return SupabaseConversation(**kwargs)
92
+ elif backend == "redis":
93
+ from swarms.communication.redis_wrap import (
94
+ RedisConversation,
95
+ )
96
+
97
+ return RedisConversation(**kwargs)
98
+ elif backend == "sqlite":
99
+ from swarms.communication.sqlite_wrap import (
100
+ SQLiteConversation,
101
+ )
102
+
103
+ return SQLiteConversation(**kwargs)
104
+ elif backend == "duckdb":
105
+ from swarms.communication.duckdb_wrap import (
106
+ DuckDBConversation,
107
+ )
108
+
109
+ return DuckDBConversation(**kwargs)
110
+ elif backend == "pulsar":
111
+ from swarms.communication.pulsar_struct import (
112
+ PulsarConversation,
113
+ )
114
+
115
+ return PulsarConversation(**kwargs)
116
+ else:
117
+ raise ValueError(
118
+ f"Unsupported backend: {backend}. "
119
+ f"Available backends: supabase, redis, sqlite, duckdb, pulsar"
120
+ )
121
+ except ImportError as e:
122
+ # Provide helpful error messages for missing dependencies
123
+ backend_deps = {
124
+ "supabase": "pip install supabase",
125
+ "redis": "pip install redis",
126
+ "sqlite": "Built-in to Python - check your installation",
127
+ "duckdb": "pip install duckdb",
128
+ "pulsar": "pip install pulsar-client",
129
+ }
130
+
131
+ install_cmd = backend_deps.get(
132
+ backend, f"Check documentation for {backend}"
133
+ )
134
+ logger.error(
135
+ f"Failed to initialize {backend} backend. "
136
+ f"Missing dependencies. Install with: {install_cmd}"
137
+ )
138
+ raise ImportError(
139
+ f"Backend '{backend}' dependencies not available. "
140
+ f"Install with: {install_cmd}. Original error: {e}"
141
+ )
142
+ except Exception as e:
143
+ logger.error(f"Failed to create {backend} backend: {e}")
144
+ raise
56
145
 
57
146
 
58
147
  class Conversation(BaseStructure):
@@ -61,6 +150,19 @@ class Conversation(BaseStructure):
61
150
  and retrieval of messages, as well as saving and loading the conversation
62
151
  history in various formats.
63
152
 
153
+ The Conversation class now supports multiple backends for persistent storage:
154
+ - "in-memory": Default memory-based storage (no persistence)
155
+ - "mem0": Memory-based storage with mem0 integration (requires: pip install mem0ai)
156
+ - "supabase": PostgreSQL-based storage using Supabase (requires: pip install supabase)
157
+ - "redis": Redis-based storage (requires: pip install redis)
158
+ - "sqlite": SQLite-based storage (built-in to Python)
159
+ - "duckdb": DuckDB-based storage (requires: pip install duckdb)
160
+ - "pulsar": Apache Pulsar messaging backend (requires: pip install pulsar-client)
161
+
162
+ All backends use lazy loading - database dependencies are only imported when the
163
+ specific backend is instantiated. Each backend class provides its own detailed
164
+ error messages if required packages are not installed.
165
+
64
166
  Attributes:
65
167
  system_prompt (Optional[str]): The system prompt for the conversation.
66
168
  time_enabled (bool): Flag to enable time tracking for messages.
@@ -96,13 +198,50 @@ class Conversation(BaseStructure):
96
198
  save_as_yaml: bool = False,
97
199
  save_as_json_bool: bool = False,
98
200
  token_count: bool = True,
201
+ message_id_on: bool = False,
99
202
  provider: providers = "in-memory",
203
+ backend: Optional[str] = None,
204
+ # Backend-specific parameters
205
+ supabase_url: Optional[str] = None,
206
+ supabase_key: Optional[str] = None,
207
+ redis_host: str = "localhost",
208
+ redis_port: int = 6379,
209
+ redis_db: int = 0,
210
+ redis_password: Optional[str] = None,
211
+ db_path: Optional[str] = None,
212
+ table_name: str = "conversations",
213
+ # Additional backend parameters
214
+ use_embedded_redis: bool = True,
215
+ persist_redis: bool = True,
216
+ auto_persist: bool = True,
217
+ redis_data_dir: Optional[str] = None,
100
218
  conversations_dir: Optional[str] = None,
101
219
  *args,
102
220
  **kwargs,
103
221
  ):
104
222
  super().__init__()
105
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
+
106
245
  # Initialize all attributes first
107
246
  self.id = id
108
247
  self.name = name or id
@@ -111,6 +250,7 @@ class Conversation(BaseStructure):
111
250
  self.autosave = autosave
112
251
  self.save_enabled = save_enabled
113
252
  self.conversations_dir = conversations_dir
253
+ self.message_id_on = message_id_on
114
254
 
115
255
  # Handle save filepath
116
256
  if save_enabled and save_filepath:
@@ -132,37 +272,169 @@ class Conversation(BaseStructure):
132
272
  self.save_as_yaml = save_as_yaml
133
273
  self.save_as_json_bool = save_as_json_bool
134
274
  self.token_count = token_count
135
- self.provider = provider
136
-
137
- # Create conversation directory if saving is enabled
138
- if self.save_enabled and self.conversations_dir:
139
- os.makedirs(self.conversations_dir, exist_ok=True)
140
-
141
- # Try to load existing conversation or initialize new one
142
- self.setup()
275
+ self.provider = provider # Keep for backwards compatibility
276
+ self.conversations_dir = conversations_dir
143
277
 
144
- def setup(self):
145
- """Set up the conversation by either loading existing data or initializing new."""
146
- if self.load_filepath and os.path.exists(self.load_filepath):
278
+ # Initialize backend if using persistent storage
279
+ if self.backend in [
280
+ "supabase",
281
+ "redis",
282
+ "sqlite",
283
+ "duckdb",
284
+ "pulsar",
285
+ ]:
147
286
  try:
148
- self.load_from_json(self.load_filepath)
149
- logger.info(
150
- f"Loaded existing conversation from {self.load_filepath}"
287
+ self._initialize_backend(
288
+ supabase_url=supabase_url,
289
+ supabase_key=supabase_key,
290
+ redis_host=redis_host,
291
+ redis_port=redis_port,
292
+ redis_db=redis_db,
293
+ redis_password=redis_password,
294
+ db_path=db_path,
295
+ table_name=table_name,
296
+ use_embedded_redis=use_embedded_redis,
297
+ persist_redis=persist_redis,
298
+ auto_persist=auto_persist,
299
+ redis_data_dir=redis_data_dir,
300
+ **kwargs,
151
301
  )
152
302
  except Exception as e:
153
- logger.error(f"Failed to load conversation: {str(e)}")
154
- self._initialize_new_conversation()
155
- elif self.save_filepath and os.path.exists(
156
- self.save_filepath
157
- ):
158
- try:
159
- self.load_from_json(self.save_filepath)
160
- logger.info(
161
- f"Loaded existing conversation from {self.save_filepath}"
303
+ logger.warning(
304
+ f"Failed to initialize {self.backend} backend: {e}. "
305
+ f"Falling back to in-memory storage."
306
+ )
307
+ self.backend = "in-memory"
308
+ self.backend_instance = None
309
+ self.setup()
310
+ else:
311
+ # For in-memory and mem0 backends, use the original setup
312
+ self.setup()
313
+
314
+ def _initialize_backend(self, **kwargs):
315
+ """
316
+ Initialize the persistent storage backend.
317
+
318
+ Args:
319
+ **kwargs: Backend-specific configuration parameters
320
+ """
321
+ # Prepare common backend arguments
322
+ backend_kwargs = {
323
+ "system_prompt": self.system_prompt,
324
+ "time_enabled": self.time_enabled,
325
+ "autosave": self.autosave,
326
+ "save_filepath": self.save_filepath,
327
+ "tokenizer": self.tokenizer,
328
+ "context_length": self.context_length,
329
+ "rules": self.rules,
330
+ "custom_rules_prompt": self.custom_rules_prompt,
331
+ "user": self.user,
332
+ "save_as_yaml": self.save_as_yaml,
333
+ "save_as_json_bool": self.save_as_json_bool,
334
+ "token_count": self.token_count,
335
+ }
336
+
337
+ # Add backend-specific parameters
338
+ if self.backend == "supabase":
339
+ supabase_url = kwargs.get("supabase_url") or os.getenv(
340
+ "SUPABASE_URL"
341
+ )
342
+ supabase_key = kwargs.get("supabase_key") or os.getenv(
343
+ "SUPABASE_ANON_KEY"
344
+ )
345
+
346
+ if not supabase_url or not supabase_key:
347
+ raise ValueError(
348
+ "Supabase backend requires 'supabase_url' and 'supabase_key' parameters "
349
+ "or SUPABASE_URL and SUPABASE_ANON_KEY environment variables"
350
+ )
351
+ backend_kwargs.update(
352
+ {
353
+ "supabase_url": supabase_url,
354
+ "supabase_key": supabase_key,
355
+ "table_name": kwargs.get(
356
+ "table_name", "conversations"
357
+ ),
358
+ }
359
+ )
360
+
361
+ elif self.backend == "redis":
362
+ backend_kwargs.update(
363
+ {
364
+ "redis_host": kwargs.get(
365
+ "redis_host", "localhost"
366
+ ),
367
+ "redis_port": kwargs.get("redis_port", 6379),
368
+ "redis_db": kwargs.get("redis_db", 0),
369
+ "redis_password": kwargs.get("redis_password"),
370
+ "use_embedded_redis": kwargs.get(
371
+ "use_embedded_redis", True
372
+ ),
373
+ "persist_redis": kwargs.get(
374
+ "persist_redis", True
375
+ ),
376
+ "auto_persist": kwargs.get("auto_persist", True),
377
+ "redis_data_dir": kwargs.get("redis_data_dir"),
378
+ "conversation_id": self.id,
379
+ "name": self.name,
380
+ }
381
+ )
382
+
383
+ elif self.backend in ["sqlite", "duckdb"]:
384
+ db_path = kwargs.get("db_path")
385
+ if db_path:
386
+ backend_kwargs["db_path"] = db_path
387
+
388
+ elif self.backend == "pulsar":
389
+ # Add pulsar-specific parameters
390
+ backend_kwargs.update(
391
+ {
392
+ "pulsar_url": kwargs.get(
393
+ "pulsar_url", "pulsar://localhost:6650"
394
+ ),
395
+ "topic": kwargs.get(
396
+ "topic", f"conversation-{self.id}"
397
+ ),
398
+ }
399
+ )
400
+
401
+ # Create the backend instance
402
+ logger.info(f"Initializing {self.backend} backend...")
403
+ self.backend_instance = _create_backend_conversation(
404
+ self.backend, **backend_kwargs
405
+ )
406
+
407
+ # Log successful initialization
408
+ logger.info(
409
+ f"Successfully initialized {self.backend} backend for conversation '{self.name}'"
410
+ )
411
+
412
+ def setup(self):
413
+ # Set up conversations directory
414
+ self.conversations_dir = (
415
+ self.conversations_dir
416
+ or os.path.join(
417
+ os.path.expanduser("~"), ".swarms", "conversations"
418
+ )
419
+ )
420
+ os.makedirs(self.conversations_dir, exist_ok=True)
421
+
422
+ # Try to load existing conversation if it exists
423
+ conversation_file = os.path.join(
424
+ self.conversations_dir, f"{self.name}.json"
425
+ )
426
+ if os.path.exists(conversation_file):
427
+ with open(conversation_file, "r") as f:
428
+ saved_data = json.load(f)
429
+ # Update attributes from saved data
430
+ for key, value in saved_data.get(
431
+ "metadata", {}
432
+ ).items():
433
+ if hasattr(self, key):
434
+ setattr(self, key, value)
435
+ self.conversation_history = saved_data.get(
436
+ "history", []
162
437
  )
163
- except Exception as e:
164
- logger.error(f"Failed to load conversation: {str(e)}")
165
- self._initialize_new_conversation()
166
438
  else:
167
439
  self._initialize_new_conversation()
168
440
 
@@ -211,7 +483,7 @@ class Conversation(BaseStructure):
211
483
  def add_in_memory(
212
484
  self,
213
485
  role: str,
214
- content: Union[str, dict, list],
486
+ content: Union[str, dict, list, Any],
215
487
  *args,
216
488
  **kwargs,
217
489
  ):
@@ -224,19 +496,14 @@ class Conversation(BaseStructure):
224
496
  # Base message with role and timestamp
225
497
  message = {
226
498
  "role": role,
227
- "timestamp": datetime.datetime.now().isoformat(),
228
- "message_id": str(uuid.uuid4()),
499
+ "content": content,
229
500
  }
230
501
 
231
- # Handle different content types
232
- if isinstance(content, dict) or isinstance(content, list):
233
- message["content"] = content
234
- elif self.time_enabled:
235
- message["content"] = (
236
- f"Time: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} \n {content}"
237
- )
238
- else:
239
- message["content"] = content
502
+ if self.time_enabled:
503
+ message["timestamp"] = datetime.datetime.now().isoformat()
504
+
505
+ if self.message_id_on:
506
+ message["message_id"] = str(uuid.uuid4())
240
507
 
241
508
  # Add message to conversation history
242
509
  self.conversation_history.append(message)
@@ -262,12 +529,19 @@ class Conversation(BaseStructure):
262
529
  """Add a message to the conversation history using the Mem0 provider."""
263
530
  if self.provider == "mem0":
264
531
  memory = self.mem0_provider()
265
- memory.add(
266
- messages=content,
267
- agent_id=role,
268
- run_id=self.id,
269
- metadata=metadata,
270
- )
532
+ if memory is not None:
533
+ memory.add(
534
+ messages=content,
535
+ agent_id=role,
536
+ run_id=self.id,
537
+ metadata=metadata,
538
+ )
539
+ else:
540
+ # Fallback to in-memory if mem0 is not available
541
+ logger.warning(
542
+ "Mem0 provider not available, falling back to in-memory storage"
543
+ )
544
+ self.add_in_memory(role, content)
271
545
 
272
546
  def add(
273
547
  self,
@@ -276,10 +550,21 @@ class Conversation(BaseStructure):
276
550
  metadata: Optional[dict] = None,
277
551
  ):
278
552
  """Add a message to the conversation history."""
279
- if self.provider == "in-memory":
280
- self.add_in_memory(role, content)
553
+ # If using a persistent backend, delegate to it
554
+ if self.backend_instance:
555
+ try:
556
+ return self.backend_instance.add(
557
+ role=role, content=content, metadata=metadata
558
+ )
559
+ except Exception as e:
560
+ logger.error(
561
+ f"Backend add failed: {e}. Falling back to in-memory."
562
+ )
563
+ return self.add_in_memory(role, content)
564
+ elif self.provider == "in-memory":
565
+ return self.add_in_memory(role, content)
281
566
  elif self.provider == "mem0":
282
- self.add_mem0(
567
+ return self.add_mem0(
283
568
  role=role, content=content, metadata=metadata
284
569
  )
285
570
  else:
@@ -337,50 +622,77 @@ class Conversation(BaseStructure):
337
622
  concurrent.futures.wait(futures)
338
623
 
339
624
  def delete(self, index: str):
340
- """Delete a message from the conversation history.
341
-
342
- Args:
343
- index (str): Index of the message to delete.
344
- """
345
- self.conversation_history.pop(index)
625
+ """Delete a message from the conversation history."""
626
+ if self.backend_instance:
627
+ try:
628
+ return self.backend_instance.delete(index)
629
+ except Exception as e:
630
+ logger.error(f"Backend delete failed: {e}")
631
+ raise
632
+ self.conversation_history.pop(int(index))
346
633
 
347
634
  def update(self, index: str, role, content):
348
635
  """Update a message in the conversation history.
349
636
 
350
637
  Args:
351
- index (str): Index of the message to update.
352
- role (str): Role of the speaker.
353
- content (Union[str, dict]): New content of the message.
638
+ index (int): The index of the message to update.
639
+ role (str): The role of the speaker.
640
+ content: The new content of the message.
354
641
  """
355
- self.conversation_history[index] = {
356
- "role": role,
357
- "content": content,
358
- }
642
+ if self.backend_instance:
643
+ try:
644
+ return self.backend_instance.update(
645
+ index, role, content
646
+ )
647
+ except Exception as e:
648
+ logger.error(f"Backend update failed: {e}")
649
+ raise
650
+ if 0 <= int(index) < len(self.conversation_history):
651
+ self.conversation_history[int(index)]["role"] = role
652
+ self.conversation_history[int(index)]["content"] = content
653
+ else:
654
+ logger.warning(f"Invalid index: {index}")
359
655
 
360
656
  def query(self, index: str):
361
- """Query a message in the conversation history.
657
+ """Query a message from the conversation history.
362
658
 
363
659
  Args:
364
- index (str): Index of the message to query.
660
+ index (int): The index of the message to query.
365
661
 
366
662
  Returns:
367
- dict: The message with its role and content.
663
+ dict: The message at the specified index.
368
664
  """
369
- return self.conversation_history[index]
665
+ if self.backend_instance:
666
+ try:
667
+ return self.backend_instance.query(index)
668
+ except Exception as e:
669
+ logger.error(f"Backend query failed: {e}")
670
+ raise
671
+ if 0 <= int(index) < len(self.conversation_history):
672
+ return self.conversation_history[int(index)]
673
+ return None
370
674
 
371
675
  def search(self, keyword: str):
372
- """Search for a message in the conversation history.
676
+ """Search for messages containing a keyword.
373
677
 
374
678
  Args:
375
- keyword (str): Keyword to search for.
679
+ keyword (str): The keyword to search for.
376
680
 
377
681
  Returns:
378
- list: List of messages containing the keyword.
682
+ list: A list of messages containing the keyword.
379
683
  """
684
+ if self.backend_instance:
685
+ try:
686
+ return self.backend_instance.search(keyword)
687
+ except Exception as e:
688
+ logger.error(f"Backend search failed: {e}")
689
+ # Fallback to in-memory search
690
+ pass
691
+
380
692
  return [
381
- msg
382
- for msg in self.conversation_history
383
- if keyword in msg["content"]
693
+ message
694
+ for message in self.conversation_history
695
+ if keyword in str(message["content"])
384
696
  ]
385
697
 
386
698
  def display_conversation(self, detailed: bool = False):
@@ -389,9 +701,20 @@ class Conversation(BaseStructure):
389
701
  Args:
390
702
  detailed (bool, optional): Flag to display detailed information. Defaults to False.
391
703
  """
704
+ if self.backend_instance:
705
+ try:
706
+ return self.backend_instance.display_conversation(
707
+ detailed
708
+ )
709
+ except Exception as e:
710
+ logger.error(f"Backend display failed: {e}")
711
+ # Fallback to in-memory display
712
+ pass
713
+
714
+ # In-memory display implementation with proper formatting
392
715
  for message in self.conversation_history:
393
- content = message["content"]
394
- role = message["role"]
716
+ content = message.get("content", "")
717
+ role = message.get("role", "Unknown")
395
718
 
396
719
  # Format the message content
397
720
  if isinstance(content, (dict, list)):
@@ -417,9 +740,28 @@ class Conversation(BaseStructure):
417
740
  Args:
418
741
  filename (str): Filename to export to.
419
742
  """
420
- with open(filename, "w") as f:
421
- for message in self.conversation_history:
422
- f.write(f"{message['role']}: {message['content']}\n")
743
+
744
+ if self.backend_instance:
745
+ try:
746
+ return self.backend_instance.export_conversation(
747
+ filename, *args, **kwargs
748
+ )
749
+ except Exception as e:
750
+ logger.error(f"Backend export failed: {e}")
751
+ # Fallback to in-memory export
752
+ pass
753
+
754
+ # In-memory export implementation
755
+ # If the filename ends with .json, use save_as_json
756
+ if filename.endswith(".json"):
757
+ self.save_as_json(filename)
758
+ else:
759
+ # Simple text export for non-JSON files
760
+ with open(filename, "w", encoding="utf-8") as f:
761
+ for message in self.conversation_history:
762
+ f.write(
763
+ f"{message['role']}: {message['content']}\n"
764
+ )
423
765
 
424
766
  def import_conversation(self, filename: str):
425
767
  """Import a conversation history from a file.
@@ -427,10 +769,16 @@ class Conversation(BaseStructure):
427
769
  Args:
428
770
  filename (str): Filename to import from.
429
771
  """
430
- with open(filename) as f:
431
- for line in f:
432
- role, content = line.split(": ", 1)
433
- self.add(role, content.strip())
772
+ if self.backend_instance:
773
+ try:
774
+ return self.backend_instance.import_conversation(
775
+ filename
776
+ )
777
+ except Exception as e:
778
+ logger.error(f"Backend import failed: {e}")
779
+ # Fallback to in-memory import
780
+ pass
781
+ self.load_from_json(filename)
434
782
 
435
783
  def count_messages_by_role(self):
436
784
  """Count the number of messages by role.
@@ -438,14 +786,33 @@ class Conversation(BaseStructure):
438
786
  Returns:
439
787
  dict: A dictionary with counts of messages by role.
440
788
  """
789
+ # Check backend instance first
790
+ if self.backend_instance:
791
+ try:
792
+ return self.backend_instance.count_messages_by_role()
793
+ except Exception as e:
794
+ logger.error(
795
+ f"Backend count_messages_by_role failed: {e}"
796
+ )
797
+ # Fallback to local implementation below
798
+ pass
799
+ # Initialize counts with expected roles
441
800
  counts = {
442
801
  "system": 0,
443
802
  "user": 0,
444
803
  "assistant": 0,
445
804
  "function": 0,
446
805
  }
806
+
807
+ # Count messages by role
447
808
  for message in self.conversation_history:
448
- counts[message["role"]] += 1
809
+ role = message["role"]
810
+ if role in counts:
811
+ counts[role] += 1
812
+ else:
813
+ # Handle unexpected roles dynamically
814
+ counts[role] = counts.get(role, 0) + 1
815
+
449
816
  return counts
450
817
 
451
818
  def return_history_as_string(self):
@@ -454,6 +821,18 @@ class Conversation(BaseStructure):
454
821
  Returns:
455
822
  str: The conversation history formatted as a string.
456
823
  """
824
+ if self.backend_instance:
825
+ try:
826
+ return (
827
+ self.backend_instance.return_history_as_string()
828
+ )
829
+ except Exception as e:
830
+ logger.error(
831
+ f"Backend return_history_as_string failed: {e}"
832
+ )
833
+ # Fallback to in-memory implementation
834
+ pass
835
+
457
836
  formatted_messages = []
458
837
  for message in self.conversation_history:
459
838
  formatted_messages.append(
@@ -468,6 +847,13 @@ class Conversation(BaseStructure):
468
847
  Returns:
469
848
  str: The conversation history.
470
849
  """
850
+ if self.backend_instance:
851
+ try:
852
+ return self.backend_instance.get_str()
853
+ except Exception as e:
854
+ logger.error(f"Backend get_str failed: {e}")
855
+ # Fallback to in-memory implementation
856
+ pass
471
857
  return self.return_history_as_string()
472
858
 
473
859
  def save_as_json(self, filename: str = None):
@@ -476,6 +862,14 @@ class Conversation(BaseStructure):
476
862
  Args:
477
863
  filename (str): Filename to save the conversation history.
478
864
  """
865
+ # Check backend instance first
866
+ if self.backend_instance:
867
+ try:
868
+ return self.backend_instance.save_as_json(filename)
869
+ except Exception as e:
870
+ logger.error(f"Backend save_as_json failed: {e}")
871
+ # Fallback to local save implementation below
872
+
479
873
  # Don't save if saving is disabled
480
874
  if not self.save_enabled:
481
875
  return
@@ -605,6 +999,13 @@ class Conversation(BaseStructure):
605
999
 
606
1000
  def clear(self):
607
1001
  """Clear the conversation history."""
1002
+ if self.backend_instance:
1003
+ try:
1004
+ return self.backend_instance.clear()
1005
+ except Exception as e:
1006
+ logger.error(f"Backend clear failed: {e}")
1007
+ # Fallback to in-memory clear
1008
+ pass
608
1009
  self.conversation_history = []
609
1010
 
610
1011
  def to_json(self):
@@ -613,6 +1014,13 @@ class Conversation(BaseStructure):
613
1014
  Returns:
614
1015
  str: The conversation history as a JSON string.
615
1016
  """
1017
+ if self.backend_instance:
1018
+ try:
1019
+ return self.backend_instance.to_json()
1020
+ except Exception as e:
1021
+ logger.error(f"Backend to_json failed: {e}")
1022
+ # Fallback to in-memory implementation
1023
+ pass
616
1024
  return json.dumps(self.conversation_history)
617
1025
 
618
1026
  def to_dict(self):
@@ -621,6 +1029,13 @@ class Conversation(BaseStructure):
621
1029
  Returns:
622
1030
  list: The conversation history as a list of dictionaries.
623
1031
  """
1032
+ if self.backend_instance:
1033
+ try:
1034
+ return self.backend_instance.to_dict()
1035
+ except Exception as e:
1036
+ logger.error(f"Backend to_dict failed: {e}")
1037
+ # Fallback to in-memory implementation
1038
+ pass
624
1039
  return self.conversation_history
625
1040
 
626
1041
  def to_yaml(self):
@@ -629,6 +1044,13 @@ class Conversation(BaseStructure):
629
1044
  Returns:
630
1045
  str: The conversation history as a YAML string.
631
1046
  """
1047
+ if self.backend_instance:
1048
+ try:
1049
+ return self.backend_instance.to_yaml()
1050
+ except Exception as e:
1051
+ logger.error(f"Backend to_yaml failed: {e}")
1052
+ # Fallback to in-memory implementation
1053
+ pass
632
1054
  return yaml.dump(self.conversation_history)
633
1055
 
634
1056
  def get_visible_messages(self, agent: "Agent", turn: int):
@@ -664,11 +1086,24 @@ class Conversation(BaseStructure):
664
1086
  Returns:
665
1087
  str: The last message formatted as 'role: content'.
666
1088
  """
667
- if self.provider == "mem0":
1089
+ if self.backend_instance:
1090
+ try:
1091
+ return (
1092
+ self.backend_instance.get_last_message_as_string()
1093
+ )
1094
+ except Exception as e:
1095
+ logger.error(
1096
+ f"Backend get_last_message_as_string failed: {e}"
1097
+ )
1098
+ # Fallback to in-memory implementation
1099
+ pass
1100
+ elif self.provider == "mem0":
668
1101
  memory = self.mem0_provider()
669
1102
  return memory.get_all(run_id=self.id)
670
1103
  elif self.provider == "in-memory":
671
- return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}"
1104
+ if self.conversation_history:
1105
+ return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}"
1106
+ return ""
672
1107
  else:
673
1108
  raise ValueError(f"Invalid provider: {self.provider}")
674
1109
 
@@ -678,6 +1113,15 @@ class Conversation(BaseStructure):
678
1113
  Returns:
679
1114
  list: List of messages formatted as 'role: content'.
680
1115
  """
1116
+ if self.backend_instance:
1117
+ try:
1118
+ return self.backend_instance.return_messages_as_list()
1119
+ except Exception as e:
1120
+ logger.error(
1121
+ f"Backend return_messages_as_list failed: {e}"
1122
+ )
1123
+ # Fallback to in-memory implementation
1124
+ pass
681
1125
  return [
682
1126
  f"{message['role']}: {message['content']}"
683
1127
  for message in self.conversation_history
@@ -689,6 +1133,17 @@ class Conversation(BaseStructure):
689
1133
  Returns:
690
1134
  list: List of dictionaries containing role and content of each message.
691
1135
  """
1136
+ if self.backend_instance:
1137
+ try:
1138
+ return (
1139
+ self.backend_instance.return_messages_as_dictionary()
1140
+ )
1141
+ except Exception as e:
1142
+ logger.error(
1143
+ f"Backend return_messages_as_dictionary failed: {e}"
1144
+ )
1145
+ # Fallback to in-memory implementation
1146
+ pass
692
1147
  return [
693
1148
  {
694
1149
  "role": message["role"],
@@ -723,7 +1178,16 @@ class Conversation(BaseStructure):
723
1178
  Returns:
724
1179
  str: The final message formatted as 'role: content'.
725
1180
  """
726
- return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}"
1181
+ if self.backend_instance:
1182
+ try:
1183
+ return self.backend_instance.get_final_message()
1184
+ except Exception as e:
1185
+ logger.error(f"Backend get_final_message failed: {e}")
1186
+ # Fallback to in-memory implementation
1187
+ pass
1188
+ if self.conversation_history:
1189
+ return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}"
1190
+ return ""
727
1191
 
728
1192
  def get_final_message_content(self):
729
1193
  """Return the content of the final message from the conversation history.
@@ -731,9 +1195,21 @@ class Conversation(BaseStructure):
731
1195
  Returns:
732
1196
  str: The content of the final message.
733
1197
  """
734
- output = self.conversation_history[-1]["content"]
735
- # print(output)
736
- return output
1198
+ if self.backend_instance:
1199
+ try:
1200
+ return (
1201
+ self.backend_instance.get_final_message_content()
1202
+ )
1203
+ except Exception as e:
1204
+ logger.error(
1205
+ f"Backend get_final_message_content failed: {e}"
1206
+ )
1207
+ # Fallback to in-memory implementation
1208
+ pass
1209
+ if self.conversation_history:
1210
+ output = self.conversation_history[-1]["content"]
1211
+ return output
1212
+ return ""
737
1213
 
738
1214
  def return_all_except_first(self):
739
1215
  """Return all messages except the first one.
@@ -741,6 +1217,15 @@ class Conversation(BaseStructure):
741
1217
  Returns:
742
1218
  list: List of messages except the first one.
743
1219
  """
1220
+ if self.backend_instance:
1221
+ try:
1222
+ return self.backend_instance.return_all_except_first()
1223
+ except Exception as e:
1224
+ logger.error(
1225
+ f"Backend return_all_except_first failed: {e}"
1226
+ )
1227
+ # Fallback to in-memory implementation
1228
+ pass
744
1229
  return self.conversation_history[2:]
745
1230
 
746
1231
  def return_all_except_first_string(self):
@@ -749,6 +1234,17 @@ class Conversation(BaseStructure):
749
1234
  Returns:
750
1235
  str: All messages except the first one as a string.
751
1236
  """
1237
+ if self.backend_instance:
1238
+ try:
1239
+ return (
1240
+ self.backend_instance.return_all_except_first_string()
1241
+ )
1242
+ except Exception as e:
1243
+ logger.error(
1244
+ f"Backend return_all_except_first_string failed: {e}"
1245
+ )
1246
+ # Fallback to in-memory implementation
1247
+ pass
752
1248
  return "\n".join(
753
1249
  [
754
1250
  f"{msg['content']}"
@@ -762,6 +1258,13 @@ class Conversation(BaseStructure):
762
1258
  Args:
763
1259
  messages (List[dict]): List of messages to add.
764
1260
  """
1261
+ if self.backend_instance:
1262
+ try:
1263
+ return self.backend_instance.batch_add(messages)
1264
+ except Exception as e:
1265
+ logger.error(f"Backend batch_add failed: {e}")
1266
+ # Fallback to in-memory implementation
1267
+ pass
765
1268
  self.conversation_history.extend(messages)
766
1269
 
767
1270
  def clear_memory(self):
@@ -816,6 +1319,13 @@ class Conversation(BaseStructure):
816
1319
  save_enabled=True,
817
1320
  )
818
1321
 
1322
+ def return_dict_final(self):
1323
+ """Return the final message as a dictionary."""
1324
+ return (
1325
+ self.conversation_history[-1]["content"],
1326
+ self.conversation_history[-1]["content"],
1327
+ )
1328
+
819
1329
  @classmethod
820
1330
  def list_conversations(
821
1331
  cls, conversations_dir: Optional[str] = None
@@ -880,6 +1390,17 @@ class Conversation(BaseStructure):
880
1390
  conversations, key=lambda x: x["created_at"], reverse=True
881
1391
  )
882
1392
 
1393
+ def clear_memory(self):
1394
+ """Clear the memory of the conversation."""
1395
+ if self.backend_instance:
1396
+ try:
1397
+ return self.backend_instance.clear()
1398
+ except Exception as e:
1399
+ logger.error(f"Backend clear_memory failed: {e}")
1400
+ # Fallback to in-memory implementation
1401
+ pass
1402
+ self.conversation_history = []
1403
+
883
1404
 
884
1405
  # # Example usage
885
1406
  # # conversation = Conversation()