swarms 7.8.4__py3-none-any.whl → 7.8.7__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/agents/ape_agent.py +5 -22
- swarms/agents/consistency_agent.py +1 -1
- swarms/agents/i_agent.py +1 -1
- swarms/agents/reasoning_agents.py +99 -3
- swarms/agents/reasoning_duo.py +1 -1
- swarms/cli/main.py +1 -1
- swarms/communication/__init__.py +1 -0
- swarms/communication/duckdb_wrap.py +32 -2
- swarms/communication/pulsar_struct.py +45 -19
- swarms/communication/redis_wrap.py +56 -11
- swarms/communication/supabase_wrap.py +1659 -0
- swarms/prompts/prompt.py +0 -3
- swarms/schemas/agent_completion_response.py +71 -0
- swarms/schemas/agent_rag_schema.py +7 -0
- swarms/schemas/conversation_schema.py +9 -0
- swarms/schemas/llm_agent_schema.py +99 -81
- swarms/schemas/swarms_api_schemas.py +164 -0
- swarms/structs/__init__.py +14 -11
- swarms/structs/agent.py +219 -199
- swarms/structs/agent_rag_handler.py +685 -0
- swarms/structs/base_swarm.py +2 -1
- swarms/structs/conversation.py +608 -87
- swarms/structs/csv_to_agent.py +153 -100
- swarms/structs/deep_research_swarm.py +197 -193
- swarms/structs/dynamic_conversational_swarm.py +18 -7
- swarms/structs/hiearchical_swarm.py +1 -1
- swarms/structs/hybrid_hiearchical_peer_swarm.py +2 -18
- swarms/structs/image_batch_processor.py +261 -0
- swarms/structs/interactive_groupchat.py +356 -0
- swarms/structs/ma_blocks.py +75 -0
- swarms/structs/majority_voting.py +1 -1
- swarms/structs/mixture_of_agents.py +1 -1
- swarms/structs/multi_agent_router.py +3 -2
- swarms/structs/rearrange.py +3 -3
- swarms/structs/sequential_workflow.py +3 -3
- swarms/structs/swarm_matcher.py +500 -411
- swarms/structs/swarm_router.py +15 -97
- swarms/structs/swarming_architectures.py +1 -1
- swarms/tools/mcp_client_call.py +3 -0
- swarms/utils/__init__.py +10 -2
- swarms/utils/check_all_model_max_tokens.py +43 -0
- swarms/utils/generate_keys.py +0 -27
- swarms/utils/history_output_formatter.py +5 -20
- swarms/utils/litellm_wrapper.py +208 -60
- swarms/utils/output_types.py +24 -0
- swarms/utils/vllm_wrapper.py +5 -6
- swarms/utils/xml_utils.py +37 -2
- {swarms-7.8.4.dist-info → swarms-7.8.7.dist-info}/METADATA +31 -55
- {swarms-7.8.4.dist-info → swarms-7.8.7.dist-info}/RECORD +53 -48
- swarms/structs/multi_agent_collab.py +0 -242
- swarms/structs/output_types.py +0 -6
- swarms/utils/markdown_message.py +0 -21
- swarms/utils/visualizer.py +0 -510
- swarms/utils/wrapper_clusterop.py +0 -127
- /swarms/{tools → schemas}/tool_schema_base_model.py +0 -0
- {swarms-7.8.4.dist-info → swarms-7.8.7.dist-info}/LICENSE +0 -0
- {swarms-7.8.4.dist-info → swarms-7.8.7.dist-info}/WHEEL +0 -0
- {swarms-7.8.4.dist-info → swarms-7.8.7.dist-info}/entry_points.txt +0 -0
swarms/structs/conversation.py
CHANGED
@@ -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[
|
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
|
-
|
145
|
-
|
146
|
-
|
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.
|
149
|
-
|
150
|
-
|
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.
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
self.
|
160
|
-
|
161
|
-
|
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
|
-
"
|
228
|
-
"message_id": str(uuid.uuid4()),
|
499
|
+
"content": content,
|
229
500
|
}
|
230
501
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
message["
|
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
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
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
|
-
|
280
|
-
|
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
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
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 (
|
352
|
-
role (str):
|
353
|
-
content
|
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.
|
356
|
-
|
357
|
-
|
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
|
657
|
+
"""Query a message from the conversation history.
|
362
658
|
|
363
659
|
Args:
|
364
|
-
index (
|
660
|
+
index (int): The index of the message to query.
|
365
661
|
|
366
662
|
Returns:
|
367
|
-
dict: The message
|
663
|
+
dict: The message at the specified index.
|
368
664
|
"""
|
369
|
-
|
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
|
676
|
+
"""Search for messages containing a keyword.
|
373
677
|
|
374
678
|
Args:
|
375
|
-
keyword (str):
|
679
|
+
keyword (str): The keyword to search for.
|
376
680
|
|
377
681
|
Returns:
|
378
|
-
list:
|
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
|
-
|
382
|
-
for
|
383
|
-
if keyword in
|
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
|
394
|
-
role = message
|
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
|
-
|
421
|
-
|
422
|
-
|
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
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
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
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
735
|
-
|
736
|
-
|
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()
|